Skip to content

Commit

Permalink
fix: Rate limiter TokenBucket::auto_replenish()
Browse files Browse the repository at this point in the history
Frequently calling `auto_replenish` will reset `self.last_update` each
time and `tokens` may be a fractional value (0 since it is a `u64`), in
this case no tokens will replenished. To avoid this we only update
`self.last_update` when `tokens > 0`.

Signed-off-by: Jonathan Woollett-Light <jcawl@amazon.co.uk>
  • Loading branch information
JonathanWoollett-Light committed Jan 17, 2023
1 parent b790195 commit 87575cf
Showing 1 changed file with 26 additions and 2 deletions.
28 changes: 26 additions & 2 deletions src/rate_limiter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,21 @@ impl TokenBucket {
fn auto_replenish(&mut self) {
// Compute time passed since last refill/update.
let time_delta = self.last_update.elapsed().as_nanos() as u64;
self.last_update = Instant::now();

// At each 'time_delta' nanoseconds the bucket should refill with:
// refill_amount = (time_delta * size) / (complete_refill_time_ms * 1_000_000)
// `processed_capacity` and `processed_refill_time` are the result of simplifying above
// fraction formula with their greatest-common-factor.
let tokens = (time_delta * self.processed_capacity) / self.processed_refill_time;
self.budget = std::cmp::min(self.budget + tokens, self.size);

// Frequently calling `auto_replenish` will reset `self.last_update` each
// time and `tokens` may be a fractional value (0 since it is a `u64`), in
// this case no tokens will replenished. To avoid this we only update
// `self.last_update` when `tokens > 0`.
if tokens > 0 {
self.last_update = Instant::now();
self.budget = std::cmp::min(self.budget + tokens, self.size);
}
}

/// Attempts to consume `tokens` from the bucket and returns whether the action succeeded.
Expand Down Expand Up @@ -560,6 +567,23 @@ pub(crate) mod tests {
}
}

#[test]
fn test_token_bucket_auto_replenish() {
const SIZE: u64 = 1000;
const TIME: u64 = 1000;
let time = Duration::from_millis(TIME);

let mut tb = TokenBucket::new(SIZE, 0, TIME).unwrap();
tb.reduce(SIZE);
assert_eq!(tb.budget(), 0);

let now = Instant::now();
while now.elapsed() < time {
tb.auto_replenish();
}
assert_eq!(tb.budget(), SIZE - 1);
}

#[test]
fn test_token_bucket_create() {
let before = Instant::now();
Expand Down

0 comments on commit 87575cf

Please sign in to comment.