diff --git a/composer.json b/composer.json index cb34729..db89d67 100644 --- a/composer.json +++ b/composer.json @@ -30,11 +30,12 @@ "require": { "php": ">=8.1", "roadrunner-php/lock": "^1.0", - "symfony/lock": "^6.0 || ^7.0" + "symfony/lock": "^6.0 || ^7.0", + "symfony/polyfill-php83": "^1.32" }, "require-dev": { "phpunit/phpunit": "^10.0", - "vimeo/psalm": "^5.9" + "vimeo/psalm": "^5.9 || ^6.0" }, "autoload": { "psr-4": { diff --git a/src/RandomTokenGenerator.php b/src/RandomTokenGenerator.php index 5e8efb2..b5d58f9 100644 --- a/src/RandomTokenGenerator.php +++ b/src/RandomTokenGenerator.php @@ -14,6 +14,7 @@ public function __construct( ) { } + #[\Override] public function generate(): string { return \bin2hex(\random_bytes($this->length)); diff --git a/src/RoadRunnerStore.php b/src/RoadRunnerStore.php index cda3a8c..3c064fc 100644 --- a/src/RoadRunnerStore.php +++ b/src/RoadRunnerStore.php @@ -25,17 +25,24 @@ public function __construct( private readonly RR\LockInterface $lock, private readonly TokenGeneratorInterface $tokens = new RandomTokenGenerator(), private readonly float $initialTtl = 300.0, - private readonly float $initialWaitTtl = 60, + private readonly float $initialWaitTtl = 0, ) { \assert($this->initialTtl >= 0); \assert($this->initialWaitTtl >= 0); } - public function withTtl(float $ttl): self + /** + * Clone current instance with another values of ttl. + * @param float $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). + * @param float $waitTtl How long to wait to acquire lock until returning false, in seconds. + */ + public function withTtl(float $ttl, ?float $waitTtl = null): self { - return new self($this->lock, $this->tokens, $ttl, $this->initialWaitTtl); + $waitTtl ??= $this->initialWaitTtl; + return new self($this->lock, $this->tokens, $ttl, $waitTtl); } + #[\Override] public function save(Key $key): void { \assert(false === $key->hasState(__CLASS__)); @@ -46,7 +53,7 @@ public function save(Key $key): void /** @var non-empty-string $resource */ $resource = (string)$key; - $status = $this->lock->lock($resource, $lockId, $this->initialTtl); + $status = $this->lock->lock($resource, $lockId, $this->initialTtl, $this->initialWaitTtl); if (false === $status) { throw new LockConflictedException('RoadRunner. Failed to make lock'); @@ -58,6 +65,7 @@ public function save(Key $key): void } } + #[\Override] public function saveRead(Key $key): void { \assert(false === $key->hasState(__CLASS__)); @@ -65,7 +73,7 @@ public function saveRead(Key $key): void /** @var non-empty-string $resource */ $resource = (string)$key; - $status = $this->lock->lockRead($resource, $lockId, $this->initialTtl); + $status = $this->lock->lockRead($resource, $lockId, $this->initialTtl, $this->initialWaitTtl); if (false === $status) { throw new LockConflictedException('RoadRunner. Failed to make read lock'); @@ -74,6 +82,7 @@ public function saveRead(Key $key): void $key->setState(__CLASS__, $lockId); } + #[\Override] public function exists(Key $key): bool { \assert($key->hasState(__CLASS__)); @@ -86,6 +95,7 @@ public function exists(Key $key): bool return $this->lock->exists($resource, $lockId); } + #[\Override] public function putOffExpiration(Key $key, float $ttl): void { \assert($key->hasState(__CLASS__)); @@ -101,6 +111,7 @@ public function putOffExpiration(Key $key, float $ttl): void } } + #[\Override] public function delete(Key $key): void { \assert($key->hasState(__CLASS__)); @@ -111,6 +122,7 @@ public function delete(Key $key): void $this->lock->release($resource, $lockId); } + #[\Override] public function waitAndSave(Key $key): void { $lockId = $this->getUniqueToken($key); diff --git a/tests/RoadRunnerStoreTest.php b/tests/RoadRunnerStoreTest.php index 1d74891..12acf1e 100644 --- a/tests/RoadRunnerStoreTest.php +++ b/tests/RoadRunnerStoreTest.php @@ -166,7 +166,7 @@ public function testWaitAndSaveSuccess(): void { $this->rrLock->expects($this->once()) ->method('lock') - ->with('resource-name', 'random-id', 300, 60) + ->with('resource-name', 'random-id', 300, 0) ->willReturn('lock-id'); $store = new RoadRunnerStore($this->rrLock, $this->tokens); @@ -184,10 +184,51 @@ public function testWaitAndSaveFail(): void $this->rrLock->expects($this->once()) ->method('lock') - ->with('resource-name', 'random-id', 300, 60) + ->with('resource-name', 'random-id', 300, 0) ->willReturn(false); $store = new RoadRunnerStore($this->rrLock, $this->tokens); $store->waitAndSave(new Key('resource-name')); } + + /** + * @dataProvider dataWithTtl + */ + public function testWithTtl(float $ttl, ?float $waitTtl, float $ttlExp, float $waitTtlExp): void + { + $this->rrLock->expects($this->once()) + ->method('lock') + ->with('resource-name', 'random-id', $ttlExp, $waitTtlExp) + ->willReturn('lock-id'); + + $s = new RoadRunnerStore($this->rrLock, $this->tokens); + $s->withTtl($ttl, $waitTtl)->save(new Key('resource-name')); + } + + /** + * @return iterable + */ + public static function dataWithTtl(): iterable + { + yield [ + 100, + null, + 100, + 0, + ]; + + yield [ + 0.1, + 0.1, + 0.1, + 0.1, + ]; + + yield [ + 0, + 0, + 0, + 0, + ]; + } }