[RateLimiter] Fix sliding_window misbehaving with stale records
This commit is contained in:
parent
ec38bab34a
commit
57033164c6
@ -63,8 +63,12 @@ final class SlidingWindow implements LimiterStateInterface
|
|||||||
public static function createFromPreviousWindow(self $window, int $intervalInSeconds): self
|
public static function createFromPreviousWindow(self $window, int $intervalInSeconds): self
|
||||||
{
|
{
|
||||||
$new = new self($window->id, $intervalInSeconds);
|
$new = new self($window->id, $intervalInSeconds);
|
||||||
$new->hitCountForLastWindow = $window->hitCount;
|
$windowEndAt = $window->windowEndAt + $intervalInSeconds;
|
||||||
$new->windowEndAt = $window->windowEndAt + $intervalInSeconds;
|
|
||||||
|
if (time() < $windowEndAt) {
|
||||||
|
$new->hitCountForLastWindow = $window->hitCount;
|
||||||
|
$new->windowEndAt = $windowEndAt;
|
||||||
|
}
|
||||||
|
|
||||||
return $new;
|
return $new;
|
||||||
}
|
}
|
||||||
@ -112,7 +116,7 @@ final class SlidingWindow implements LimiterStateInterface
|
|||||||
public function getHitCount(): int
|
public function getHitCount(): int
|
||||||
{
|
{
|
||||||
$startOfWindow = $this->windowEndAt - $this->intervalInSeconds;
|
$startOfWindow = $this->windowEndAt - $this->intervalInSeconds;
|
||||||
$percentOfCurrentTimeFrame = (time() - $startOfWindow) / $this->intervalInSeconds;
|
$percentOfCurrentTimeFrame = min((time() - $startOfWindow) / $this->intervalInSeconds, 1);
|
||||||
|
|
||||||
return (int) floor($this->hitCountForLastWindow * (1 - $percentOfCurrentTimeFrame) + $this->hitCount);
|
return (int) floor($this->hitCountForLastWindow * (1 - $percentOfCurrentTimeFrame) + $this->hitCount);
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,13 @@
|
|||||||
namespace Symfony\Component\RateLimiter\Tests\Policy;
|
namespace Symfony\Component\RateLimiter\Tests\Policy;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bridge\PhpUnit\ClockMock;
|
||||||
use Symfony\Component\RateLimiter\Exception\InvalidIntervalException;
|
use Symfony\Component\RateLimiter\Exception\InvalidIntervalException;
|
||||||
use Symfony\Component\RateLimiter\Policy\SlidingWindow;
|
use Symfony\Component\RateLimiter\Policy\SlidingWindow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group time-sensitive
|
||||||
|
*/
|
||||||
class SlidingWindowTest extends TestCase
|
class SlidingWindowTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testGetExpirationTime()
|
public function testGetExpirationTime()
|
||||||
@ -36,4 +40,36 @@ class SlidingWindowTest extends TestCase
|
|||||||
$this->expectException(InvalidIntervalException::class);
|
$this->expectException(InvalidIntervalException::class);
|
||||||
new SlidingWindow('foo', 0);
|
new SlidingWindow('foo', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testLongInterval()
|
||||||
|
{
|
||||||
|
ClockMock::register(SlidingWindow::class);
|
||||||
|
$window = new SlidingWindow('foo', 60);
|
||||||
|
$this->assertSame(0, $window->getHitCount());
|
||||||
|
$window->add(20);
|
||||||
|
$this->assertSame(20, $window->getHitCount());
|
||||||
|
|
||||||
|
sleep(60);
|
||||||
|
$new = SlidingWindow::createFromPreviousWindow($window, 60);
|
||||||
|
$this->assertSame(20, $new->getHitCount());
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$this->assertSame(10, $new->getHitCount());
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$this->assertSame(0, $new->getHitCount());
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$this->assertSame(0, $new->getHitCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLongIntervalCreate()
|
||||||
|
{
|
||||||
|
ClockMock::register(SlidingWindow::class);
|
||||||
|
$window = new SlidingWindow('foo', 60);
|
||||||
|
|
||||||
|
sleep(300);
|
||||||
|
$new = SlidingWindow::createFromPreviousWindow($window, 60);
|
||||||
|
$this->assertFalse($new->isExpired());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user