diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php index 98b25a83581d09193996e01d01affcb2f0c43272..a4d99a09dc029d50d56265f2920c6b86059f5722 100644 --- a/core/assets/scaffold/files/default.settings.php +++ b/core/assets/scaffold/files/default.settings.php @@ -807,6 +807,16 @@ */ $settings['entity_update_backup'] = TRUE; +/** + * State caching. + * + * State caching uses the cache collector pattern to cache all requested keys + * from the state API in a single cache entry, which can greatly reduce the + * amount of database queries. However, some sites may use state with a + * lot of dynamic keys which could result in a very large cache. + */ +$settings['state_cache'] = TRUE; + /** * Node migration type. * diff --git a/core/core.api.php b/core/core.api.php index 209178aec0ebb74916cfba1d67036cb6b0f9a930..6ed67d9fd26fdfe2334d4141398d3b42a00cb9f4 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -2597,8 +2597,12 @@ function hook_validation_constraint_alter(array &$definitions) { * called for each one. Example: * @code * public static function getSubscribedEvents() { - * // Subscribe to kernel terminate with priority 100. - * $events[KernelEvents::TERMINATE][] = array('onTerminate', 100); + * // Subscribe to kernel terminate with priority 105. The priority of 105 + * // ensures this subscriber is called before + * // \Drupal\Core\EventSubscriber\KernelDestructionSubscriber::onKernelTerminate() + * // and therefore can benefit from any caches using the cache collector + * // pattern. + * $events[KernelEvents::TERMINATE][] = array('onTerminate', 105); * // Subscribe to kernel request with default priority of 0. * $events[KernelEvents::REQUEST][] = array('onRequest'); * return $events; diff --git a/core/core.services.yml b/core/core.services.yml index 476b1443fbf5d5216c6ebbd9d3cb39c75628776b..bd6e5555052d30da6f41fc9760d378f89bc6886e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -551,7 +551,9 @@ services: Drupal\Core\Site\Settings: '@settings' state: class: Drupal\Core\State\State - arguments: ['@keyvalue'] + arguments: ['@keyvalue', '@cache.bootstrap', '@lock'] + tags: + - { name: needs_destruction } Drupal\Core\State\StateInterface: '@state' queue: class: Drupal\Core\Queue\QueueFactory diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/DevelopmentSettingsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/DevelopmentSettingsPass.php index 15b8d73cd29c286edd2fa710ee9c862234d9b8f3..33ff086a1f2d91ee3c0e10d72ebd6876364ea3be 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/DevelopmentSettingsPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DevelopmentSettingsPass.php @@ -15,10 +15,12 @@ class DevelopmentSettingsPass implements CompilerPassInterface { * {@inheritdoc} */ public function process(ContainerBuilder $container) { - /** @var \Drupal\Core\State\StateInterface $state */ - $state = $container->get('state'); - $twig_debug = $state->get('twig_debug', FALSE); - $twig_cache_disable = $state->get('twig_cache_disable', FALSE); + // This does access the state key value store directly to avoid edge-cases + // with lazy ghost objects during early bootstrap. + /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state_store */ + $state_store = $container->get('keyvalue')->get('state'); + $twig_debug = $state_store->get('twig_debug', FALSE); + $twig_cache_disable = $state_store->get('twig_cache_disable', FALSE); if ($twig_debug || $twig_cache_disable) { $twig_config = $container->getParameter('twig.config'); $twig_config['debug'] = $twig_debug; @@ -26,7 +28,7 @@ public function process(ContainerBuilder $container) { $container->setParameter('twig.config', $twig_config); } - if ($state->get('disable_rendered_output_cache_bins', FALSE)) { + if ($state_store->get('disable_rendered_output_cache_bins', FALSE)) { $cache_bins = ['page', 'dynamic_page_cache', 'render']; if (!$container->hasDefinition('cache.backend.null')) { $container->register('cache.backend.null', NullBackendFactory::class); diff --git a/core/lib/Drupal/Core/Routing/RoutePreloader.php b/core/lib/Drupal/Core/Routing/RoutePreloader.php index 673cebfd9f55e545d894bccd4b57f5dfb720775a..b3e1c1a6a4161b2cd2c8790cd779b7f21e538a72 100644 --- a/core/lib/Drupal/Core/Routing/RoutePreloader.php +++ b/core/lib/Drupal/Core/Routing/RoutePreloader.php @@ -2,8 +2,6 @@ namespace Drupal\Core\Routing; -use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\State\StateInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\KernelEvent; @@ -54,13 +52,10 @@ class RoutePreloader implements EventSubscriberInterface { * The route provider. * @param \Drupal\Core\State\StateInterface $state * The state key value store. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache - * The cache backend. */ - public function __construct(RouteProviderInterface $route_provider, StateInterface $state, CacheBackendInterface $cache) { + public function __construct(RouteProviderInterface $route_provider, StateInterface $state) { $this->routeProvider = $route_provider; $this->state = $state; - $this->cache = $cache; } /** @@ -73,17 +68,7 @@ public function onRequest(KernelEvent $event) { // Only preload on normal HTML pages, as they will display menu links. if ($this->routeProvider instanceof PreloadableRouteProviderInterface && $event->getRequest()->getRequestFormat() == 'html') { - // Ensure that the state query is cached to skip the database query, if - // possible. - $key = 'routing.non_admin_routes'; - if ($cache = $this->cache->get($key)) { - $routes = $cache->data; - } - else { - $routes = $this->state->get($key, []); - $this->cache->set($key, $routes, Cache::PERMANENT, ['routes']); - } - + $routes = $this->state->get('routing.non_admin_routes', []); if ($routes) { // Preload all the non-admin routes at once. $this->routeProvider->preLoadRoutes($routes); diff --git a/core/lib/Drupal/Core/State/State.php b/core/lib/Drupal/Core/State/State.php index 19c520d61fc5a5e62ced15a778c5659994b74781..e2d0c2c79966d07d6b1e2692140af2d42f860134 100644 --- a/core/lib/Drupal/Core/State/State.php +++ b/core/lib/Drupal/Core/State/State.php @@ -3,12 +3,16 @@ namespace Drupal\Core\State; use Drupal\Core\Asset\AssetQueryString; +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\CacheCollector; use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; +use Drupal\Core\Lock\LockBackendInterface; +use Drupal\Core\Site\Settings; /** * Provides the state system using a key value store. */ -class State implements StateInterface { +class State extends CacheCollector implements StateInterface { /** * Information about all deprecated state, keyed by legacy state key. @@ -33,21 +37,33 @@ class State implements StateInterface { */ protected $keyValueStore; - /** - * Static state cache. - * - * @var array - */ - protected $cache = []; - /** * Constructs a State object. * * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory * The key value store to use. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache + * The cache backend. + * @param \Drupal\Core\Lock\LockBackendInterface $lock + * The lock backend. */ - public function __construct(KeyValueFactoryInterface $key_value_factory) { + public function __construct(KeyValueFactoryInterface $key_value_factory, CacheBackendInterface $cache = NULL, LockBackendInterface $lock = NULL) { + if (!$cache) { + @trigger_error('Calling ' . __METHOD__ . '() without the $cache argument is deprecated in drupal:10.3.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/3177901', E_USER_DEPRECATED); + $cache = \Drupal::cache('discovery'); + } + if (!$lock) { + @trigger_error('Calling ' . __METHOD__ . '() without the $lock argument is deprecated in drupal:10.3.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/3177901', E_USER_DEPRECATED); + $lock = \Drupal::service('lock'); + } + parent::__construct('state', $cache, $lock); $this->keyValueStore = $key_value_factory->get('state'); + + // For backward compatibility, allow to opt-out of state caching, if cache + // is not explicitly enabled, flag the cache as already loaded. + if (Settings::get('state_cache') !== TRUE) { + $this->cacheLoaded = TRUE; + } } /** @@ -61,8 +77,17 @@ public function get($key, $default = NULL) { @trigger_error(self::$deprecatedState[$key]['message'], E_USER_DEPRECATED); $key = self::$deprecatedState[$key]['replacement']; } - $values = $this->getMultiple([$key]); - return $values[$key] ?? $default; + return parent::get($key) ?? $default; + } + + /** + * {@inheritdoc} + */ + protected function resolveCacheMiss($key) { + $value = $this->keyValueStore->get($key); + $this->storage[$key] = $value; + $this->persist($key); + return $value; } /** @@ -70,31 +95,8 @@ public function get($key, $default = NULL) { */ public function getMultiple(array $keys) { $values = []; - $load = []; foreach ($keys as $key) { - // Check if we have a value in the cache. - if (isset($this->cache[$key])) { - $values[$key] = $this->cache[$key]; - } - // Load the value if we don't have an explicit NULL value. - elseif (!array_key_exists($key, $this->cache)) { - $load[] = $key; - } - } - - if ($load) { - $loaded_values = $this->keyValueStore->getMultiple($load); - foreach ($load as $key) { - // If we find a value, even one that is NULL, add it to the cache and - // return it. - if (\array_key_exists($key, $loaded_values)) { - $values[$key] = $loaded_values[$key]; - $this->cache[$key] = $loaded_values[$key]; - } - else { - $this->cache[$key] = NULL; - } - } + $values[$key] = $this->get($key); } return $values; @@ -109,42 +111,69 @@ public function set($key, $value) { @trigger_error(self::$deprecatedState[$key]['message'], E_USER_DEPRECATED); $key = self::$deprecatedState[$key]['replacement']; } - $this->cache[$key] = $value; $this->keyValueStore->set($key, $value); + parent::set($key, $value); + $this->persist($key); } /** * {@inheritdoc} */ public function setMultiple(array $data) { + $this->keyValueStore->setMultiple($data); foreach ($data as $key => $value) { - $this->cache[$key] = $value; + parent::set($key, $value); + $this->persist($key); } - $this->keyValueStore->setMultiple($data); } /** * {@inheritdoc} */ public function delete($key) { - $this->deleteMultiple([$key]); + $this->keyValueStore->delete($key); + parent::delete($key); } /** * {@inheritdoc} */ public function deleteMultiple(array $keys) { + $this->keyValueStore->deleteMultiple($keys); foreach ($keys as $key) { - unset($this->cache[$key]); + parent::delete($key); } - $this->keyValueStore->deleteMultiple($keys); } /** * {@inheritdoc} */ public function resetCache() { - $this->cache = []; + $this->clear(); + } + + /** + * {@inheritdoc} + */ + protected function updateCache($lock = TRUE) { + // For backward compatibility, allow to opt-out of state caching, if cache + // is not explicitly enabled, there is no need to update it. + if (Settings::get('state_cache') !== TRUE) { + return; + } + parent::updateCache($lock); + } + + /** + * {@inheritdoc} + */ + protected function invalidateCache() { + // For backward compatibility, allow to opt-out of state caching, if cache + // is not explicitly enabled, there is no need to invalidate it. + if (Settings::get('state_cache') !== TRUE) { + return; + } + parent::invalidateCache(); } } diff --git a/core/lib/Drupal/Core/Test/RefreshVariablesTrait.php b/core/lib/Drupal/Core/Test/RefreshVariablesTrait.php index 0e00b111345a1a2897ad67bdc641163e8e193acd..d43b4fb9c86aefb095c0b6e76aa521bfaefec3ce 100644 --- a/core/lib/Drupal/Core/Test/RefreshVariablesTrait.php +++ b/core/lib/Drupal/Core/Test/RefreshVariablesTrait.php @@ -37,7 +37,7 @@ protected function refreshVariables() { } \Drupal::service('config.factory')->reset(); - \Drupal::service('state')->resetCache(); + \Drupal::service('state')->reset(); } } diff --git a/core/modules/automated_cron/src/EventSubscriber/AutomatedCron.php b/core/modules/automated_cron/src/EventSubscriber/AutomatedCron.php index c237f07ea34d7e1cd0d4feb92a47569514360825..174c2a4facd962847d68772d2ba922ef4ca8809a 100644 --- a/core/modules/automated_cron/src/EventSubscriber/AutomatedCron.php +++ b/core/modules/automated_cron/src/EventSubscriber/AutomatedCron.php @@ -74,7 +74,7 @@ public function onTerminate(TerminateEvent $event) { * An array of event listener definitions. */ public static function getSubscribedEvents(): array { - return [KernelEvents::TERMINATE => [['onTerminate', 100]]]; + return [KernelEvents::TERMINATE => [['onTerminate', 105]]]; } } diff --git a/core/modules/block/tests/src/Functional/BlockCacheTest.php b/core/modules/block/tests/src/Functional/BlockCacheTest.php index 68c0c0a108184e0e72c7256671b9a339f3d0581c..aac0ce3e1f9b91e742232f42f85971d06a7add67 100644 --- a/core/modules/block/tests/src/Functional/BlockCacheTest.php +++ b/core/modules/block/tests/src/Functional/BlockCacheTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\WaitTerminateTestTrait; /** * Tests block caching. @@ -12,6 +13,8 @@ */ class BlockCacheTest extends BrowserTestBase { + use WaitTerminateTestTrait; + /** * Modules to install. * diff --git a/core/modules/jsonapi/tests/src/Functional/CommentTest.php b/core/modules/jsonapi/tests/src/Functional/CommentTest.php index 3f03791ae7159b51e7967ccf77516c1777fbbac0..a540e05ee3b2a275c7834dd169d554bd940ddb01 100644 --- a/core/modules/jsonapi/tests/src/Functional/CommentTest.php +++ b/core/modules/jsonapi/tests/src/Functional/CommentTest.php @@ -13,6 +13,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; +use Drupal\Tests\WaitTerminateTrait; use Drupal\Tests\jsonapi\Traits\CommonCollectionFilterAccessTestPatternsTrait; use Drupal\user\Entity\User; use GuzzleHttp\RequestOptions; @@ -27,6 +28,7 @@ class CommentTest extends ResourceTestBase { use CommentTestTrait; use CommonCollectionFilterAccessTestPatternsTrait; + use WaitTerminateTrait; /** * {@inheritdoc} diff --git a/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php b/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php index d74a1def3940d8c50563c10bc44a0ef8f06239a3..567ab055e510e9cea90419bca30fb5e7a1838ee6 100644 --- a/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php +++ b/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php @@ -8,6 +8,7 @@ use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity; use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\WaitTerminateTestTrait; use Drupal\Core\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; @@ -21,6 +22,8 @@ */ class LanguageNegotiationContentEntityTest extends BrowserTestBase { + use WaitTerminateTestTrait; + /** * Modules to enable. * @@ -70,7 +73,8 @@ protected function setUp(): void { public function testDefaultConfiguration() { $translation = $this->entity; $this->drupalGet($translation->toUrl()); - $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + \Drupal::state()->clear(); + $last = \Drupal::state()->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertSame($last_content_language, $last_interface_language); @@ -78,7 +82,8 @@ public function testDefaultConfiguration() { $translation = $this->entity->getTranslation('es'); $this->drupalGet($translation->toUrl()); - $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + \Drupal::state()->clear(); + $last = \Drupal::state()->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertSame($last_content_language, $last_interface_language); @@ -86,7 +91,8 @@ public function testDefaultConfiguration() { $translation = $this->entity->getTranslation('fr'); $this->drupalGet($translation->toUrl()); - $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + \Drupal::state()->clear(); + $last = \Drupal::state()->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertSame($last_content_language, $last_interface_language); @@ -138,7 +144,8 @@ public function testEnabledLanguageContentNegotiator() { $translation = $this->entity; $this->drupalGet($translation->toUrl()); - $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + \Drupal::state()->clear(); + $last = \Drupal::state()->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; // Check that interface language and content language are the same as the @@ -149,7 +156,8 @@ public function testEnabledLanguageContentNegotiator() { $translation = $this->entity->getTranslation('es'); $this->drupalGet($translation->toUrl()); - $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + \Drupal::state()->clear(); + $last = \Drupal::state()->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertSame($last_interface_language, $default_site_langcode, 'Interface language did not change from the default site language.'); @@ -157,7 +165,8 @@ public function testEnabledLanguageContentNegotiator() { $translation = $this->entity->getTranslation('fr'); $this->drupalGet($translation->toUrl()); - $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + \Drupal::state()->clear(); + $last = \Drupal::state()->get('language_test.language_negotiation_last'); $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; $this->assertSame($last_interface_language, $default_site_langcode, 'Interface language did not change from the default site language.'); diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 2029f6608d9ce4861219abc364aa44c81d5b1b63..b981d235cb341e40e3c8ea18ea9c09da7acc3be1 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1586,6 +1586,17 @@ function (callable $hook, string $module) use (&$module_list, $update_registry, } } + // Add warning if state caching is not explicitly set. + if ($phase === 'runtime') { + if (Settings::get('state_cache') === NULL) { + $requirements['state_cache_not_set'] = [ + 'title' => t('State cache flag not set'), + 'value' => t("State cache flag \$settings['state_cache'] is not set. It is recommended to be set to TRUE in settings.php unless there are too many state keys. Drupal 11 will default to having state cache enabled."), + 'severity' => REQUIREMENT_WARNING, + ]; + } + } + return $requirements; } diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php index d0884c31b70589963592b052c1c14f5cc3ab90e0..d59628a13181512dd6c36cb402ffc460b8ca66fb 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php @@ -35,9 +35,9 @@ public function testFrontPageAuthenticatedWarmCache(): void { $performance_data = $this->collectPerformanceData(function () { $this->drupalGet('<front>'); }, 'authenticatedFrontPage'); - $this->assertGreaterThanOrEqual(15, $performance_data->getQueryCount()); - $this->assertLessThanOrEqual(17, $performance_data->getQueryCount()); - $this->assertSame(45, $performance_data->getCacheGetCount()); + $this->assertGreaterThanOrEqual(11, $performance_data->getQueryCount()); + $this->assertLessThanOrEqual(13, $performance_data->getQueryCount()); + $this->assertSame(46, $performance_data->getCacheGetCount()); $this->assertSame(0, $performance_data->getCacheSetCount()); $this->assertSame(0, $performance_data->getCacheDeleteCount()); } diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index aef02c1f032dd077bbe6adcaadf9635843ed13c6..e5766df9db610de6e9b2f21f8d5d19ea6b543f87 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -55,7 +55,8 @@ public function testAnonymous() { $this->drupalGet(''); }, 'standardFrontPage'); $this->assertNoJavaScript($performance_data); - $this->assertSame(68, $performance_data->getQueryCount()); + $this->assertGreaterThanOrEqual(53, $performance_data->getQueryCount()); + $this->assertLessThanOrEqual(58, $performance_data->getQueryCount()); $this->assertSame(137, $performance_data->getCacheGetCount()); $this->assertSame(47, $performance_data->getCacheSetCount()); $this->assertSame(0, $performance_data->getCacheDeleteCount()); @@ -66,7 +67,7 @@ public function testAnonymous() { }); $this->assertNoJavaScript($performance_data); - $this->assertSame(39, $performance_data->getQueryCount()); + $this->assertSame(34, $performance_data->getQueryCount()); $this->assertSame(95, $performance_data->getCacheGetCount()); $this->assertSame(16, $performance_data->getCacheSetCount()); $this->assertSame(0, $performance_data->getCacheDeleteCount()); @@ -77,7 +78,7 @@ public function testAnonymous() { $this->drupalGet('user/' . $user->id()); }); $this->assertNoJavaScript($performance_data); - $this->assertSame(41, $performance_data->getQueryCount()); + $this->assertSame(36, $performance_data->getQueryCount()); $this->assertSame(81, $performance_data->getCacheGetCount()); $this->assertSame(16, $performance_data->getCacheSetCount()); $this->assertSame(0, $performance_data->getCacheDeleteCount()); @@ -105,9 +106,9 @@ public function testLogin(): void { $this->submitLoginForm($account); }); - $this->assertGreaterThanOrEqual(38, $performance_data->getQueryCount()); - $this->assertLessThanOrEqual(40, $performance_data->getQueryCount()); - $this->assertSame(64, $performance_data->getCacheGetCount()); + $this->assertGreaterThanOrEqual(32, $performance_data->getQueryCount()); + $this->assertLessThanOrEqual(34, $performance_data->getQueryCount()); + $this->assertSame(65, $performance_data->getCacheGetCount()); $this->assertSame(1, $performance_data->getCacheSetCount()); $this->assertSame(1, $performance_data->getCacheDeleteCount()); } @@ -136,8 +137,8 @@ public function testLoginBlock(): void { $performance_data = $this->collectPerformanceData(function () use ($account) { $this->submitLoginForm($account); }); - $this->assertSame(49, $performance_data->getQueryCount()); - $this->assertSame(85, $performance_data->getCacheGetCount()); + $this->assertSame(39, $performance_data->getQueryCount()); + $this->assertSame(86, $performance_data->getCacheGetCount()); $this->assertSame(1, $performance_data->getCacheSetCount()); $this->assertSame(1, $performance_data->getCacheDeleteCount()); } diff --git a/core/tests/Drupal/KernelTests/Core/Routing/MatcherDumperTest.php b/core/tests/Drupal/KernelTests/Core/Routing/MatcherDumperTest.php index c156512c4c4ee18923ddbf0bcbdb39f07c7f90f0..1cf28a915dc00318e678f6a51ee4644b3ad615a8 100644 --- a/core/tests/Drupal/KernelTests/Core/Routing/MatcherDumperTest.php +++ b/core/tests/Drupal/KernelTests/Core/Routing/MatcherDumperTest.php @@ -4,9 +4,11 @@ use ColinODell\PsrTestLogger\TestLogger; use Drupal\Core\Database\Database; +use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; use Drupal\Core\Routing\MatcherDumper; use Drupal\Core\Routing\RouteCompiler; +use Drupal\Core\Lock\NullLockBackend; use Drupal\Core\State\State; use Drupal\KernelTests\KernelTestBase; use Drupal\Tests\Core\Routing\RoutingFixtures; @@ -46,7 +48,7 @@ protected function setUp(): void { parent::setUp(); $this->fixtures = new RoutingFixtures(); - $this->state = new State(new KeyValueMemoryFactory()); + $this->state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend()); $this->logger = new TestLogger(); } diff --git a/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php b/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php index 6df9a249fe228aca7d3bb7afe3b043820022e0f5..8f79ad498053ff29526b4251dce21efb20855577 100644 --- a/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php +++ b/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php @@ -6,6 +6,7 @@ use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Database\Database; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\Lock\NullLockBackend; use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Routing\MatcherDumper; use Drupal\Core\Routing\RouteProvider; @@ -95,7 +96,7 @@ class RouteProviderTest extends KernelTestBase { protected function setUp(): void { parent::setUp(); $this->fixtures = new RoutingFixtures(); - $this->state = new State(new KeyValueMemoryFactory()); + $this->state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend()); $this->currentPath = new CurrentPathStack(new RequestStack()); $this->cache = new MemoryBackend(); $this->pathProcessor = \Drupal::service('path_processor_manager'); diff --git a/core/tests/Drupal/Tests/Core/CronTest.php b/core/tests/Drupal/Tests/Core/CronTest.php index 09d3d5325b8040d9b0f83e5a20f2b09449efb032..1fada00f585de551e9064623bc6282330d63693c 100644 --- a/core/tests/Drupal/Tests/Core/CronTest.php +++ b/core/tests/Drupal/Tests/Core/CronTest.php @@ -6,8 +6,10 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ImmutableConfig; +use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Cron; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\Lock\LockBackendInterface; use Drupal\Core\Queue\DelayedRequeueException; use Drupal\Core\Queue\Memory; use Drupal\Core\Queue\RequeueException; @@ -64,7 +66,8 @@ protected function setUp(): void { parent::setUp(); // Construct a state object used for testing logger assertions. - $this->state = new State(new KeyValueMemoryFactory()); + $lock = $this->prophesize(LockBackendInterface::class); + $this->state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), $lock->reveal()); // Create a mock logger to set a flag in the resulting state. $logger = $this->prophesize('Drupal\Core\Logger\LoggerChannelInterface'); diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php index 19a9d2aabb51c9d9cf39ebcaeff2d20d8241c434..cf6360ea8a83942ea34e277773773d5b966033e8 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php @@ -13,6 +13,7 @@ use Drupal\Core\Extension\ThemeEngineExtensionList; use Drupal\Core\Extension\ThemeExtensionList; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\Lock\NullLockBackend; use Drupal\Core\State\State; use Drupal\Tests\UnitTestCase; use Prophecy\Argument; @@ -65,7 +66,7 @@ public function testRebuildThemeDataWithThemeParents() { ->alter('system_info', Argument::type('array'), Argument::type(Extension::class), Argument::any()) ->shouldBeCalled(); - $state = new State(new KeyValueMemoryFactory()); + $state = new State(new KeyValueMemoryFactory(), new NullBackend('bin'), new NullLockBackend()); $config_factory = $this->getConfigFactoryStub([ 'core.extension' => [ @@ -121,7 +122,7 @@ public function testRebuildThemeDataWithThemeParents() { public function testGetBaseThemes(array $themes, $theme, array $expected) { // Mocks and stubs. $module_handler = $this->prophesize(ModuleHandlerInterface::class); - $state = new State(new KeyValueMemoryFactory()); + $state = new State(new KeyValueMemoryFactory(), new NullBackend('bin'), new NullLockBackend()); $config_factory = $this->getConfigFactoryStub([]); $theme_engine_list = $this->prophesize(ThemeEngineExtensionList::class); $theme_listing = new ThemeExtensionList($this->root, 'theme', new NullBackend('test'), new InfoParser($this->root), $module_handler->reveal(), $state, $config_factory, $theme_engine_list->reveal(), 'test'); diff --git a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php index b1ef2e2f7f04c903325dbd4e5c407b907d1cd4a0..ed00c8ba6e0e7acfb03af428eabec70140b702ec 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Cache\VariationCache; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; use Drupal\Core\Security\TrustedCallbackInterface; +use Drupal\Core\Lock\NullLockBackend; use Drupal\Core\State\State; use Drupal\Core\Cache\Cache; @@ -447,7 +448,7 @@ public function testBubblingWithPrerender($test_element) { $this->setupMemoryCache(); // Mock the State service. - $memory_state = new State(new KeyValueMemoryFactory()); + $memory_state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend()); \Drupal::getContainer()->set('state', $memory_state); // Simulate the theme system/Twig: a recursive call to Renderer::render(), diff --git a/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php index 772a95679d02a7add2f9b07d11f22f078fb4e22c..a18b0a80af33706c7f89f7acc7b2edc9c4082227 100644 --- a/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php @@ -38,13 +38,6 @@ class RoutePreloaderTest extends UnitTestCase { */ protected $preloader; - /** - * The mocked cache. - * - * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit\Framework\MockObject\MockObject - */ - protected $cache; - /** * {@inheritdoc} */ @@ -53,8 +46,7 @@ protected function setUp(): void { $this->routeProvider = $this->createMock('Drupal\Core\Routing\PreloadableRouteProviderInterface'); $this->state = $this->createMock('\Drupal\Core\State\StateInterface'); - $this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); - $this->preloader = new RoutePreloader($this->routeProvider, $this->state, $this->cache); + $this->preloader = new RoutePreloader($this->routeProvider, $this->state); } /** diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 98b25a83581d09193996e01d01affcb2f0c43272..a4d99a09dc029d50d56265f2920c6b86059f5722 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -807,6 +807,16 @@ */ $settings['entity_update_backup'] = TRUE; +/** + * State caching. + * + * State caching uses the cache collector pattern to cache all requested keys + * from the state API in a single cache entry, which can greatly reduce the + * amount of database queries. However, some sites may use state with a + * lot of dynamic keys which could result in a very large cache. + */ +$settings['state_cache'] = TRUE; + /** * Node migration type. *