[lock] Provides default implementation when store does not supports the behavior
This commit is contained in:
parent
7cb8ba585d
commit
575b391b9b
@ -42,6 +42,8 @@ Lock
|
|||||||
----
|
----
|
||||||
|
|
||||||
* `MongoDbStore` does not implement `BlockingStoreInterface` anymore, typehint against `PersistingStoreInterface` instead.
|
* `MongoDbStore` does not implement `BlockingStoreInterface` anymore, typehint against `PersistingStoreInterface` instead.
|
||||||
|
* deprecated `NotSupportedException`, it shouldn't be thrown anymore.
|
||||||
|
* deprecated `RetryTillSaveStore`, logic has been moved in `Lock` and is not needed anymore.
|
||||||
|
|
||||||
Mime
|
Mime
|
||||||
----
|
----
|
||||||
|
@ -77,6 +77,12 @@ Inflector
|
|||||||
|
|
||||||
* The component has been removed, use `EnglishInflector` from the String component instead.
|
* The component has been removed, use `EnglishInflector` from the String component instead.
|
||||||
|
|
||||||
|
Lock
|
||||||
|
----
|
||||||
|
|
||||||
|
* Removed the `NotSupportedException`. It shouldn't be thrown anymore.
|
||||||
|
* Removed the `RetryTillSaveStore`. Logic has been moved in `Lock` and is not needed anymore.
|
||||||
|
|
||||||
Mailer
|
Mailer
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Lock;
|
||||||
|
|
||||||
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*/
|
||||||
|
interface BlockingSharedLockStoreInterface extends SharedLockStoreInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Waits until a key becomes free for reading, then stores the resource.
|
||||||
|
*
|
||||||
|
* @throws LockConflictedException
|
||||||
|
*/
|
||||||
|
public function waitAndSaveRead(Key $key);
|
||||||
|
}
|
@ -7,6 +7,8 @@ CHANGELOG
|
|||||||
* `MongoDbStore` does not implement `BlockingStoreInterface` anymore, typehint against `PersistingStoreInterface` instead.
|
* `MongoDbStore` does not implement `BlockingStoreInterface` anymore, typehint against `PersistingStoreInterface` instead.
|
||||||
* added support for shared locks
|
* added support for shared locks
|
||||||
* added `NoLock`
|
* added `NoLock`
|
||||||
|
* deprecated `NotSupportedException`, it shouldn't be thrown anymore.
|
||||||
|
* deprecated `RetryTillSaveStore`, logic has been moved in `Lock` and is not needed anymore.
|
||||||
|
|
||||||
5.1.0
|
5.1.0
|
||||||
-----
|
-----
|
||||||
|
@ -11,10 +11,14 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock\Exception;
|
namespace Symfony\Component\Lock\Exception;
|
||||||
|
|
||||||
|
trigger_deprecation('symfony/lock', '5.2', '%s is deprecated, You should stop using it, as it will be removed in 6.0.', NotSupportedException::class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NotSupportedException is thrown when an unsupported method is called.
|
* NotSupportedException is thrown when an unsupported method is called.
|
||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*
|
||||||
|
* @deprecated since Symfony 5.2
|
||||||
*/
|
*/
|
||||||
class NotSupportedException extends \LogicException implements ExceptionInterface
|
class NotSupportedException extends \LogicException implements ExceptionInterface
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,6 @@ use Symfony\Component\Lock\Exception\LockAcquiringException;
|
|||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\LockExpiredException;
|
use Symfony\Component\Lock\Exception\LockExpiredException;
|
||||||
use Symfony\Component\Lock\Exception\LockReleasingException;
|
use Symfony\Component\Lock\Exception\LockReleasingException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock is the default implementation of the LockInterface.
|
* Lock is the default implementation of the LockInterface.
|
||||||
@ -70,9 +69,16 @@ final class Lock implements SharedLockInterface, LoggerAwareInterface
|
|||||||
try {
|
try {
|
||||||
if ($blocking) {
|
if ($blocking) {
|
||||||
if (!$this->store instanceof BlockingStoreInterface) {
|
if (!$this->store instanceof BlockingStoreInterface) {
|
||||||
throw new NotSupportedException(sprintf('The store "%s" does not support blocking locks.', get_debug_type($this->store)));
|
while (true) {
|
||||||
|
try {
|
||||||
|
$this->store->wait($this->key);
|
||||||
|
} catch (LockConflictedException $e) {
|
||||||
|
usleep((100 + random_int(-10, 10)) * 1000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
$this->store->waitAndSave($this->key);
|
$this->store->waitAndSave($this->key);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->store->save($this->key);
|
$this->store->save($this->key);
|
||||||
}
|
}
|
||||||
@ -116,7 +122,9 @@ final class Lock implements SharedLockInterface, LoggerAwareInterface
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->store instanceof SharedLockStoreInterface) {
|
if (!$this->store instanceof SharedLockStoreInterface) {
|
||||||
throw new NotSupportedException(sprintf('The store "%s" does not support shared locks.', get_debug_type($this->store)));
|
$this->logger->debug('Store does not support ReadLocks, fallback to WriteLock.', ['resource' => $this->key]);
|
||||||
|
|
||||||
|
return $this->acquire($blocking);
|
||||||
}
|
}
|
||||||
if ($blocking) {
|
if ($blocking) {
|
||||||
$this->store->waitAndSaveRead($this->key);
|
$this->store->waitAndSaveRead($this->key);
|
||||||
@ -125,7 +133,7 @@ final class Lock implements SharedLockInterface, LoggerAwareInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->dirty = true;
|
$this->dirty = true;
|
||||||
$this->logger->debug('Successfully acquired the "{resource}" lock.', ['resource' => $this->key]);
|
$this->logger->debug('Successfully acquired the "{resource}" lock for reading.', ['resource' => $this->key]);
|
||||||
|
|
||||||
if ($this->ttl) {
|
if ($this->ttl) {
|
||||||
$this->refresh();
|
$this->refresh();
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
namespace Symfony\Component\Lock;
|
namespace Symfony\Component\Lock;
|
||||||
|
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
@ -22,15 +21,7 @@ interface SharedLockStoreInterface extends PersistingStoreInterface
|
|||||||
/**
|
/**
|
||||||
* Stores the resource if it's not locked for reading by someone else.
|
* Stores the resource if it's not locked for reading by someone else.
|
||||||
*
|
*
|
||||||
* @throws NotSupportedException
|
|
||||||
* @throws LockConflictedException
|
* @throws LockConflictedException
|
||||||
*/
|
*/
|
||||||
public function saveRead(Key $key);
|
public function saveRead(Key $key);
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits until a key becomes free for reading, then stores the resource.
|
|
||||||
*
|
|
||||||
* @throws LockConflictedException
|
|
||||||
*/
|
|
||||||
public function waitAndSaveRead(Key $key);
|
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Symfony\Component\Lock\Store;
|
|
||||||
|
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
|
||||||
use Symfony\Component\Lock\Key;
|
|
||||||
|
|
||||||
trait BlockingSharedLockStoreTrait
|
|
||||||
{
|
|
||||||
abstract public function saveRead(Key $key);
|
|
||||||
|
|
||||||
public function waitAndSaveRead(Key $key)
|
|
||||||
{
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
$this->saveRead($key);
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (LockConflictedException $e) {
|
|
||||||
usleep((100 + random_int(-10, 10)) * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,7 +16,6 @@ use Psr\Log\LoggerAwareTrait;
|
|||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||||
use Symfony\Component\Lock\SharedLockStoreInterface;
|
use Symfony\Component\Lock\SharedLockStoreInterface;
|
||||||
@ -29,7 +28,6 @@ use Symfony\Component\Lock\Strategy\StrategyInterface;
|
|||||||
*/
|
*/
|
||||||
class CombinedStore implements SharedLockStoreInterface, LoggerAwareInterface
|
class CombinedStore implements SharedLockStoreInterface, LoggerAwareInterface
|
||||||
{
|
{
|
||||||
use BlockingSharedLockStoreTrait;
|
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
|
|
||||||
@ -97,26 +95,17 @@ class CombinedStore implements SharedLockStoreInterface, LoggerAwareInterface
|
|||||||
|
|
||||||
public function saveRead(Key $key)
|
public function saveRead(Key $key)
|
||||||
{
|
{
|
||||||
if (null === $this->sharedLockStores) {
|
|
||||||
$this->sharedLockStores = [];
|
|
||||||
foreach ($this->stores as $store) {
|
|
||||||
if ($store instanceof SharedLockStoreInterface) {
|
|
||||||
$this->sharedLockStores[] = $store;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$successCount = 0;
|
$successCount = 0;
|
||||||
|
$failureCount = 0;
|
||||||
$storesCount = \count($this->stores);
|
$storesCount = \count($this->stores);
|
||||||
$failureCount = $storesCount - \count($this->sharedLockStores);
|
|
||||||
|
|
||||||
if (!$this->strategy->canBeMet($failureCount, $storesCount)) {
|
foreach ($this->stores as $store) {
|
||||||
throw new NotSupportedException(sprintf('The store "%s" does not contains enough compatible store to met the requirements.', get_debug_type($this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->sharedLockStores as $store) {
|
|
||||||
try {
|
try {
|
||||||
|
if ($store instanceof SharedLockStoreInterface) {
|
||||||
$store->saveRead($key);
|
$store->saveRead($key);
|
||||||
|
} else {
|
||||||
|
$store->save($key);
|
||||||
|
}
|
||||||
++$successCount;
|
++$successCount;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->debug('One store failed to save the "{resource}" lock.', ['resource' => $key, 'store' => $store, 'exception' => $e]);
|
$this->logger->debug('One store failed to save the "{resource}" lock.', ['resource' => $key, 'store' => $store, 'exception' => $e]);
|
||||||
|
@ -31,7 +31,6 @@ use Symfony\Component\Lock\SharedLockStoreInterface;
|
|||||||
class RedisStore implements SharedLockStoreInterface
|
class RedisStore implements SharedLockStoreInterface
|
||||||
{
|
{
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
use BlockingSharedLockStoreTrait;
|
|
||||||
|
|
||||||
private $redis;
|
private $redis;
|
||||||
private $initialTtl;
|
private $initialTtl;
|
||||||
|
@ -16,18 +16,21 @@ use Psr\Log\LoggerAwareTrait;
|
|||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Lock\BlockingStoreInterface;
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\Lock;
|
||||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||||
use Symfony\Component\Lock\SharedLockStoreInterface;
|
|
||||||
|
trigger_deprecation('symfony/lock', '5.2', '%s is deprecated, the "%s" class provides the logic when store is not blocking.', RetryTillSaveStore::class, Lock::class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RetryTillSaveStore is a PersistingStoreInterface implementation which decorate a non blocking PersistingStoreInterface to provide a
|
* RetryTillSaveStore is a PersistingStoreInterface implementation which decorate a non blocking PersistingStoreInterface to provide a
|
||||||
* blocking storage.
|
* blocking storage.
|
||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*
|
||||||
|
* @deprecated since Symfony 5.2
|
||||||
*/
|
*/
|
||||||
class RetryTillSaveStore implements BlockingStoreInterface, SharedLockStoreInterface, LoggerAwareInterface
|
class RetryTillSaveStore implements BlockingStoreInterface, LoggerAwareInterface
|
||||||
{
|
{
|
||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
|
|
||||||
@ -78,24 +81,6 @@ class RetryTillSaveStore implements BlockingStoreInterface, SharedLockStoreInter
|
|||||||
throw new LockConflictedException();
|
throw new LockConflictedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveRead(Key $key)
|
|
||||||
{
|
|
||||||
if (!$this->decorated instanceof SharedLockStoreInterface) {
|
|
||||||
throw new NotSupportedException(sprintf('The "%s" store must decorate a "%s" store.', get_debug_type($this), ShareLockStoreInterface::class));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->decorated->saveRead($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function waitAndSaveRead(Key $key)
|
|
||||||
{
|
|
||||||
if (!$this->decorated instanceof SharedLockStoreInterface) {
|
|
||||||
throw new NotSupportedException(sprintf('The "%s" store must decorate a "%s" store.', get_debug_type($this), ShareLockStoreInterface::class));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->decorated->waitAndSaveRead($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
namespace Symfony\Component\Lock\Tests\Store;
|
namespace Symfony\Component\Lock\Tests\Store;
|
||||||
|
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||||
|
|
||||||
@ -61,7 +60,6 @@ trait BlockingStoreTestTrait
|
|||||||
// This call should failed given the lock should already by acquired by the child
|
// This call should failed given the lock should already by acquired by the child
|
||||||
$store->save($key);
|
$store->save($key);
|
||||||
$this->fail('The store saves a locked key.');
|
$this->fail('The store saves a locked key.');
|
||||||
} catch (NotSupportedException $e) {
|
|
||||||
} catch (LockConflictedException $e) {
|
} catch (LockConflictedException $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +67,6 @@ trait BlockingStoreTestTrait
|
|||||||
posix_kill($childPID, \SIGHUP);
|
posix_kill($childPID, \SIGHUP);
|
||||||
|
|
||||||
// This call should be blocked by the child #1
|
// This call should be blocked by the child #1
|
||||||
try {
|
|
||||||
$store->waitAndSave($key);
|
$store->waitAndSave($key);
|
||||||
$this->assertTrue($store->exists($key));
|
$this->assertTrue($store->exists($key));
|
||||||
$store->delete($key);
|
$store->delete($key);
|
||||||
@ -77,9 +74,6 @@ trait BlockingStoreTestTrait
|
|||||||
// Now, assert the child process worked well
|
// Now, assert the child process worked well
|
||||||
pcntl_waitpid($childPID, $status1);
|
pcntl_waitpid($childPID, $status1);
|
||||||
$this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource');
|
$this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource');
|
||||||
} catch (NotSupportedException $e) {
|
|
||||||
$this->markTestSkipped(sprintf('The store %s does not support waitAndSave.', \get_class($store)));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Block SIGHUP signal
|
// Block SIGHUP signal
|
||||||
pcntl_sigprocmask(\SIG_BLOCK, [\SIGHUP]);
|
pcntl_sigprocmask(\SIG_BLOCK, [\SIGHUP]);
|
||||||
|
@ -14,7 +14,6 @@ namespace Symfony\Component\Lock\Tests\Store;
|
|||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use Symfony\Component\Lock\BlockingStoreInterface;
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||||
use Symfony\Component\Lock\SharedLockStoreInterface;
|
use Symfony\Component\Lock\SharedLockStoreInterface;
|
||||||
@ -355,18 +354,6 @@ class CombinedStoreTest extends AbstractStoreTest
|
|||||||
$this->store->delete($key);
|
$this->store->delete($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSaveReadWithIncompatibleStores()
|
|
||||||
{
|
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
|
||||||
|
|
||||||
$badStore = $this->createMock(PersistingStoreInterface::class);
|
|
||||||
|
|
||||||
$store = new CombinedStore([$badStore], new UnanimousStrategy());
|
|
||||||
$this->expectException(NotSupportedException::class);
|
|
||||||
|
|
||||||
$store->saveRead($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSaveReadWithCompatibleStore()
|
public function testSaveReadWithCompatibleStore()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
|
Reference in New Issue
Block a user