diff --git a/includes/cache.inc b/includes/cache.inc index a19d3c3..b095eec 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -344,9 +344,6 @@ class DrupalDatabaseCache implements DrupalCacheInterface { */ function getMultiple(&$cids) { try { - // Garbage collection necessary when enforcing a minimum cache lifetime. - $this->garbageCollection($this->bin); - // When serving cached pages, the overhead of using db_select() was found // to add around 30% overhead to the request. Since $this->bin is a // variable, this means the call to db_query() here uses a concatenated @@ -381,37 +378,22 @@ class DrupalDatabaseCache implements DrupalCacheInterface { protected function garbageCollection() { $cache_lifetime = variable_get('cache_lifetime', 0); - // Clean-up the per-user cache expiration session data, so that the session - // handler can properly clean-up the session data for anonymous users. - if (isset($_SESSION['cache_expiration'])) { - $expire = REQUEST_TIME - $cache_lifetime; - foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) { - if ($timestamp < $expire) { - unset($_SESSION['cache_expiration'][$bin]); - } - } - if (!$_SESSION['cache_expiration']) { - unset($_SESSION['cache_expiration']); - } - } + // Clear expired items from the cache. + db_delete($this->bin) + ->condition('expire', CACHE_PERMANENT, '>') + ->condition('expire', REQUEST_TIME, '<') + ->execute(); - // Garbage collection of temporary items is only necessary when enforcing - // a minimum cache lifetime. - if (!$cache_lifetime) { - return; - } - // When cache lifetime is in force, avoid running garbage collection too - // often since this will remove temporary cache items indiscriminately. - $cache_flush = variable_get('cache_flush_' . $this->bin, 0); - if ($cache_flush && ($cache_flush + $cache_lifetime <= REQUEST_TIME)) { - // Reset the variable immediately to prevent a meltdown in heavy load situations. - variable_set('cache_flush_' . $this->bin, 0); - // Time to flush old cache data - db_delete($this->bin) - ->condition('expire', CACHE_PERMANENT, '<>') - ->condition('expire', $cache_flush, '<=') - ->execute(); - } + // Clear old temporary cache items. 'Old' is defined as older than the + // minimum cache lifetime, or 24 hours, whichever is the greatest. + $old = max($cache_lifetime, 86400); + + // Clear CACHE_TEMPORARY items that are older than the minimum cache + // lifetime. + db_delete($this->bin) + ->condition('expire', CACHE_TEMPORARY) + ->condition('created', REQUEST_TIME - $old, '<') + ->execute(); } /** @@ -428,8 +410,6 @@ class DrupalDatabaseCache implements DrupalCacheInterface { * valid item to load. */ protected function prepareItem($cache) { - global $user; - if (!isset($cache->data)) { return FALSE; } @@ -451,9 +431,30 @@ class DrupalDatabaseCache implements DrupalCacheInterface { } /** + * Cleans up the per-session cache expiration data. + */ + protected function cleanSession() { + $cache_lifetime = variable_get('cache_lifetime', 0); + if (isset($_SESSION['cache_expiration'])) { + $expire = REQUEST_TIME - $cache_lifetime; + foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) { + if ($timestamp < $expire) { + unset($_SESSION['cache_expiration'][$bin]); + } + } + if (!$_SESSION['cache_expiration']) { + unset($_SESSION['cache_expiration']); + } + } + } + + /** * Implements DrupalCacheInterface::set(). */ function set($cid, $data, $expire = CACHE_PERMANENT) { + // When setting cache items, clean up old session data in case it is stale. + $this->cleanSession(); + $fields = array( 'serialized' => 0, 'created' => REQUEST_TIME, @@ -483,32 +484,24 @@ class DrupalDatabaseCache implements DrupalCacheInterface { * Implements DrupalCacheInterface::clear(). */ function clear($cid = NULL, $wildcard = FALSE) { - global $user; - if (empty($cid)) { - if (variable_get('cache_lifetime', 0)) { - // We store the time in the current user's session. We then simulate - // that the cache was flushed for this user by not returning cached - // data that was cached before the timestamp. - $_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME; + if ($cache_lifetime = variable_get('cache_lifetime', 0)) { + // Clear expired items from the cache. + db_delete($this->bin) + ->condition('expire', CACHE_PERMANENT, '>') + ->condition('expire', REQUEST_TIME, '<') + ->execute(); - $cache_flush = variable_get('cache_flush_' . $this->bin, 0); - if ($cache_flush == 0) { - // This is the first request to clear the cache, start a timer. - variable_set('cache_flush_' . $this->bin, REQUEST_TIME); - } - elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) { - // Clear the cache for everyone, cache_lifetime seconds have - // passed since the first request to clear the cache. - db_delete($this->bin) - ->condition('expire', CACHE_PERMANENT, '<>') - ->condition('expire', REQUEST_TIME, '<') - ->execute(); - variable_set('cache_flush_' . $this->bin, 0); - } + // Clear CACHE_TEMPORARY items that are older than the minimum cache + // lifetime. + db_delete($this->bin) + ->condition('expire', CACHE_TEMPORARY) + ->condition('created', REQUEST_TIME - $cache_lifetime, '<') + ->execute(); } else { - // No minimum cache lifetime, flush all temporary cache entries now. + // No minimum cache lifetime. Flush all expired and temporary cache + // entries now. db_delete($this->bin) ->condition('expire', CACHE_PERMANENT, '<>') ->condition('expire', REQUEST_TIME, '<') diff --git a/modules/simpletest/tests/cache.test b/modules/simpletest/tests/cache.test index 47f2df3..92da6a7 100644 --- a/modules/simpletest/tests/cache.test +++ b/modules/simpletest/tests/cache.test @@ -320,6 +320,44 @@ class CacheClearCase extends CacheTestCase { } /** + * Tests CACHE_TEMPORARY behavior. + */ + function testCacheTemporary() { + $cache = _cache_get_object($this->default_bin); + // Set a permanent and temporary cache item. + $cache->set('test_cid_clear1', $this->default_value, CACHE_TEMPORARY); + $cache->set('test_cid_clear2', $this->default_value); + // Also set expired and yet to expire cache items. + $cache->set('test_cid_clear3', $this->default_value, REQUEST_TIME - 1000); + $cache->set('test_cid_clear4', $this->default_value, REQUEST_TIME + 1000); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear2', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear3', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear4', $this->default_value)); + + // Clear all items in the bin. Only the temporary and expired items should + // be removed. + $cache->clear(); + $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear2', $this->default_value)); + $this->assertFalse($this->checkCacheExists('test_cid_clear3', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear4', $this->default_value)); + + // Set a minimum cache lifetime. + $cache->set('test_cid_clear1', $this->default_value, CACHE_TEMPORARY); + $cache->set('test_cid_clear3', $this->default_value, REQUEST_TIME - 1000); + $cache->set('test_cid_clear4', $this->default_value, REQUEST_TIME + 1000); + $this->setUpLifetime(300); + + // Now after clearing the bin, only the expired item should be removed. + $cache->clear(); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear2', $this->default_value)); + $this->assertFalse($this->checkCacheExists('test_cid_clear3', $this->default_value)); + $this->assertTrue($this->checkCacheExists('test_cid_clear4', $this->default_value)); + } + + /** * Test drupal_flush_all_caches(). */ function testFlushAllCaches() { diff --git a/modules/system/system.install b/modules/system/system.install index ad15851..9a91c62 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -684,7 +684,7 @@ function system_schema() { ), ), 'indexes' => array( - 'expire' => array('expire'), + 'expire_created' => array('expire', 'created'), ), 'primary key' => array('cid'), ); @@ -3016,6 +3016,22 @@ function system_update_7073() { } /** + * Update all cache tables to use expire and created for their indexes. + */ +function system_update_7074() { + // Define the new index. + $fields = array('expire', 'created'); + + // Get a list of all cache tables. + $core = array('cache', 'cache_form', 'cache_path', 'cache_filter', 'cache_bootstrap', 'cache_page'); + $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + foreach ($cache_tables as $bin) { + db_drop_index($bin, 'expire'); + db_add_index($bin, 'expire_created', $fields); + } +} + +/** * @} End of "defgroup updates-7.x-extra". * The next series of updates should start at 8000. */ diff --git a/modules/system/system.module b/modules/system/system.module index 072850e..a70d30d 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -3037,7 +3037,17 @@ function system_cron() { $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); foreach ($cache_tables as $table) { - cache_clear_all(NULL, $table); + // If the cache backend provides a garbageCollection() method, call that. + // If it does not, expire all temporary and expired cache items. Limit + // these calls to once every 24 hours by default to avoid wiping the page + // and block caches every cron run. + $cache = _cache_get_object($table); + $name = 'cache_garbage_collect_' . $table; + $window = max(variable_get('cache_lifetime', 0), variable_get('cache_garbage_collection_frequency', 86400)); + if (flood_is_allowed($name, 1, $window, 'cron')) { + flood_register_event($name, 1, $window, 'cron'); + $cache->clear(); + } } // Cleanup the batch table and the queue for failed batches.