Unverified Commit 2999d457 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3097393 by godotislate, catch, berdir, jweowu, ndobromirov, wim leers,...

Issue #3097393 by godotislate, catch, berdir, jweowu, ndobromirov, wim leers, gapple: Cache tags grow endlessly
parent 65683671
Loading
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@

use Drupal\Component\Utility\SortArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheTagsPurgeInterface;
use Drupal\Core\DrupalKernel;

/**
@@ -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();
  }
+12 −1
Original line number Diff line number Diff line
@@ -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.
@@ -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.
   *
+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;

}
+17 −1
Original line number Diff line number Diff line
@@ -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;

@@ -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.
   */
+31 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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.');
  }

}