@@ -247,6 +247,34 @@ mod tests {
247247 }
248248
249249 #[ test]
250+ #[ cfg_attr( miri, ignore) ]
251+ fn test_rate_limiter_accumulates_fractional_tokens ( ) {
252+ // With rate=2/s each token takes 500ms. Sleeping 300ms twice (600ms total) must
253+ // yield at least one token. Before the fix, each sub-token call reset last_update
254+ // to the call time, so the second 300ms window also computed only 0.6 tokens and
255+ // the limiter starved indefinitely. Margins: the first assert!(!..) has 200ms of
256+ // headroom below 500ms; the final assert!(..) has 100ms of headroom above 500ms.
257+ let limiter = RateLimiter :: new ( 2 , None ) ;
258+
259+ // Drain all initial tokens.
260+ for _ in 0 ..2 {
261+ assert ! ( limiter. is_allowed( ) ) ;
262+ }
263+ assert ! ( !limiter. is_allowed( ) ) ;
264+
265+ // First sleep: 300ms → 0.6 tokens, not enough to allow.
266+ thread:: sleep ( Duration :: from_millis ( 300 ) ) ;
267+ assert ! ( !limiter. is_allowed( ) ) ;
268+
269+ // Second sleep: another 300ms. Total elapsed since drain ≈ 600ms → 1.2 tokens.
270+ // The fix preserves fractional progress so this succeeds; the old code reset
271+ // last_update on the first call and only saw another 0.6 tokens here.
272+ thread:: sleep ( Duration :: from_millis ( 300 ) ) ;
273+ assert ! ( limiter. is_allowed( ) ) ;
274+ }
275+
276+ #[ test]
277+ #[ cfg_attr( miri, ignore) ]
250278 fn test_rate_limiter_limit_rate ( ) {
251279 let limiter = RateLimiter :: new ( 5 , None ) ; // 5 per second
252280
@@ -269,6 +297,7 @@ mod tests {
269297 }
270298
271299 #[ test]
300+ #[ cfg_attr( miri, ignore) ]
272301 fn test_rate_limiter_effective_rate ( ) {
273302 let limiter = RateLimiter :: new ( 50 , None ) ; // 50 per second
274303
@@ -292,6 +321,7 @@ mod tests {
292321 }
293322
294323 #[ test]
324+ #[ cfg_attr( miri, ignore) ]
295325 fn test_rate_limiter_thread_safety ( ) {
296326 let limiter = RateLimiter :: new ( 100 , None ) ;
297327 let limiter_clone = limiter. clone ( ) ;
0 commit comments