Closed
Description
Symfony version(s) affected
5.2., 6.
Description
TokenBucket strategy for RateLimiter tries to refill tokens by every call of \Symfony\Component\RateLimiter\Policy\TokenBucketLimiter::reserve. Amount of refilled tokens calculated in \Symfony\Component\RateLimiter\Policy\Rate::calculateNewTokensDuringInterval. The problem is that refill actually executes no more than once per second, even if RPS is high as well as refill rate.
So some of requests in the end of a second may be skiped being unable to reserve tokens which should be restored already.
How to reproduce
$limiter = (new Symfony\Component\RateLimiter\RateLimiterFactory(
[
'id' => 'id',
'policy' => 'token_bucket',
'limit' => 10,
'rate' => [
'interval' => '1 second',
'amount' => 2,
],
],
new Symfony\Component\RateLimiter\Storage\InMemoryStorage()
))->create();
$res = $limiter->consume(1);
var_dump($res->getRemainingTokens()); //9 tokens last
usleep(600000); //with rate "1 token/0.5 second" it should be 10 tokens
$res = $limiter->consume(10);
var_dump($res->isAccepted()); //false but should be true
Possible Solution
Calculate tokens to restore everytime, not just every second.
\Symfony\Component\RateLimiter\Policy\Rate::calculateNewTokensDuringInterval
public function calculateNewTokensDuringInterval(float $duration): int
{
return floor($this->refillAmount * $duration / TimeUtil::dateIntervalToSeconds($this->refillTime));
}
Additional Context
No response