Loading core/includes/common.inc +8 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ use Drupal\Component\Utility\SortArray; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheTagsPurgeInterface; use Drupal\Core\DrupalKernel; /** Loading Loading @@ -418,6 +419,13 @@ function drupal_flush_all_caches($kernel = NULL): void { $module_handler = \Drupal::moduleHandler(); // Flush all persistent caches. $module_handler->invokeAll('cache_flush'); // Purge cache tags immediately before flushing cache bins. If a cache tag is // invalidated between the tags being purged and cache bins are flushed, then // it will be included in the checksum of any new cache items, but still valid // because the tag was written before the creation of cache item. if (($invalidator = \Drupal::service('cache_tags.invalidator')) && ($invalidator instanceof CacheTagsPurgeInterface)) { $invalidator->purge(); } foreach (Cache::getBins() as $cache_backend) { $cache_backend->deleteAll(); } Loading core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php +12 −1 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ /** * Passes cache tag events to classes that wish to respond to them. */ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface { class CacheTagsInvalidator implements CacheTagsInvalidatorInterface, CacheTagsPurgeInterface { /** * Holds an array of cache tags invalidators. Loading Loading @@ -53,6 +53,17 @@ public function resetChecksums() { } } /** * {@inheritdoc} */ public function purge(): void { foreach ($this->invalidators as $invalidator) { if ($invalidator instanceof CacheTagsPurgeInterface) { $invalidator->purge(); } } } /** * Adds a cache tags invalidator. * Loading core/lib/Drupal/Core/Cache/CacheTagsPurgeInterface.php 0 → 100644 +24 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Cache; /** * Provides purging of cache tag invalidations. * * Backends that persistently store cache tag invalidations can use this * interface to implement purging of cache tag invalidations. By default, cache * tag purging will only be called during drupal_flush_all_caches(), after all * other caches have been cleared. * * @ingroup cache */ interface CacheTagsPurgeInterface { /** * Purge cache tag invalidations. */ public function purge(): void; } core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php +17 −1 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ /** * Cache tags invalidations checksum implementation that uses the database. */ class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface, CacheTagsChecksumPreloadInterface { class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface, CacheTagsChecksumPreloadInterface, CacheTagsPurgeInterface { use CacheTagsChecksumTrait; Loading Loading @@ -69,6 +69,22 @@ protected function getTagInvalidationCounts(array $tags) { return []; } /** * {@inheritdoc} */ public function purge(): void { try { $this->connection->truncate('cachetags')->execute(); } catch (\Throwable $e) { // If the table does not exist yet, there is nothing to purge. if (!$this->ensureTableExists()) { throw $e; } } $this->reset(); } /** * Check if the cache tags table exists and create it if not. */ Loading core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTagTest.php +31 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Cache; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheTagsPurgeInterface; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\KernelTests\KernelTestBase; Loading Loading @@ -62,4 +63,34 @@ public function testTagInvalidations(): void { $this->assertEquals($invalidations_before + 1, $invalidations_after, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.'); } /** * Test cache tag purging. */ public function testTagsPurge(): void { $tags = ['test_tag:1', 'test_tag:2', 'test_tag:3']; /** @var \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_invalidator */ $checksum_invalidator = \Drupal::service('cache_tags.invalidator.checksum'); // Assert that initial current tag checksum is 0. This also ensures that the // 'cachetags' table is created, which at this point does not exist yet. $this->assertEquals(0, $checksum_invalidator->getCurrentChecksum($tags)); /** @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator */ $invalidator = \Drupal::service('cache_tags.invalidator'); $invalidator->invalidateTags($tags); // Checksum should be incremented by 1 by the invalidation for each tag. $this->assertEquals(3, $checksum_invalidator->getCurrentChecksum($tags)); // After purging, confirm checksum is 0 and the 'cachetags' table is empty. $this->assertInstanceOf(CacheTagsPurgeInterface::class, $invalidator); $invalidator->purge(); $this->assertEquals(0, $checksum_invalidator->getCurrentChecksum($tags)); $rows = Database::getConnection()->select('cachetags') ->fields('cachetags') ->countQuery() ->execute() ->fetchField(); $this->assertEmpty($rows, 'cachetags table is empty.'); } } Loading
core/includes/common.inc +8 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ use Drupal\Component\Utility\SortArray; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheTagsPurgeInterface; use Drupal\Core\DrupalKernel; /** Loading Loading @@ -418,6 +419,13 @@ function drupal_flush_all_caches($kernel = NULL): void { $module_handler = \Drupal::moduleHandler(); // Flush all persistent caches. $module_handler->invokeAll('cache_flush'); // Purge cache tags immediately before flushing cache bins. If a cache tag is // invalidated between the tags being purged and cache bins are flushed, then // it will be included in the checksum of any new cache items, but still valid // because the tag was written before the creation of cache item. if (($invalidator = \Drupal::service('cache_tags.invalidator')) && ($invalidator instanceof CacheTagsPurgeInterface)) { $invalidator->purge(); } foreach (Cache::getBins() as $cache_backend) { $cache_backend->deleteAll(); } Loading
core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php +12 −1 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ /** * Passes cache tag events to classes that wish to respond to them. */ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface { class CacheTagsInvalidator implements CacheTagsInvalidatorInterface, CacheTagsPurgeInterface { /** * Holds an array of cache tags invalidators. Loading Loading @@ -53,6 +53,17 @@ public function resetChecksums() { } } /** * {@inheritdoc} */ public function purge(): void { foreach ($this->invalidators as $invalidator) { if ($invalidator instanceof CacheTagsPurgeInterface) { $invalidator->purge(); } } } /** * Adds a cache tags invalidator. * Loading
core/lib/Drupal/Core/Cache/CacheTagsPurgeInterface.php 0 → 100644 +24 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Cache; /** * Provides purging of cache tag invalidations. * * Backends that persistently store cache tag invalidations can use this * interface to implement purging of cache tag invalidations. By default, cache * tag purging will only be called during drupal_flush_all_caches(), after all * other caches have been cleared. * * @ingroup cache */ interface CacheTagsPurgeInterface { /** * Purge cache tag invalidations. */ public function purge(): void; }
core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php +17 −1 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ /** * Cache tags invalidations checksum implementation that uses the database. */ class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface, CacheTagsChecksumPreloadInterface { class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface, CacheTagsChecksumPreloadInterface, CacheTagsPurgeInterface { use CacheTagsChecksumTrait; Loading Loading @@ -69,6 +69,22 @@ protected function getTagInvalidationCounts(array $tags) { return []; } /** * {@inheritdoc} */ public function purge(): void { try { $this->connection->truncate('cachetags')->execute(); } catch (\Throwable $e) { // If the table does not exist yet, there is nothing to purge. if (!$this->ensureTableExists()) { throw $e; } } $this->reset(); } /** * Check if the cache tags table exists and create it if not. */ Loading
core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTagTest.php +31 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Cache; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheTagsPurgeInterface; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\KernelTests\KernelTestBase; Loading Loading @@ -62,4 +63,34 @@ public function testTagInvalidations(): void { $this->assertEquals($invalidations_before + 1, $invalidations_after, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.'); } /** * Test cache tag purging. */ public function testTagsPurge(): void { $tags = ['test_tag:1', 'test_tag:2', 'test_tag:3']; /** @var \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_invalidator */ $checksum_invalidator = \Drupal::service('cache_tags.invalidator.checksum'); // Assert that initial current tag checksum is 0. This also ensures that the // 'cachetags' table is created, which at this point does not exist yet. $this->assertEquals(0, $checksum_invalidator->getCurrentChecksum($tags)); /** @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator */ $invalidator = \Drupal::service('cache_tags.invalidator'); $invalidator->invalidateTags($tags); // Checksum should be incremented by 1 by the invalidation for each tag. $this->assertEquals(3, $checksum_invalidator->getCurrentChecksum($tags)); // After purging, confirm checksum is 0 and the 'cachetags' table is empty. $this->assertInstanceOf(CacheTagsPurgeInterface::class, $invalidator); $invalidator->purge(); $this->assertEquals(0, $checksum_invalidator->getCurrentChecksum($tags)); $rows = Database::getConnection()->select('cachetags') ->fields('cachetags') ->countQuery() ->execute() ->fetchField(); $this->assertEmpty($rows, 'cachetags table is empty.'); } }