[Lock] Expose an expiringDate and isExpired method in Lock
This commit is contained in:
parent
c36262eb1f
commit
279430800e
@ -19,6 +19,7 @@ namespace Symfony\Component\Lock;
|
||||
final class Key
|
||||
{
|
||||
private $resource;
|
||||
private $expiringDate;
|
||||
private $state = array();
|
||||
|
||||
/**
|
||||
@ -70,4 +71,29 @@ final class Key
|
||||
{
|
||||
return $this->state[$stateKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $ttl The expiration delay of locks in seconds.
|
||||
*/
|
||||
public function reduceLifetime($ttl)
|
||||
{
|
||||
$newExpiringDate = \DateTimeImmutable::createFromFormat('U.u', (string) (microtime(true) + $ttl));
|
||||
|
||||
if (null === $this->expiringDate || $newExpiringDate < $this->expiringDate) {
|
||||
$this->expiringDate = $newExpiringDate;
|
||||
}
|
||||
}
|
||||
|
||||
public function resetExpiringDate()
|
||||
{
|
||||
$this->expiringDate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeImmutable
|
||||
*/
|
||||
public function getExpiringDate()
|
||||
{
|
||||
return $this->expiringDate;
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ final class Lock implements LockInterface, LoggerAwareInterface
|
||||
}
|
||||
|
||||
try {
|
||||
$this->key->resetExpiringDate();
|
||||
$this->store->putOffExpiration($this->key, $this->ttl);
|
||||
$this->logger->info('Expiration defined for "{resource}" lock for "{ttl}" seconds.', array('resource' => $this->key, 'ttl' => $this->ttl));
|
||||
} catch (LockConflictedException $e) {
|
||||
@ -120,4 +121,21 @@ final class Lock implements LockInterface, LoggerAwareInterface
|
||||
throw new LockReleasingException(sprintf('Failed to release the "%s" lock.', $this->key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
{
|
||||
if (null === $expireDate = $this->key->getExpiringDate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $expireDate <= new \DateTime();
|
||||
}
|
||||
|
||||
public function getExpiringDate()
|
||||
{
|
||||
return $this->key->getExpiringDate();
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ class MemcachedStore implements StoreInterface
|
||||
{
|
||||
$token = $this->getToken($key);
|
||||
|
||||
$key->reduceLifetime($this->initialTtl);
|
||||
if ($this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
|
||||
return;
|
||||
}
|
||||
@ -87,6 +88,7 @@ class MemcachedStore implements StoreInterface
|
||||
|
||||
list($value, $cas) = $this->getValueAndCas($key);
|
||||
|
||||
$key->reduceLifetime($ttl);
|
||||
// Could happens when we ask a putOff after a timeout but in luck nobody steal the lock
|
||||
if (\Memcached::RES_NOTFOUND === $this->memcached->getResultCode()) {
|
||||
if ($this->memcached->add((string) $key, $token, $ttl)) {
|
||||
|
@ -57,8 +57,8 @@ class RedisStore implements StoreInterface
|
||||
end
|
||||
';
|
||||
|
||||
$expire = (int) ceil($this->initialTtl * 1000);
|
||||
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), $expire))) {
|
||||
$key->reduceLifetime($this->initialTtl);
|
||||
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($this->initialTtl * 1000)))) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
}
|
||||
@ -81,8 +81,8 @@ class RedisStore implements StoreInterface
|
||||
end
|
||||
';
|
||||
|
||||
$expire = (int) ceil($ttl * 1000);
|
||||
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), $expire))) {
|
||||
$key->reduceLifetime($ttl);
|
||||
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($ttl * 1000)))) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
}
|
||||
|
@ -153,4 +153,34 @@ class LockTest extends TestCase
|
||||
|
||||
$lock->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideExpiredDates
|
||||
*/
|
||||
public function testExpiration($ttls, $expected)
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
foreach ($ttls as $ttl) {
|
||||
if (null === $ttl) {
|
||||
$key->resetExpiringDate();
|
||||
} else {
|
||||
$key->reduceLifetime($ttl);
|
||||
}
|
||||
}
|
||||
$this->assertSame($expected, $lock->isExpired());
|
||||
}
|
||||
|
||||
public function provideExpiredDates()
|
||||
{
|
||||
yield array(array(-1.0), true);
|
||||
yield array(array(1, -1.0), true);
|
||||
yield array(array(-1.0, 1), true);
|
||||
|
||||
yield array(array(), false);
|
||||
yield array(array(1), false);
|
||||
yield array(array(-1.0, null), false);
|
||||
}
|
||||
}
|
||||
|
@ -49,14 +49,17 @@ abstract class AbstractStoreTest extends TestCase
|
||||
$store->save($key1);
|
||||
$this->assertTrue($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
$store->save($key2);
|
||||
|
||||
$store->save($key2);
|
||||
$this->assertTrue($store->exists($key1));
|
||||
$this->assertTrue($store->exists($key2));
|
||||
|
||||
$store->delete($key1);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertTrue($store->exists($key2));
|
||||
|
||||
$store->delete($key2);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
}
|
||||
|
||||
@ -74,7 +77,7 @@ abstract class AbstractStoreTest extends TestCase
|
||||
|
||||
try {
|
||||
$store->save($key2);
|
||||
throw new \Exception('The store shouldn\'t save the second key');
|
||||
$this->fail('The store shouldn\'t save the second key');
|
||||
} catch (LockConflictedException $e) {
|
||||
}
|
||||
|
||||
|
@ -75,4 +75,16 @@ trait ExpiringStoreTestTrait
|
||||
usleep(2.1 * $clockDelay);
|
||||
$this->assertFalse($store->exists($key));
|
||||
}
|
||||
|
||||
public function testSetExpiration()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
/** @var StoreInterface $store */
|
||||
$store = $this->getStore();
|
||||
|
||||
$store->save($key);
|
||||
$store->putOffExpiration($key, 1);
|
||||
$this->assertNotNull($key->getExpiringDate());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user