PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/Memory/Manager.php

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