diff --git a/src/Symfony/Component/Lock/BlockingStoreInterface.php b/src/Symfony/Component/Lock/BlockingStoreInterface.php new file mode 100644 index 000000000000..220e922be265 --- /dev/null +++ b/src/Symfony/Component/Lock/BlockingStoreInterface.php @@ -0,0 +1,36 @@ + + * + * 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 + */ +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; +} diff --git a/src/Symfony/Component/Lock/CHANGELOG.md b/src/Symfony/Component/Lock/CHANGELOG.md index b76988b409fc..65dd191adcbf 100644 --- a/src/Symfony/Component/Lock/CHANGELOG.md +++ b/src/Symfony/Component/Lock/CHANGELOG.md @@ -4,8 +4,9 @@ CHANGELOG 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 ----- diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index db185c374dd5..5329f3992f0d 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -19,6 +19,7 @@ use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Exception\LockReleasingException; +use Symfony\Component\Lock\Exception\NotSupportedException; /** * Lock is the default implementation of the LockInterface. @@ -36,12 +37,12 @@ final class Lock implements LockInterface, LoggerAwareInterface private $dirty = false; /** - * @param Key $key Resource to lock - * @param StoreInterface $store Store used to handle lock persistence - * @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 Key $key Resource to lock + * @param PersistStoreInterface $store Store used to handle lock persistence + * @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 */ - 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->key = $key; @@ -70,6 +71,9 @@ public function acquire($blocking = false) { try { 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); } else { $this->store->save($this->key); diff --git a/src/Symfony/Component/Lock/PersistStoreInterface.php b/src/Symfony/Component/Lock/PersistStoreInterface.php new file mode 100644 index 000000000000..b79ad3bf8f1b --- /dev/null +++ b/src/Symfony/Component/Lock/PersistStoreInterface.php @@ -0,0 +1,53 @@ + + * + * 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é + */ +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); +} diff --git a/src/Symfony/Component/Lock/Store/CombinedStore.php b/src/Symfony/Component/Lock/Store/CombinedStore.php index 6038b3be93d3..4af7770b2ba7 100644 --- a/src/Symfony/Component/Lock/Store/CombinedStore.php +++ b/src/Symfony/Component/Lock/Store/CombinedStore.php @@ -18,6 +18,7 @@ use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\NotSupportedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; use Symfony\Component\Lock\Strategy\StrategyInterface; @@ -26,27 +27,27 @@ * * @author Jérémy Derussé */ -class CombinedStore implements StoreInterface, LoggerAwareInterface +class CombinedStore implements StoreInterface, PersistStoreInterface, LoggerAwareInterface { use LoggerAwareTrait; use ExpiringStoreTrait; - /** @var StoreInterface[] */ + /** @var PersistStoreInterface[] */ private $stores; /** @var StrategyInterface */ private $strategy; /** - * @param StoreInterface[] $stores The list of synchronized stores - * @param StrategyInterface $strategy + * @param PersistStoreInterface[] $stores The list of synchronized stores + * @param StrategyInterface $strategy * * @throws InvalidArgumentException */ public function __construct(array $stores, StrategyInterface $strategy) { foreach ($stores as $store) { - if (!$store instanceof StoreInterface) { - throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', StoreInterface::class, \get_class($store))); + if (!$store instanceof PersistStoreInterface) { + throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', PersistStoreInterface::class, \get_class($store))); } } @@ -92,8 +93,12 @@ public function save(Key $key) throw new LockConflictedException(); } + /** + * {@inheritdoc} + */ 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))); } @@ -181,4 +186,12 @@ public function exists(Key $key) return false; } + + /** + * {@inheritdoc} + */ + public function supportsWaitAndSave(): bool + { + return false; + } } diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php index 5b2732d30ac6..e5b2b4c5c3c1 100644 --- a/src/Symfony/Component/Lock/Store/FlockStore.php +++ b/src/Symfony/Component/Lock/Store/FlockStore.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Lock\Store; +use Symfony\Component\Lock\BlockingStoreInterface; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\LockStorageException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -27,7 +29,7 @@ * @author Romain Neutron * @author Nicolas Grekas */ -class FlockStore implements StoreInterface +class FlockStore implements StoreInterface, BlockingStoreInterface, PersistStoreInterface { private $lockPath; @@ -64,6 +66,14 @@ public function waitAndSave(Key $key) $this->lock($key, true); } + /** + * {@inheritdoc} + */ + public function supportsWaitAndSave(): bool + { + return true; + } + private function lock(Key $key, $blocking) { // The lock is maybe already acquired. diff --git a/src/Symfony/Component/Lock/Store/MemcachedStore.php b/src/Symfony/Component/Lock/Store/MemcachedStore.php index 3857dfc68906..30673c4355de 100644 --- a/src/Symfony/Component/Lock/Store/MemcachedStore.php +++ b/src/Symfony/Component/Lock/Store/MemcachedStore.php @@ -15,6 +15,7 @@ use Symfony\Component\Lock\Exception\InvalidTtlException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -22,7 +23,7 @@ * * @author Jérémy Derussé */ -class MemcachedStore implements StoreInterface +class MemcachedStore implements StoreInterface, PersistStoreInterface { use ExpiringStoreTrait; @@ -69,8 +70,12 @@ public function save(Key $key) $this->checkNotExpired($key); } + /** + * {@inheritdoc} + */ 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))); } diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php index 159c6f514bef..3e5d7b35b06f 100644 --- a/src/Symfony/Component/Lock/Store/PdoStore.php +++ b/src/Symfony/Component/Lock/Store/PdoStore.php @@ -19,6 +19,7 @@ use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\NotSupportedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -34,7 +35,7 @@ * * @author Jérémy Derussé */ -class PdoStore implements StoreInterface +class PdoStore implements StoreInterface, PersistStoreInterface { use ExpiringStoreTrait; @@ -145,6 +146,7 @@ public function save(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__)); } diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index d082a79e10d5..e3f4675724c9 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -17,6 +17,7 @@ use Symfony\Component\Lock\Exception\InvalidTtlException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -24,7 +25,7 @@ * * @author Jérémy Derussé */ -class RedisStore implements StoreInterface +class RedisStore implements StoreInterface, PersistStoreInterface { use ExpiringStoreTrait; @@ -77,6 +78,7 @@ public function save(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))); } diff --git a/src/Symfony/Component/Lock/Store/RetryTillSaveStore.php b/src/Symfony/Component/Lock/Store/RetryTillSaveStore.php index 32055ecffe70..4c66e3ba82a9 100644 --- a/src/Symfony/Component/Lock/Store/RetryTillSaveStore.php +++ b/src/Symfony/Component/Lock/Store/RetryTillSaveStore.php @@ -14,8 +14,10 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\NullLogger; +use Symfony\Component\Lock\BlockingStoreInterface; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -24,7 +26,7 @@ * * @author Jérémy Derussé */ -class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface +class RetryTillSaveStore implements PersistStoreInterface, BlockingStoreInterface, StoreInterface, LoggerAwareInterface { use LoggerAwareTrait; @@ -33,11 +35,11 @@ class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface private $retryCount; /** - * @param StoreInterface $decorated The decorated StoreInterface - * @param int $retrySleep Duration in ms between 2 retry - * @param int $retryCount Maximum amount of retry + * @param PersistStoreInterface $decorated The decorated StoreInterface + * @param int $retrySleep Duration in ms between 2 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->retrySleep = $retrySleep; @@ -99,4 +101,12 @@ public function exists(Key $key) { return $this->decorated->exists($key); } + + /** + * {@inheritdoc} + */ + public function supportsWaitAndSave(): bool + { + return true; + } } diff --git a/src/Symfony/Component/Lock/Store/SemaphoreStore.php b/src/Symfony/Component/Lock/Store/SemaphoreStore.php index 47f8616b0a84..32f49f637c4d 100644 --- a/src/Symfony/Component/Lock/Store/SemaphoreStore.php +++ b/src/Symfony/Component/Lock/Store/SemaphoreStore.php @@ -11,9 +11,11 @@ namespace Symfony\Component\Lock\Store; +use Symfony\Component\Lock\BlockingStoreInterface; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -21,7 +23,7 @@ * * @author Jérémy Derussé */ -class SemaphoreStore implements StoreInterface +class SemaphoreStore implements StoreInterface, PersistStoreInterface, BlockingStoreInterface { /** * Returns whether or not the store is supported. @@ -112,4 +114,12 @@ public function exists(Key $key) { return $key->hasState(__CLASS__); } + + /** + * {@inheritdoc} + */ + public function supportsWaitAndSave(): bool + { + return true; + } } diff --git a/src/Symfony/Component/Lock/Store/ZookeeperStore.php b/src/Symfony/Component/Lock/Store/ZookeeperStore.php index 10987c5f7293..8ea91e76db19 100644 --- a/src/Symfony/Component/Lock/Store/ZookeeperStore.php +++ b/src/Symfony/Component/Lock/Store/ZookeeperStore.php @@ -16,6 +16,7 @@ use Symfony\Component\Lock\Exception\LockReleasingException; use Symfony\Component\Lock\Exception\NotSupportedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -23,7 +24,7 @@ * * @author Ganesh Chandrasekaran */ -class ZookeeperStore implements StoreInterface +class ZookeeperStore implements StoreInterface, PersistStoreInterface { use ExpiringStoreTrait; @@ -87,6 +88,7 @@ public function exists(Key $key): bool */ 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(); } diff --git a/src/Symfony/Component/Lock/StoreInterface.php b/src/Symfony/Component/Lock/StoreInterface.php index e5a1dfa58870..883136ed5abf 100644 --- a/src/Symfony/Component/Lock/StoreInterface.php +++ b/src/Symfony/Component/Lock/StoreInterface.php @@ -11,26 +11,18 @@ namespace Symfony\Component\Lock; -use Symfony\Component\Lock\Exception\LockAcquiringException; use Symfony\Component\Lock\Exception\LockConflictedException; -use Symfony\Component\Lock\Exception\LockReleasingException; use Symfony\Component\Lock\Exception\NotSupportedException; /** * StoreInterface defines an interface to manipulate a lock store. * * @author Jérémy Derussé + * + * @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. * @@ -40,29 +32,4 @@ public function save(Key $key); * @throws NotSupportedException */ 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); } diff --git a/src/Symfony/Component/Lock/Tests/LockTest.php b/src/Symfony/Component/Lock/Tests/LockTest.php index a5e905a80f3f..3fddbc3eca01 100644 --- a/src/Symfony/Component/Lock/Tests/LockTest.php +++ b/src/Symfony/Component/Lock/Tests/LockTest.php @@ -13,9 +13,11 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Symfony\Component\Lock\BlockingStoreInterface; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\Lock; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\StoreInterface; /** @@ -24,6 +26,37 @@ class LockTest extends TestCase { 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)); $store = $this->getMockBuilder(StoreInterface::class)->getMock(); @@ -37,6 +70,20 @@ public function testAcquireNoBlocking() } 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)); $store = $this->getMockBuilder(StoreInterface::class)->getMock(); @@ -53,8 +100,13 @@ public function testAcquireReturnsFalse() public function testAcquireBlocking() { $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); + $store + ->expects($this->once()) + ->method('supportsWaitAndSave') + ->with() + ->willReturn(true); $store ->expects($this->never()) @@ -114,7 +166,7 @@ public function testRefreshCustom() public function testIsAquired() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -127,6 +179,26 @@ public function testIsAquired() } 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)); $store = $this->getMockBuilder(StoreInterface::class)->getMock(); @@ -149,7 +221,7 @@ public function testRelease() public function testReleaseOnDestruction() { $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); $store @@ -168,7 +240,7 @@ public function testReleaseOnDestruction() public function testNoAutoReleaseWhenNotConfigured() { $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); $store @@ -190,7 +262,7 @@ public function testNoAutoReleaseWhenNotConfigured() public function testReleaseThrowsExceptionWhenDeletionFail() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -213,7 +285,7 @@ public function testReleaseThrowsExceptionWhenDeletionFail() public function testReleaseThrowsExceptionIfNotWellDeleted() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -236,7 +308,7 @@ public function testReleaseThrowsExceptionIfNotWellDeleted() public function testReleaseThrowsAndLog() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); $lock = new Lock($key, $store, 10, true); $lock->setLogger($logger); @@ -263,6 +335,25 @@ public function testReleaseThrowsAndLog() * @dataProvider provideExpiredDates */ 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)); $store = $this->getMockBuilder(StoreInterface::class)->getMock(); diff --git a/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php b/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php index 139fc2511160..a22224215c04 100644 --- a/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php +++ b/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Tests\Store; use Symfony\Component\Lock\Exception\LockConflictedException; +use Symfony\Component\Lock\Exception\NotSupportedException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; @@ -56,6 +57,7 @@ public function testBlockingLocks() // This call should failed given the lock should already by acquired by the child $store->save($key); $this->fail('The store saves a locked key.'); + } catch (NotSupportedException $e) { } catch (LockConflictedException $e) { } @@ -63,13 +65,17 @@ public function testBlockingLocks() posix_kill($childPID, SIGHUP); // This call should be blocked by the child #1 - $store->waitAndSave($key); - $this->assertTrue($store->exists($key)); - $store->delete($key); + try { + $store->waitAndSave($key); + $this->assertTrue($store->exists($key)); + $store->delete($key); - // Now, assert the child process worked well - pcntl_waitpid($childPID, $status1); - $this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource'); + // Now, assert the child process worked well + pcntl_waitpid($childPID, $status1); + $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 { // Block SIGHUP signal pcntl_sigprocmask(SIG_BLOCK, [SIGHUP]); diff --git a/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php index bcfc40b1bc26..e8eae7f3101d 100644 --- a/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php @@ -11,11 +11,12 @@ namespace Symfony\Component\Lock\Tests\Store; +use Symfony\Component\Lock\BlockingStoreInterface; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\Store\CombinedStore; use Symfony\Component\Lock\Store\RedisStore; -use Symfony\Component\Lock\StoreInterface; use Symfony\Component\Lock\Strategy\StrategyInterface; use Symfony\Component\Lock\Strategy\UnanimousStrategy; @@ -61,8 +62,8 @@ public function getStore() protected function setUp() { $this->strategy = $this->getMockBuilder(StrategyInterface::class)->getMock(); - $this->store1 = $this->getMockBuilder(StoreInterface::class)->getMock(); - $this->store2 = $this->getMockBuilder(StoreInterface::class)->getMock(); + $this->store1 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock(); + $this->store2 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock(); $this->store = new CombinedStore([$this->store1, $this->store2], $this->strategy); } @@ -266,8 +267,8 @@ public function testputOffExpirationAbortWhenStrategyCantBeMet() public function testPutOffExpirationIgnoreNonExpiringStorage() { - $store1 = $this->getMockBuilder(StoreInterface::class)->getMock(); - $store2 = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store1 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock(); + $store2 = $this->getMockBuilder([PersistStoreInterface::class, BlockingStoreInterface::class])->getMock(); $store = new CombinedStore([$store1, $store2], $this->strategy);