use std::sync::Mutex;
use super::clock::{Clock, SystemClock};
struct State {
tokens_scaled: u128,
last_ns: u64,
}
pub struct TokenBucket {
capacity: u64,
rate_per_sec: f64,
units_per_ns_num: u128,
units_per_ns_den: u128,
clock: Box<dyn Clock>,
state: Mutex<State>,
}
const SCALE: u128 = 1_000_000_000;
impl TokenBucket {
pub fn new(capacity: u64, rate_per_sec: f64) -> Self {
Self::with_clock(capacity, rate_per_sec, Box::new(SystemClock::new()))
}
pub fn with_clock(capacity: u64, rate_per_sec: f64, clock: Box<dyn Clock>) -> Self {
let cap = capacity.max(1);
let rate = rate_per_sec.max(0.0);
let num = (rate * SCALE as f64) as u128;
let den = 1_000_000_000u128;
let now = clock.now_ns();
Self {
capacity: cap,
rate_per_sec: rate,
units_per_ns_num: num,
units_per_ns_den: den,
clock,
state: Mutex::new(State {
tokens_scaled: (cap as u128).saturating_mul(SCALE),
last_ns: now,
}),
}
}
pub fn try_acquire(&self, n: u64) -> bool {
if n == 0 {
return true;
}
let mut s = self.state.lock().unwrap();
self.refill_locked(&mut s);
let want = (n as u128).saturating_mul(SCALE);
if s.tokens_scaled >= want {
s.tokens_scaled -= want;
true
} else {
false
}
}
pub fn try_acquire_one(&self) -> bool {
self.try_acquire(1)
}
pub fn available(&self) -> u64 {
let mut s = self.state.lock().unwrap();
self.refill_locked(&mut s);
(s.tokens_scaled / SCALE) as u64
}
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn rate_per_sec(&self) -> f64 {
self.rate_per_sec
}
fn refill_locked(&self, s: &mut State) {
let now = self.clock.now_ns();
let elapsed = now.saturating_sub(s.last_ns) as u128;
if elapsed == 0 {
return;
}
let add = elapsed.saturating_mul(self.units_per_ns_num) / self.units_per_ns_den;
let cap_scaled = (self.capacity as u128).saturating_mul(SCALE);
s.tokens_scaled = (s.tokens_scaled.saturating_add(add)).min(cap_scaled);
s.last_ns = now;
}
}
#[cfg(test)]
#[path = "token_bucket_tests.rs"]
mod tests;