diff --git a/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php b/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php index 3f982e602ce6..298cd1ba2432 100644 --- a/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php +++ b/src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php @@ -68,7 +68,7 @@ public function reserve(int $tokens = 1, float $maxTime = null): Reservation $reservation = new Reservation($now, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now)), true, $this->limit)); } else { - $waitDuration = $window->calculateTimeForTokens($tokens); + $waitDuration = $window->calculateTimeForTokens($tokens, $now); if (null !== $maxTime && $waitDuration > $maxTime) { // process needs to wait longer than set interval diff --git a/src/Symfony/Component/RateLimiter/Policy/Window.php b/src/Symfony/Component/RateLimiter/Policy/Window.php index 93452797075a..39248a53d7f7 100644 --- a/src/Symfony/Component/RateLimiter/Policy/Window.php +++ b/src/Symfony/Component/RateLimiter/Policy/Window.php @@ -75,15 +75,13 @@ public function getAvailableTokens(float $now) return $this->maxSize - $this->hitCount; } - public function calculateTimeForTokens(int $tokens): int + public function calculateTimeForTokens(int $tokens, float $now): int { if (($this->maxSize - $this->hitCount) >= $tokens) { return 0; } - $cyclesRequired = ceil($tokens / $this->maxSize); - - return $cyclesRequired * $this->intervalInSeconds; + return (int) ceil($this->timer + $this->intervalInSeconds - $now); } public function __serialize(): array diff --git a/src/Symfony/Component/RateLimiter/Tests/Policy/FixedWindowLimiterTest.php b/src/Symfony/Component/RateLimiter/Tests/Policy/FixedWindowLimiterTest.php index 0cffc14e1aee..84df7bc850aa 100644 --- a/src/Symfony/Component/RateLimiter/Tests/Policy/FixedWindowLimiterTest.php +++ b/src/Symfony/Component/RateLimiter/Tests/Policy/FixedWindowLimiterTest.php @@ -37,6 +37,7 @@ protected function setUp(): void public function testConsume() { + $now = time(); $limiter = $this->createLimiter(); // fill 9 tokens in 45 seconds @@ -51,6 +52,9 @@ public function testConsume() $rateLimit = $limiter->consume(); $this->assertFalse($rateLimit->isAccepted()); $this->assertSame(10, $rateLimit->getLimit()); + // Window ends after 1 minute + $retryAfter = \DateTimeImmutable::createFromFormat('U', $now + 60); + $this->assertEquals($retryAfter, $rateLimit->getRetryAfter()); } /**