web/lib/Zend/Memory/Manager.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     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 }