feature #32198 [Lock] Split "StoreInterface" into multiple interfaces with less responsability (Simperfit)

This PR was squashed before being merged into the 4.4 branch (closes #32198).

Discussion
----------

[Lock] Split "StoreInterface" into multiple interfaces with less responsability

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | yes <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | Contribute to #28694 <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | TODO <!-- required for new features -->

<!--
Replace this notice by a short README for your feature/bugfix. This will help people
understand your PR and can be used as a start for the documentation.

Additionally (see https://symfony.com/roadmap):
 - Bug fixes must be submitted against the lowest maintained branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).
 - Features and deprecations must be submitted against branch 4.4.
 - Legacy code removals go to the master branch.
-->

We are removing the StoreInterface in order to split into multiple interface, it will help reduce de responsability of the StoreInterface.

Firstly, since not all stores needs to have all the methods of the StoreInterface, we can split this like this in order to limit the methods that are needed for each store.

Secondly, we add supportsX methods in order to avoid throwing exception when a store does not supports a feature it's easier an instance of the special interface or not, and it can return true/false on the support method.

**Really big thanks to** @jderusse for working with me on this, 1-2 hours of talking together, and another 1-2 hours of pre-review :). now giving it to the whole community !

*some time has been sponsored by* @izisolutions

Commits
-------

91fcbea977 [Lock] Split \"StoreInterface\" into multiple interfaces with less responsability
This commit is contained in:
Fabien Potencier 2019-07-08 15:38:00 +02:00
commit 608d428160
16 changed files with 291 additions and 78 deletions

View 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;
}

View File

@ -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
-----

View File

@ -19,6 +19,7 @@ use Symfony\Component\Lock\Exception\LockAcquiringException;
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 @@ final class Lock implements LockInterface, LoggerAwareInterface
{
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);

View 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);
}

View File

@ -18,6 +18,7 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
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 @@ use Symfony\Component\Lock\Strategy\StrategyInterface;
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
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 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface
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 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface
return false;
}
/**
* {@inheritdoc}
*/
public function supportsWaitAndSave(): bool
{
return false;
}
}

View File

@ -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 @@ use Symfony\Component\Lock\StoreInterface;
* @author Romain Neutron <imprec@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class FlockStore implements StoreInterface
class FlockStore implements StoreInterface, BlockingStoreInterface, PersistStoreInterface
{
private $lockPath;
@ -64,6 +66,14 @@ class FlockStore implements StoreInterface
$this->lock($key, true);
}
/**
* {@inheritdoc}
*/
public function supportsWaitAndSave(): bool
{
return true;
}
private function lock(Key $key, $blocking)
{
// The lock is maybe already acquired.

View File

@ -15,6 +15,7 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
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 @@ use Symfony\Component\Lock\StoreInterface;
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class MemcachedStore implements StoreInterface
class MemcachedStore implements StoreInterface, PersistStoreInterface
{
use ExpiringStoreTrait;
@ -69,8 +70,12 @@ class MemcachedStore implements StoreInterface
$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)));
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Lock\Exception\InvalidTtlException;
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 @@ use Symfony\Component\Lock\StoreInterface;
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class PdoStore implements StoreInterface
class PdoStore implements StoreInterface, PersistStoreInterface
{
use ExpiringStoreTrait;
@ -145,6 +146,7 @@ class PdoStore implements StoreInterface
*/
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__));
}

View File

@ -17,6 +17,7 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
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 @@ use Symfony\Component\Lock\StoreInterface;
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class RedisStore implements StoreInterface
class RedisStore implements StoreInterface, PersistStoreInterface
{
use ExpiringStoreTrait;
@ -77,6 +78,7 @@ class RedisStore implements StoreInterface
*/
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)));
}

View File

@ -14,8 +14,10 @@ namespace Symfony\Component\Lock\Store;
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 @@ use Symfony\Component\Lock\StoreInterface;
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
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 @@ class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
{
return $this->decorated->exists($key);
}
/**
* {@inheritdoc}
*/
public function supportsWaitAndSave(): bool
{
return true;
}
}

View File

@ -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 @@ use Symfony\Component\Lock\StoreInterface;
*
* @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.
@ -112,4 +114,12 @@ class SemaphoreStore implements StoreInterface
{
return $key->hasState(__CLASS__);
}
/**
* {@inheritdoc}
*/
public function supportsWaitAndSave(): bool
{
return true;
}
}

View File

@ -16,6 +16,7 @@ use Symfony\Component\Lock\Exception\LockConflictedException;
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 @@ use Symfony\Component\Lock\StoreInterface;
*
* @author Ganesh Chandrasekaran <gchandrasekaran@wayfair.com>
*/
class ZookeeperStore implements StoreInterface
class ZookeeperStore implements StoreInterface, PersistStoreInterface
{
use ExpiringStoreTrait;
@ -87,6 +88,7 @@ class ZookeeperStore implements StoreInterface
*/
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();
}

View File

@ -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é <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.
*
@ -40,29 +32,4 @@ interface StoreInterface
* @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);
}

View File

@ -13,9 +13,11 @@ namespace Symfony\Component\Lock\Tests;
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 @@ use Symfony\Component\Lock\StoreInterface;
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 @@ class LockTest extends TestCase
}
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
}
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
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 @@ class LockTest extends TestCase
* @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();

View File

@ -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 @@ trait BlockingStoreTestTrait
// 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 @@ trait BlockingStoreTestTrait
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]);

View File

@ -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 @@ class CombinedStoreTest extends AbstractStoreTest
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 @@ class CombinedStoreTest extends AbstractStoreTest
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);