From 0390a962655e2351f28a66919954763c2250e950 Mon Sep 17 00:00:00 2001 From: Mario Juarez Date: Tue, 18 Aug 2026 15:22:17 +0200 Subject: [PATCH] Fix save() persisting a non-positive TTL when the clock ticks over mid-save --- src/Limit.php | 2 +- tests/Unit/LimitStoreTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Limit.php b/src/Limit.php index 7d82c85..7b6a376 100644 --- a/src/Limit.php +++ b/src/Limit.php @@ -372,7 +372,7 @@ public function save(RateLimitStore $store, int $resetHits = 1): static $successful = $store->set( key: $this->getName(), value: json_encode($data, JSON_THROW_ON_ERROR), - ttl: $this->getRemainingSeconds(), + ttl: max($this->getRemainingSeconds(), 1), ); if ($successful === false) { diff --git a/tests/Unit/LimitStoreTest.php b/tests/Unit/LimitStoreTest.php index 6945e64..fe24201 100644 --- a/tests/Unit/LimitStoreTest.php +++ b/tests/Unit/LimitStoreTest.php @@ -4,6 +4,7 @@ use Saloon\RateLimitPlugin\Limit; use Saloon\RateLimitPlugin\Stores\MemoryStore; +use Saloon\RateLimitPlugin\Contracts\RateLimitStore; use Saloon\RateLimitPlugin\Exceptions\LimitException; use Saloon\RateLimitPlugin\Tests\Fixtures\Connectors\TestConnector; @@ -90,3 +91,35 @@ 'hits' => 1, ]); }); + +test('the limit is not saved with a non-positive ttl when the clock ticks over mid-save', function () { + $store = new class implements RateLimitStore { + public ?int $ttl = null; + + public function get(string $key): ?string + { + return null; + } + + public function set(string $key, string $value, int $ttl): bool + { + $this->ttl = $ttl; + + return true; + } + }; + + $limit = (new class(15) extends Limit { + protected int $clockReads = 0; + + // The tick lands between the two reads of the remaining time inside save(). + protected function getCurrentTimestamp(): int + { + return parent::getCurrentTimestamp() + ($this->clockReads++ > 0 ? 1 : 0); + } + })->everySeconds(1)->setPrefix('custom')->name('limit'); + + $limit->hit()->save($store); + + expect($store->ttl)->toBe(1); +});