Skip to content

Commit 6b11b36

Browse files
authored
Merge pull request #2 from roadrunner-php/wip
Beta
2 parents 2dd3cb4 + e5885a3 commit 6b11b36

File tree

4 files changed

+207
-32
lines changed

4 files changed

+207
-32
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "roadrunner-php/symfony-lock-driver",
33
"type": "library",
4-
"description": "RoadRunner: HTTP and PSR-7 worker",
4+
"description": "RoadRunner: symfony/lock bridge",
55
"license": "MIT",
66
"authors": [
77
{

src/RoadRunnerStore.php

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,74 @@
44

55
namespace Spiral\RoadRunner\Symfony\Lock;
66

7-
use RoadRunner\Lock\LockInterface as RrLockInterface;
7+
use RoadRunner\Lock as RR;
8+
use Spiral\Goridge\RPC\Exception\RPCException;
9+
use Symfony\Component\Lock\Exception\LockAcquiringException;
10+
use Symfony\Component\Lock\Exception\LockConflictedException;
11+
use Symfony\Component\Lock\Exception\LockReleasingException;
812
use Symfony\Component\Lock\Key;
9-
use Symfony\Component\Lock\PersistingStoreInterface;
1013
use Symfony\Component\Lock\SharedLockStoreInterface;
1114

1215
final class RoadRunnerStore implements SharedLockStoreInterface
1316
{
14-
private string $processId;
15-
17+
/**
18+
* @param RR\LockInterface $rrLock
19+
* @param float $initialTtl The time-to-live of the lock, in seconds. Defaults to 0 (forever).
20+
* @param float $initialWaitTtl How long to wait to acquire lock until returning false.
21+
*/
1622
public function __construct(
17-
private readonly RrLockInterface $rrLock,
18-
?string $processId = null
23+
private readonly RR\LockInterface $rrLock,
24+
private readonly float $initialTtl = 300.0,
25+
private readonly float $initialWaitTtl = 0,
1926
) {
20-
$this->processId = $processId ?? '*';
27+
\assert($this->initialTtl >= 0);
28+
\assert($this->initialWaitTtl >= 0);
2129
}
2230

2331
public function save(Key $key): void
2432
{
25-
$this->rrLock->lock((string) $key, $this->processId);
33+
\assert(false === $key->hasState(__CLASS__));
34+
try {
35+
$lockId = $this->rrLock->lock((string) $key, null, $this->initialTtl, $this->initialWaitTtl);
36+
if (false === $lockId) {
37+
throw new LockConflictedException('RoadRunner. Failed to make lock');
38+
}
39+
$key->setState(__CLASS__, $lockId);
40+
} catch (RPCException $e) {
41+
throw new LockAcquiringException(message: 'RoadRunner. RPC call error', previous: $e);
42+
}
2643
}
2744

28-
public function exists(Key $key): bool
45+
public function saveRead(Key $key): void
2946
{
30-
return $this->rrLock->exists((string) $key, $this->processId);
47+
\assert(false === $key->hasState(__CLASS__));
48+
$lockId = $this->rrLock->lockRead((string)$key, null, $this->initialTtl, $this->initialWaitTtl);
49+
if (false === $lockId) {
50+
throw new LockConflictedException('RoadRunner. Failed to make read lock');
51+
}
52+
$key->setState(__CLASS__, $lockId);
3153
}
3254

33-
public function putOffExpiration(Key $key, float $ttl): void
55+
public function exists(Key $key): bool
3456
{
35-
$this->rrLock->updateTTL((string) $key, $this->processId, $ttl);
57+
\assert($key->hasState(__CLASS__));
58+
return $this->rrLock->exists((string) $key, $key->getState(__CLASS__));
3659
}
3760

38-
public function delete(Key $key): void
61+
public function putOffExpiration(Key $key, float $ttl): void
3962
{
40-
$this->rrLock->release((string) $key, $this->processId);
63+
\assert($key->hasState(__CLASS__));
64+
\assert($ttl > 0);
65+
if (false === $this->rrLock->updateTTL((string) $key, $key->getState(__CLASS__), $ttl)) {
66+
throw new LockConflictedException('RoadRunner. Failed to update lock ttl');
67+
}
4168
}
4269

43-
public function saveRead(Key $key): void
70+
public function delete(Key $key): void
4471
{
45-
$this->rrLock->lockRead((string) $key, $this->processId);
72+
\assert($key->hasState(__CLASS__));
73+
if (false === $this->rrLock->release((string) $key, $key->getState(__CLASS__))) {
74+
throw new LockReleasingException('RoadRunner. Failed to release lock');
75+
}
4676
}
4777
}

tests/IntegrationTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Spiral\RoadRunner\Symfony\Lock\Tests;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use RoadRunner\Lock as RR;
9+
use Spiral\RoadRunner\Symfony\Lock\RoadRunnerStore;
10+
use Symfony\Component\Lock\LockFactory;
11+
12+
final class IntegrationTest extends TestCase
13+
{
14+
public function testLock(): void
15+
{
16+
$responseList = [
17+
'test-lock' => [
18+
'uuid1',
19+
false,
20+
'uuid2'
21+
],
22+
];
23+
$rrLock = $this->createMock(RR\LockInterface::class);
24+
$rrLock
25+
->expects(self::exactly(3))
26+
->method('lock')
27+
->willReturnCallback(function (string $name) use (&$responseList) {
28+
$array_shift = \array_shift($responseList[$name]);
29+
return $array_shift;
30+
});
31+
$rrLock
32+
->expects(self::exactly(2))
33+
->method('updateTTL')
34+
->willReturn(true);
35+
36+
$rrLock
37+
->expects(self::exactly(2))
38+
->method('release')
39+
->willReturn(true);
40+
41+
// lock
42+
$factory = new LockFactory(new RoadRunnerStore($rrLock));
43+
$lock1 = $factory->createLock('test-lock');
44+
self::assertTrue($lock1->acquire());
45+
46+
$lock2 = $factory->createLock('test-lock');
47+
self::assertFalse($lock2->acquire());
48+
49+
$lock1->release();
50+
51+
// lock 2
52+
self::assertTrue($lock2->acquire());
53+
$lock2->release();
54+
}
55+
}

tests/RoadRunnerStoreTest.php

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,147 @@
77
use PHPUnit\Framework\TestCase;
88
use RoadRunner\Lock\LockInterface as RrLock;
99
use Spiral\RoadRunner\Symfony\Lock\RoadRunnerStore;
10+
use Symfony\Component\Lock\Exception\LockConflictedException;
11+
use Symfony\Component\Lock\Exception\LockReleasingException;
1012
use Symfony\Component\Lock\Key;
1113

1214
final class RoadRunnerStoreTest extends TestCase
1315
{
14-
public function testSave(): void
16+
public function testSaveSuccess(): void
1517
{
1618
$rrLock = $this->createMock(RrLock::class);
1719
$rrLock->expects(self::once())
1820
->method('lock')
19-
->with('resource-name', '*');
21+
->with('resource-name', null)
22+
->willReturn('lock-id');
2023
$store = new RoadRunnerStore($rrLock);
21-
$store->save(new Key('resource-name'));
24+
$key = new Key('resource-name');
25+
$store->save($key);
26+
self::assertTrue($key->hasState(RoadRunnerStore::class));
27+
self::assertSame('lock-id', $key->getState(RoadRunnerStore::class));
2228
}
2329

24-
public function testExists(): void
30+
public function testSaveReadSuccess(): void
31+
{
32+
$rrLock = $this->createMock(RrLock::class);
33+
$rrLock->expects(self::once())
34+
->method('lockRead')
35+
->with('resource-name', null)
36+
->willReturn('lock-id');
37+
$store = new RoadRunnerStore($rrLock);
38+
$key = new Key('resource-name');
39+
$store->saveRead($key);
40+
}
41+
42+
public function testExistsSuccess(): void
2543
{
2644
$rrLock = $this->createMock(RrLock::class);
2745
$rrLock->expects(self::once())
2846
->method('exists')
29-
->with('resource-name', '*');
47+
->with('resource-name')
48+
->willReturn(true);
3049
$store = new RoadRunnerStore($rrLock);
31-
$store->exists(new Key('resource-name'));
50+
$key = new Key('resource-name');
51+
$key->setState(RoadRunnerStore::class, 'lock-id');
52+
$store->exists($key);
3253
}
3354

34-
public function testPutOffExpiration(): void
55+
public function testPutOffExpirationSuccess(): void
3556
{
3657
$rrLock = $this->createMock(RrLock::class);
3758
$rrLock->expects(self::once())
3859
->method('updateTTL')
39-
->with('resource-name', '*', 3600.0);
60+
->with('resource-name', 'lock-id', 3600.0)
61+
->willReturn(true);
4062
$store = new RoadRunnerStore($rrLock);
41-
$store->putOffExpiration(new Key('resource-name'), 3600.0);
63+
$key = new Key('resource-name');
64+
$key->setState(RoadRunnerStore::class, 'lock-id');
65+
$store->putOffExpiration($key, 3600.0);
4266
}
4367

44-
public function testDelete(): void
68+
public function testDeleteSuccess(): void
4569
{
4670
$rrLock = $this->createMock(RrLock::class);
4771
$rrLock->expects(self::once())
4872
->method('release')
49-
->with('resource-name', '*');
73+
->with('resource-name')
74+
->willReturn(true);
5075
$store = new RoadRunnerStore($rrLock);
51-
$store->delete(new Key('resource-name'));
76+
$key = new Key('resource-name');
77+
$key->setState(RoadRunnerStore::class, 'lock-id');
78+
$store->delete($key);
5279
}
5380

54-
public function testSaveRead(): void
81+
public function testSaveFail(): void
5582
{
83+
self::expectException(LockConflictedException::class);
84+
self::expectExceptionMessage('RoadRunner. Failed to make lock');
85+
86+
$rrLock = $this->createMock(RrLock::class);
87+
$rrLock->expects(self::once())
88+
->method('lock')
89+
->with('resource-name', null)
90+
->willReturn(false);
91+
$store = new RoadRunnerStore($rrLock);
92+
$store->save(new Key('resource-name'));
93+
}
94+
95+
public function testSaveReadFail(): void
96+
{
97+
self::expectException(LockConflictedException::class);
98+
self::expectExceptionMessage('RoadRunner. Failed to make read lock');
99+
56100
$rrLock = $this->createMock(RrLock::class);
57101
$rrLock->expects(self::once())
58102
->method('lockRead')
59-
->with('resource-name', '*');
103+
->with('resource-name', null);
104+
$store = new RoadRunnerStore($rrLock);
105+
$key = new Key('resource-name');
106+
$store->saveRead($key);
107+
}
108+
109+
public function testExistsFail(): void
110+
{
111+
$rrLock = $this->createMock(RrLock::class);
112+
$rrLock->expects(self::once())
113+
->method('exists')
114+
->with('resource-name')
115+
->willReturn(false);
116+
$store = new RoadRunnerStore($rrLock);
117+
$key = new Key('resource-name');
118+
$key->setState(RoadRunnerStore::class, 'lock-id');
119+
self::assertFalse($store->exists($key));
120+
}
121+
122+
public function testPutOffExpirationFail(): void
123+
{
124+
self::expectException(LockConflictedException::class);
125+
self::expectExceptionMessage('RoadRunner. Failed to update lock ttl');
126+
127+
$rrLock = $this->createMock(RrLock::class);
128+
$rrLock->expects(self::once())
129+
->method('updateTTL')
130+
->with('resource-name', 'lock-id', 3600.0)
131+
->willReturn(false);
132+
$store = new RoadRunnerStore($rrLock);
133+
$key = new Key('resource-name');
134+
$key->setState(RoadRunnerStore::class, 'lock-id');
135+
$store->putOffExpiration($key, 3600.0);
136+
}
137+
138+
public function testDeleteFail(): void
139+
{
140+
self::expectException(LockReleasingException::class);
141+
self::expectExceptionMessage('RoadRunner. Failed to release lock');
142+
143+
$rrLock = $this->createMock(RrLock::class);
144+
$rrLock->expects(self::once())
145+
->method('release')
146+
->with('resource-name')
147+
->willReturn(false);
60148
$store = new RoadRunnerStore($rrLock);
61-
$store->saveRead(new Key('resource-name'));
149+
$key = new Key('resource-name');
150+
$key->setState(RoadRunnerStore::class, 'lock-id');
151+
$store->delete($key);
62152
}
63153
}

0 commit comments

Comments
 (0)