Manager.php 12 KB

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