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 }