Skip to content

Commit ff10390

Browse files
committed
Throttler::tokenTime fix, use cases
1 parent 2303685 commit ff10390

2 files changed

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

0 commit comments

Comments
 (0)