Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 76587d0

Browse filesBrowse files
committed
Check TTL expiration in lock acquisition
1 parent c36262e commit 76587d0
Copy full SHA for 76587d0

File tree

7 files changed

+86
-14
lines changed
Filter options

7 files changed

+86
-14
lines changed
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock\Exception;
13+
14+
/**
15+
* LockExpiredException is thrown when a lock may conflict due to a TTL expiration.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class LockExpiredException extends LockConflictedException
20+
{
21+
}

‎src/Symfony/Component/Lock/Store/CombinedStore.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Store/CombinedStore.php
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Psr\Log\NullLogger;
1717
use Symfony\Component\Lock\Exception\InvalidArgumentException;
1818
use Symfony\Component\Lock\Exception\LockConflictedException;
19+
use Symfony\Component\Lock\Exception\LockExpiredException;
1920
use Symfony\Component\Lock\Exception\NotSupportedException;
2021
use Symfony\Component\Lock\Key;
2122
use Symfony\Component\Lock\Strategy\StrategyInterface;
@@ -102,10 +103,16 @@ public function putOffExpiration(Key $key, $ttl)
102103
$successCount = 0;
103104
$failureCount = 0;
104105
$storesCount = count($this->stores);
106+
$expireAt = microtime(true) + $ttl;
105107

106108
foreach ($this->stores as $store) {
107109
try {
108-
$store->putOffExpiration($key, $ttl);
110+
if (0.0 >= $adjustedTtl = $expireAt - microtime(true)) {
111+
$this->logger->warning('TTL expires during the put off the expiration of the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'ttl' => $ttl));
112+
break;
113+
}
114+
115+
$store->putOffExpiration($key, $adjustedTtl);
109116
++$successCount;
110117
} catch (\Exception $e) {
111118
$this->logger->warning('One store failed to put off the expiration of the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'exception' => $e));
@@ -117,6 +124,10 @@ public function putOffExpiration(Key $key, $ttl)
117124
}
118125
}
119126

127+
if (microtime(true) >= $expireAt) {
128+
throw new LockExpiredException(sprintf('Failed to put of the expiration the "%s" lock in less than "%f".', $key, $ttl));
129+
}
130+
120131
if ($this->strategy->isMet($successCount, $storesCount)) {
121132
return;
122133
}

‎src/Symfony/Component/Lock/Store/MemcachedStore.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Store/MemcachedStore.php
+14-4Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Lock\Exception\InvalidArgumentException;
1515
use Symfony\Component\Lock\Exception\LockConflictedException;
16+
use Symfony\Component\Lock\Exception\LockExpiredException;
1617
use Symfony\Component\Lock\Key;
1718
use Symfony\Component\Lock\StoreInterface;
1819

@@ -58,12 +59,15 @@ public function save(Key $key)
5859
{
5960
$token = $this->getToken($key);
6061

61-
if ($this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
62-
return;
62+
$expireAt = microtime(true) + $this->initialTtl;
63+
if (!$this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
64+
// the lock is already acquire. It could be us. Let's try to put off.
65+
$this->putOffExpiration($key, $this->initialTtl);
6366
}
6467

65-
// the lock is already acquire. It could be us. Let's try to put off.
66-
$this->putOffExpiration($key, $this->initialTtl);
68+
if (microtime(true) >= $expireAt) {
69+
throw new LockExpiredException(sprintf('Failed to store the "%s" lock in less than "%f".', $key, $this->initialTtl));
70+
}
6771
}
6872

6973
public function waitAndSave(Key $key)
@@ -80,6 +84,8 @@ public function putOffExpiration(Key $key, $ttl)
8084
throw new InvalidArgumentException(sprintf('%s() expects a TTL greater or equals to 1. Got %s.', __METHOD__, $ttl));
8185
}
8286

87+
$expireAt = microtime(true) + $ttl;
88+
8389
// Interface defines a float value but Store required an integer.
8490
$ttl = (int) ceil($ttl);
8591

@@ -105,6 +111,10 @@ public function putOffExpiration(Key $key, $ttl)
105111
if (!$this->memcached->cas($cas, (string) $key, $token, $ttl)) {
106112
throw new LockConflictedException();
107113
}
114+
115+
if (microtime(true) >= $expireAt) {
116+
throw new LockExpiredException(sprintf('Failed to put of the expiration the "%s" lock in less than "%f".', $key, $ttl));
117+
}
108118
}
109119

110120
/**

‎src/Symfony/Component/Lock/Store/RedisStore.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Store/RedisStore.php
+13-4Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Lock\Exception\InvalidArgumentException;
1515
use Symfony\Component\Lock\Exception\LockConflictedException;
16+
use Symfony\Component\Lock\Exception\LockExpiredException;
1617
use Symfony\Component\Lock\Key;
1718
use Symfony\Component\Lock\StoreInterface;
1819

@@ -57,10 +58,14 @@ public function save(Key $key)
5758
end
5859
';
5960

60-
$expire = (int) ceil($this->initialTtl * 1000);
61-
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), $expire))) {
61+
$expireAt = microtime(true) + $this->initialTtl;
62+
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($this->initialTtl * 1000)))) {
6263
throw new LockConflictedException();
6364
}
65+
66+
if (microtime(true) >= $expireAt) {
67+
throw new LockExpiredException(sprintf('Failed to store the "%s" lock in less than "%f".', $key, $this->initialTtl));
68+
}
6469
}
6570

6671
public function waitAndSave(Key $key)
@@ -81,10 +86,14 @@ public function putOffExpiration(Key $key, $ttl)
8186
end
8287
';
8388

84-
$expire = (int) ceil($ttl * 1000);
85-
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), $expire))) {
89+
$expireAt = microtime(true) + $ttl;
90+
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($ttl * 1000)))) {
8691
throw new LockConflictedException();
8792
}
93+
94+
if (microtime(true) >= $expireAt) {
95+
throw new LockExpiredException(sprintf('Failed to put of the expiration the "%s" lock in less than "%f".', $key, $ttl));
96+
}
8897
}
8998

9099
/**

‎src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php
+5-5Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,12 @@ public function testputOffExpirationThrowsExceptionOnFailure()
175175
$this->store1
176176
->expects($this->once())
177177
->method('putOffExpiration')
178-
->with($key, $ttl)
178+
->with($key, $this->lessThanOrEqual($ttl))
179179
->willThrowException(new LockConflictedException());
180180
$this->store2
181181
->expects($this->once())
182182
->method('putOffExpiration')
183-
->with($key, $ttl)
183+
->with($key, $this->lessThanOrEqual($ttl))
184184
->willThrowException(new LockConflictedException());
185185

186186
$this->strategy
@@ -203,12 +203,12 @@ public function testputOffExpirationCleanupOnFailure()
203203
$this->store1
204204
->expects($this->once())
205205
->method('putOffExpiration')
206-
->with($key, $ttl)
206+
->with($key, $this->lessThanOrEqual($ttl))
207207
->willThrowException(new LockConflictedException());
208208
$this->store2
209209
->expects($this->once())
210210
->method('putOffExpiration')
211-
->with($key, $ttl)
211+
->with($key, $this->lessThanOrEqual($ttl))
212212
->willThrowException(new LockConflictedException());
213213

214214
$this->store1
@@ -242,7 +242,7 @@ public function testputOffExpirationAbortWhenStrategyCantBeMet()
242242
$this->store1
243243
->expects($this->once())
244244
->method('putOffExpiration')
245-
->with($key, $ttl)
245+
->with($key, $this->lessThanOrEqual($ttl))
246246
->willThrowException(new LockConflictedException());
247247
$this->store2
248248
->expects($this->never())

‎src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ public function testExpiration()
5252
$this->assertFalse($store->exists($key));
5353
}
5454

55+
/**
56+
* Tests the store thrown exception when TTL expires.
57+
*
58+
* @expectedException \Symfony\Component\Lock\Exception\LockExpiredException
59+
*/
60+
public function testAbortAfterExpiration()
61+
{
62+
$key = new Key(uniqid(__METHOD__, true));
63+
64+
/** @var StoreInterface $store */
65+
$store = $this->getStore();
66+
67+
$store->save($key);
68+
$store->putOffExpiration($key, 1 / 1000000);
69+
}
70+
5571
/**
5672
* Tests the refresh can push the limits to the expiration.
5773
*

‎src/Symfony/Component/Lock/Tests/Store/MemcachedStoreTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Tests/Store/MemcachedStoreTest.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ public function getStore()
4949

5050
return new MemcachedStore($memcached);
5151
}
52+
53+
public function testAbortAfterExpiration()
54+
{
55+
$this->markTestSkipped('Memcached expecte a TTL greater than 1 sec. Simulating slow network is too complex');
56+
}
5257
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.