diff --git a/core/core.services.yml b/core/core.services.yml index f3c6cd9..7091ee2 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -274,6 +274,9 @@ services: lock: class: Drupal\Core\Lock\DatabaseLockBackend arguments: ['@database'] + lock.persistent: + class: Drupal\Core\Lock\PersistentLockBackend + arguments: ['@keyvalue.expirable'] user.tempstore: class: Drupal\user\TempStoreFactory arguments: ['@database', '@lock'] diff --git a/core/lib/Drupal/Core/Lock/PersistentLockBackend.php b/core/lib/Drupal/Core/Lock/PersistentLockBackend.php new file mode 100644 index 0000000..e8e12a1 --- /dev/null +++ b/core/lib/Drupal/Core/Lock/PersistentLockBackend.php @@ -0,0 +1,70 @@ +keyValue = $keyValueFactory->get('persistent_locks'); + } + + /** + * {@inheritdoc} + */ + public function acquire($name, $timeout = 30.0) { + if (!$this->lockMayBeAvailable($name)) { + return false; + } + + $this->keyValue->setWithExpire($name, true, $timeout); + + return true; + } + + /** + * {@inheritdoc} + */ + public function lockMayBeAvailable($name) { + return !$this->keyValue->has($name); + } + + /** + * {@inheritdoc} + */ + public function release($name) { + $this->keyValue->delete($name); + } + + /** + * {@inheritdoc} + */ + public function releaseAll($lockId = NULL) { + $this->keyValue->deleteAll(); + } + + /** + * {@inheritdoc} + */ + public function getLockId() { + return ''; + } +} \ No newline at end of file diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index 907340c..241bf90 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -131,7 +131,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('config.storage.staging'), $container->get('config.storage'), - $container->get('lock'), + $container->get('lock.persistent'), $container->get('event_dispatcher'), $container->get('config.manager'), $container->get('url_generator'), diff --git a/core/tests/Drupal/Tests/Core/Lock/PersistentLockBackendTest.php b/core/tests/Drupal/Tests/Core/Lock/PersistentLockBackendTest.php new file mode 100644 index 0000000..84f4aee --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Lock/PersistentLockBackendTest.php @@ -0,0 +1,156 @@ + 'PersistentLockBackend test', + 'description' => 'Tests the PersistentLockBackend class.', + 'group' => 'Lock', + ); + } + + /** + * Test acquiring a lock when it is available. + */ + public function testAcquireWhenAvailable() { + $store = $this->getStoreMock(FALSE); + + $store->expects($this->once()) + ->method('setWithExpire') + ->with($this->equalTo('door'), + $this->anything(), // We don't really care about the value. + $this->equalTo(static::TIMEOUT) + ); + + $factory = $this->getFactoryMock($store); + + $backend = new PersistentLockBackend($factory); + + $result = $backend->acquire('door', static::TIMEOUT); + + $this->assertTrue($result); + } + + /** + * Test acquiring a lock when it is not available. + */ + public function testAcquireWhenNotAvailable() { + $store = $this->getStoreMock(TRUE); + + $store->expects($this->never()) + ->method('setWithExpire'); + + $factory = $this->getFactoryMock($store); + + $backend = new PersistentLockBackend($factory); + + $result = $backend->acquire('door', static::TIMEOUT); + + $this->assertFalse($result); + } + + /** + * Test checking if a lock is available when it actually is available. + */ + public function testLockMayBeAvailableWhenAvailable() { + $store = $this->getStoreMock(FALSE); + $factory = $this->getFactoryMock($store); + $backend = new PersistentLockBackend($factory); + + $this->assertTrue($backend->lockMayBeAvailable("door")); + } + + /** + * Test checking if a lock is available when it is not available. + */ + public function testLockMayBeAvailableWhenNotAvailable() { + $store = $this->getStoreMock(TRUE); + $factory = $this->getFactoryMock($store); + $backend = new PersistentLockBackend($factory); + + $this->assertFalse($backend->lockMayBeAvailable("door")); + } + + /** + * Test releasing a lock. + */ + public function testRelease() { + $store = $this->getStoreMock(TRUE); + + $store->expects($this->once()) + ->method('delete') + ->with('door'); + + $factory = $this->getFactoryMock($store); + + $backend = new PersistentLockBackend($factory); + + $backend->release('door'); + } + + /** + * Test releasing all locks. + */ + public function testReleaseAll() { + $store = $this->getStoreMock(TRUE); + + $store->expects($this->once()) + ->method('deleteAll'); + + $factory = $this->getFactoryMock($store); + + (new PersistentLockBackend($factory))->releaseAll(); + } + + /** + * Mock a KeyValueExpirableFactory. + * + * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $store + * The store that you want the factor to return + * + * @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface + */ + protected function getFactoryMock(KeyValueStoreExpirableInterface $store) { + $factory = $this->getMock('\Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface'); + $factory->expects($this->any()) + ->method('get') + ->with($this->equalTo('persistent_locks')) + ->will($this->returnValue($store)); + return $factory; + } + + /** + * Mock a KeyValueStoreExpirable. + * + * @param $exists + * Whether the key that we use should exist. + * + * @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface + */ + protected function getStoreMock($exists) { + $store = $this->getMock('\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface'); + $store->expects($this->any()) + ->method('has') + ->with($this->equalTo('door')) + ->will($this->returnValue($exists)); + return $store; + } +}