diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php index b8ac259ef5ae8..efff1948a88b1 100644 --- a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php +++ b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php @@ -62,18 +62,28 @@ public function save(Key $key) // prevent concurrency within the same connection $this->getInternalStore()->save($key); - $sql = 'SELECT pg_try_advisory_lock(:key)'; - $result = $this->conn->executeQuery($sql, [ - 'key' => $this->getHashedKey($key), - ]); + $lockAcquired = false; - // Check if lock is acquired - if (true === $result->fetchOne()) { - $key->markUnserializable(); - // release sharedLock in case of promotion - $this->unlockShared($key); + try { + $sql = 'SELECT pg_try_advisory_lock(:key)'; + $result = $this->conn->executeQuery($sql, [ + 'key' => $this->getHashedKey($key), + ]); - return; + // Check if lock is acquired + if (true === $result->fetchOne()) { + $key->markUnserializable(); + // release sharedLock in case of promotion + $this->unlockShared($key); + + $lockAcquired = true; + + return; + } + } finally { + if (!$lockAcquired) { + $this->getInternalStore()->delete($key); + } } throw new LockConflictedException(); @@ -84,18 +94,28 @@ public function saveRead(Key $key) // prevent concurrency within the same connection $this->getInternalStore()->saveRead($key); - $sql = 'SELECT pg_try_advisory_lock_shared(:key)'; - $result = $this->conn->executeQuery($sql, [ - 'key' => $this->getHashedKey($key), - ]); + $lockAcquired = false; + + try { + $sql = 'SELECT pg_try_advisory_lock_shared(:key)'; + $result = $this->conn->executeQuery($sql, [ + 'key' => $this->getHashedKey($key), + ]); - // Check if lock is acquired - if (true === $result->fetchOne()) { - $key->markUnserializable(); - // release lock in case of demotion - $this->unlock($key); + // Check if lock is acquired + if (true === $result->fetchOne()) { + $key->markUnserializable(); + // release lock in case of demotion + $this->unlock($key); - return; + $lockAcquired = true; + + return; + } + } finally { + if (!$lockAcquired) { + $this->getInternalStore()->delete($key); + } } throw new LockConflictedException(); diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php index 9133280ddc133..30a5d0a1f503b 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php @@ -13,6 +13,7 @@ use Doctrine\DBAL\DriverManager; use Symfony\Component\Lock\Exception\InvalidArgumentException; +use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\PersistingStoreInterface; use Symfony\Component\Lock\Store\DoctrineDbalPostgreSqlStore; @@ -59,4 +60,30 @@ public function getInvalidDrivers() yield ['sqlite:///tmp/foo.db']; yield [DriverManager::getConnection(['url' => 'sqlite:///tmp/foo.db'])]; } + + public function testSaveAfterConflict() + { + $store1 = $this->getStore(); + $store2 = $this->getStore(); + + $key = new Key(uniqid(__METHOD__, true)); + + $store1->save($key); + $this->assertTrue($store1->exists($key)); + + $lockConflicted = false; + try { + $store2->save($key); + } catch (LockConflictedException $lockConflictedException) { + $lockConflicted = true; + } + + $this->assertTrue($lockConflicted); + $this->assertFalse($store2->exists($key)); + + $store1->delete($key); + + $store2->save($key); + $this->assertTrue($store2->exists($key)); + } }