@@ -172,10 +172,13 @@ impl RateLimiter {
172172 if state. tokens > state. max_tokens {
173173 state. tokens = state. max_tokens ;
174174 }
175+ // Only advance last_update by the time consumed by whole tokens, preserving
176+ // fractional progress toward the next token. Use u128 to avoid overflow in
177+ // the intermediate product; integer division guarantees consumed_ns ≤ elapsed.
178+ let consumed_ns = ( tokens_to_add as u128 * state. time_window_ns as u128
179+ / self . rate_limit as u128 ) as u64 ;
180+ state. last_update += std:: time:: Duration :: from_nanos ( consumed_ns) ;
175181 }
176- // Always update last_update, even if no tokens were added (e.g., very short elapsed time
177- // yielding tokens_to_add = 0)
178- state. last_update = timestamp;
179182 }
180183
181184 /// Calculate the current window rate
@@ -243,6 +246,32 @@ mod tests {
243246 assert_eq ! ( limiter. effective_rate( ) , 0.0 ) ;
244247 }
245248
249+ #[ test]
250+ fn test_rate_limiter_accumulates_fractional_tokens ( ) {
251+ // With rate=2/s each token takes 500ms. Sleeping 300ms twice (600ms total) must
252+ // yield at least one token. Before the fix, each sub-token call reset last_update
253+ // to the call time, so the second 300ms window also computed only 0.6 tokens and
254+ // the limiter starved indefinitely. Margins: the first assert!(!..) has 200ms of
255+ // headroom below 500ms; the final assert!(..) has 100ms of headroom above 500ms.
256+ let limiter = RateLimiter :: new ( 2 , None ) ;
257+
258+ // Drain all initial tokens.
259+ for _ in 0 ..2 {
260+ assert ! ( limiter. is_allowed( ) ) ;
261+ }
262+ assert ! ( !limiter. is_allowed( ) ) ;
263+
264+ // First sleep: 300ms → 0.6 tokens, not enough to allow.
265+ thread:: sleep ( Duration :: from_millis ( 300 ) ) ;
266+ assert ! ( !limiter. is_allowed( ) ) ;
267+
268+ // Second sleep: another 300ms. Total elapsed since drain ≈ 600ms → 1.2 tokens.
269+ // The fix preserves fractional progress so this succeeds; the old code reset
270+ // last_update on the first call and only saw another 0.6 tokens here.
271+ thread:: sleep ( Duration :: from_millis ( 300 ) ) ;
272+ assert ! ( limiter. is_allowed( ) ) ;
273+ }
274+
246275 #[ test]
247276 fn test_rate_limiter_limit_rate ( ) {
248277 let limiter = RateLimiter :: new ( 5 , None ) ; // 5 per second
0 commit comments