bug #38329 [lock] Fix non-blocking store fallback (jderusse)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[lock] Fix non-blocking store fallback

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | /
| License       | MIT
| Doc PR        | /

Fix issue introduced in #38328

Tests added to avoid regressions.

Commits
-------

7a80e41cd8 Fix non-blocking store fallback
This commit is contained in:
Fabien Potencier 2020-09-28 13:34:12 +02:00
commit a429deee62
2 changed files with 148 additions and 4 deletions

View File

@ -71,7 +71,8 @@ final class Lock implements SharedLockInterface, LoggerAwareInterface
if (!$this->store instanceof BlockingStoreInterface) {
while (true) {
try {
$this->store->wait($this->key);
$this->store->save($this->key);
break;
} catch (LockConflictedException $e) {
usleep((100 + random_int(-10, 10)) * 1000);
}
@ -127,7 +128,18 @@ final class Lock implements SharedLockInterface, LoggerAwareInterface
return $this->acquire($blocking);
}
if ($blocking) {
$this->store->waitAndSaveRead($this->key);
if (!$this->store instanceof BlockingSharedLockStoreInterface) {
while (true) {
try {
$this->store->saveRead($this->key);
break;
} catch (LockConflictedException $e) {
usleep((100 + random_int(-10, 10)) * 1000);
}
}
} else {
$this->store->waitAndSaveRead($this->key);
}
} else {
$this->store->saveRead($this->key);
}

View File

@ -13,11 +13,13 @@ namespace Symfony\Component\Lock\Tests;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Lock\BlockingSharedLockStoreInterface;
use Symfony\Component\Lock\BlockingStoreInterface;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\PersistingStoreInterface;
use Symfony\Component\Lock\SharedLockStoreInterface;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
@ -40,7 +42,7 @@ class LockTest extends TestCase
$this->assertTrue($lock->acquire(false));
}
public function testAcquireNoBlockingStoreInterface()
public function testAcquireNoBlockingWithPersistingStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
@ -56,6 +58,44 @@ class LockTest extends TestCase
$this->assertTrue($lock->acquire(false));
}
public function testAcquireBlockingWithPersistingStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
$lock = new Lock($key, $store);
$store
->expects($this->once())
->method('save');
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquire(true));
}
public function testAcquireBlockingRetryWithPersistingStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
$lock = new Lock($key, $store);
$store
->expects($this->any())
->method('save')
->willReturnCallback(static function () {
if (1 === random_int(0, 1)) {
return;
}
throw new LockConflictedException('boom');
});
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquire(true));
}
public function testAcquireReturnsFalse()
{
$key = new Key(uniqid(__METHOD__, true));
@ -90,7 +130,7 @@ class LockTest extends TestCase
$this->assertFalse($lock->acquire(false));
}
public function testAcquireBlocking()
public function testAcquireBlockingWithBlockingStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->createMock(BlockingStoreInterface::class);
@ -372,4 +412,96 @@ class LockTest extends TestCase
yield [[0.1], false];
yield [[-0.1, null], false];
}
public function testAcquireReadNoBlockingWithSharedLockStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->createMock(SharedLockStoreInterface::class);
$lock = new Lock($key, $store);
$store
->expects($this->once())
->method('saveRead');
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquireRead(false));
}
public function testAcquireReadBlockingWithBlockingSharedLockStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->createMock(BlockingSharedLockStoreInterface::class);
$lock = new Lock($key, $store);
$store
->expects($this->once())
->method('waitAndSaveRead');
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquireRead(true));
}
public function testAcquireReadBlockingWithSharedLockStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->createMock(SharedLockStoreInterface::class);
$lock = new Lock($key, $store);
$store
->expects($this->any())
->method('saveRead')
->willReturnCallback(static function () {
if (1 === random_int(0, 1)) {
return;
}
throw new LockConflictedException('boom');
});
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquireRead(true));
}
public function testAcquireReadBlockingWithBlockingLockStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->createMock(BlockingStoreInterface::class);
$lock = new Lock($key, $store);
$store
->expects($this->once())
->method('waitAndSave');
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquireRead(true));
}
public function testAcquireReadBlockingWithPersistingStoreInterface()
{
$key = new Key(uniqid(__METHOD__, true));
$store = $this->createMock(PersistingStoreInterface::class);
$lock = new Lock($key, $store);
$store
->expects($this->any())
->method('save')
->willReturnCallback(static function () {
if (1 === random_int(0, 1)) {
return;
}
throw new LockConflictedException('boom');
});
$store
->method('exists')
->willReturnOnConsecutiveCalls(true, false);
$this->assertTrue($lock->acquireRead(true));
}
}