[Lock] Split \"StoreInterface\" into multiple interfaces with less responsability
This commit is contained in:
parent
b79a1bf229
commit
91fcbea977
36
src/Symfony/Component/Lock/BlockingStoreInterface.php
Normal file
36
src/Symfony/Component/Lock/BlockingStoreInterface.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?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;
|
||||||
|
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Hamza Amrouche <hamza.simperfit@gmail.com>
|
||||||
|
*/
|
||||||
|
interface BlockingStoreInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Waits until a key becomes free, then stores the resource.
|
||||||
|
*
|
||||||
|
* If the store does not support this feature it should throw a NotSupportedException.
|
||||||
|
*
|
||||||
|
* @throws LockConflictedException
|
||||||
|
* @throws NotSupportedException
|
||||||
|
*/
|
||||||
|
public function waitAndSave(Key $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the store can wait until a key becomes free before storing the resource.
|
||||||
|
*/
|
||||||
|
public function supportsWaitAndSave(): bool;
|
||||||
|
}
|
@ -4,8 +4,9 @@ CHANGELOG
|
|||||||
4.4.0
|
4.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
* added InvalidTtlException
|
* added InvalidTtlException
|
||||||
|
* deprecated `Symfony\Component\Lock\StoreInterface` in favor of `Symfony\Component\Lock\BlockingStoreInterface` and `Symfony\Component\Lock\PersistStoreInterface`
|
||||||
|
|
||||||
4.2.0
|
4.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ 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.
|
||||||
@ -36,12 +37,12 @@ final class Lock implements LockInterface, LoggerAwareInterface
|
|||||||
private $dirty = false;
|
private $dirty = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Key $key Resource to lock
|
* @param Key $key Resource to lock
|
||||||
* @param StoreInterface $store Store used to handle lock persistence
|
* @param PersistStoreInterface $store Store used to handle lock persistence
|
||||||
* @param float|null $ttl Maximum expected lock duration in seconds
|
* @param float|null $ttl Maximum expected lock duration in seconds
|
||||||
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
|
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
|
||||||
*/
|
*/
|
||||||
public function __construct(Key $key, StoreInterface $store, float $ttl = null, bool $autoRelease = true)
|
public function __construct(Key $key, PersistStoreInterface $store, float $ttl = null, bool $autoRelease = true)
|
||||||
{
|
{
|
||||||
$this->store = $store;
|
$this->store = $store;
|
||||||
$this->key = $key;
|
$this->key = $key;
|
||||||
@ -70,6 +71,9 @@ final class Lock implements LockInterface, LoggerAwareInterface
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($blocking) {
|
if ($blocking) {
|
||||||
|
if (!($this->store instanceof StoreInterface) && !($this->store instanceof BlockingStoreInterface && $this->store->supportsWaitAndSave())) {
|
||||||
|
throw new NotSupportedException(sprintf('The store "%s" does not support blocking locks.', \get_class($this->store)));
|
||||||
|
}
|
||||||
$this->store->waitAndSave($this->key);
|
$this->store->waitAndSave($this->key);
|
||||||
} else {
|
} else {
|
||||||
$this->store->save($this->key);
|
$this->store->save($this->key);
|
||||||
|
53
src/Symfony/Component/Lock/PersistStoreInterface.php
Normal file
53
src/Symfony/Component/Lock/PersistStoreInterface.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?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\LockAcquiringException;
|
||||||
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
|
use Symfony\Component\Lock\Exception\LockReleasingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*/
|
||||||
|
interface PersistStoreInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Stores the resource if it's not locked by someone else.
|
||||||
|
*
|
||||||
|
* @throws LockAcquiringException
|
||||||
|
* @throws LockConflictedException
|
||||||
|
*/
|
||||||
|
public function save(Key $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a resource from the storage.
|
||||||
|
*
|
||||||
|
* @throws LockReleasingException
|
||||||
|
*/
|
||||||
|
public function delete(Key $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the resource exists in the storage.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function exists(Key $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the TTL of a resource.
|
||||||
|
*
|
||||||
|
* @param float $ttl amount of seconds to keep the lock in the store
|
||||||
|
*
|
||||||
|
* @throws LockConflictedException
|
||||||
|
*/
|
||||||
|
public function putOffExpiration(Key $key, $ttl);
|
||||||
|
}
|
@ -18,6 +18,7 @@ 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\Exception\NotSupportedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
use Symfony\Component\Lock\Strategy\StrategyInterface;
|
use Symfony\Component\Lock\Strategy\StrategyInterface;
|
||||||
|
|
||||||
@ -26,27 +27,27 @@ use Symfony\Component\Lock\Strategy\StrategyInterface;
|
|||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
*/
|
*/
|
||||||
class CombinedStore implements StoreInterface, LoggerAwareInterface
|
class CombinedStore implements StoreInterface, PersistStoreInterface, LoggerAwareInterface
|
||||||
{
|
{
|
||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
|
|
||||||
/** @var StoreInterface[] */
|
/** @var PersistStoreInterface[] */
|
||||||
private $stores;
|
private $stores;
|
||||||
/** @var StrategyInterface */
|
/** @var StrategyInterface */
|
||||||
private $strategy;
|
private $strategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param StoreInterface[] $stores The list of synchronized stores
|
* @param PersistStoreInterface[] $stores The list of synchronized stores
|
||||||
* @param StrategyInterface $strategy
|
* @param StrategyInterface $strategy
|
||||||
*
|
*
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function __construct(array $stores, StrategyInterface $strategy)
|
public function __construct(array $stores, StrategyInterface $strategy)
|
||||||
{
|
{
|
||||||
foreach ($stores as $store) {
|
foreach ($stores as $store) {
|
||||||
if (!$store instanceof StoreInterface) {
|
if (!$store instanceof PersistStoreInterface) {
|
||||||
throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', StoreInterface::class, \get_class($store)));
|
throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', PersistStoreInterface::class, \get_class($store)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +93,12 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface
|
|||||||
throw new LockConflictedException();
|
throw new LockConflictedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function waitAndSave(Key $key)
|
public function waitAndSave(Key $key)
|
||||||
{
|
{
|
||||||
|
@trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED);
|
||||||
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
|
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,4 +186,12 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supportsWaitAndSave(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock\Store;
|
namespace Symfony\Component\Lock\Store;
|
||||||
|
|
||||||
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
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\LockStorageException;
|
use Symfony\Component\Lock\Exception\LockStorageException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +29,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
* @author Romain Neutron <imprec@gmail.com>
|
* @author Romain Neutron <imprec@gmail.com>
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
*/
|
*/
|
||||||
class FlockStore implements StoreInterface
|
class FlockStore implements StoreInterface, BlockingStoreInterface, PersistStoreInterface
|
||||||
{
|
{
|
||||||
private $lockPath;
|
private $lockPath;
|
||||||
|
|
||||||
@ -64,6 +66,14 @@ class FlockStore implements StoreInterface
|
|||||||
$this->lock($key, true);
|
$this->lock($key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supportsWaitAndSave(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private function lock(Key $key, $blocking)
|
private function lock(Key $key, $blocking)
|
||||||
{
|
{
|
||||||
// The lock is maybe already acquired.
|
// The lock is maybe already acquired.
|
||||||
|
@ -15,6 +15,7 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
|||||||
use Symfony\Component\Lock\Exception\InvalidTtlException;
|
use Symfony\Component\Lock\Exception\InvalidTtlException;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,7 +23,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
*/
|
*/
|
||||||
class MemcachedStore implements StoreInterface
|
class MemcachedStore implements StoreInterface, PersistStoreInterface
|
||||||
{
|
{
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
|
|
||||||
@ -69,8 +70,12 @@ class MemcachedStore implements StoreInterface
|
|||||||
$this->checkNotExpired($key);
|
$this->checkNotExpired($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function waitAndSave(Key $key)
|
public function waitAndSave(Key $key)
|
||||||
{
|
{
|
||||||
|
@trigger_error(sprintf('%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__));
|
||||||
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
|
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Lock\Exception\InvalidTtlException;
|
|||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,7 +35,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
*/
|
*/
|
||||||
class PdoStore implements StoreInterface
|
class PdoStore implements StoreInterface, PersistStoreInterface
|
||||||
{
|
{
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
|
|
||||||
@ -145,6 +146,7 @@ class PdoStore implements StoreInterface
|
|||||||
*/
|
*/
|
||||||
public function waitAndSave(Key $key)
|
public function waitAndSave(Key $key)
|
||||||
{
|
{
|
||||||
|
@trigger_error(sprintf('%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__));
|
||||||
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', __METHOD__));
|
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', __METHOD__));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
|||||||
use Symfony\Component\Lock\Exception\InvalidTtlException;
|
use Symfony\Component\Lock\Exception\InvalidTtlException;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +25,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
*/
|
*/
|
||||||
class RedisStore implements StoreInterface
|
class RedisStore implements StoreInterface, PersistStoreInterface
|
||||||
{
|
{
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
|
|
||||||
@ -77,6 +78,7 @@ class RedisStore implements StoreInterface
|
|||||||
*/
|
*/
|
||||||
public function waitAndSave(Key $key)
|
public function waitAndSave(Key $key)
|
||||||
{
|
{
|
||||||
|
@trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED);
|
||||||
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
|
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,8 +14,10 @@ namespace Symfony\Component\Lock\Store;
|
|||||||
use Psr\Log\LoggerAwareInterface;
|
use Psr\Log\LoggerAwareInterface;
|
||||||
use Psr\Log\LoggerAwareTrait;
|
use Psr\Log\LoggerAwareTrait;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +26,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
*/
|
*/
|
||||||
class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
|
class RetryTillSaveStore implements PersistStoreInterface, BlockingStoreInterface, StoreInterface, LoggerAwareInterface
|
||||||
{
|
{
|
||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
|
|
||||||
@ -33,11 +35,11 @@ class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
|
|||||||
private $retryCount;
|
private $retryCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param StoreInterface $decorated The decorated StoreInterface
|
* @param PersistStoreInterface $decorated The decorated StoreInterface
|
||||||
* @param int $retrySleep Duration in ms between 2 retry
|
* @param int $retrySleep Duration in ms between 2 retry
|
||||||
* @param int $retryCount Maximum amount of retry
|
* @param int $retryCount Maximum amount of retry
|
||||||
*/
|
*/
|
||||||
public function __construct(StoreInterface $decorated, int $retrySleep = 100, int $retryCount = PHP_INT_MAX)
|
public function __construct(PersistStoreInterface $decorated, int $retrySleep = 100, int $retryCount = PHP_INT_MAX)
|
||||||
{
|
{
|
||||||
$this->decorated = $decorated;
|
$this->decorated = $decorated;
|
||||||
$this->retrySleep = $retrySleep;
|
$this->retrySleep = $retrySleep;
|
||||||
@ -99,4 +101,12 @@ class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
|
|||||||
{
|
{
|
||||||
return $this->decorated->exists($key);
|
return $this->decorated->exists($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supportsWaitAndSave(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,11 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock\Store;
|
namespace Symfony\Component\Lock\Store;
|
||||||
|
|
||||||
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
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\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +23,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
*/
|
*/
|
||||||
class SemaphoreStore implements StoreInterface
|
class SemaphoreStore implements StoreInterface, PersistStoreInterface, BlockingStoreInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the store is supported.
|
* Returns whether or not the store is supported.
|
||||||
@ -112,4 +114,12 @@ class SemaphoreStore implements StoreInterface
|
|||||||
{
|
{
|
||||||
return $key->hasState(__CLASS__);
|
return $key->hasState(__CLASS__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supportsWaitAndSave(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ use Symfony\Component\Lock\Exception\LockConflictedException;
|
|||||||
use Symfony\Component\Lock\Exception\LockReleasingException;
|
use Symfony\Component\Lock\Exception\LockReleasingException;
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +24,7 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*
|
*
|
||||||
* @author Ganesh Chandrasekaran <gchandrasekaran@wayfair.com>
|
* @author Ganesh Chandrasekaran <gchandrasekaran@wayfair.com>
|
||||||
*/
|
*/
|
||||||
class ZookeeperStore implements StoreInterface
|
class ZookeeperStore implements StoreInterface, PersistStoreInterface
|
||||||
{
|
{
|
||||||
use ExpiringStoreTrait;
|
use ExpiringStoreTrait;
|
||||||
|
|
||||||
@ -87,6 +88,7 @@ class ZookeeperStore implements StoreInterface
|
|||||||
*/
|
*/
|
||||||
public function waitAndSave(Key $key)
|
public function waitAndSave(Key $key)
|
||||||
{
|
{
|
||||||
|
@trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED);
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,26 +11,18 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock;
|
namespace Symfony\Component\Lock;
|
||||||
|
|
||||||
use Symfony\Component\Lock\Exception\LockAcquiringException;
|
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\LockReleasingException;
|
|
||||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StoreInterface defines an interface to manipulate a lock store.
|
* StoreInterface defines an interface to manipulate a lock store.
|
||||||
*
|
*
|
||||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*
|
||||||
|
* @deprecated "Symfony\Component\Lock\StoreInterface" is deprecated since Symfony 4.4 and has been split into "Symfony\Component\Lock\PersistStoreInterface", "Symfony\Component\Lock\BlockingStoreInterface".'
|
||||||
*/
|
*/
|
||||||
interface StoreInterface
|
interface StoreInterface extends PersistStoreInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Stores the resource if it's not locked by someone else.
|
|
||||||
*
|
|
||||||
* @throws LockAcquiringException
|
|
||||||
* @throws LockConflictedException
|
|
||||||
*/
|
|
||||||
public function save(Key $key);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until a key becomes free, then stores the resource.
|
* Waits until a key becomes free, then stores the resource.
|
||||||
*
|
*
|
||||||
@ -40,29 +32,4 @@ interface StoreInterface
|
|||||||
* @throws NotSupportedException
|
* @throws NotSupportedException
|
||||||
*/
|
*/
|
||||||
public function waitAndSave(Key $key);
|
public function waitAndSave(Key $key);
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends the ttl of a resource.
|
|
||||||
*
|
|
||||||
* If the store does not support this feature it should throw a NotSupportedException.
|
|
||||||
*
|
|
||||||
* @param float $ttl amount of seconds to keep the lock in the store
|
|
||||||
*
|
|
||||||
* @throws LockConflictedException
|
|
||||||
*/
|
|
||||||
public function putOffExpiration(Key $key, $ttl);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a resource from the storage.
|
|
||||||
*
|
|
||||||
* @throws LockReleasingException
|
|
||||||
*/
|
|
||||||
public function delete(Key $key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the resource exists in the storage.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function exists(Key $key);
|
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@ namespace Symfony\Component\Lock\Tests;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
use Symfony\Component\Lock\Lock;
|
use Symfony\Component\Lock\Lock;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,6 +26,37 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
class LockTest extends TestCase
|
class LockTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testAcquireNoBlocking()
|
public function testAcquireNoBlocking()
|
||||||
|
{
|
||||||
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
|
$lock = new Lock($key, $store);
|
||||||
|
|
||||||
|
$store
|
||||||
|
->expects($this->once())
|
||||||
|
->method('save');
|
||||||
|
|
||||||
|
$this->assertTrue($lock->acquire(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAcquireNoBlockingStoreInterface()
|
||||||
|
{
|
||||||
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
|
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||||
|
$lock = new Lock($key, $store);
|
||||||
|
|
||||||
|
$store
|
||||||
|
->expects($this->once())
|
||||||
|
->method('save');
|
||||||
|
|
||||||
|
$this->assertTrue($lock->acquire(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public function testPassingOldStoreInterface()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||||
@ -37,6 +70,20 @@ class LockTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testAcquireReturnsFalse()
|
public function testAcquireReturnsFalse()
|
||||||
|
{
|
||||||
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
|
$lock = new Lock($key, $store);
|
||||||
|
|
||||||
|
$store
|
||||||
|
->expects($this->once())
|
||||||
|
->method('save')
|
||||||
|
->willThrowException(new LockConflictedException());
|
||||||
|
|
||||||
|
$this->assertFalse($lock->acquire(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAcquireReturnsFalseStoreInterface()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||||
@ -53,8 +100,13 @@ class LockTest extends TestCase
|
|||||||
public function testAcquireBlocking()
|
public function testAcquireBlocking()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
$lock = new Lock($key, $store);
|
$lock = new Lock($key, $store);
|
||||||
|
$store
|
||||||
|
->expects($this->once())
|
||||||
|
->method('supportsWaitAndSave')
|
||||||
|
->with()
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
$store
|
$store
|
||||||
->expects($this->never())
|
->expects($this->never())
|
||||||
@ -114,7 +166,7 @@ class LockTest extends TestCase
|
|||||||
public function testIsAquired()
|
public function testIsAquired()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
$lock = new Lock($key, $store, 10);
|
$lock = new Lock($key, $store, 10);
|
||||||
|
|
||||||
$store
|
$store
|
||||||
@ -127,6 +179,26 @@ class LockTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testRelease()
|
public function testRelease()
|
||||||
|
{
|
||||||
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
|
$lock = new Lock($key, $store, 10);
|
||||||
|
|
||||||
|
$store
|
||||||
|
->expects($this->once())
|
||||||
|
->method('delete')
|
||||||
|
->with($key);
|
||||||
|
|
||||||
|
$store
|
||||||
|
->expects($this->once())
|
||||||
|
->method('exists')
|
||||||
|
->with($key)
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReleaseStoreInterface()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||||
@ -149,7 +221,7 @@ class LockTest extends TestCase
|
|||||||
public function testReleaseOnDestruction()
|
public function testReleaseOnDestruction()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
$lock = new Lock($key, $store, 10);
|
$lock = new Lock($key, $store, 10);
|
||||||
|
|
||||||
$store
|
$store
|
||||||
@ -168,7 +240,7 @@ class LockTest extends TestCase
|
|||||||
public function testNoAutoReleaseWhenNotConfigured()
|
public function testNoAutoReleaseWhenNotConfigured()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
$lock = new Lock($key, $store, 10, false);
|
$lock = new Lock($key, $store, 10, false);
|
||||||
|
|
||||||
$store
|
$store
|
||||||
@ -190,7 +262,7 @@ class LockTest extends TestCase
|
|||||||
public function testReleaseThrowsExceptionWhenDeletionFail()
|
public function testReleaseThrowsExceptionWhenDeletionFail()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
$lock = new Lock($key, $store, 10);
|
$lock = new Lock($key, $store, 10);
|
||||||
|
|
||||||
$store
|
$store
|
||||||
@ -213,7 +285,7 @@ class LockTest extends TestCase
|
|||||||
public function testReleaseThrowsExceptionIfNotWellDeleted()
|
public function testReleaseThrowsExceptionIfNotWellDeleted()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
$lock = new Lock($key, $store, 10);
|
$lock = new Lock($key, $store, 10);
|
||||||
|
|
||||||
$store
|
$store
|
||||||
@ -236,7 +308,7 @@ class LockTest extends TestCase
|
|||||||
public function testReleaseThrowsAndLog()
|
public function testReleaseThrowsAndLog()
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
||||||
$lock = new Lock($key, $store, 10, true);
|
$lock = new Lock($key, $store, 10, true);
|
||||||
$lock->setLogger($logger);
|
$lock->setLogger($logger);
|
||||||
@ -263,6 +335,25 @@ class LockTest extends TestCase
|
|||||||
* @dataProvider provideExpiredDates
|
* @dataProvider provideExpiredDates
|
||||||
*/
|
*/
|
||||||
public function testExpiration($ttls, $expected)
|
public function testExpiration($ttls, $expected)
|
||||||
|
{
|
||||||
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
|
$store = $this->getMockBuilder(PersistStoreInterface::class)->getMock();
|
||||||
|
$lock = new Lock($key, $store, 10);
|
||||||
|
|
||||||
|
foreach ($ttls as $ttl) {
|
||||||
|
if (null === $ttl) {
|
||||||
|
$key->resetLifetime();
|
||||||
|
} else {
|
||||||
|
$key->reduceLifetime($ttl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertSame($expected, $lock->isExpired());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideExpiredDates
|
||||||
|
*/
|
||||||
|
public function testExpirationStoreInterface($ttls, $expected)
|
||||||
{
|
{
|
||||||
$key = new Key(uniqid(__METHOD__, true));
|
$key = new Key(uniqid(__METHOD__, true));
|
||||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
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\StoreInterface;
|
use Symfony\Component\Lock\StoreInterface;
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ 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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,13 +65,17 @@ 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
|
||||||
$store->waitAndSave($key);
|
try {
|
||||||
$this->assertTrue($store->exists($key));
|
$store->waitAndSave($key);
|
||||||
$store->delete($key);
|
$this->assertTrue($store->exists($key));
|
||||||
|
$store->delete($key);
|
||||||
|
|
||||||
// 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]);
|
||||||
|
@ -11,11 +11,12 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock\Tests\Store;
|
namespace Symfony\Component\Lock\Tests\Store;
|
||||||
|
|
||||||
|
use Symfony\Component\Lock\BlockingStoreInterface;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
|
use Symfony\Component\Lock\PersistStoreInterface;
|
||||||
use Symfony\Component\Lock\Store\CombinedStore;
|
use Symfony\Component\Lock\Store\CombinedStore;
|
||||||
use Symfony\Component\Lock\Store\RedisStore;
|
use Symfony\Component\Lock\Store\RedisStore;
|
||||||
use Symfony\Component\Lock\StoreInterface;
|
|
||||||
use Symfony\Component\Lock\Strategy\StrategyInterface;
|
use Symfony\Component\Lock\Strategy\StrategyInterface;
|
||||||
use Symfony\Component\Lock\Strategy\UnanimousStrategy;
|
use Symfony\Component\Lock\Strategy\UnanimousStrategy;
|
||||||
|
|
||||||
@ -61,8 +62,8 @@ class CombinedStoreTest extends AbstractStoreTest
|
|||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->strategy = $this->getMockBuilder(StrategyInterface::class)->getMock();
|
$this->strategy = $this->getMockBuilder(StrategyInterface::class)->getMock();
|
||||||
$this->store1 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$this->store1 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
$this->store2 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$this->store2 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
|
|
||||||
$this->store = new CombinedStore([$this->store1, $this->store2], $this->strategy);
|
$this->store = new CombinedStore([$this->store1, $this->store2], $this->strategy);
|
||||||
}
|
}
|
||||||
@ -266,8 +267,8 @@ class CombinedStoreTest extends AbstractStoreTest
|
|||||||
|
|
||||||
public function testPutOffExpirationIgnoreNonExpiringStorage()
|
public function testPutOffExpirationIgnoreNonExpiringStorage()
|
||||||
{
|
{
|
||||||
$store1 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store1 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
$store2 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
$store2 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock();
|
||||||
|
|
||||||
$store = new CombinedStore([$store1, $store2], $this->strategy);
|
$store = new CombinedStore([$store1, $store2], $this->strategy);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user