Skip to content

Commit 629eae1

Browse files
authored
Merge pull request #7160 from rumpfc/fix-throttler
2 parents f79b175 + 30df450 commit 629eae1

2 files changed

Lines changed: 135 additions & 1 deletion

File tree

system/Throttle/Throttler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public function check(string $key, int $capacity, int $seconds, int $cost = 1):
139139
// How many seconds till a new token is available.
140140
// We must have a minimum wait of 1 second for a new token.
141141
// Primarily stored to allow devs to report back to users.
142-
$newTokenAvailable = (int) ($refresh - $elapsed - $refresh * $tokens);
142+
$newTokenAvailable = (int) round((1 - $tokens) * $refresh);
143143
$this->tokenTime = max(1, $newTokenAvailable);
144144

145145
return false;

tests/system/Throttle/ThrottleTest.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,138 @@ public function testFlooding()
187187
$this->assertTrue($throttler->check('127.0.0.1', $rate, MINUTE, 0));
188188
$this->assertSame(10.0, round($this->cache->get('throttler_127.0.0.1')));
189189
}
190+
191+
/**
192+
* @dataProvider tokenTimeUsecases
193+
*/
194+
public function testTokenTimeCalculationUCs(int $capacity, int $seconds, array $checkInputs): void
195+
{
196+
$key = 'testkey';
197+
$throttler = new Throttler($this->cache);
198+
199+
// clear $key before test start
200+
$throttler->remove($key);
201+
202+
foreach ($checkInputs as $index => $checkInput) {
203+
$throttler->setTestTime($checkInput['testTime']);
204+
$checkResult = $throttler->check($key, $capacity, $seconds, $checkInput['cost']);
205+
206+
$this->assertSame($checkInput['expectedCheckResult'], $checkResult, "Input#{$index}: Wrong check() result");
207+
$this->assertSame($checkInput['expectedTokenTime'], $throttler->getTokenTime(), "Input#{$index}: Wrong tokenTime");
208+
}
209+
}
210+
211+
public function tokenTimeUsecases(): array
212+
{
213+
return [
214+
'2 capacity / 200 seconds (100s refresh, 0.01 tokens/s) -> 5 checks, 1 cost each' => [
215+
'capacity' => 2,
216+
'seconds' => 200,
217+
'checkInputs' => [
218+
[ // 2 -> 1
219+
'testTime' => 0,
220+
'cost' => 1,
221+
'expectedCheckResult' => true,
222+
'expectedTokenTime' => 0,
223+
],
224+
[ // +3s / 1.03 -> 0.03
225+
'testTime' => 3,
226+
'cost' => 1,
227+
'expectedCheckResult' => true,
228+
'expectedTokenTime' => 0,
229+
],
230+
[ // +0s / 0.03 -> 0.03 / (1 - 0.03) * 100 = 97
231+
'testTime' => 3,
232+
'cost' => 1,
233+
'expectedCheckResult' => false,
234+
'expectedTokenTime' => 97,
235+
],
236+
[ // +1m 32s / 0.95 -> 0.95 / (1 - 0.95) * 100 = 5
237+
'testTime' => 95,
238+
'cost' => 1,
239+
'expectedCheckResult' => false,
240+
'expectedTokenTime' => 5,
241+
],
242+
[ // +7s / 1.02 -> 0.02
243+
'testTime' => 102,
244+
'cost' => 1,
245+
'expectedCheckResult' => true,
246+
'expectedTokenTime' => 0,
247+
],
248+
[ // +13s / 0.15 / (1 - 0.15) * 100 = 85
249+
'testTime' => 115,
250+
'cost' => 1,
251+
'expectedCheckResult' => false,
252+
'expectedTokenTime' => 85,
253+
],
254+
],
255+
],
256+
'1 capacity / 3600 seconds (3600s refresh, 2.77e-4 tokens/s) -> 2 checks with 1 cost each' => [
257+
'capacity' => 1,
258+
'seconds' => 3600,
259+
'checkInputs' => [
260+
[ // 1 -> 0
261+
'testTime' => 0,
262+
'cost' => 1,
263+
'expectedCheckResult' => true,
264+
'expectedTokenTime' => 0,
265+
],
266+
[ // +6m / 0.1 -> 0.1 / (1 - 0.1) * 3600 = 3240
267+
'testTime' => 360,
268+
'cost' => 1,
269+
'expectedCheckResult' => false,
270+
'expectedTokenTime' => 3240,
271+
],
272+
],
273+
],
274+
'10 capacity / 200 seconds (20s refresh, 0.05 tokens/s) -> 7 checks with different costs (RNG)' => [
275+
'capacity' => 10,
276+
'seconds' => 200,
277+
'checkInputs' => [
278+
[ // -2t / 10 -> 8
279+
'testTime' => 0,
280+
'cost' => 2,
281+
'expectedCheckResult' => true,
282+
'expectedTokenTime' => 0,
283+
],
284+
[ // +19s -2t / 8.95 -> 6.95
285+
'testTime' => 19,
286+
'cost' => 2,
287+
'expectedCheckResult' => true,
288+
'expectedTokenTime' => 0,
289+
],
290+
[ // +16s -3t / 7.75 -> 4.75
291+
'testTime' => 35,
292+
'cost' => 3,
293+
'expectedCheckResult' => true,
294+
'expectedTokenTime' => 0,
295+
],
296+
[ // +4s -2t / 4.95 -> 2.95
297+
'testTime' => 39,
298+
'cost' => 2,
299+
'expectedCheckResult' => true,
300+
'expectedTokenTime' => 0,
301+
],
302+
[ // +13s -5t / 3.6 -> -1.4 (blow allowed)
303+
'testTime' => 52,
304+
'cost' => 5,
305+
'expectedCheckResult' => true,
306+
'expectedTokenTime' => 0,
307+
],
308+
[ // +2s -2t / -1.3 -> -1.3 / (1 - (-1.3)) * 20 = 46
309+
'testTime' => 54,
310+
'cost' => 2,
311+
'expectedCheckResult' => false,
312+
'expectedTokenTime' => 46,
313+
],
314+
[ // +7s -2t / -0.95 - -0.95 / (1 - (-0.95)) * 20 = 39
315+
'testTime' => 61,
316+
'cost' => 2,
317+
'expectedCheckResult' => false,
318+
'expectedTokenTime' => 39,
319+
],
320+
],
321+
],
322+
];
323+
}
190324
}

0 commit comments

Comments
 (0)