Skip to content

Commit

Permalink
[Lock] Expose an expiringDate and isExpired method in Lock
Browse files Browse the repository at this point in the history
  • Loading branch information
jderusse authored and fabpot committed Aug 29, 2017
1 parent c36262e commit 2794308
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 6 deletions.
26 changes: 26 additions & 0 deletions src/Symfony/Component/Lock/Key.php
Expand Up @@ -19,6 +19,7 @@
final class Key
{
private $resource;
private $expiringDate;
private $state = array();

/**
Expand Down Expand Up @@ -70,4 +71,29 @@ public function getState($stateKey)
{
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;
}
}
18 changes: 18 additions & 0 deletions src/Symfony/Component/Lock/Lock.php
Expand Up @@ -89,6 +89,7 @@ public function refresh()
}

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) {
Expand Down Expand Up @@ -120,4 +121,21 @@ public function release()
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();
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/Lock/Store/MemcachedStore.php
Expand Up @@ -58,6 +58,7 @@ public function save(Key $key)
{
$token = $this->getToken($key);

$key->reduceLifetime($this->initialTtl);
if ($this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
return;
}
Expand Down Expand Up @@ -87,6 +88,7 @@ public function putOffExpiration(Key $key, $ttl)

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)) {
Expand Down
8 changes: 4 additions & 4 deletions src/Symfony/Component/Lock/Store/RedisStore.php
Expand Up @@ -57,8 +57,8 @@ public function save(Key $key)
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();
}
}
Expand All @@ -81,8 +81,8 @@ public function putOffExpiration(Key $key, $ttl)
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();
}
}
Expand Down
30 changes: 30 additions & 0 deletions src/Symfony/Component/Lock/Tests/LockTest.php
Expand Up @@ -153,4 +153,34 @@ public function testReleaseThrowsExceptionIfNotWellDeleted()

$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);
}
}
7 changes: 5 additions & 2 deletions src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php
Expand Up @@ -49,14 +49,17 @@ public function testSaveWithDifferentResources()
$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));
}

Expand All @@ -74,7 +77,7 @@ public function testSaveWithDifferentKeysOnSameResources()

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) {
}

Expand Down
12 changes: 12 additions & 0 deletions src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php
Expand Up @@ -75,4 +75,16 @@ public function testRefreshLock()
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());
}
}

0 comments on commit 2794308

Please sign in to comment.