File indexing completed on 2024-12-22 05:36:52
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Memory 0017 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0018 * @license http://framework.zend.com/license/new-bsd New BSD License 0019 * @version $Id$ 0020 */ 0021 0022 /** Zend_Memory_Container_Movable */ 0023 // require_once 'Zend/Memory/Container/Movable.php'; 0024 0025 /** Zend_Memory_Container_Locked */ 0026 // require_once 'Zend/Memory/Container/Locked.php'; 0027 0028 /** Zend_Memory_AccessController */ 0029 // require_once 'Zend/Memory/AccessController.php'; 0030 0031 0032 /** 0033 * Memory manager 0034 * 0035 * This class encapsulates memory menagement operations, when PHP works 0036 * in limited memory mode. 0037 * 0038 * 0039 * @category Zend 0040 * @package Zend_Memory 0041 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0042 * @license http://framework.zend.com/license/new-bsd New BSD License 0043 */ 0044 class Zend_Memory_Manager 0045 { 0046 /** 0047 * Object storage backend 0048 * 0049 * @var Zend_Cache_Backend_Interface 0050 */ 0051 private $_backend = null; 0052 0053 /** 0054 * Memory grow limit. 0055 * Default value is 2/3 of memory_limit php.ini variable 0056 * Negative value means no limit 0057 * 0058 * @var integer 0059 */ 0060 private $_memoryLimit = -1; 0061 0062 /** 0063 * Minimum value size to be swapped. 0064 * Default value is 16K 0065 * Negative value means that memory objects are never swapped 0066 * 0067 * @var integer 0068 */ 0069 private $_minSize = 16384; 0070 0071 /** 0072 * Overall size of memory, used by values 0073 * 0074 * @var integer 0075 */ 0076 private $_memorySize = 0; 0077 0078 /** 0079 * Id for next Zend_Memory object 0080 * 0081 * @var integer 0082 */ 0083 private $_nextId = 0; 0084 0085 /** 0086 * List of candidates to unload 0087 * 0088 * It also represents objects access history. Last accessed objects are moved to the end of array 0089 * 0090 * array( 0091 * <id> => <memory container object>, 0092 * ... 0093 * ) 0094 * 0095 * @var array 0096 */ 0097 private $_unloadCandidates = array(); 0098 0099 /** 0100 * List of object sizes. 0101 * 0102 * This list is used to calculate modification of object sizes 0103 * 0104 * array( <id> => <size>, ...) 0105 * 0106 * @var array 0107 */ 0108 private $_sizes = array(); 0109 0110 /** 0111 * Last modified object 0112 * 0113 * It's used to reduce number of calls necessary to trace objects' modifications 0114 * Modification is not processed by memory manager until we do not switch to another 0115 * object. 0116 * So we have to trace only _first_ object modification and do nothing for others 0117 * 0118 * @var Zend_Memory_Container_Movable 0119 */ 0120 private $_lastModified = null; 0121 0122 /** 0123 * Unique memory manager id 0124 * 0125 * @var integer 0126 */ 0127 private $_managerId; 0128 0129 /** 0130 * Tags array, used by backend to categorize stored values 0131 * 0132 * @var array 0133 */ 0134 private $_tags; 0135 0136 /** 0137 * This function is intended to generate unique id, used by memory manager 0138 */ 0139 private function _generateMemManagerId() 0140 { 0141 /** 0142 * @todo !!! 0143 * uniqid() php function doesn't really garantee the id to be unique 0144 * it should be changed by something else 0145 * (Ex. backend interface should be extended to provide this functionality) 0146 */ 0147 $this->_managerId = uniqid('ZendMemManager', true); 0148 $this->_tags = array($this->_managerId); 0149 $this->_managerId .= '_'; 0150 } 0151 0152 0153 /** 0154 * Memory manager constructor 0155 * 0156 * If backend is not specified, then memory objects are never swapped 0157 * 0158 * @param Zend_Cache_Backend $backend 0159 * @param array $backendOptions associative array of options for the corresponding backend constructor 0160 */ 0161 public function __construct($backend = null) 0162 { 0163 if ($backend === null) { 0164 return; 0165 } 0166 0167 $this->_backend = $backend; 0168 $this->_generateMemManagerId(); 0169 0170 $memoryLimitStr = trim(ini_get('memory_limit')); 0171 if ($memoryLimitStr != '' && $memoryLimitStr != -1) { 0172 $this->_memoryLimit = (integer)$memoryLimitStr; 0173 switch (strtolower($memoryLimitStr[strlen($memoryLimitStr)-1])) { 0174 case 'g': 0175 $this->_memoryLimit *= 1024; 0176 // Break intentionally omitted 0177 case 'm': 0178 $this->_memoryLimit *= 1024; 0179 // Break intentionally omitted 0180 case 'k': 0181 $this->_memoryLimit *= 1024; 0182 break; 0183 0184 default: 0185 break; 0186 } 0187 0188 $this->_memoryLimit = (int)($this->_memoryLimit*2/3); 0189 } // No limit otherwise 0190 } 0191 0192 /** 0193 * Object destructor 0194 * 0195 * Clean up backend storage 0196 */ 0197 public function __destruct() 0198 { 0199 if ($this->_backend !== null) { 0200 $this->_backend->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $this->_tags); 0201 } 0202 } 0203 0204 /** 0205 * Set memory grow limit 0206 * 0207 * @param integer $newLimit 0208 * @throws Zend_Exception 0209 */ 0210 public function setMemoryLimit($newLimit) 0211 { 0212 $this->_memoryLimit = $newLimit; 0213 0214 $this->_swapCheck(); 0215 } 0216 0217 /** 0218 * Get memory grow limit 0219 * 0220 * @return integer 0221 */ 0222 public function getMemoryLimit() 0223 { 0224 return $this->_memoryLimit; 0225 } 0226 0227 /** 0228 * Set minimum size of values, which may be swapped 0229 * 0230 * @param integer $newSize 0231 */ 0232 public function setMinSize($newSize) 0233 { 0234 $this->_minSize = $newSize; 0235 } 0236 0237 /** 0238 * Get minimum size of values, which may be swapped 0239 * 0240 * @return integer 0241 */ 0242 public function getMinSize() 0243 { 0244 return $this->_minSize; 0245 } 0246 0247 /** 0248 * Create new Zend_Memory value container 0249 * 0250 * @param string $value 0251 * @return Zend_Memory_Container_Interface 0252 * @throws Zend_Memory_Exception 0253 */ 0254 public function create($value = '') 0255 { 0256 return $this->_create($value, false); 0257 } 0258 0259 /** 0260 * Create new Zend_Memory value container, which has value always 0261 * locked in memory 0262 * 0263 * @param string $value 0264 * @return Zend_Memory_Container_Interface 0265 * @throws Zend_Memory_Exception 0266 */ 0267 public function createLocked($value = '') 0268 { 0269 return $this->_create($value, true); 0270 } 0271 0272 /** 0273 * Create new Zend_Memory object 0274 * 0275 * @param string $value 0276 * @param boolean $locked 0277 * @return Zend_Memory_Container_Interface 0278 * @throws Zend_Memory_Exception 0279 */ 0280 private function _create($value, $locked) 0281 { 0282 $id = $this->_nextId++; 0283 0284 if ($locked || ($this->_backend === null) /* Use only memory locked objects if backend is not specified */) { 0285 return new Zend_Memory_Container_Locked($value); 0286 } 0287 0288 // Commit other objects modifications 0289 $this->_commit(); 0290 0291 $valueObject = new Zend_Memory_Container_Movable($this, $id, $value); 0292 0293 // Store last object size as 0 0294 $this->_sizes[$id] = 0; 0295 // prepare object for next modifications 0296 $this->_lastModified = $valueObject; 0297 0298 return new Zend_Memory_AccessController($valueObject); 0299 } 0300 0301 /** 0302 * Unlink value container from memory manager 0303 * 0304 * Used by Memory container destroy() method 0305 * 0306 * @internal 0307 * @param integer $id 0308 * @return Zend_Memory_Container 0309 */ 0310 public function unlink(Zend_Memory_Container_Movable $container, $id) 0311 { 0312 if ($this->_lastModified === $container) { 0313 // Drop all object modifications 0314 $this->_lastModified = null; 0315 unset($this->_sizes[$id]); 0316 return; 0317 } 0318 0319 if (isset($this->_unloadCandidates[$id])) { 0320 unset($this->_unloadCandidates[$id]); 0321 } 0322 0323 $this->_memorySize -= $this->_sizes[$id]; 0324 unset($this->_sizes[$id]); 0325 } 0326 0327 /** 0328 * Process value update 0329 * 0330 * @internal 0331 * @param Zend_Memory_Container_Movable $container 0332 * @param integer $id 0333 */ 0334 public function processUpdate(Zend_Memory_Container_Movable $container, $id) 0335 { 0336 /** 0337 * This method is automatically invoked by memory container only once per 0338 * "modification session", but user may call memory container touch() method 0339 * several times depending on used algorithm. So we have to use this check 0340 * to optimize this case. 0341 */ 0342 if ($container === $this->_lastModified) { 0343 return; 0344 } 0345 0346 // Remove just updated object from list of candidates to unload 0347 if( isset($this->_unloadCandidates[$id])) { 0348 unset($this->_unloadCandidates[$id]); 0349 } 0350 0351 // Reduce used memory mark 0352 $this->_memorySize -= $this->_sizes[$id]; 0353 0354 // Commit changes of previously modified object if necessary 0355 $this->_commit(); 0356 0357 $this->_lastModified = $container; 0358 } 0359 0360 /** 0361 * Commit modified object and put it back to the loaded objects list 0362 */ 0363 private function _commit() 0364 { 0365 if (($container = $this->_lastModified) === null) { 0366 return; 0367 } 0368 0369 $this->_lastModified = null; 0370 0371 $id = $container->getId(); 0372 0373 // Calculate new object size and increase used memory size by this value 0374 $this->_memorySize += ($this->_sizes[$id] = strlen($container->getRef())); 0375 0376 if ($this->_sizes[$id] > $this->_minSize) { 0377 // Move object to "unload candidates list" 0378 $this->_unloadCandidates[$id] = $container; 0379 } 0380 0381 $container->startTrace(); 0382 0383 $this->_swapCheck(); 0384 } 0385 0386 /** 0387 * Check and swap objects if necessary 0388 * 0389 * @throws Zend_MemoryException 0390 */ 0391 private function _swapCheck() 0392 { 0393 if ($this->_memoryLimit < 0 || $this->_memorySize < $this->_memoryLimit) { 0394 // Memory limit is not reached 0395 // Do nothing 0396 return; 0397 } 0398 0399 // walk through loaded objects in access history order 0400 foreach ($this->_unloadCandidates as $id => $container) { 0401 $this->_swap($container, $id); 0402 unset($this->_unloadCandidates[$id]); 0403 0404 if ($this->_memorySize < $this->_memoryLimit) { 0405 // We've swapped enough objects 0406 return; 0407 } 0408 } 0409 0410 // require_once 'Zend/Memory/Exception.php'; 0411 throw new Zend_Memory_Exception('Memory manager can\'t get enough space.'); 0412 } 0413 0414 0415 /** 0416 * Swap object data to disk 0417 * Actualy swaps data or only unloads it from memory, 0418 * if object is not changed since last swap 0419 * 0420 * @param Zend_Memory_Container_Movable $container 0421 * @param integer $id 0422 */ 0423 private function _swap(Zend_Memory_Container_Movable $container, $id) 0424 { 0425 if ($container->isLocked()) { 0426 return; 0427 } 0428 0429 if (!$container->isSwapped()) { 0430 $this->_backend->save($container->getRef(), $this->_managerId . $id, $this->_tags); 0431 } 0432 0433 $this->_memorySize -= $this->_sizes[$id]; 0434 0435 $container->markAsSwapped(); 0436 $container->unloadValue(); 0437 } 0438 0439 /** 0440 * Load value from swap file. 0441 * 0442 * @internal 0443 * @param Zend_Memory_Container_Movable $container 0444 * @param integer $id 0445 */ 0446 public function load(Zend_Memory_Container_Movable $container, $id) 0447 { 0448 $value = $this->_backend->load($this->_managerId . $id, true); 0449 0450 // Try to swap other objects if necessary 0451 // (do not include specified object into check) 0452 $this->_memorySize += strlen($value); 0453 $this->_swapCheck(); 0454 0455 // Add loaded obect to the end of loaded objects list 0456 $container->setValue($value); 0457 0458 if ($this->_sizes[$id] > $this->_minSize) { 0459 // Add object to the end of "unload candidates list" 0460 $this->_unloadCandidates[$id] = $container; 0461 } 0462 } 0463 }