Skip to content

Commit 3e73887

Browse files
jderussefabpot
authored andcommitted
[lock] Provides default implementation when store does not supports the behavior
1 parent 7e81098 commit 3e73887

11 files changed

+65
-112
lines changed

BlockingSharedLockStoreInterface.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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;
13+
14+
use Symfony\Component\Lock\Exception\LockConflictedException;
15+
16+
/**
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
interface BlockingSharedLockStoreInterface extends SharedLockStoreInterface
20+
{
21+
/**
22+
* Waits until a key becomes free for reading, then stores the resource.
23+
*
24+
* @throws LockConflictedException
25+
*/
26+
public function waitAndSaveRead(Key $key);
27+
}

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ CHANGELOG
77
* `MongoDbStore` does not implement `BlockingStoreInterface` anymore, typehint against `PersistingStoreInterface` instead.
88
* added support for shared locks
99
* added `NoLock`
10+
* deprecated `NotSupportedException`, it shouldn't be thrown anymore.
11+
* deprecated `RetryTillSaveStore`, logic has been moved in `Lock` and is not needed anymore.
1012

1113
5.1.0
1214
-----

Exception/NotSupportedException.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111

1212
namespace Symfony\Component\Lock\Exception;
1313

14+
trigger_deprecation('symfony/lock', '5.2', '%s is deprecated, You should stop using it, as it will be removed in 6.0.', NotSupportedException::class);
15+
1416
/**
1517
* NotSupportedException is thrown when an unsupported method is called.
1618
*
1719
* @author Jérémy Derussé <jeremy@derusse.com>
20+
*
21+
* @deprecated since Symfony 5.2
1822
*/
1923
class NotSupportedException extends \LogicException implements ExceptionInterface
2024
{

Lock.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use Symfony\Component\Lock\Exception\LockConflictedException;
2020
use Symfony\Component\Lock\Exception\LockExpiredException;
2121
use Symfony\Component\Lock\Exception\LockReleasingException;
22-
use Symfony\Component\Lock\Exception\NotSupportedException;
2322

2423
/**
2524
* Lock is the default implementation of the LockInterface.
@@ -70,9 +69,16 @@ public function acquire(bool $blocking = false): bool
7069
try {
7170
if ($blocking) {
7271
if (!$this->store instanceof BlockingStoreInterface) {
73-
throw new NotSupportedException(sprintf('The store "%s" does not support blocking locks.', get_debug_type($this->store)));
72+
while (true) {
73+
try {
74+
$this->store->wait($this->key);
75+
} catch (LockConflictedException $e) {
76+
usleep((100 + random_int(-10, 10)) * 1000);
77+
}
78+
}
79+
} else {
80+
$this->store->waitAndSave($this->key);
7481
}
75-
$this->store->waitAndSave($this->key);
7682
} else {
7783
$this->store->save($this->key);
7884
}
@@ -116,7 +122,9 @@ public function acquireRead(bool $blocking = false): bool
116122
{
117123
try {
118124
if (!$this->store instanceof SharedLockStoreInterface) {
119-
throw new NotSupportedException(sprintf('The store "%s" does not support shared locks.', get_debug_type($this->store)));
125+
$this->logger->debug('Store does not support ReadLocks, fallback to WriteLock.', ['resource' => $this->key]);
126+
127+
return $this->acquire($blocking);
120128
}
121129
if ($blocking) {
122130
$this->store->waitAndSaveRead($this->key);
@@ -125,7 +133,7 @@ public function acquireRead(bool $blocking = false): bool
125133
}
126134

127135
$this->dirty = true;
128-
$this->logger->debug('Successfully acquired the "{resource}" lock.', ['resource' => $this->key]);
136+
$this->logger->debug('Successfully acquired the "{resource}" lock for reading.', ['resource' => $this->key]);
129137

130138
if ($this->ttl) {
131139
$this->refresh();

SharedLockStoreInterface.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\Lock;
1313

1414
use Symfony\Component\Lock\Exception\LockConflictedException;
15-
use Symfony\Component\Lock\Exception\NotSupportedException;
1615

1716
/**
1817
* @author Jérémy Derussé <jeremy@derusse.com>
@@ -22,15 +21,7 @@ interface SharedLockStoreInterface extends PersistingStoreInterface
2221
/**
2322
* Stores the resource if it's not locked for reading by someone else.
2423
*
25-
* @throws NotSupportedException
2624
* @throws LockConflictedException
2725
*/
2826
public function saveRead(Key $key);
29-
30-
/**
31-
* Waits until a key becomes free for reading, then stores the resource.
32-
*
33-
* @throws LockConflictedException
34-
*/
35-
public function waitAndSaveRead(Key $key);
3627
}

