diff --git a/README.md b/README.md index 1ed356c..770fbc0 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ $id = $lock->lock('pdf:create', ttl: new \DateInterval('PT10S')); $id = $lock->lock('pdf:create', wait: 5); // or $id = $lock->lock('pdf:create', wait: new \DateInterval('PT5S')); + +// Acquire lock with id - 14e1b600-9e97-11d8-9f32-f2801f1b9fd1 +$id = $lock->lock('pdf:create', id: '14e1b600-9e97-11d8-9f32-f2801f1b9fd1'); ``` ### Acquire read lock @@ -72,6 +75,9 @@ $id = $lock->lockRead('pdf:create', ttl: new \DateInterval('PT10S')); $id = $lock->lockRead('pdf:create', wait: 5); // or $id = $lock->lockRead('pdf:create', wait: new \DateInterval('PT5S')); + +// Acquire lock with id - 14e1b600-9e97-11d8-9f32-f2801f1b9fd1 +$id = $lock->lockRead('pdf:create', id: '14e1b600-9e97-11d8-9f32-f2801f1b9fd1'); ``` ### Release lock diff --git a/src/Lock.php b/src/Lock.php index 6a6becd..a5fb112 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -29,18 +29,20 @@ public function __construct( * other processes that attempt to lock the same resource will be blocked until the lock is released. * * @param non-empty-string $resource The name of the resource to be locked. + * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). * @param int|DateInterval $waitTTL How long to wait to acquire lock until returning false. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. */ public function lock( string $resource, + ?string $id = null, int|DateInterval $ttl = 0, int|DateInterval $waitTTL = 0, ): false|string { $request = new Request(); $request->setResource($resource); - $request->setId($id = $this->identityGenerator->generate()); + $request->setId($id ??= $this->identityGenerator->generate()); $request->setTtl($this->convertTimeToSeconds($ttl)); $request->setWait($this->convertTimeToSeconds($waitTTL)); @@ -57,18 +59,20 @@ public function lock( * will be blocked until all shared locks are released. * * @param non-empty-string $resource The name of the resource to be locked. + * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). * @param int|DateInterval $waitTTL How long to wait to acquire lock until returning false. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. */ public function lockRead( string $resource, + ?string $id = null, int|DateInterval $ttl = 0, int|DateInterval $waitTTL = 0, ): false|string { $request = new Request(); $request->setResource($resource); - $request->setId($id = $this->identityGenerator->generate()); + $request->setId($id ??= $this->identityGenerator->generate()); $request->setTtl($this->convertTimeToSeconds($ttl)); $request->setWait($this->convertTimeToSeconds($waitTTL)); diff --git a/tests/src/LockTest.php b/tests/src/LockTest.php index 6fbc88e..22d7541 100644 --- a/tests/src/LockTest.php +++ b/tests/src/LockTest.php @@ -45,28 +45,33 @@ public function testLock( int|\DateInterval|\DateTimeInterface $wait, int $expectedWaitSec, bool $expectedResult = true, + ?string $id = null, ): void { - $this->idGenerator->shouldReceive('generate')->once()->andReturn('some-id'); + if ($id === null) { + $this->idGenerator->shouldReceive('generate')->once()->andReturn('some-id'); + } $this->rpc->shouldReceive('call') ->withArgs(function (string $method, Request $request, string $response) use ( $expectedTtlSec, $expectedWaitSec, - $callMethod + $callMethod, + $id ): bool { return $method === $callMethod && $request->getResource() === 'resource' - && $request->getId() === 'some-id' + && $request->getId() === ($id === null ? 'some-id' : $id) && $request->getTtl() === $expectedTtlSec - && $request->getWait() === $expectedWaitSec - && $response === Response::class; + && $request->getWait() === $expectedWaitSec + && $response === Response::class; }) ->andReturn(new Response(['ok' => $expectedResult])); + $result = $this->lock->$method(resource: 'resource', id: $id, ttl: $ttl, waitTTL: $wait); if ($expectedResult) { - $this->assertSame('some-id', $this->lock->$method('resource', $ttl, $wait)); + $this->assertSame(($id === null ? 'some-id' : $id), $result); } else { - $this->assertFalse($this->lock->$method('resource', $ttl, $wait)); + $this->assertFalse($result); } } @@ -131,7 +136,7 @@ public function testUpdateTTL($ttl, int $expectedTtl, bool $result): void { $this->rpc->shouldReceive('call') ->once() - ->withArgs(function (string $method, Request $request, string $response) use($expectedTtl): bool { + ->withArgs(function (string $method, Request $request, string $response) use ($expectedTtl): bool { return $method === 'lock.UpdateTTL' && $request->getResource() === 'resource' && $request->getId() === 'some-id' @@ -165,13 +170,23 @@ public function lockTypeDataProvider(): \Generator { foreach ($this->lockDataProvider() as $name => $data) { foreach ([true, false] as $result) { - yield 'lock: ' . $name . ' | ' . \var_export($result, true) => ['lock', 'lock.Lock', ...$data, $result]; - yield 'read-lock: ' . $name . ' | ' . \var_export($result, true) => [ - 'lockRead', - 'lock.LockRead', - ...$data, - $result, - ]; + foreach (['id1', null] as $id) { + yield 'lock: ' . $name . ' | ' .$id. ' | ' . \var_export($result, true) => [ + 'lock', + 'lock.Lock', + ...$data, + $result, + $id + ]; + + yield 'read-lock: ' . $name . ' | ' .$id. ' | ' . \var_export($result, true) => [ + 'lockRead', + 'lock.LockRead', + ...$data, + $result, + $id + ]; + } } } }