Commit 5b8960df authored by Dries's avatar Dries

- Patch #730060 by beejeebus, catch, Damien Tournoud, moshe weitzman,...

- Patch #730060 by beejeebus, catch, Damien Tournoud, moshe weitzman, msonnabaum, oriol_e9g, aspilicious: Fixed Replace CACHE_TEMPORARY, cache_clear_all() (no arguments) and minimum cache lifetime with cache tags support.
parent 75edfb15
......@@ -39,11 +39,6 @@
*/
const CACHE_PERMANENT = 0;
/**
* Indicates that the item should be removed at the next general cache wipe.
*/
const CACHE_TEMPORARY = -1;
/**
* @defgroup logging_severity_levels Logging severity levels
* @{
......
......@@ -67,19 +67,3 @@ function cache_invalidate(array $tags) {
function cache_get_backends() {
return variable_get('cache_classes', array('cache' => 'Drupal\Core\Cache\DatabaseBackend'));
}
/**
* Expires data from the block and page caches.
*/
function cache_clear_all() {
// @todo: remove before release.
if (func_get_args()) {
throw new Exception(t('cache_clear_all() no longer takes arguments, use cache() instead.'));
}
// Clear the block cache first, so stale data will
// not end up in the page cache.
if (module_exists('block')) {
cache('block')->expire();
}
cache('page')->expire();
}
......@@ -110,10 +110,11 @@
* specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
* implement
*
* The block cache is cleared in cache_clear_all(), and uses the same clearing
* policy than page cache (node, comment, user, taxonomy added or updated...).
* Blocks requiring more fine-grained clearing might consider disabling the
* built-in block cache (DRUPAL_NO_CACHE) and roll their own.
* The block cache is cleared when the 'content' cache tag is invalidated,
* following the same pattern as the page cache (node, comment, user, taxonomy
* added or updated...). Blocks requiring more fine-grained clearing might
* consider disabling the built-in block cache (DRUPAL_NO_CACHE)
* and roll their own.
*
* Note that user 1 is excluded from block caching.
*/
......@@ -5129,7 +5130,8 @@ function drupal_page_set_cache() {
'title' => drupal_get_title(),
'headers' => array(),
),
'expire' => CACHE_TEMPORARY,
'tags' => array('content' => TRUE),
'expire' => CACHE_PERMANENT,
'created' => REQUEST_TIME,
);
......@@ -5148,7 +5150,7 @@ function drupal_page_set_cache() {
if (config('system.performance')->get('page_compression') && extension_loaded('zlib')) {
$cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
}
cache('page')->set($cache->cid, $cache->data, $cache->expire);
cache('page')->set($cache->cid, $cache->data, $cache->expire, $cache->tags);
}
return $cache;
}
......@@ -6050,7 +6052,8 @@ function drupal_render_cache_set(&$markup, $elements) {
}
$bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
$expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT;
cache($bin)->set($cid, $data, $expire);
$tags = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
cache($bin)->set($cid, $data, $expire, $tags);
}
/**
......@@ -6123,7 +6126,7 @@ function drupal_render_collect_attached($elements, $return = FALSE) {
* - #pre_render: $function with a _pre_render suffix.
* - #cache: An associative array prepared for drupal_render_cache_set().
*/
function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORARY, $granularity = NULL) {
function drupal_render_cache_by_query($query, $function, $expire = CACHE_PERMANENT, $granularity = NULL) {
$cache_keys = array_merge(array($function), drupal_render_cid_parts($granularity));
$query->preExecute();
$cache_keys[] = hash('sha256', serialize(array((string) $query, $query->getArguments())));
......
......@@ -3257,13 +3257,13 @@ function _menu_clear_page_cache() {
// Clear the page and block caches, but at most twice, including at
// the end of the page load when there are multiple links saved or deleted.
if ($cache_cleared == 0) {
cache_clear_all();
cache_invalidate(array('content' => TRUE));
// Keep track of which menus have expanded items.
_menu_set_expanded_menus();
$cache_cleared = 1;
}
elseif ($cache_cleared == 1) {
drupal_register_shutdown_function('cache_clear_all');
drupal_register_shutdown_function('cache_invalidate', array('content', TRUE));
// Keep track of which menus have expanded items.
drupal_register_shutdown_function('_menu_set_expanded_menus');
$cache_cleared = 2;
......
......@@ -94,11 +94,9 @@ function getMultiple(&$cids);
* @param $expire
* One of the following values:
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
* explicitly told to using cache_clear_all() with a cache ID.
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
* general cache wipe.
* explicitly told to using cache->delete($cid).
* - A Unix timestamp: Indicates that the item should be kept at least until
* the given time, after which it behaves like CACHE_TEMPORARY.
* the given time.
* @param array $tags
* An array of tags to be stored with the cache item. These should normally
* identify objects used to build the cache item, which should trigger
......
......@@ -100,14 +100,6 @@ protected function prepareItem($cache) {
if (!isset($cache->data)) {
return FALSE;
}
// If the cached data is temporary and subject to a per-user minimum
// lifetime, compare the cache entry timestamp with the user session
// cache_expiration timestamp. If the cache entry is too old, ignore it.
$config = config('system.performance');
if ($cache->expire != CACHE_PERMANENT && $config->get('cache_lifetime') && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) {
// Ignore cache data that is too old and thus not valid for this user.
return FALSE;
}
// The cache data is invalid if any of its tags have been cleared since.
if ($cache->tags) {
......@@ -199,73 +191,17 @@ function flush() {
* Implements Drupal\Core\Cache\CacheBackendInterface::expire().
*/
function expire() {
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;
$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);
}
}
else {
// No minimum cache lifetime, flush all temporary cache entries now.
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::garbageCollection().
*/
function garbageCollection() {
$cache_lifetime = config('system.performance')->get('cache_lifetime');
// 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']);
}
}
// 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();
}
$this->expire();
}
/**
......
......@@ -44,8 +44,8 @@ function aggregator_aggregator_parse($feed) {
$feed->etag = $etag;
$feed->modified = $modified;
// Clear the cache.
cache_clear_all();
// Clear the page and block caches.
cache_invalidate(array('content' => TRUE));
return TRUE;
}
......
......@@ -205,7 +205,7 @@ function block_admin_display_form_submit($form, &$form_state) {
throw $e;
}
drupal_set_message(t('The block settings have been updated.'));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......@@ -603,7 +603,7 @@ function block_admin_configure_submit($form, &$form_state) {
throw $e;
}
drupal_set_message(t('The block configuration has been saved.'));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$form_state['redirect'] = 'admin/structure/block';
}
}
......@@ -712,7 +712,7 @@ function block_add_block_form_submit($form, &$form_state) {
}
drupal_set_message(t('The block has been created.'));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$form_state['redirect'] = 'admin/structure/block';
}
......@@ -760,7 +760,7 @@ function block_custom_block_delete_submit($form, &$form_state) {
->execute();
drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['info'])));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$form_state['redirect'] = 'admin/structure/block';
return;
}
......@@ -812,4 +812,3 @@ function template_preprocess_block_admin_display_form(&$variables) {
$variables['form_submit'] = drupal_render_children($variables['form']);
}
......@@ -374,7 +374,7 @@ function _block_get_renderable_region($list = array()) {
'keys' => array($block->module, $block->delta),
'granularity' => $block->cache,
'bin' => 'block',
'expire' => CACHE_TEMPORARY,
'tags' => array('content' => TRUE),
),
);
}
......
......@@ -66,7 +66,7 @@ function testCachePerRole() {
$this->assertText($old_content, t('Block is served from the cache.'));
// Clear the cache and verify that the stale data is no longer there.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$this->drupalGet('');
$this->assertNoText($old_content, t('Block cache clear removes stale cache data.'));
$this->assertText($current_content, t('Fresh block content is displayed after clearing the cache.'));
......
......@@ -194,7 +194,7 @@ function comment_admin_overview_submit($form, &$form_state) {
}
drupal_set_message(t('The update has been performed.'));
$form_state['redirect'] = 'admin/content/comment';
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......@@ -242,7 +242,7 @@ function comment_multiple_delete_confirm($form, &$form_state) {
function comment_multiple_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
comment_delete_multiple(array_keys($form_state['values']['comments']));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$count = count($form_state['values']['comments']);
watchdog('content', 'Deleted @count comments.', array('@count' => $count));
drupal_set_message(format_plural($count, 'Deleted 1 comment.', 'Deleted @count comments.'));
......@@ -301,7 +301,7 @@ function comment_confirm_delete_submit($form, &$form_state) {
drupal_set_message(t('The comment and all its replies have been deleted.'));
watchdog('content', 'Deleted comment @cid and its replies.', array('@cid' => $comment->cid));
// Clear the cache so an anonymous user sees that his comment was deleted.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$form_state['redirect'] = "node/$comment->nid";
}
......@@ -2096,7 +2096,7 @@ function comment_form_submit($form, &$form_state) {
$form_state['redirect'] = $redirect;
// Clear the block and page caches so that anonymous users see the comment
// they have posted.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......@@ -2439,7 +2439,7 @@ function comment_unpublish_by_keyword_action_submit($form, $form_state) {
*/
function comment_save_action(Comment $comment) {
comment_save($comment);
cache_clear_all();
cache_invalidate(array('content' => TRUE));
watchdog('action', 'Saved comment %title', array('%title' => $comment->subject));
}
......
......@@ -51,7 +51,7 @@ function testRecentCommentBlock() {
user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access comments'));
// drupalCreateNode() does not automatically flush content caches unlike
// posting a node from a node form.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
$this->drupalGet('');
$this->assertNoText($block['title'], t('Block was not found.'));
user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access comments'));
......
......@@ -96,7 +96,7 @@ function forum_form_submit($form, &$form_state) {
case SAVED_UPDATED:
drupal_set_message(t('The @type %term has been updated.', array('%term' => $form_state['values']['name'], '@type' => $type)));
// Clear the page and block caches to avoid stale data.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
break;
}
$form_state['redirect'] = 'admin/structure/forum';
......
......@@ -213,7 +213,7 @@ function testJavaScriptTranslation() {
// Test JavaScript translation rebuilding.
file_unmanaged_delete($js_file);
$this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found'))));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
_locale_rebuild_js($langcode);
$this->assertTrue($result = file_exists($js_file), t('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : t('not found'))));
}
......
......@@ -105,7 +105,7 @@ function testRecentNodeBlock() {
$node4 = $this->drupalCreateNode($default_settings);
// drupalCreateNode() does not automatically flush content caches unlike
// posting a node from a node form.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
// Test that all four nodes are shown.
$this->drupalGet('');
......
......@@ -611,7 +611,7 @@ function node_admin_nodes_submit($form, &$form_state) {
}
call_user_func_array($function, $args);
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
else {
// We need to rebuild the form to go to a second step. For example, to
......
......@@ -3447,7 +3447,7 @@ function node_access_rebuild($batch_mode = FALSE) {
if (!isset($batch)) {
drupal_set_message(t('Content permissions have been rebuilt.'));
node_access_needs_rebuild(FALSE);
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
}
......@@ -3497,7 +3497,7 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) {
else {
drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
}
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......
......@@ -520,7 +520,7 @@ function node_form_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
}
// Clear the page and block caches.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......
......@@ -756,7 +756,7 @@ function poll_vote($form, &$form_state) {
->condition('chid', $choice)
->execute();
cache_clear_all();
cache_invalidate(array('content' => TRUE));
if (!$user->uid) {
// The vote is recorded so the user gets the result view instead of the
......
cache: '0'
cache_lifetime: '0'
page_cache_maximum_age: '0'
page_compression: '0'
preprocess_css: '0'
......
......@@ -717,7 +717,7 @@ function system_theme_settings_submit($form, &$form_state) {
variable_set($key, $values);
drupal_set_message(t('The configuration options have been saved.'));
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......@@ -1689,15 +1689,9 @@ function system_performance_settings($form, &$form_state) {
'#default_value' => $config->get('cache'),
'#weight' => -2,
);
$period = drupal_map_assoc(array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval');
$period[0] = '<' . t('none') . '>';
$form['caching']['cache_lifetime'] = array(
'#type' => 'select',
'#title' => t('Minimum cache lifetime'),
'#default_value' => $config->get('cache_lifetime'),
'#options' => $period,
'#description' => t('Cached pages will not be re-created until at least this much time has elapsed.'),
);
$form['caching']['page_cache_maximum_age'] = array(
'#type' => 'select',
'#title' => t('Expiration of cached pages'),
......@@ -1765,7 +1759,6 @@ function system_performance_settings($form, &$form_state) {
function system_performance_settings_submit($form, &$form_state) {
$config = config('system.performance');
$config->set('cache', $form_state['values']['cache']);
$config->set('cache_lifetime', $form_state['values']['cache_lifetime']);
$config->set('page_cache_maximum_age', $form_state['values']['page_cache_maximum_age']);
$config->set('page_compression', $form_state['values']['page_compression']);
$config->set('preprocess_css', $form_state['values']['preprocess_css']);
......
......@@ -91,19 +91,6 @@ class CacheTestCase extends WebTestBase {
cache($bin)->expire();
}
/**
* Setup the lifetime settings for caching.
*
* @param $time
* The time in seconds the cache should minimal live.
*/
protected function setupLifetime($time) {
$config = config('system.performance');
$config->set('cache_lifetime', $time);
$config->save();
variable_set('cache_flush', 0);
}
}
class CacheSavingCase extends CacheTestCase {
......@@ -342,40 +329,6 @@ class CacheClearCase extends CacheTestCase {
}
}
/**
* Test minimum cache lifetime.
*/
function testMinimumCacheLifetime() {
// Set a minimum/maximum cache lifetime.
$this->setupLifetime(300);
// Login as a newly-created user.
$account = $this->drupalCreateUser(array());
$this->drupalLogin($account);
// Set two cache objects in different bins.
$data = $this->randomName(100);
cache()->set($data, $data, CACHE_TEMPORARY);
$cached = cache()->get($data);
$this->assertTrue(isset($cached->data) && $cached->data === $data, 'Cached item retrieved.');
cache('page')->set($data, $data, CACHE_TEMPORARY);
// Expire temporary items in the 'page' bin.
cache('page')->expire();
// Since the database cache uses REQUEST_TIME, set the $_SESSION variable
// manually to force it to the current time.
$_SESSION['cache_expiration']['cache_page'] = time();
// Items in the default cache bin should not be expired.
$cached = cache()->get($data);
$this->assertTrue(isset($cached->data) && $cached->data == $data, 'Cached item retrieved');
// Despite the minimum cache lifetime, the item in the 'page' bin should
// be invalidated for the current user.
$cached = cache('page')->get($data);
$this->assertFalse($cached, 'Cached item was invalidated');
}
/**
* Test clearing using cache tags.
*/
......@@ -545,32 +498,6 @@ class CacheInstallTestCase extends CacheTestCase {
$this->assertFalse($database_cache->get('cache_one'));
$this->assertFalse($database_cache->get('cache_two'));
// Store multiple items in the database cache with a temporary expiration,
// then use the installer's cache backend to expire outdated items.
// Afterwards, confirm that they are no longer in the database cache.
$database_cache->set('cache_one', 'One', CACHE_TEMPORARY);
$database_cache->set('cache_two', 'Two', CACHE_TEMPORARY);
$this->assertEqual($database_cache->get('cache_one')->data, 'One');
$this->assertEqual($database_cache->get('cache_two')->data, 'Two');
$install_cache->expire();
$this->assertFalse($database_cache->get('cache_one'));
$this->assertFalse($database_cache->get('cache_two'));
// Store multiple items in the database cache with a temporary expiration,
// then use the installer's cache backend to perform garbage collection.
// Afterwards, confirm that they are no longer in the database cache.
$database_cache->set('cache_one', 'One', CACHE_TEMPORARY);
$database_cache->set('cache_two', 'Two', CACHE_TEMPORARY);
$this->assertEqual($database_cache->get('cache_one')->data, 'One');
$this->assertEqual($database_cache->get('cache_two')->data, 'Two');
// After we've checked that the items are in the cache, we need to enable
// garbage collection before running the garbage collector.
config('system.performance')->set('cache_lifetime', 1)->save();
variable_set('cache_flush_cache_test', 1);
$install_cache->garbageCollection();
$this->assertFalse($database_cache->get('cache_one'));
$this->assertFalse($database_cache->get('cache_two'));
// Invalidate a tag using the installer cache, then check that the
// invalidation was recorded correctly in the database.
$install_cache->invalidateTags(array('tag'));
......
......@@ -84,6 +84,6 @@ protected function postDelete($entities) {
public function resetCache(array $ids = NULL) {
drupal_static_reset('taxonomy_vocabulary_get_names');
parent::resetCache($ids);
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
}
......@@ -825,7 +825,7 @@ function taxonomy_form_term_submit($form, &$form_state) {
drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
// Clear the page and block caches to avoid stale data.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
break;
}
......@@ -924,7 +924,7 @@ function taxonomy_term_confirm_delete_submit($form, &$form_state) {
if (!isset($_GET['destination'])) {
$form_state['redirect'] = 'admin/structure/taxonomy';
}
cache_clear_all();
cache_invalidate(array('content' => TRUE));
return;
}
......@@ -964,7 +964,7 @@ function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
$form_state['redirect'] = 'admin/structure/taxonomy';
cache_clear_all();
cache_invalidate(array('content' => TRUE));
return;
}
......
......@@ -865,9 +865,7 @@ function _update_cache_clear($cid = NULL, $wildcard = FALSE) {
* running update.php, we are likely to install a new version of something, in
* which case, we want to check for available update data again. However,
* because we have our own caching system, we need to directly clear the
* database table ourselves at this point and return nothing, for example, on
* sites that use memcache where cache_clear_all() won't know how to purge this
* data.
* the database table ourselves at this point and return nothing.
*
* However, we only want to do this from update.php, since otherwise, we'd lose
* all the available update data on every cron run. So, we specifically check if
......
......@@ -738,7 +738,7 @@ function user_admin_permissions_submit($form, &$form_state) {
drupal_set_message(t('The changes have been saved.'));
// Clear the cached pages and blocks.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......
......@@ -2266,7 +2266,7 @@ function _user_cancel($edit, $account, $method) {
}
// Clear the cache for anonymous users.
cache_clear_all();
cache_invalidate(array('content' => TRUE));
}
/**
......
......@@ -275,7 +275,7 @@ function user_profile_form_submit($form, &$form_state) {
unset($_SESSION['pass_reset_'. $account->uid]);
}
// Clear the page cache because pages can contain usernames and/or profile information:
cache_clear_all();
cache_invalidate(array('content' => TRUE));
drupal_set_message(t('The changes have been saved.'));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment