Token Bucket Rate Limiter in Rust
Refill a token bucket from a monotonic clock, allow short bursts, and compute how long a caller must wait when it is empty.
Code
use std::time::{Duration, Instant};
struct TokenBucket {
capacity: f64,
tokens: f64,
refill_per_second: f64,
last_refill: Instant,
}
impl TokenBucket {
fn new(capacity: f64, refill_per_second: f64) -> Result<Self, String> {
if capacity <= 0.0 || refill_per_second <= 0.0 {
return Err("capacity and refill rate must be positive".to_string());
}
Ok(Self {
capacity,
tokens: capacity,
refill_per_second,
last_refill: Instant::now(),
})
}
fn refill(&mut self) {
let now = Instant::now();
let elapsed = now.duration_since(self.last_refill).as_secs_f64();
self.tokens = (self.tokens + elapsed * self.refill_per_second).min(self.capacity);
self.last_refill = now;
}
fn try_acquire(&mut self, cost: f64) -> Result<(), Duration> {
assert!(
cost > 0.0 && cost <= self.capacity,
"cost must be within the bucket capacity"
);
self.refill();
if self.tokens >= cost {
self.tokens -= cost;
return Ok(());
}
let missing = cost - self.tokens;
Err(Duration::from_secs_f64(missing / self.refill_per_second))
}
}
fn main() {
let mut bucket = TokenBucket::new(2.0, 4.0).expect("valid configuration");
for request in 1..=3 {
match bucket.try_acquire(1.0) {
Ok(()) => println!("request {request}: allowed"),
Err(wait) => println!("request {request}: denied, retry in {wait:?}"),
}
}
std::thread::sleep(Duration::from_millis(260));
match bucket.try_acquire(1.0) {
Ok(()) => println!("after refill: allowed"),
Err(wait) => println!("after refill: denied, retry in {wait:?}"),
}
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.