@@ -246,15 +246,16 @@ def test_multiple_actions(self):
246
246
self .assertTrue (allowed )
247
247
self .assertEqual (10.0 , time_allowed )
248
248
249
- # Test that, after doing these 3 actions, we can't do any more action without
249
+ # Test that, after doing these 3 actions, we can't do any more actions without
250
250
# waiting.
251
251
allowed , time_allowed = self .get_success_or_raise (
252
252
limiter .can_do_action (None , key = "test_id" , n_actions = 1 , _time_now_s = 0 )
253
253
)
254
254
self .assertFalse (allowed )
255
255
self .assertEqual (10.0 , time_allowed )
256
256
257
- # Test that after waiting we can do only 1 action.
257
+ # Test that after waiting we would be able to do only 1 action.
258
+ # Note that we don't actually do it (update=False) here.
258
259
allowed , time_allowed = self .get_success_or_raise (
259
260
limiter .can_do_action (
260
261
None ,
@@ -265,23 +266,51 @@ def test_multiple_actions(self):
265
266
)
266
267
)
267
268
self .assertTrue (allowed )
268
- # The time allowed is the current time because we could still repeat the action
269
- # once.
270
- self .assertEqual (10.0 , time_allowed )
269
+ # We would be able to do the 5th action at t=20.
270
+ self .assertEqual (20.0 , time_allowed )
271
271
272
+ # Attempt (but fail) to perform TWO actions at t=10.
273
+ # Those would be the 4th and 5th actions.
272
274
allowed , time_allowed = self .get_success_or_raise (
273
275
limiter .can_do_action (None , key = "test_id" , n_actions = 2 , _time_now_s = 10 )
274
276
)
275
277
self .assertFalse (allowed )
276
- # The time allowed doesn't change despite allowed being False because, while we
277
- # don't allow 2 actions, we could still do 1.
278
+ # The returned time allowed for the next action is now even though we weren't
279
+ # allowed to perform the action because whilst we don't allow 2 actions,
280
+ # we could still do 1.
278
281
self .assertEqual (10.0 , time_allowed )
279
282
280
- # Test that after waiting a bit more we can do 2 actions.
283
+ # Test that after waiting until t=20, we can do perform 2 actions.
284
+ # These are the 4th and 5th actions.
281
285
allowed , time_allowed = self .get_success_or_raise (
282
286
limiter .can_do_action (None , key = "test_id" , n_actions = 2 , _time_now_s = 20 )
283
287
)
284
288
self .assertTrue (allowed )
285
- # The time allowed is the current time because we could still repeat the action
286
- # once.
287
- self .assertEqual (20.0 , time_allowed )
289
+ # We would be able to do the 6th action at t=30.
290
+ self .assertEqual (30.0 , time_allowed )
291
+
292
+ def test_rate_limit_burst_only_given_once (self ) -> None :
293
+ """
294
+ Regression test against a bug that meant that you could build up
295
+ extra tokens by timing requests.
296
+ """
297
+ limiter = Ratelimiter (
298
+ store = self .hs .get_datastores ().main , clock = None , rate_hz = 0.1 , burst_count = 3
299
+ )
300
+
301
+ def consume_at (time : float ) -> bool :
302
+ success , _ = self .get_success_or_raise (
303
+ limiter .can_do_action (requester = None , key = "a" , _time_now_s = time )
304
+ )
305
+ return success
306
+
307
+ # Use all our 3 burst tokens
308
+ self .assertTrue (consume_at (0.0 ))
309
+ self .assertTrue (consume_at (0.1 ))
310
+ self .assertTrue (consume_at (0.2 ))
311
+
312
+ # Wait to recover 1 token (10 seconds at 0.1 Hz).
313
+ self .assertTrue (consume_at (10.1 ))
314
+
315
+ # Check that we get rate limited after using that token.
316
+ self .assertFalse (consume_at (11.1 ))
0 commit comments