diff --git a/core/core.services.yml b/core/core.services.yml index d9b1839..823efc5 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -14,6 +14,7 @@ parameters: max-age: 0 contexts: ['session', 'user'] tags: [] + cache.configuration.override_permanent_ttl: 86400 factory.keyvalue: default: keyvalue.database http.response.debug_cacheability_headers: false @@ -207,31 +208,31 @@ services: tags: - { name: cache.bin } factory: cache_factory:get - arguments: [default] + arguments: [default, {'override_permanent_ttl': '%cache.configuration.override_permanent_ttl%'}] cache.entity: class: Drupal\Core\Cache\CacheBackendInterface tags: - { name: cache.bin } factory: cache_factory:get - arguments: [entity] + arguments: [entity, {'override_permanent_ttl': '%cache.configuration.override_permanent_ttl%'}] cache.menu: class: Drupal\Core\Cache\CacheBackendInterface tags: - { name: cache.bin } factory: cache_factory:get - arguments: [menu] + arguments: [menu, {'override_permanent_ttl': '%cache.configuration.override_permanent_ttl%'}] cache.render: class: Drupal\Core\Cache\CacheBackendInterface tags: - { name: cache.bin } factory: cache_factory:get - arguments: [render] + arguments: [render, {'override_permanent_ttl': '%cache.configuration.override_permanent_ttl%'}] cache.data: class: Drupal\Core\Cache\CacheBackendInterface tags: - { name: cache.bin } factory: cache_factory:get - arguments: [data] + arguments: [data, {'override_permanent_ttl': '%cache.configuration.override_permanent_ttl%'}] cache.discovery: class: Drupal\Core\Cache\CacheBackendInterface tags: diff --git a/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php b/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php index 50d1fa9..808851a 100644 --- a/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php @@ -7,7 +7,7 @@ /** * Defines the chained fast cache backend factory. */ -class ChainedFastBackendFactory implements CacheFactoryInterface { +class ChainedFastBackendFactory implements ConfigurableCacheFactoryInterface { use ContainerAwareTrait; @@ -63,26 +63,45 @@ public function __construct(Settings $settings = NULL, $consistent_service_name } /** - * Instantiates a chained, fast cache backend class for a given cache bin. - * - * @param string $bin - * The cache bin for which a cache backend object should be returned. - * - * @return \Drupal\Core\Cache\CacheBackendInterface - * The cache backend object associated with the specified bin. + * {@inheritdoc} */ - public function get($bin) { + public function get($bin, array $options = []) { + $consistent_backend = $this->getCacheBackendWithOptions($bin, $this->consistentServiceName, $options); // Use the chained backend only if there is a fast backend available; // otherwise, just return the consistent backend directly. if (isset($this->fastServiceName)) { + $fast_backend = $this->getCacheBackendWithOptions($bin, $this->fastServiceName, $options); return new ChainedFastBackend( - $this->container->get($this->consistentServiceName)->get($bin), - $this->container->get($this->fastServiceName)->get($bin), + $consistent_backend, + $fast_backend, $bin ); } else { - return $this->container->get($this->consistentServiceName)->get($bin); + return $consistent_backend; + } + } + + /** + * Gets a cache backend with options if it supports it. + * + * @param string $bin + * The cache bin. + * @param string $name + * The service name. + * @param array $options + * The options array to pass in. + * + * @return \Drupal\Core\Cache\CacheBackendInterface + * The cache backend. + */ + protected function getCacheBackendWithOptions($bin, $name, $options) { + $factory = $this->container->get($name); + if ($factory instanceof ConfigurableCacheBackendFactoryInterface) { + return $factory->get($bin, $options); + } + else { + return $factory->get($bin); } } diff --git a/core/lib/Drupal/Core/Cache/ConfigurableCacheFactoryInterface.php b/core/lib/Drupal/Core/Cache/ConfigurableCacheFactoryInterface.php new file mode 100644 index 0000000..a96a087 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/ConfigurableCacheFactoryInterface.php @@ -0,0 +1,23 @@ + $item) { + if (!isset($item['expire']) || $item['expire'] === CacheBackendInterface::CACHE_PERMANENT) { + if ($this->permanentTtl === CacheBackendInterface::CACHE_PERMANENT) { + $item['expire'] = CacheBackendInterface::CACHE_PERMANENT; + } + else { + $item['expire'] = $this->calculatePermanentExpire(); + } + } $item += array( - 'expire' => CacheBackendInterface::CACHE_PERMANENT, 'tags' => array(), ); @@ -235,6 +258,21 @@ protected function doSetMultiple(array $items) { } /** + * Calculates permanent expiry. + * + * @return int + * An expires timestamp based on the TTL with added 'jitter'. + */ + protected function calculatePermanentExpire() { + $minimum = $this->permanentTtl; + // Take the permanent TTL and apply jitter, so that items set immediately + // after a cache clear do not all expire at the same time. Use mt_rand() + // since rand() has limitations with large numbers on certain platforms such + // as Windows. + return mt_rand($minimum, $minimum * self::JITTER_MULTIPLIER) + REQUEST_TIME; + } + + /** * {@inheritdoc} */ public function delete($cid) { @@ -351,6 +389,13 @@ public function removeBin() { } /** + * {@inheritdoc} + */ + public function setPermanentTtl($ttl) { + $this->permanentTtl = $ttl; + } + + /** * Check if the cache bin exists and create it if not. */ protected function ensureBinExists() { diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php index 5cc87e9..130d996 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php @@ -4,7 +4,7 @@ use Drupal\Core\Database\Connection; -class DatabaseBackendFactory implements CacheFactoryInterface { +class DatabaseBackendFactory implements ConfigurableCacheFactoryInterface { /** * The database connection. @@ -34,16 +34,14 @@ function __construct(Connection $connection, CacheTagsChecksumInterface $checksu } /** - * Gets DatabaseBackend for the specified cache bin. - * - * @param $bin - * The cache bin for which the object is created. - * - * @return \Drupal\Core\Cache\DatabaseBackend - * The cache backend object for the specified cache bin. + * {@inheritdoc} */ - function get($bin) { - return new DatabaseBackend($this->connection, $this->checksumProvider, $bin); + function get($bin, array $options = []) { + $class = new DatabaseBackend($this->connection, $this->checksumProvider, $bin); + if (isset($options['override_permanent_ttl'])) { + $class->setPermanentTtl($options['override_permanent_ttl']); + } + return $class; } } diff --git a/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php b/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php index f2ddcf2..9117009 100644 --- a/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php +++ b/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php @@ -29,4 +29,33 @@ protected function createCacheBackend($bin) { return $backend; } + /** + * Test permanent TTL. + */ + public function testPermanentTtl() { + $bin = 'test'; + $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin); + $consistent_backend->setPermanentTtl(4); + // Since we're using two database backends, we need to provide a different + // bin name to avoid reading to and writing from the same table. + $fast_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin . '_2'); + $fast_backend->setPermanentTtl(2); + $backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin); + $cid = 'test'; + $value = 'test'; + $backend->set($cid, $value); + + // The item expiry will be taken from the fast backend, so reflects the + // settings on that backend. + $item = $backend->get($cid); + $this->assertTrue($item->expire <= REQUEST_TIME + 2 * DatabaseBackend::JITTER_MULTIPLIER); + $this->assertTrue($item->expire >= REQUEST_TIME + 2); + $item = $consistent_backend->get($cid); + $this->assertTrue($item->expire <= REQUEST_TIME + 4 * DatabaseBackend::JITTER_MULTIPLIER); + $this->assertTrue($item->expire >= REQUEST_TIME + 4); + $item = $fast_backend->get($cid); + $this->assertTrue($item->expire <= REQUEST_TIME + 2 * DatabaseBackend::JITTER_MULTIPLIER); + $this->assertTrue($item->expire >= REQUEST_TIME + 2); + } + } diff --git a/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php b/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php index 13630b5..23246c1 100644 --- a/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php +++ b/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php @@ -2,6 +2,7 @@ namespace Drupal\system\Tests\Cache; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\DatabaseBackend; /** @@ -48,4 +49,18 @@ public function testSetGet() { $this->assertIdentical($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id."); } + /** + * Test permanent TTL. + */ + public function testPermanentTtl() { + $backend = $this->getCacheBackend(); + $backend->setPermanentTtl(2); + $cid = 'test'; + $value = 'test'; + $backend->set($cid, $value); + $item = $backend->get($cid); + $this->assertTrue($item->expire !== CacheBackendInterface::CACHE_PERMANENT); + $this->assertTrue($item->expire <= REQUEST_TIME + 2 * DatabaseBackend::JITTER_MULTIPLIER); + } + }