Skip to content

[RateLimiter] rename Limit to RateLimit and add RateLimit::getLimit() #38608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
namespace Symfony\Component\HttpFoundation\RateLimiter;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\Limit;
use Symfony\Component\RateLimiter\LimiterInterface;
use Symfony\Component\RateLimiter\NoLimiter;
use Symfony\Component\RateLimiter\RateLimit;

/**
* An implementation of RequestRateLimiterInterface that
Expand All @@ -26,23 +26,23 @@
*/
abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface
{
public function consume(Request $request): Limit
public function consume(Request $request): RateLimit
{
$limiters = $this->getLimiters($request);
if (0 === \count($limiters)) {
$limiters = [new NoLimiter()];
}

$minimalLimit = null;
$minimalRateLimit = null;
foreach ($limiters as $limiter) {
$limit = $limiter->consume(1);
$rateLimit = $limiter->consume(1);

if (null === $minimalLimit || $limit->getRemainingTokens() < $minimalLimit->getRemainingTokens()) {
$minimalLimit = $limit;
if (null === $minimalRateLimit || $rateLimit->getRemainingTokens() < $minimalRateLimit->getRemainingTokens()) {
$minimalRateLimit = $rateLimit;
}
}

return $minimalLimit;
return $minimalRateLimit;
}

public function reset(Request $request): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace Symfony\Component\HttpFoundation\RateLimiter;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\Limit;
use Symfony\Component\RateLimiter\RateLimit;

/**
* A special type of limiter that deals with requests.
Expand All @@ -26,7 +26,7 @@
*/
interface RequestRateLimiterInterface
{
public function consume(Request $request): Limit;
public function consume(Request $request): RateLimit;

public function reset(Request $request): void;
}
12 changes: 6 additions & 6 deletions src/Symfony/Component/RateLimiter/CompoundLimiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,18 @@ public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation
throw new ReserveNotSupportedException(__CLASS__);
}

public function consume(int $tokens = 1): Limit
public function consume(int $tokens = 1): RateLimit
{
$minimalLimit = null;
$minimalRateLimit = null;
foreach ($this->limiters as $limiter) {
$limit = $limiter->consume($tokens);
$rateLimit = $limiter->consume($tokens);

if (null === $minimalLimit || $limit->getRemainingTokens() < $minimalLimit->getRemainingTokens()) {
$minimalLimit = $limit;
if (null === $minimalRateLimit || $rateLimit->getRemainingTokens() < $minimalRateLimit->getRemainingTokens()) {
$minimalRateLimit = $rateLimit;
}
}

return $minimalLimit;
return $minimalRateLimit;
}

public function reset(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\Component\RateLimiter\Exception;

use Symfony\Component\RateLimiter\Limit;
use Symfony\Component\RateLimiter\RateLimit;

/**
* @author Wouter de Jong <wouter@wouterj.nl>
Expand All @@ -20,17 +20,17 @@
*/
class MaxWaitDurationExceededException extends \RuntimeException
{
private $limit;
private $rateLimit;

public function __construct(string $message, Limit $limit, int $code = 0, ?\Throwable $previous = null)
public function __construct(string $message, RateLimit $rateLimit, int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);

$this->limit = $limit;
$this->rateLimit = $rateLimit;
}

public function getLimit(): Limit
public function getRateLimit(): RateLimit
{
return $this->limit;
return $this->rateLimit;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\Component\RateLimiter\Exception;

use Symfony\Component\RateLimiter\Limit;
use Symfony\Component\RateLimiter\RateLimit;

/**
* @author Kevin Bond <kevinbond@gmail.com>
Expand All @@ -20,27 +20,32 @@
*/
class RateLimitExceededException extends \RuntimeException
{
private $limit;
private $rateLimit;

public function __construct(Limit $limit, $code = 0, \Throwable $previous = null)
public function __construct(RateLimit $rateLimit, $code = 0, \Throwable $previous = null)
{
parent::__construct('Rate Limit Exceeded', $code, $previous);

$this->limit = $limit;
$this->rateLimit = $rateLimit;
}

public function getLimit(): Limit
public function getRateLimit(): RateLimit
{
return $this->limit;
return $this->rateLimit;
}

public function getRetryAfter(): \DateTimeImmutable
{
return $this->limit->getRetryAfter();
return $this->rateLimit->getRetryAfter();
}

public function getRemainingTokens(): int
{
return $this->limit->getRemainingTokens();
return $this->rateLimit->getRemainingTokens();
}

public function getLimit(): int
{
return $this->rateLimit->getLimit();
}
}
12 changes: 6 additions & 6 deletions src/Symfony/Component/RateLimiter/FixedWindowLimiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,19 @@ public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation
if ($availableTokens >= $tokens) {
$window->add($tokens);

$reservation = new Reservation($now, new Limit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now)), true));
$reservation = new Reservation($now, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now)), true, $this->limit));
} else {
$remainingTokens = $tokens - $availableTokens;
$waitDuration = $window->calculateTimeForTokens($remainingTokens);

if (null !== $maxTime && $waitDuration > $maxTime) {
// process needs to wait longer than set interval
throw new MaxWaitDurationExceededException(sprintf('The rate limiter wait time ("%d" seconds) is longer than the provided maximum time ("%d" seconds).', $waitDuration, $maxTime), new Limit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false));
throw new MaxWaitDurationExceededException(sprintf('The rate limiter wait time ("%d" seconds) is longer than the provided maximum time ("%d" seconds).', $waitDuration, $maxTime), new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->limit));
}

