Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 54cacfe

Browse filesBrowse files
Seldaeknicolas-grekas
authored andcommitted
[RateLimiter] Always store SlidingWindows with an expiration set
1 parent ed9f973 commit 54cacfe
Copy full SHA for 54cacfe

File tree

Expand file treeCollapse file tree

4 files changed

+55
-38
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+55
-38
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/RateLimiter/Policy/SlidingWindow.php
+18-23Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@ final class SlidingWindow implements LimiterStateInterface
4343
*/
4444
private $windowEndAt;
4545

46-
/**
47-
* @var bool true if this window has been cached
48-
*/
49-
private $cached = true;
50-
5146
public function __construct(string $id, int $intervalInSeconds)
5247
{
5348
if ($intervalInSeconds < 1) {
@@ -56,7 +51,6 @@ public function __construct(string $id, int $intervalInSeconds)
5651
$this->id = $id;
5752
$this->intervalInSeconds = $intervalInSeconds;
5853
$this->windowEndAt = microtime(true) + $intervalInSeconds;
59-
$this->cached = false;
6054
}
6155

6256
public static function createFromPreviousWindow(self $window, int $intervalInSeconds): self
@@ -72,31 +66,17 @@ public static function createFromPreviousWindow(self $window, int $intervalInSec
7266
return $new;
7367
}
7468

75-
/**
76-
* @internal
77-
*/
78-
public function __sleep(): array
79-
{
80-
// $cached is not serialized, it should only be set
81-
// upon first creation of the window.
82-
return ['id', 'hitCount', 'intervalInSeconds', 'hitCountForLastWindow', 'windowEndAt'];
83-
}
84-
8569
public function getId(): string
8670
{
8771
return $this->id;
8872
}
8973

9074
/**
91-
* Store for the rest of this time frame and next.
75+
* Returns the remaining of this timeframe and the next one.
9276
*/
93-
public function getExpirationTime(): ?int
77+
public function getExpirationTime(): int
9478
{
95-
if ($this->cached) {
96-
return null;
97-
}
98-
99-
return 2 * $this->intervalInSeconds;
79+
return $this->windowEndAt + $this->intervalInSeconds - microtime(true);
10080
}
10181

10282
public function isExpired(): bool
@@ -124,4 +104,19 @@ public function getRetryAfter(): \DateTimeImmutable
124104
{
125105
return \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $this->windowEndAt));
126106
}
107+
108+
public function __serialize(): array
109+
{
110+
return [
111+
pack('NNN', $this->hitCount, $this->hitCountForLastWindow, $this->intervalInSeconds).$this->id => $this->windowEndAt,
112+
];
113+
}
114+
115+
public function __unserialize(array $data): void
116+
{
117+
$pack = key($data);
118+
$this->windowEndAt = $data[$pack];
119+
['a' => $this->hitCount, 'b' => $this->hitCountForLastWindow, 'c' => $this->intervalInSeconds] = unpack('Na/Nb/Nc', $pack);
120+
$this->id = substr($pack, 12);
121+
}
127122
}

‎src/Symfony/Component/RateLimiter/Policy/TokenBucket.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/RateLimiter/Policy/TokenBucket.php
+20-14Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
*/
2121
final class TokenBucket implements LimiterStateInterface
2222
{
23-
private $stringRate;
2423
private $id;
2524
private $rate;
2625

@@ -47,8 +46,6 @@ final class TokenBucket implements LimiterStateInterface
4746
*/
4847
public function __construct(string $id, int $initialTokens, Rate $rate, float $timer = null)
4948
{
50-
unset($this->stringRate);
51-
5249
if ($initialTokens < 1) {
5350
throw new \InvalidArgumentException(sprintf('Cannot set the limit of "%s" to 0, as that would never accept any hit.', TokenBucketLimiter::class));
5451
}
@@ -91,26 +88,35 @@ public function getExpirationTime(): int
9188
return $this->rate->calculateTimeForTokens($this->burstSize);
9289
}
9390

94-
/**
95-
* @internal
96-
*/
91+
public function __serialize(): array
92+
{
93+
return [
94+
pack('N', $this->burstSize).$this->id => $this->tokens,
95+
(string) $this->rate => $this->timer,
96+
];
97+
}
98+
99+
public function __unserialize(array $data): void
100+
{
101+
[$this->tokens, $this->timer] = array_values($data);
102+
[$pack, $rate] = array_keys($data);
103+
$this->rate = Rate::fromString($rate);
104+
$this->burstSize = unpack('Na', $pack)['a'];
105+
$this->id = substr($pack, 4);
106+
}
107+
97108
public function __sleep(): array
98109
{
99110
$this->stringRate = (string) $this->rate;
100111

101112
return ['id', 'tokens', 'timer', 'burstSize', 'stringRate'];
102113
}
103114

104-
/**
105-
* @internal
106-
*/
107115
public function __wakeup(): void
108116
{
109-
if (!\is_string($this->stringRate)) {
110-
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
117+
if (\is_string($rate = $this->stringRate ?? null)) {
118+
$this->rate = Rate::fromString($rate);
119+
unset($this->stringRate);
111120
}
112-
113-
$this->rate = Rate::fromString($this->stringRate);
114-
unset($this->stringRate);
115121
}
116122
}

‎src/Symfony/Component/RateLimiter/Policy/Window.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/RateLimiter/Policy/Window.php
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,19 @@ public function calculateTimeForTokens(int $tokens): int
8585

8686
return $cyclesRequired * $this->intervalInSeconds;
8787
}
88+
89+
public function __serialize(): array
90+
{
91+
return [
92+
$this->id => $this->timer,
93+
pack('NN', $this->hitCount, $this->intervalInSeconds) => $this->maxSize,
94+
];
95+
}
96+
97+
public function __unserialize(array $data): void
98+
{
99+
[$this->timer, $this->maxSize] = array_values($data);
100+
[$this->id, $pack] = array_keys($data);
101+
['a' => $this->hitCount, 'b' => $this->intervalInSeconds] = unpack('Na/Nb', $pack);
102+
}
88103
}

‎src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/RateLimiter/Tests/Policy/SlidingWindowTest.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ public function testGetExpirationTime()
2828
$this->assertSame(2 * 10, $window->getExpirationTime());
2929

3030
$data = serialize($window);
31+
sleep(10);
3132
$cachedWindow = unserialize($data);
32-
$this->assertNull($cachedWindow->getExpirationTime());
33+
$this->assertSame(10, $cachedWindow->getExpirationTime());
3334

3435
$new = SlidingWindow::createFromPreviousWindow($cachedWindow, 15);
3536
$this->assertSame(2 * 15, $new->getExpirationTime());

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.