diff --git includes/common.inc includes/common.inc index f7c596b..b5ff393 100644 --- includes/common.inc +++ includes/common.inc @@ -6253,6 +6253,7 @@ function entity_get_info($entity_type = NULL) { 'fieldable' => FALSE, 'controller class' => 'DrupalDefaultEntityController', 'static cache' => TRUE, + 'static cache max size' => 100, 'load hook' => $name . '_load', 'bundles' => array(), 'view modes' => array(), diff --git includes/entity.inc includes/entity.inc index ef30b8f..85aff95 100644 --- includes/entity.inc +++ includes/entity.inc @@ -48,7 +48,7 @@ interface DrupalEntityControllerInterface { */ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { - protected $entityCache; + public $entityCache; protected $entityType; protected $entityInfo; protected $hookLoadArguments; @@ -131,13 +131,6 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { $entities += $queried_entities; } - if ($this->cache) { - // Add entities to the cache if we are not loading a revision. - if (!empty($queried_entities) && !$revision_id) { - $this->cacheSet($queried_entities); - } - } - // Ensure that the returned array is ordered the same as the original // $ids array if this was passed in and remove any invalid ids. if ($passed_ids) { @@ -148,6 +141,12 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { } $entities = $passed_ids; } + if ($this->cache) { + // Add entities to the cache if we are not loading a revision. + if (!empty($queried_entities) && !$revision_id) { + $this->cacheSet($queried_entities); + } + } return $entities; } @@ -292,9 +291,18 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { } /** - * Store entities in the static entity cache. + * Store entities in the static entity cache. Least recently used (LRU) are + * purged in order to honor 'static cache max size' property. Use + * hook_entity_info_alter() to change this property. */ protected function cacheSet($entities) { - $this->entityCache += $entities; + // Prepend these items. + $this->entityCache = $entities + $this->entityCache; + + // Check if the cache is greater than the max cache size and purge + // the nodes at the bottom of the array as they are the least recently used. + if (count($this->entityCache) > $this->entityInfo['static cache max size']) { + $this->entityCache = array_slice($this->entityCache, 0, $this->entityInfo['static cache max size'], TRUE); + } } } diff --git modules/node/node.test modules/node/node.test index 889503b..b837b13 100644 --- modules/node/node.test +++ modules/node/node.test @@ -1574,3 +1574,71 @@ class NodeQueryAlter extends DrupalWebTestCase { } } } + +/** + * Test restriction of the number of nodes to be held in the static cache. + */ +class NodeLoadRestrictionTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Node restriction', + 'description' => "Check cache size restriction of node_load() to only retain no more than 'static cache max size'.", + 'group' => 'Node', + ); + } + + function testNodeRestrictions() { + $info = entity_get_info('node'); + for ($i = 0; $i < $info['static cache max size'] + 10; $i++) { + $node = $this->drupalCreateNode(array('type' => 'article')); + $node_keys[] = $node->nid; + } + + $node_cache = &drupal_static('node_load_multiple', array(), TRUE); + + // Attempt to overfill the node cache. + foreach ($node_keys as $nid) { + node_load($nid); + } + + // Check if the cache has too many nodes in it. + $this->assertTrue(count($node_cache) <= $info['static cache max size'], t('The static node cache is less than or equal to the Maximum cache size')); + } +} + +/** + * Test restriction of the number of nodes to make sure the most recent is at + * the beginning of the node cache and less likey to be removed. + */ +class NodeLoadPurgeOrderTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Node restriction purge order', + 'description' => 'Check cache size restriction of node_load() is moving the most recently loaded nodes to the beginning.', + 'group' => 'Node', + ); + } + + function testNodeRestrictionsPurgeOrder() { + $info = entity_get_info('node'); + for ($i = 0; $i < $info['static cache max size'] + 10; $i++) { + $node = $this->drupalCreateNode(array('type' => 'article')); + $node_keys[] = $node->nid; + } + + $popular_nid = array_shift($node_keys); + + $controller = entity_get_controller('node'); + $controller->resetCache(); + + // Fill the node cache. + foreach ($node_keys as $nid) { + node_load($nid); + node_load($popular_nid); + } + + $popular_node = reset($controller->entityCache); + // Check if the cache has the right node on top. + $this->assertEqual($popular_node->nid, $popular_nid, t('The node at the top of the cache is the popular node')); + } +}