$window->add($tokens);

$reservation = new Reservation($now + $waitDuration, new Limit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false));
$reservation = new Reservation($now + $waitDuration, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->limit));
}
$this->storage->save($window);
} finally {
Expand All @@ -89,12 +89,12 @@ public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation
/**
* {@inheritdoc}
*/
public function consume(int $tokens = 1): Limit
public function consume(int $tokens = 1): RateLimit
{
try {
return $this->reserve($tokens, 0)->getLimit();
return $this->reserve($tokens, 0)->getRateLimit();
} catch (MaxWaitDurationExceededException $e) {
return $e->getLimit();
return $e->getRateLimit();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/RateLimiter/LimiterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation;
*
* @param int $tokens the number of tokens required
*/
public function consume(int $tokens = 1): Limit;
public function consume(int $tokens = 1): RateLimit;

/**
* Resets the limit.
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/RateLimiter/NoLimiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ final class NoLimiter implements LimiterInterface
{
public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation
{
return new Reservation(time(), new Limit(\INF, new \DateTimeImmutable(), true));
return new Reservation(time(), new RateLimit(\INF, new \DateTimeImmutable(), true, \INF));
}

public function consume(int $tokens = 1): Limit
public function consume(int $tokens = 1): RateLimit
{
return new Limit(\INF, new \DateTimeImmutable(), true);
return new RateLimit(\INF, new \DateTimeImmutable(), true, \INF);
}

public function reset(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@
*
* @experimental in 5.2
*/
class Limit
class RateLimit
{
private $availableTokens;
private $retryAfter;
private $accepted;
private $limit;

public function __construct(int $availableTokens, \DateTimeImmutable $retryAfter, bool $accepted)
public function __construct(int $availableTokens, \DateTimeImmutable $retryAfter, bool $accepted, int $limit)
{
$this->availableTokens = $availableTokens;
$this->retryAfter = $retryAfter;
$this->accepted = $accepted;
$this->limit = $limit;
}

public function isAccepted(): bool
Expand Down Expand Up @@ -58,6 +60,11 @@ public function getRemainingTokens(): int
return $this->availableTokens;
}

public function getLimit(): int
{
return $this->limit;
}

public function wait(): void
{
sleep(($this->retryAfter->getTimestamp() - time()) * 1e6);
Expand Down
10 changes: 5 additions & 5 deletions src/Symfony/Component/RateLimiter/Reservation.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
final class Reservation
{
private $timeToAct;
private $limit;
private $rateLimit;

/**
* @param float $timeToAct Unix timestamp in seconds when this reservation should act
*/
public function __construct(float $timeToAct, Limit $limit)
public function __construct(float $timeToAct, RateLimit $rateLimit)
{
$this->timeToAct = $timeToAct;
$this->limit = $limit;
$this->rateLimit = $rateLimit;
}

public function getTimeToAct(): float
Expand All @@ -40,9 +40,9 @@ public function getWaitDuration(): float
return max(0, (-microtime(true)) + $this->timeToAct);
}

public function getLimit(): Limit
public function getRateLimit(): RateLimit
{
return $this->limit;
return $this->rateLimit;
}

public function wait(): void
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/RateLimiter/SlidingWindowLimiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function reserve(int $tokens = 1, ?float $maxTime = null): Reservation
/**
* {@inheritdoc}
*/
public function consume(int $tokens = 1): Limit
public function consume(int $tokens = 1): RateLimit
{
$this->lock->acquire(true);

Expand All @@ -91,13 +91,13 @@ public function consume(int $tokens = 1): Limit
$hitCount = $window->getHitCount();
$availableTokens = $this->getAvailableTokens($hitCount);
if ($availableTokens < $tokens) {
return new Limit($availableTokens, $window->getRetryAfter(), false);
return new RateLimit($availableTokens, $window->getRetryAfter(), false, $this->limit);
}

$window->add($tokens);
$this->storage->save($window);

return new Limit($this->getAvailableTokens($window->getHitCount()), $window->getRetryAfter(), true);
return new RateLimit($this->getAvailableTokens($window->getHitCount()), $window->getRetryAfter(), true, $this->limit);
} finally {
$this->lock->release();
}
Expand Down
22 changes: 12 additions & 10 deletions src/Symfony/Component/RateLimiter/Tests/FixedWindowLimiterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ public function testConsume()
sleep(5);
}

$limit = $limiter->consume();
$this->assertTrue($limit->isAccepted());
$limit = $limiter->consume();
$this->assertFalse($limit->isAccepted());
$rateLimit = $limiter->consume();
$this->assertSame(10, $rateLimit->getLimit());
$this->assertTrue($rateLimit->isAccepted());
$rateLimit = $limiter->consume();
$this->assertFalse($rateLimit->isAccepted());
$this->assertSame(10, $rateLimit->getLimit());
}

public function testConsumeOutsideInterval()
Expand All @@ -58,18 +60,18 @@ public function testConsumeOutsideInterval()
$limiter->consume(9);
// ...try bursting again at the start of the next window
sleep(10);
$limit = $limiter->consume(10);
$this->assertEquals(0, $limit->getRemainingTokens());
$this->assertTrue($limit->isAccepted());
$rateLimit = $limiter->consume(10);
$this->assertEquals(0, $rateLimit->getRemainingTokens());
$this->assertTrue($rateLimit->isAccepted());
}

public function testWrongWindowFromCache()
{
$this->storage->save(new DummyWindow());
$limiter = $this->createLimiter();
$limit = $limiter->consume();
$this->assertTrue($limit->isAccepted());
$this->assertEquals(9, $limit->getRemainingTokens());
$rateLimit = $limiter->consume();
$this->assertTrue($rateLimit->isAccepted());
$this->assertEquals(9, $rateLimit->getRemainingTokens());
}

private function createLimiter(): FixedWindowLimiter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,25 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\RateLimiter\Exception\RateLimitExceededException;
use Symfony\Component\RateLimiter\Limit;
use Symfony\Component\RateLimiter\RateLimit;

class LimitTest extends TestCase
class RateLimitTest extends TestCase
{
public function testEnsureAcceptedDoesNotThrowExceptionIfAccepted()
{
$limit = new Limit(10, new \DateTimeImmutable(), true);
$rateLimit = new RateLimit(10, new \DateTimeImmutable(), true, 10);

$this->assertSame($limit, $limit->ensureAccepted());
$this->assertSame($rateLimit, $rateLimit->ensureAccepted());
}

public function testEnsureAcceptedThrowsRateLimitExceptionIfNotAccepted()
{
$limit = new Limit(10, $retryAfter = new \DateTimeImmutable(), false);
$rateLimit = new RateLimit(10, $retryAfter = new \DateTimeImmutable(), false, 10);

try {
$limit->ensureAccepted();
$rateLimit->ensureAccepted();
} catch (RateLimitExceededException $exception) {
$this->assertSame($limit, $exception->getLimit());
$this->assertSame($rateLimit, $exception->getRateLimit());
$this->assertSame(10, $exception->getRemainingTokens());
$this->assertSame($retryAfter, $exception->getRetryAfter());

Expand Down
Loading