2
0

Manager.php 12 KB

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