From 2c6fca72bc3a3ec78d59b39003c05829a6accc9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 17 Jun 2019 16:05:50 +0200 Subject: [PATCH] Fix expired lock not cleaned --- src/Symfony/Component/Lock/Lock.php | 10 ++++++++ .../Component/Lock/Store/CombinedStore.php | 7 +++--- .../Lock/Store/ExpiringStoreTrait.php | 21 ++++++++++++++++ .../Component/Lock/Store/MemcachedStore.php | 10 +++----- .../Component/Lock/Store/RedisStore.php | 11 +++----- .../Tests/Store/ExpiringStoreTestTrait.php | 25 +++++++++++++++++++ 6 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index 1a6c0e26ee0c9..0d5714bde20c0 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -83,6 +83,11 @@ public function acquire($blocking = false) } if ($this->key->isExpired()) { + try { + $this->release(); + } catch (\Exception $e) { + // swallow exception to not hide the original issue + } throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $this->key)); } @@ -117,6 +122,11 @@ public function refresh() $this->dirty = true; if ($this->key->isExpired()) { + try { + $this->release(); + } catch (\Exception $e) { + // swallow exception to not hide the original issue + } throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $this->key)); } diff --git a/src/Symfony/Component/Lock/Store/CombinedStore.php b/src/Symfony/Component/Lock/Store/CombinedStore.php index 38f3f35e750e9..5ae00299d6aa5 100644 --- a/src/Symfony/Component/Lock/Store/CombinedStore.php +++ b/src/Symfony/Component/Lock/Store/CombinedStore.php @@ -30,6 +30,7 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface { use LoggerAwareTrait; + use ExpiringStoreTrait; /** @var StoreInterface[] */ private $stores; @@ -78,6 +79,8 @@ public function save(Key $key) } } + $this->checkNotExpired($key); + if ($this->strategy->isMet($successCount, $storesCount)) { return; } @@ -125,9 +128,7 @@ public function putOffExpiration(Key $key, $ttl) } } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key)); - } + $this->checkNotExpired($key); if ($this->strategy->isMet($successCount, $storesCount)) { return; diff --git a/src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php b/src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php new file mode 100644 index 0000000000000..d0bccfa77a126 --- /dev/null +++ b/src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php @@ -0,0 +1,21 @@ +isExpired()) { + try { + $this->delete($key); + } catch (\Exception $e) { + // swallow exception to not hide the original issue + } + throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); + } + } +} diff --git a/src/Symfony/Component/Lock/Store/MemcachedStore.php b/src/Symfony/Component/Lock/Store/MemcachedStore.php index 9b795504c990f..07353e21aa9b5 100644 --- a/src/Symfony/Component/Lock/Store/MemcachedStore.php +++ b/src/Symfony/Component/Lock/Store/MemcachedStore.php @@ -24,6 +24,8 @@ */ class MemcachedStore implements StoreInterface { + use ExpiringStoreTrait; + private $memcached; private $initialTtl; /** @var bool */ @@ -64,9 +66,7 @@ public function save(Key $key) $this->putOffExpiration($key, $this->initialTtl); } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); - } + $this->checkNotExpired($key); } public function waitAndSave(Key $key) @@ -110,9 +110,7 @@ public function putOffExpiration(Key $key, $ttl) throw new LockConflictedException(); } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key)); - } + $this->checkNotExpired($key); } /** diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index 2d7ba45366784..6070c2e74e760 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -14,7 +14,6 @@ use Symfony\Component\Cache\Traits\RedisProxy; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; -use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; @@ -25,6 +24,8 @@ */ class RedisStore implements StoreInterface { + use ExpiringStoreTrait; + private $redis; private $initialTtl; @@ -66,9 +67,7 @@ public function save(Key $key) throw new LockConflictedException(); } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); - } + $this->checkNotExpired($key); } public function waitAndSave(Key $key) @@ -94,9 +93,7 @@ public function putOffExpiration(Key $key, $ttl) throw new LockConflictedException(); } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key)); - } + $this->checkNotExpired($key); } /** diff --git a/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php b/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php index f523780ce1444..bc96ff66f5dfb 100644 --- a/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php +++ b/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Lock\Tests\Store; +use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; @@ -105,4 +106,28 @@ public function testSetExpiration() $this->assertGreaterThanOrEqual(0, $key->getRemainingLifetime()); $this->assertLessThanOrEqual(1, $key->getRemainingLifetime()); } + + public function testExpiredLockCleaned() + { + $resource = uniqid(__METHOD__, true); + + $key1 = new Key($resource); + $key2 = new Key($resource); + + /** @var StoreInterface $store */ + $store = $this->getStore(); + $key1->reduceLifetime(0); + + $this->assertTrue($key1->isExpired()); + try { + $store->save($key1); + $this->fail('The store shouldn\'t have save an expired key'); + } catch (LockExpiredException $e) { + } + + $this->assertFalse($store->exists($key1)); + + $store->save($key2); + $this->assertTrue($store->exists($key2)); + } }