8000 calculateTimeForTokens for SlidingWindow · symfony/symfony@459b332 · GitHub
[go: up one dir, main page]

Skip to content

Commit 459b332

Browse files
committed
calculateTimeForTokens for SlidingWindow
1 parent e8826b7 commit 459b332

File tree

2 files changed

+52
-11
lines changed

2 files changed

+52
-11
lines changed

src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,27 @@ public function getRetryAfter(): \DateTimeImmutable
8989
return \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $this->windowEndAt));
9090
}
9191

92+
public function calculateTimeForTokens(int $maxSize, int $tokens): int
93+
{
94+
$remaining = $maxSize - $this->getHitCount();
95+
if ($remaining >= $tokens) {
96+
return 0;
97+
}
98+
99+
$startOfWindow = $this->windowEndAt - $this->intervalInSeconds;
100+
$percentOfCurrentTimeFrame = min((microtime(true) - $startOfWindow) / $this->intervalInSeconds, 1);
101+
$releasable = $maxSize - floor($this->hitCountForLastWindow * (1 - $percentOfCurrentTimeFrame));
102+
$remainingWindow = (microtime(true) - $startOfWindow) - $this->intervalInSeconds;
103+
$timePerToken = $remainingWindow / $releasable;
104+
$needed = $tokens - $remaining;
105+
106+
if ($releasable <= $needed) {
107+
return ceil($needed * $timePerToken);
108+
}
109+
110+
return ($this->windowEndAt - microtime(true)) + ceil(($needed - $releasable) * ($this->intervalInSeconds / $maxSize));
111+
}
112+
92113
public function __serialize(): array
93114
{
94115
return [

src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Symfony\Component\Lock\LockInterface;
1515
use Symfony\Component\Lock\NoLock;
16-
use Symfony\Component\RateLimiter\Exception\ReserveNotSupportedException;
16+
use Symfony\Component\RateLimiter\Exception\MaxWaitDurationExceededException;
1717
use Symfony\Component\RateLimiter\LimiterInterface;
1818
use Symfony\Component\RateLimiter\RateLimit;
1919
use Symfony\Component\RateLimiter\Reservation;
@@ -49,11 +49,10 @@ public function __construct(string $id, int $limit, \DateInterval $interval, Sto
4949

5050
public function reserve(int $tokens = 1, float $maxTime = null): Reservation
5151
{
52-
throw new ReserveNotSupportedException(__CLASS__);
53-
}
52+
if ($tokens > $this->limit) {
53+
throw new \InvalidArgumentException(sprintf('Cannot reserve more tokens (%d) than the size of the rate limiter (%d).', $tokens, $this->limit));
54+
}
5455

55-
public function consume(int $tokens = 1): RateLimit
56-
{
5756
$this->lock->acquire(true);
5857

5958
try {
@@ -64,22 +63,43 @@ public function consume(int $tokens = 1): RateLimit
6463
$window = SlidingWindow::createFromPreviousWindow($window, $this->interval);
6564
}
6665

66+
$now = microtime(true);
6767
$hitCount = $window->getHitCount();
6868
$availableTokens = $this->getAvailableTokens($hitCount);
69-
if ($availableTokens < $tokens) {
70-
return new RateLimit($availableTokens, $window->getRetryAfter(), false, $this->limit);
71-
}
69+
if ($availableTokens >= $tokens) {
70+
$window->add($tokens);
7271

73-
$window->add($tokens);
72+
$reservation = new Reservation($now, new RateLimit($this->getAvailableTokens($window->getHitCount()), \DateTimeImmutable::createFromFormat('U', floor($now)), true, $this->limit));
73+
} else {
74+
$waitDuration = $window->calculateTimeForTokens($this->limit, max(1, $tokens));
75+
76+
if (null !== $maxTime && $waitDuration > $maxTime) {
77+
// process needs to wait longer than set interval
78+
throw new MaxWaitDurationExceededException(sprintf('The rate limiter wait time ("%d" seconds) is longer than the provided maximum time ("%d" seconds).', $waitDuration, $maxTime), new RateLimit($this->getAvailableTokens($window->getHitCount()), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->limit));
79+
}
80+
81+
$window->add($tokens);
82+
83+
$reservation = new Reservation($now + $waitDuration, new RateLimit($t 8000 his->getAvailableTokens($window->getHitCount()), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->limit));
84+
}
7485

7586
if (0 < $tokens) {
7687
$this->storage->save($window);
7788
}
78-
79-
return new RateLimit($this->getAvailableTokens($window->getHitCount()), $window->getRetryAfter(), true, $this->limit);
8089
} finally {
8190
$this->lock->release();
8291
}
92+
93+
return $reservation;
94+
}
95+
96+
public function consume(int $tokens = 1): RateLimit
97+
{
98+
try {
99+
return $this->reserve($tokens, 0)->getRateLimit();
100+
} catch (MaxWaitDurationExceededException $e) {
101+
return $e->getRateLimit();
102+
}
83103
}
84104

85105
private function getAvailableTokens(int $hitCount): int

0 commit comments

Comments
 (0)
0