diff --git a/core/core.services.yml b/core/core.services.yml index 655bbe94e6e7a03fa325242ab3236c0ba6c913ef..db5240658f91dd22c4b2bd6b17bba326adce1cf9 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -450,6 +450,9 @@ services: arguments: ['@database'] tags: - { name: backend_overridable } + lock.persistent: + class: Drupal\Core\Lock\PersistentDatabaseLockBackend + arguments: ['@database'] router.request_context: class: Drupal\Core\Routing\RequestContext tags: diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 3efcd762e9629c3f50061dc8231d940be724053d..948330c5d4865934630aca3897ef0d0c5c9a373e 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -44,7 +44,7 @@ class ConfigImporter { /** * The name used to identify the lock. */ - const LOCK_ID = 'config_importer'; + const LOCK_NAME = 'config_importer'; /** * The storage comparer used to discover configuration changes. @@ -512,9 +512,9 @@ public function initialize() { // Ensure that the changes have been validated. $this->validate(); - if (!$this->lock->acquire(static::LOCK_ID)) { + if (!$this->lock->acquire(static::LOCK_NAME)) { // Another process is synchronizing configuration. - throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID)); + throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_NAME)); } $sync_steps = array(); @@ -611,7 +611,7 @@ protected function processConfigurations(array &$context) { protected function finish(array &$context) { $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this)); // The import is now complete. - $this->lock->release(static::LOCK_ID); + $this->lock->release(static::LOCK_NAME); $this->reset(); $context['message'] = t('Finalizing configuration synchronization.'); $context['finished'] = 1; @@ -996,7 +996,7 @@ protected function importInvokeRename($collection, $rename_name) { * TRUE if an import is already running, FALSE if not. */ public function alreadyImporting() { - return !$this->lock->lockMayBeAvailable(static::LOCK_ID); + return !$this->lock->lockMayBeAvailable(static::LOCK_NAME); } /** @@ -1007,13 +1007,13 @@ public function alreadyImporting() { * keep the services used by the importer in sync. */ protected function reInjectMe() { - $this->eventDispatcher = \Drupal::service('event_dispatcher'); - $this->configManager = \Drupal::service('config.manager'); - $this->lock = \Drupal::lock(); - $this->typedConfigManager = \Drupal::service('config.typed'); - $this->moduleHandler = \Drupal::moduleHandler(); - $this->themeHandler = \Drupal::service('theme_handler'); - $this->stringTranslation = \Drupal::service('string_translation'); + $this->_serviceIds = array(); + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value) && isset($value->_serviceId)) { + $this->$key = \Drupal::service($value->_serviceId); + } + } } } diff --git a/core/lib/Drupal/Core/Lock/PersistentDatabaseLockBackend.php b/core/lib/Drupal/Core/Lock/PersistentDatabaseLockBackend.php new file mode 100644 index 0000000000000000000000000000000000000000..6c7dfa246419a14045d84e754b0b3ae602f0cb96 --- /dev/null +++ b/core/lib/Drupal/Core/Lock/PersistentDatabaseLockBackend.php @@ -0,0 +1,36 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Lock\PersistentDatabaseLockBackend. + */ + +namespace Drupal\Core\Lock; + +use Drupal\Core\Database\Connection; + +/** + * Defines the persistent database lock backend. This backend is global for this + * Drupal installation. + * + * @ingroup lock + */ +class PersistentDatabaseLockBackend extends DatabaseLockBackend { + + /** + * Constructs a new PersistentDatabaseLockBackend. + * + * @param \Drupal\Core\Database\Connection $database + * The database connection. + */ + public function __construct(Connection $database) { + // Do not call the parent constructor to avoid registering a shutdown + // function that releases all the locks at the end of a request. + $this->database = $database; + // Set the lockId to a fixed string to make the lock ID the same across + // multiple requests. The lock ID is used as a page token to relate all the + // locks set during a request to each other. + // @see \Drupal\Core\Lock\LockBackendInterface::getLockId() + $this->lockId = 'persistent'; + } +} diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php index 21840a5ce6334812a565b5a71f8431267c68e104..b99c2903bde911cc3a84418eaa01852d3f8549f9 100644 --- a/core/modules/config/src/Form/ConfigSync.php +++ b/core/modules/config/src/Form/ConfigSync.php @@ -134,7 +134,7 @@ public static function create(ContainerInterface $container) { $container->get('config.storage.staging'), $container->get('config.storage'), $container->get('config.storage.snapshot'), - $container->get('lock'), + $container->get('lock.persistent'), $container->get('event_dispatcher'), $container->get('config.manager'), $container->get('config.typed'), diff --git a/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php b/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php index 60aab7fb41ede9b1aa5bdd2e510f126bc31ee29c..fc0d0ed565e50845cbbc6469c66dc45b62542fc8 100644 --- a/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php +++ b/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php @@ -55,7 +55,7 @@ protected function setUp() { $storage_comparer->createChangelist(), $this->container->get('event_dispatcher'), $this->container->get('config.manager'), - $this->container->get('lock'), + $this->container->get('lock.persistent'), $this->container->get('config.typed'), $this->container->get('module_handler'), $this->container->get('theme_handler'), diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php index 20187161c6629cdeb7b2a9f5c128bca6e5ae90aa..f3a7f398053ec85c0823a7a3354ef64480fc8911 100644 --- a/core/modules/config/src/Tests/ConfigImportUITest.php +++ b/core/modules/config/src/Tests/ConfigImportUITest.php @@ -228,14 +228,14 @@ function testImportLock() { // Acquire a fake-lock on the import mechanism. $config_importer = $this->configImporter(); - $this->container->get('lock')->acquire($config_importer::LOCK_ID); + $this->container->get('lock.persistent')->acquire($config_importer::LOCK_NAME); // Attempt to import configuration and verify that an error message appears. $this->drupalPostForm(NULL, array(), t('Import all')); $this->assertText(t('Another request may be synchronizing configuration already.')); // Release the lock, just to keep testing sane. - $this->container->get('lock')->release($config_importer::LOCK_ID); + $this->container->get('lock.persistent')->release($config_importer::LOCK_NAME); // Verify site name has not changed. $this->assertNotEqual($new_site_name, \Drupal::config('system.site')->get('name')); diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php index eff180c4c9a9242c1422ffef29b38af50d92b1b5..6e003974bede8748daaadca01af423f9c2234b09 100644 --- a/core/modules/config/src/Tests/ConfigImporterTest.php +++ b/core/modules/config/src/Tests/ConfigImporterTest.php @@ -527,4 +527,3 @@ function testUpdated() { $this->assertEqual(count($logs), 0); } } - diff --git a/core/modules/system/src/Tests/Lock/LockFunctionalTest.php b/core/modules/system/src/Tests/Lock/LockFunctionalTest.php index 6b1518cdb322aaa5f1bef12eba21caee350e95f4..846b44db3b100b8687aea0d934cc1ad1ea75219b 100644 --- a/core/modules/system/src/Tests/Lock/LockFunctionalTest.php +++ b/core/modules/system/src/Tests/Lock/LockFunctionalTest.php @@ -59,4 +59,29 @@ public function testLockAcquire() { $this->assertText($lock_acquired_exit, 'Lock acquired by the other request before exit.', 'Lock'); $this->assertTrue($lock->acquire('system_test_lock_exit'), 'Lock acquired by this request after the other request exits.', 'Lock'); } + + /** + * Tests that the persistent lock is persisted between requests. + */ + public function testPersistentLock() { + $persistent_lock = $this->container->get('lock.persistent'); + // Get a persistent lock. + $this->drupalGet('system-test/lock-persist/lock1'); + $this->assertText('TRUE: Lock successfully acquired in SystemTestController::lockPersist()'); + // Ensure that a shutdown function has not released the lock. + $this->assertFalse($persistent_lock->lockMayBeAvailable('lock1')); + $this->drupalGet('system-test/lock-persist/lock1'); + $this->assertText('FALSE: Lock not acquired in SystemTestController::lockPersist()'); + + // Get another persistent lock. + $this->drupalGet('system-test/lock-persist/lock2'); + $this->assertText('TRUE: Lock successfully acquired in SystemTestController::lockPersist()'); + $this->assertFalse($persistent_lock->lockMayBeAvailable('lock2')); + + // Release the first lock and try getting it again. + $persistent_lock->release('lock1'); + $this->drupalGet('system-test/lock-persist/lock1'); + $this->assertText('TRUE: Lock successfully acquired in SystemTestController::lockPersist()'); + } + } diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php index dd04e6c1cab1c925e53d1d1f439fcc0faf77fe31..c6b6903ac12381579d7e159e60c153947be9f287 100644 --- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php +++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php @@ -10,12 +10,38 @@ use Drupal\Core\Controller\ControllerBase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Drupal\Core\Lock\LockBackendInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Controller routines for system_test routes. */ class SystemTestController extends ControllerBase { + /** + * The persistent lock service. + * + * @var \Drupal\Core\Lock\LockBackendInterface + */ + protected $persistentLock; + + /** + * Constructs the SystemTestController. + * + * @param \Drupal\Core\Lock\LockBackendInterface $persistent_lock + * The persistent lock service. + */ + public function __construct(LockBackendInterface $persistent_lock) { + $this->persistentLock = $persistent_lock; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('lock.persistent')); + } + /** * Tests main content fallback. * @@ -56,6 +82,24 @@ public function lockExit() { return system_test_lock_exit(); } + /** + * Creates a lock that will persist across requests. + * + * @param string $lock_name + * The name of the persistent lock to acquire. + * + * @return string + * The text to display. + */ + public function lockPersist($lock_name) { + if ($this->persistentLock->acquire($lock_name)) { + return 'TRUE: Lock successfully acquired in SystemTestController::lockPersist()'; + } + else { + return 'FALSE: Lock not acquired in SystemTestController::lockPersist()'; + } + } + /** * Set cache tag on on the returned render array. */ diff --git a/core/modules/system/tests/modules/system_test/system_test.routing.yml b/core/modules/system/tests/modules/system_test/system_test.routing.yml index 32d235bab9faa27d30deed1b33eedec090232a67..530a2ca28b295856597d257e2105f6c8cffcd967 100644 --- a/core/modules/system/tests/modules/system_test/system_test.routing.yml +++ b/core/modules/system/tests/modules/system_test/system_test.routing.yml @@ -53,6 +53,14 @@ system_test.lock_exit: requirements: _access: 'TRUE' +system_test.lock_persist: + path: '/system-test/lock-persist/{lock_name}' + defaults: + _title: 'Persistent lock acquire' + _content: '\Drupal\system_test\Controller\SystemTestController::lockPersist' + requirements: + _access: 'TRUE' + system_test.cache_tags_page: path: '/system-test/cache_tags_page' defaults: