From c22202ecdb6ca63e94e8e5a22e19ab298a61077a Mon Sep 17 00:00:00 2001 From: catch Date: Thu, 18 Jul 2019 12:03:22 +0100 Subject: [PATCH] Issue #3055443 by alexpott, amateescu, catch, Berdir, chr.fritsch: Switch to a memory backend for running the database updates --- core/lib/Drupal/Core/Cache/MemoryBackend.php | 30 +++++----- .../Core/Cache/MemoryCache/MemoryCache.php | 34 +++++------ core/lib/Drupal/Core/Update/UpdateBackend.php | 57 +++++++++++++++++++ .../Core/Update/UpdateCacheBackendFactory.php | 51 +++++++++++++++++ .../Core/Update/UpdateServiceProvider.php | 27 +++------ .../src/Functional/Update/UpdateCacheTest.php | 44 ++++++++++++++ 6 files changed, 187 insertions(+), 56 deletions(-) create mode 100644 core/lib/Drupal/Core/Update/UpdateBackend.php create mode 100644 core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php create mode 100644 core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php index d402d70b543..a0269f53010 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackend.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php @@ -64,26 +64,21 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf * Checks that items are either permanent or did not expire, and returns data * as appropriate. * - * @param object $cache + * @param array $cache * An item loaded from self::get() or self::getMultiple(). * @param bool $allow_invalid * (optional) If TRUE, cache items may be returned even if they have expired * or been invalidated. * - * @return mixed + * @return object|false * The item with data as appropriate or FALSE if there is no * valid item to load. */ - protected function prepareItem($cache, $allow_invalid) { - if (!isset($cache->data)) { + protected function prepareItem(array $cache, $allow_invalid) { + if (!isset($cache['data'])) { return FALSE; } - // The object passed into this function is the one stored in $this->cache. - // We must clone it as part of the preparation step so that the actual - // cache object is not affected by the unserialize() call or other - // manipulations of the returned object. - - $prepared = clone $cache; + $prepared = (object) $cache; $prepared->data = unserialize($prepared->data); // Check expire time. @@ -104,7 +99,10 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf $tags = array_unique($tags); // Sort the cache tags so that they are stored consistently in the database. sort($tags); - $this->cache[$cid] = (object) [ + + // Do not create an object at this point to minimize the number of objects + // garbage collection has to keep a track off. + $this->cache[$cid] = [ 'cid' => $cid, 'data' => serialize($data), 'created' => $this->getRequestTime(), @@ -148,7 +146,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf */ public function invalidate($cid) { if (isset($this->cache[$cid])) { - $this->cache[$cid]->expire = $this->getRequestTime() - 1; + $this->cache[$cid]['expire'] = $this->getRequestTime() - 1; } } @@ -158,7 +156,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf public function invalidateMultiple(array $cids) { $items = array_intersect_key($this->cache, array_flip($cids)); foreach ($items as $cid => $item) { - $this->cache[$cid]->expire = $this->getRequestTime() - 1; + $this->cache[$cid]['expire'] = $this->getRequestTime() - 1; } } @@ -167,8 +165,8 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf */ public function invalidateTags(array $tags) { foreach ($this->cache as $cid => $item) { - if (array_intersect($tags, $item->tags)) { - $this->cache[$cid]->expire = $this->getRequestTime() - 1; + if (array_intersect($tags, $item['tags'])) { + $this->cache[$cid]['expire'] = $this->getRequestTime() - 1; } } } @@ -178,7 +176,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf */ public function invalidateAll() { foreach ($this->cache as $cid => $item) { - $this->cache[$cid]->expire = $this->getRequestTime() - 1; + $this->cache[$cid]['expire'] = $this->getRequestTime() - 1; } } diff --git a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php index 32711082115..841e4568a07 100644 --- a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php @@ -8,40 +8,29 @@ use Drupal\Core\Cache\MemoryBackend; /** * Defines a memory cache implementation. * - * Stores cache items in memory using a PHP array. + * Stores cache items in memory using a PHP array. Cache data is not serialized + * thereby returning the same object as was cached. * * @ingroup cache */ class MemoryCache extends MemoryBackend implements MemoryCacheInterface { /** - * Prepares a cached item. - * - * Checks that items are either permanent or did not expire, and returns data - * as appropriate. - * - * @param object $cache - * An item loaded from self::get() or self::getMultiple(). - * @param bool $allow_invalid - * (optional) If TRUE, cache items may be returned even if they have expired - * or been invalidated. Defaults to FALSE. - * - * @return mixed - * The item with data as appropriate or FALSE if there is no - * valid item to load. + * {@inheritdoc} */ - protected function prepareItem($cache, $allow_invalid = FALSE) { - if (!isset($cache->data)) { + protected function prepareItem(array $cache, $allow_invalid = FALSE) { + if (!isset($cache['data'])) { return FALSE; } + $prepared = (object) $cache; // Check expire time. - $cache->valid = $cache->expire == static::CACHE_PERMANENT || $cache->expire >= $this->getRequestTime(); + $prepared->valid = $prepared->expire == static::CACHE_PERMANENT || $prepared->expire >= $this->getRequestTime(); - if (!$allow_invalid && !$cache->valid) { + if (!$allow_invalid && !$prepared->valid) { return FALSE; } - return $cache; + return $prepared; } /** @@ -51,8 +40,11 @@ class MemoryCache extends MemoryBackend implements MemoryCacheInterface { assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.'); $tags = array_unique($tags); - $this->cache[$cid] = (object) [ + // Do not create an object at this point to minimize the number of objects + // garbage collection has to keep a track off. + $this->cache[$cid] = [ 'cid' => $cid, + // Note that $data is not serialized. 'data' => $data, 'created' => $this->getRequestTime(), 'expire' => $expire, diff --git a/core/lib/Drupal/Core/Update/UpdateBackend.php b/core/lib/Drupal/Core/Update/UpdateBackend.php new file mode 100644 index 00000000000..08e4cfd1f6e --- /dev/null +++ b/core/lib/Drupal/Core/Update/UpdateBackend.php @@ -0,0 +1,57 @@ +backend = $backend; + } + + /** + * {@inheritdoc} + */ + public function delete($cid) { + parent::delete($cid); + $this->backend->delete($cid); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $cids) { + parent::deleteMultiple($cids); + $this->backend->deleteMultiple($cids); + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + parent::deleteAll(); + $this->backend->deleteAll(); + } + +} diff --git a/core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php b/core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php new file mode 100644 index 00000000000..e3f7dd12f16 --- /dev/null +++ b/core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php @@ -0,0 +1,51 @@ +cacheFactory = $cache_factory; + } + + /** + * {@inheritdoc} + */ + public function get($bin) { + if (!isset($this->bins[$bin])) { + $this->bins[$bin] = new UpdateBackend($this->cacheFactory->get($bin)); + } + return $this->bins[$bin]; + } + +} diff --git a/core/lib/Drupal/Core/Update/UpdateServiceProvider.php b/core/lib/Drupal/Core/Update/UpdateServiceProvider.php index 22c9131eb38..7ac68e6b18e 100644 --- a/core/lib/Drupal/Core/Update/UpdateServiceProvider.php +++ b/core/lib/Drupal/Core/Update/UpdateServiceProvider.php @@ -19,8 +19,16 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier */ public function register(ContainerBuilder $container) { $definition = new Definition('Drupal\Core\Cache\NullBackend', ['null']); + $definition->setDeprecated(TRUE, 'The "%service_id%\" service is deprecated. While updating Drupal all caches use \Drupal\Core\Update\UpdateBackend. See https://www.drupal.org/node/3066407'); $container->setDefinition('cache.null', $definition); + // Decorate the cache factory in order to use + // \Drupal\Core\Update\UpdateBackend while running updates. + $container + ->register('update.cache_factory', UpdateCacheBackendFactory::class) + ->setDecoratedService('cache_factory') + ->addArgument(new Reference('update.cache_factory.inner')); + $container->addCompilerPass(new UpdateCompilerPass(), PassConfig::TYPE_REMOVE, 128); } @@ -28,25 +36,6 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier * {@inheritdoc} */ public function alter(ContainerBuilder $container) { - // Ensures for some services that they don't cache. - $null_cache_service = new Reference('cache.null'); - - $definition = $container->getDefinition('asset.resolver'); - $definition->replaceArgument(5, $null_cache_service); - - $definition = $container->getDefinition('library.discovery.collector'); - $definition->replaceArgument(0, $null_cache_service); - - $definition = $container->getDefinition('theme.registry'); - $definition->replaceArgument(1, $null_cache_service); - $definition->replaceArgument(7, $null_cache_service); - - $definition = $container->getDefinition('theme.initialization'); - $definition->replaceArgument(2, $null_cache_service); - - $definition = $container->getDefinition('plugin.manager.element_info'); - $definition->replaceArgument(1, $null_cache_service); - // Prevent the alias-based path processor, which requires a path_alias db // table, from being registered to the path processor manager. We do this by // removing the tags that the compiler pass looks for. This means the url diff --git a/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php b/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php new file mode 100644 index 00000000000..1d9765da4dd --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php @@ -0,0 +1,44 @@ +set('will_not_exist_after_update', TRUE); + // The site might be broken at the time so logging in using the UI might + // not work, so we use the API itself. + $this->writeSettings([ + 'settings' => [ + 'update_free_access' => (object) [ + 'value' => TRUE, + 'required' => TRUE, + ], + ], + ]); + + // Clicking continue should clear the caches. + $this->drupalGet(Url::fromRoute('system.db_update', [], ['path_processing' => FALSE])); + $this->updateRequirementsProblem(); + $this->clickLink(t('Continue')); + + $this->assertFalse(\Drupal::cache()->get('will_not_exist_after_update', FALSE)); + } + +}