Store/BlockingSharedLockStoreTrait.php

Lines changed: 0 additions & 33 deletions
This file was deleted.

Store/CombinedStore.php

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
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\NotSupportedException;
2019
use Symfony\Component\Lock\Key;
2120
use Symfony\Component\Lock\PersistingStoreInterface;
2221
use Symfony\Component\Lock\SharedLockStoreInterface;
@@ -29,7 +28,6 @@
2928
*/
3029
class CombinedStore implements SharedLockStoreInterface, LoggerAwareInterface
3130
{
32-
use BlockingSharedLockStoreTrait;
3331
use ExpiringStoreTrait;
3432
use LoggerAwareTrait;
3533

@@ -97,26 +95,17 @@ public function save(Key $key)
9795

9896
public function saveRead(Key $key)
9997
{
100-
if (null === $this->sharedLockStores) {
101-
$this->sharedLockStores = [];
102-
foreach ($this->stores as $store) {
103-
if ($store instanceof SharedLockStoreInterface) {
104-
$this->sharedLockStores[] = $store;
105-
}
106-
}
107-
}
108-
10998
$successCount = 0;
99+
$failureCount = 0;
110100
$storesCount = \count($this->stores);
111-
$failureCount = $storesCount - \count($this->sharedLockStores);
112101

113-
if (!$this->strategy->canBeMet($failureCount, $storesCount)) {
114-
throw new NotSupportedException(sprintf('The store "%s" does not contains enough compatible store to met the requirements.', get_debug_type($this)));
115-
}
116-
117-
foreach ($this->sharedLockStores as $store) {
102+
foreach ($this->stores as $store) {
118103
try {
119-
$store->saveRead($key);
104+
if ($store instanceof SharedLockStoreInterface) {
105+
$store->saveRead($key);
106+
} else {
107+
$store->save($key);
108+
}
120109
++$successCount;
121110
} catch (\Exception $e) {
122111
$this->logger->debug('One store failed to save the "{resource}" lock.', ['resource' => $key, 'store' => $store, 'exception' => $e]);

Store/RedisStore.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
class RedisStore implements SharedLockStoreInterface
3232
{
3333
use ExpiringStoreTrait;
34-
use BlockingSharedLockStoreTrait;
3534

3635
private $redis;
3736
private $initialTtl;

Store/RetryTillSaveStore.php

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@
1616
use Psr\Log\NullLogger;
1717
use Symfony\Component\Lock\BlockingStoreInterface;
1818
use Symfony\Component\Lock\Exception\LockConflictedException;
19-
use Symfony\Component\Lock\Exception\NotSupportedException;
2019
use Symfony\Component\Lock\Key;
20+
use Symfony\Component\Lock\Lock;
2121
use Symfony\Component\Lock\PersistingStoreInterface;
22-
use Symfony\Component\Lock\SharedLockStoreInterface;
22+
23+
trigger_deprecation('symfony/lock', '5.2', '%s is deprecated, the "%s" class provides the logic when store is not blocking.', RetryTillSaveStore::class, Lock::class);
2324

2425
/**
2526
* RetryTillSaveStore is a PersistingStoreInterface implementation which decorate a non blocking PersistingStoreInterface to provide a
2627
* blocking storage.
2728
*
2829
* @author Jérémy Derussé <jeremy@derusse.com>
30+
*
31+
* @deprecated since Symfony 5.2
2932
*/
30-
class RetryTillSaveStore implements BlockingStoreInterface, SharedLockStoreInterface, LoggerAwareInterface
33+
class RetryTillSaveStore implements BlockingStoreInterface, LoggerAwareInterface
3134
{
3235
use LoggerAwareTrait;
3336

@@ -78,24 +81,6 @@ public function waitAndSave(Key $key)
7881
throw new LockConflictedException();
7982
}
8083

81-
public function saveRead(Key $key)
82-
{
83-
if (!$this->decorated instanceof SharedLockStoreInterface) {
84-
throw new NotSupportedException(sprintf('The "%s" store must decorate a "%s" store.', get_debug_type($this), ShareLockStoreInterface::class));
85-
}
86-
87-
$this->decorated->saveRead($key);
88-
}
89-
90-
public function waitAndSaveRead(Key $key)
91-
{
92-
if (!$this->decorated instanceof SharedLockStoreInterface) {
93-
throw new NotSupportedException(sprintf('The "%s" store must decorate a "%s" store.', get_debug_type($this), ShareLockStoreInterface::class));
94-
}
95-
96-
$this->decorated->waitAndSaveRead($key);
97-
}
98-
9984
/**
10085
* {@inheritdoc}
10186
*/

Tests/Store/BlockingStoreTestTrait.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\Lock\Tests\Store;
1313

1414
use Symfony\Component\Lock\Exception\LockConflictedException;
15-
use Symfony\Component\Lock\Exception\NotSupportedException;
1615
use Symfony\Component\Lock\Key;
1716
use Symfony\Component\Lock\PersistingStoreInterface;
1817

@@ -61,25 +60,20 @@ public function testBlockingLocks()
6160
// This call should failed given the lock should already by acquired by the child
6261
$store->save($key);
6362
$this->fail('The store saves a locked key.');
64-
} catch (NotSupportedException $e) {
6563
} catch (LockConflictedException $e) {
6664
}
6765

6866
// send the ready signal to the child
6967
posix_kill($childPID, \SIGHUP);
7068

7169
// This call should be blocked by the child #1
72-
try {
73-
$store->waitAndSave($key);
74-
$this->assertTrue($store->exists($key));
75-
$store->delete($key);
70+
$store->waitAndSave($key);
71+
$this->assertTrue($store->exists($key));
72+
$store->delete($key);
7673

77-
// Now, assert the child process worked well
78-
pcntl_waitpid($childPID, $status1);
79-
$this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource');
80-
} catch (NotSupportedException $e) {
81-
$this->markTestSkipped(sprintf('The store %s does not support waitAndSave.', \get_class($store)));
82-
}
74+
// Now, assert the child process worked well
75+
pcntl_waitpid($childPID, $status1);
76+
$this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource');
8377
} else {
8478
// Block SIGHUP signal
8579
pcntl_sigprocmask(\SIG_BLOCK, [\SIGHUP]);

Tests/Store/CombinedStoreTest.php

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PHPUnit\Framework\MockObject\MockObject;
1515
use Symfony\Component\Lock\BlockingStoreInterface;
1616
use Symfony\Component\Lock\Exception\LockConflictedException;
17-
use Symfony\Component\Lock\Exception\NotSupportedException;
1817
use Symfony\Component\Lock\Key;
1918
use Symfony\Component\Lock\PersistingStoreInterface;
2019
use Symfony\Component\Lock\SharedLockStoreInterface;
@@ -355,18 +354,6 @@ public function testDeleteDontStopOnFailure()
355354
$this->store->delete($key);
356355
}
357356

358-
public function testSaveReadWithIncompatibleStores()
359-
{
360-
$key = new Key(uniqid(__METHOD__, true));
361-
362-
$badStore = $this->createMock(PersistingStoreInterface::class);
363-
364-
$store = new CombinedStore([$badStore], new UnanimousStrategy());
365-
$this->expectException(NotSupportedException::class);
366-
367-
$store->saveRead($key);
368-
}
369-
370357
public function testSaveReadWithCompatibleStore()
371358
{
372359
$key = new Key(uniqid(__METHOD__, true));

0 commit comments

Comments
 (0)