use core::cmp;
#[derive(Debug, Clone)]
pub struct TokenBucket {
capacity: u64,
tokens: u64,
rate_bps: u64,
last: u64,
}
impl TokenBucket {
pub fn new(rate_bps: u64, now: u64) -> Self {
let capacity = burst_capacity(rate_bps);
TokenBucket {
capacity,
tokens: capacity,
rate_bps,
last: now,
}
}
pub fn set_rate(&mut self, rate_bps: u64, now: u64) {
self.refill(now);
self.capacity = burst_capacity(rate_bps);
self.tokens = cmp::min(self.tokens, self.capacity);
self.rate_bps = rate_bps;
}
pub fn rate_bps(&self) -> u64 {
self.rate_bps
}
pub fn is_unlimited(&self) -> bool {
self.rate_bps == 0
}
pub fn available(&mut self, now: u64) -> u64 {
if self.rate_bps == 0 {
return u64::MAX;
}
self.refill(now);
self.tokens
}
pub fn consume(&mut self, want: u64, now: u64) -> u64 {
if self.rate_bps == 0 {
return want;
}
self.refill(now);
let take = cmp::min(want, self.tokens);
self.tokens -= take;
take
}
fn refill(&mut self, now: u64) {
if self.rate_bps == 0 || now <= self.last {
return;
}
let dt = now - self.last;
let add = dt.saturating_mul(self.rate_bps) / 1000;
self.tokens = cmp::min(self.capacity, self.tokens.saturating_add(add));
self.last = now;
}
}
fn burst_capacity(rate_bps: u64) -> u64 {
if rate_bps == 0 {
return u64::MAX;
}
rate_bps.clamp(64 * 1024, 8 * 1024 * 1024)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unlimited_always_serves() {
let mut b = TokenBucket::new(0, 0);
assert!(b.is_unlimited());
assert_eq!(b.available(1_000), u64::MAX);
assert_eq!(b.consume(u64::MAX, 2_000), u64::MAX);
}
#[test]
fn limited_consumes_and_refills() {
let mut b = TokenBucket::new(1000, 0); assert!(!b.is_unlimited());
let burst = b.available(0);
assert!(burst >= 64 * 1024);
let taken = b.consume(burst, 0);
assert_eq!(taken, burst);
assert_eq!(b.available(0), 0);
assert!(b.available(1000) >= 1000);
let c = b.consume(100_000, 1000);
assert_eq!(c, 1000);
assert_eq!(b.available(1000), 0);
}
#[test]
fn set_rate_rebounds_burst() {
let mut b = TokenBucket::new(10 * 1024 * 1024, 0);
let big = b.available(0);
assert_eq!(big, 8 * 1024 * 1024); b.set_rate(0, 0);
assert!(b.is_unlimited());
assert_eq!(b.available(0), u64::MAX);
}
}