|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Memory |
|
17 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 * @version $Id: Manager.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
20 */ |
|
21 |
|
22 /** Zend_Memory_Container_Movable */ |
|
23 require_once 'Zend/Memory/Container/Movable.php'; |
|
24 |
|
25 /** Zend_Memory_Container_Locked */ |
|
26 require_once 'Zend/Memory/Container/Locked.php'; |
|
27 |
|
28 /** Zend_Memory_AccessController */ |
|
29 require_once 'Zend/Memory/AccessController.php'; |
|
30 |
|
31 |
|
32 /** |
|
33 * Memory manager |
|
34 * |
|
35 * This class encapsulates memory menagement operations, when PHP works |
|
36 * in limited memory mode. |
|
37 * |
|
38 * |
|
39 * @category Zend |
|
40 * @package Zend_Memory |
|
41 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
42 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
43 */ |
|
44 class Zend_Memory_Manager |
|
45 { |
|
46 /** |
|
47 * Object storage backend |
|
48 * |
|
49 * @var Zend_Cache_Backend_Interface |
|
50 */ |
|
51 private $_backend = null; |
|
52 |
|
53 /** |
|
54 * Memory grow limit. |
|
55 * Default value is 2/3 of memory_limit php.ini variable |
|
56 * Negative value means no limit |
|
57 * |
|
58 * @var integer |
|
59 */ |
|
60 private $_memoryLimit = -1; |
|
61 |
|
62 /** |
|
63 * Minimum value size to be swapped. |
|
64 * Default value is 16K |
|
65 * Negative value means that memory objects are never swapped |
|
66 * |
|
67 * @var integer |
|
68 */ |
|
69 private $_minSize = 16384; |
|
70 |
|
71 /** |
|
72 * Overall size of memory, used by values |
|
73 * |
|
74 * @var integer |
|
75 */ |
|
76 private $_memorySize = 0; |
|
77 |
|
78 /** |
|
79 * Id for next Zend_Memory object |
|
80 * |
|
81 * @var integer |
|
82 */ |
|
83 private $_nextId = 0; |
|
84 |
|
85 /** |
|
86 * List of candidates to unload |
|
87 * |
|
88 * It also represents objects access history. Last accessed objects are moved to the end of array |
|
89 * |
|
90 * array( |
|
91 * <id> => <memory container object>, |
|
92 * ... |
|
93 * ) |
|
94 * |
|
95 * @var array |
|
96 */ |
|
97 private $_unloadCandidates = array(); |
|
98 |
|
99 /** |
|
100 * List of object sizes. |
|
101 * |
|
102 * This list is used to calculate modification of object sizes |
|
103 * |
|
104 * array( <id> => <size>, ...) |
|
105 * |
|
106 * @var array |
|
107 */ |
|
108 private $_sizes = array(); |
|
109 |
|
110 /** |
|
111 * Last modified object |
|
112 * |
|
113 * It's used to reduce number of calls necessary to trace objects' modifications |
|
114 * Modification is not processed by memory manager until we do not switch to another |
|
115 * object. |
|
116 * So we have to trace only _first_ object modification and do nothing for others |
|
117 * |
|
118 * @var Zend_Memory_Container_Movable |
|
119 */ |
|
120 private $_lastModified = null; |
|
121 |
|
122 /** |
|
123 * Unique memory manager id |
|
124 * |
|
125 * @var integer |
|
126 */ |
|
127 private $_managerId; |
|
128 |
|
129 /** |
|
130 * Tags array, used by backend to categorize stored values |
|
131 * |
|
132 * @var array |
|
133 */ |
|
134 private $_tags; |
|
135 |
|
136 /** |
|
137 * This function is intended to generate unique id, used by memory manager |
|
138 */ |
|
139 private function _generateMemManagerId() |
|
140 { |
|
141 /** |
|
142 * @todo !!! |
|
143 * uniqid() php function doesn't really garantee the id to be unique |
|
144 * it should be changed by something else |
|
145 * (Ex. backend interface should be extended to provide this functionality) |
|
146 */ |
|
147 $this->_managerId = uniqid('ZendMemManager', true); |
|
148 $this->_tags = array($this->_managerId); |
|
149 $this->_managerId .= '_'; |
|
150 } |
|
151 |
|
152 |
|
153 /** |
|
154 * Memory manager constructor |
|
155 * |
|
156 * If backend is not specified, then memory objects are never swapped |
|
157 * |
|
158 * @param Zend_Cache_Backend $backend |
|
159 * @param array $backendOptions associative array of options for the corresponding backend constructor |
|
160 */ |
|
161 public function __construct($backend = null) |
|
162 { |
|
163 if ($backend === null) { |
|
164 return; |
|
165 } |
|
166 |
|
167 $this->_backend = $backend; |
|
168 $this->_generateMemManagerId(); |
|
169 |
|
170 $memoryLimitStr = trim(ini_get('memory_limit')); |
|
171 if ($memoryLimitStr != '' && $memoryLimitStr != -1) { |
|
172 $this->_memoryLimit = (integer)$memoryLimitStr; |
|
173 switch (strtolower($memoryLimitStr[strlen($memoryLimitStr)-1])) { |
|
174 case 'g': |
|
175 $this->_memoryLimit *= 1024; |
|
176 // Break intentionally omitted |
|
177 case 'm': |
|
178 $this->_memoryLimit *= 1024; |
|
179 // Break intentionally omitted |
|
180 case 'k': |
|
181 $this->_memoryLimit *= 1024; |
|
182 break; |
|
183 |
|
184 default: |
|
185 break; |
|
186 } |
|
187 |
|
188 $this->_memoryLimit = (int)($this->_memoryLimit*2/3); |
|
189 } // No limit otherwise |
|
190 } |
|
191 |
|
192 /** |
|
193 * Object destructor |
|
194 * |
|
195 * Clean up backend storage |
|
196 */ |
|
197 public function __destruct() |
|
198 { |
|
199 if ($this->_backend !== null) { |
|
200 $this->_backend->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $this->_tags); |
|
201 } |
|
202 } |
|
203 |
|
204 /** |
|
205 * Set memory grow limit |
|
206 * |
|
207 * @param integer $newLimit |
|
208 * @throws Zend_Exception |
|
209 */ |
|
210 public function setMemoryLimit($newLimit) |
|
211 { |
|
212 $this->_memoryLimit = $newLimit; |
|
213 |
|
214 $this->_swapCheck(); |
|
215 } |
|
216 |
|
217 /** |
|
218 * Get memory grow limit |
|
219 * |
|
220 * @return integer |
|
221 */ |
|
222 public function getMemoryLimit() |
|
223 { |
|
224 return $this->_memoryLimit; |
|
225 } |
|
226 |
|
227 /** |
|
228 * Set minimum size of values, which may be swapped |
|
229 * |
|
230 * @param integer $newSize |
|
231 */ |
|
232 public function setMinSize($newSize) |
|
233 { |
|
234 $this->_minSize = $newSize; |
|
235 } |
|
236 |
|
237 /** |
|
238 * Get minimum size of values, which may be swapped |
|
239 * |
|
240 * @return integer |
|
241 */ |
|
242 public function getMinSize() |
|
243 { |
|
244 return $this->_minSize; |
|
245 } |
|
246 |
|
247 /** |
|
248 * Create new Zend_Memory value container |
|
249 * |
|
250 * @param string $value |
|
251 * @return Zend_Memory_Container_Interface |
|
252 * @throws Zend_Memory_Exception |
|
253 */ |
|
254 public function create($value = '') |
|
255 { |
|
256 return $this->_create($value, false); |
|
257 } |
|
258 |
|
259 /** |
|
260 * Create new Zend_Memory value container, which has value always |
|
261 * locked in memory |
|
262 * |
|
263 * @param string $value |
|
264 * @return Zend_Memory_Container_Interface |
|
265 * @throws Zend_Memory_Exception |
|
266 */ |
|
267 public function createLocked($value = '') |
|
268 { |
|
269 return $this->_create($value, true); |
|
270 } |
|
271 |
|
272 /** |
|
273 * Create new Zend_Memory object |
|
274 * |
|
275 * @param string $value |
|
276 * @param boolean $locked |
|
277 * @return Zend_Memory_Container_Interface |
|
278 * @throws Zend_Memory_Exception |
|
279 */ |
|
280 private function _create($value, $locked) |
|
281 { |
|
282 $id = $this->_nextId++; |
|
283 |
|
284 if ($locked || ($this->_backend === null) /* Use only memory locked objects if backend is not specified */) { |
|
285 return new Zend_Memory_Container_Locked($value); |
|
286 } |
|
287 |
|
288 // Commit other objects modifications |
|
289 $this->_commit(); |
|
290 |
|
291 $valueObject = new Zend_Memory_Container_Movable($this, $id, $value); |
|
292 |
|
293 // Store last object size as 0 |
|
294 $this->_sizes[$id] = 0; |
|
295 // prepare object for next modifications |
|
296 $this->_lastModified = $valueObject; |
|
297 |
|
298 return new Zend_Memory_AccessController($valueObject); |
|
299 } |
|
300 |
|
301 /** |
|
302 * Unlink value container from memory manager |
|
303 * |
|
304 * Used by Memory container destroy() method |
|
305 * |
|
306 * @internal |
|
307 * @param integer $id |
|
308 * @return Zend_Memory_Container |
|
309 */ |
|
310 public function unlink(Zend_Memory_Container_Movable $container, $id) |
|
311 { |
|
312 if ($this->_lastModified === $container) { |
|
313 // Drop all object modifications |
|
314 $this->_lastModified = null; |
|
315 unset($this->_sizes[$id]); |
|
316 return; |
|
317 } |
|
318 |
|
319 if (isset($this->_unloadCandidates[$id])) { |
|
320 unset($this->_unloadCandidates[$id]); |
|
321 } |
|
322 |
|
323 $this->_memorySize -= $this->_sizes[$id]; |
|
324 unset($this->_sizes[$id]); |
|
325 } |
|
326 |
|
327 /** |
|
328 * Process value update |
|
329 * |
|
330 * @internal |
|
331 * @param Zend_Memory_Container_Movable $container |
|
332 * @param integer $id |
|
333 */ |
|
334 public function processUpdate(Zend_Memory_Container_Movable $container, $id) |
|
335 { |
|
336 /** |
|
337 * This method is automatically invoked by memory container only once per |
|
338 * "modification session", but user may call memory container touch() method |
|
339 * several times depending on used algorithm. So we have to use this check |
|
340 * to optimize this case. |
|
341 */ |
|
342 if ($container === $this->_lastModified) { |
|
343 return; |
|
344 } |
|
345 |
|
346 // Remove just updated object from list of candidates to unload |
|
347 if( isset($this->_unloadCandidates[$id])) { |
|
348 unset($this->_unloadCandidates[$id]); |
|
349 } |
|
350 |
|
351 // Reduce used memory mark |
|
352 $this->_memorySize -= $this->_sizes[$id]; |
|
353 |
|
354 // Commit changes of previously modified object if necessary |
|
355 $this->_commit(); |
|
356 |
|
357 $this->_lastModified = $container; |
|
358 } |
|
359 |
|
360 /** |
|
361 * Commit modified object and put it back to the loaded objects list |
|
362 */ |
|
363 private function _commit() |
|
364 { |
|
365 if (($container = $this->_lastModified) === null) { |
|
366 return; |
|
367 } |
|
368 |
|
369 $this->_lastModified = null; |
|
370 |
|
371 $id = $container->getId(); |
|
372 |
|
373 // Calculate new object size and increase used memory size by this value |
|
374 $this->_memorySize += ($this->_sizes[$id] = strlen($container->getRef())); |
|
375 |
|
376 if ($this->_sizes[$id] > $this->_minSize) { |
|
377 // Move object to "unload candidates list" |
|
378 $this->_unloadCandidates[$id] = $container; |
|
379 } |
|
380 |
|
381 $container->startTrace(); |
|
382 |
|
383 $this->_swapCheck(); |
|
384 } |
|
385 |
|
386 /** |
|
387 * Check and swap objects if necessary |
|
388 * |
|
389 * @throws Zend_MemoryException |
|
390 */ |
|
391 private function _swapCheck() |
|
392 { |
|
393 if ($this->_memoryLimit < 0 || $this->_memorySize < $this->_memoryLimit) { |
|
394 // Memory limit is not reached |
|
395 // Do nothing |
|
396 return; |
|
397 } |
|
398 |
|
399 // walk through loaded objects in access history order |
|
400 foreach ($this->_unloadCandidates as $id => $container) { |
|
401 $this->_swap($container, $id); |
|
402 unset($this->_unloadCandidates[$id]); |
|
403 |
|
404 if ($this->_memorySize < $this->_memoryLimit) { |
|
405 // We've swapped enough objects |
|
406 return; |
|
407 } |
|
408 } |
|
409 |
|
410 require_once 'Zend/Memory/Exception.php'; |
|
411 throw new Zend_Memory_Exception('Memory manager can\'t get enough space.'); |
|
412 } |
|
413 |
|
414 |
|
415 /** |
|
416 * Swap object data to disk |
|
417 * Actualy swaps data or only unloads it from memory, |
|
418 * if object is not changed since last swap |
|
419 * |
|
420 * @param Zend_Memory_Container_Movable $container |
|
421 * @param integer $id |
|
422 */ |
|
423 private function _swap(Zend_Memory_Container_Movable $container, $id) |
|
424 { |
|
425 if ($container->isLocked()) { |
|
426 return; |
|
427 } |
|
428 |
|
429 if (!$container->isSwapped()) { |
|
430 $this->_backend->save($container->getRef(), $this->_managerId . $id, $this->_tags); |
|
431 } |
|
432 |
|
433 $this->_memorySize -= $this->_sizes[$id]; |
|
434 |
|
435 $container->markAsSwapped(); |
|
436 $container->unloadValue(); |
|
437 } |
|
438 |
|
439 /** |
|
440 * Load value from swap file. |
|
441 * |
|
442 * @internal |
|
443 * @param Zend_Memory_Container_Movable $container |
|
444 * @param integer $id |
|
445 */ |
|
446 public function load(Zend_Memory_Container_Movable $container, $id) |
|
447 { |
|
448 $value = $this->_backend->load($this->_managerId . $id, true); |
|
449 |
|
450 // Try to swap other objects if necessary |
|
451 // (do not include specified object into check) |
|
452 $this->_memorySize += strlen($value); |
|
453 $this->_swapCheck(); |
|
454 |
|
455 // Add loaded obect to the end of loaded objects list |
|
456 $container->setValue($value); |
|
457 |
|
458 if ($this->_sizes[$id] > $this->_minSize) { |
|
459 // Add object to the end of "unload candidates list" |
|
460 $this->_unloadCandidates[$id] = $container; |
|
461 } |
|
462 } |
|
463 } |