From 1747f674f9695b5f6aedfe973b983d1616385c96 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org> Date: Mon, 24 Feb 2014 10:36:22 +0000 Subject: [PATCH] Issue #2202537 by alexpott: Avoid repeated cache tag invalidations. --- .../lib/Drupal/Core/Cache/DatabaseBackend.php | 15 ++- .../lib/Drupal/simpletest/WebTestBase.php | 1 + .../Tests/Cache/DatabaseBackendTagTest.php | 95 +++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendTagTest.php diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index cec770868d37..cd81acc65a53 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -165,12 +165,17 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array protected function doSet($cid, $data, $expire, $tags) { $flat_tags = $this->flattenTags($tags); $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array()); - // Remove tags that were already deleted during this request from the static - // cache so that another deletion for them will be correctly updated. + $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array()); + // Remove tags that were already deleted or invalidated during this request + // from the static caches so that another deletion or invalidation can + // occur. foreach ($flat_tags as $tag) { if (isset($deleted_tags[$tag])) { unset($deleted_tags[$tag]); } + if (isset($invalidated_tags[$tag])) { + unset($invalidated_tags[$tag]); + } } $checksum = $this->checksumTags($flat_tags); $fields = array( @@ -301,7 +306,13 @@ public function invalidateMultiple(array $cids) { public function invalidateTags(array $tags) { try { $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array()); + $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array()); foreach ($this->flattenTags($tags) as $tag) { + // Only invalidate tags once per request unless they are written again. + if (isset($invalidated_tags[$tag])) { + continue; + } + $invalidated_tags[$tag] = TRUE; unset($tag_cache[$tag]); $this->connection->merge('cache_tags') ->insertFields(array('invalidations' => 1)) diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index ccefb316a0dc..97954558159d 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -1055,6 +1055,7 @@ protected function refreshVariables() { // Clear the tag cache. drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache'); drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags'); + drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags'); $this->container->get('config.factory')->reset(); $this->container->get('state')->resetCache(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendTagTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendTagTest.php new file mode 100644 index 000000000000..1645779b3d47 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendTagTest.php @@ -0,0 +1,95 @@ +<?php + +/** + * @file + * Contains \Drupal\system\Tests\Cache\DatabaseBackendTagTest. + */ + +namespace Drupal\system\Tests\Cache; + +use Drupal\Core\Cache\Cache; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\simpletest\DrupalUnitTestBase; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Tests DatabaseBackend cache tag implementation. + */ +class DatabaseBackendTagTest extends DrupalUnitTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('system'); + + public static function getInfo() { + return array( + 'name' => 'Database backend tag test', + 'description' => 'Tests database backend cache tag implementation.', + 'group' => 'Cache', + ); + } + + /** + * {@inheritdoc} + */ + public function containerBuild(ContainerBuilder $container) { + parent::containerBuild($container); + // Change container to database cache backends. + $container + ->register('cache_factory', 'Drupal\Core\Cache\CacheFactory') + ->addArgument(new Reference('settings')) + ->addMethodCall('setContainer', array(new Reference('service_container'))); + } + + public function testTagInvalidations() { + // Create cache entry in multiple bins. + $tags = array('test_tag' => array(1, 2, 3)); + $bins = array('path', 'bootstrap', 'page'); + foreach ($bins as $bin) { + $bin = \Drupal::cache($bin); + $bin->set('test', 'value', Cache::PERMANENT, $tags); + $this->assertTrue($bin->get('test'), 'Cache item was set in bin.'); + } + + $invalidations_before = intval(db_select('cache_tags')->fields('cache_tags', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField()); + Cache::invalidateTags(array('test_tag' => array(2))); + + // Test that cache entry has been invalidated in multiple bins. + foreach ($bins as $bin) { + $bin = \Drupal::cache($bin); + $this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.'); + } + + // Test that only one tag invalidation has occurred. + $invalidations_after = intval(db_select('cache_tags')->fields('cache_tags', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField()); + $this->assertEqual($invalidations_after, $invalidations_before + 1, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.'); + } + + public function testTagDeletetions() { + // Create cache entry in multiple bins. + $tags = array('test_tag' => array(1, 2, 3)); + $bins = array('path', 'bootstrap', 'page'); + foreach ($bins as $bin) { + $bin = \Drupal::cache($bin); + $bin->set('test', 'value', Cache::PERMANENT, $tags); + $this->assertTrue($bin->get('test'), 'Cache item was set in bin.'); + } + + $deletions_before = intval(db_select('cache_tags')->fields('cache_tags', array('deletions'))->condition('tag', 'test_tag:2')->execute()->fetchField()); + Cache::deleteTags(array('test_tag' => array(2))); + + // Test that cache entry has been deleted in multiple bins. + foreach ($bins as $bin) { + $bin = \Drupal::cache($bin); + $this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.'); + } + + // Test that only one tag deletion has occurred. + $deletions_after = intval(db_select('cache_tags')->fields('cache_tags', array('deletions'))->condition('tag', 'test_tag:2')->execute()->fetchField()); + $this->assertEqual($deletions_after, $deletions_before + 1, 'Only one addition cache tag deletion has occurred after deleting a tag used in multiple bins.'); + } + +} -- GitLab