use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct TokenBucket {
rate_bytes_per_sec: u64,
start: Instant,
}
impl TokenBucket {
#[must_use]
pub fn new(rate_bytes_per_sec: u64) -> Self {
TokenBucket {
rate_bytes_per_sec,
start: Instant::now(),
}
}
#[must_use]
pub fn next_sleep(&self, bytes_done: u64) -> Duration {
let elapsed = self.start.elapsed().as_secs_f64();
let expected = elapsed * self.rate_bytes_per_sec as f64;
let over = bytes_done as f64 - expected;
if over <= 0.0 {
return Duration::ZERO;
}
let want = over / self.rate_bytes_per_sec as f64;
Duration::from_secs_f64(want.max(0.010))
}
pub fn maybe_sleep(&self, bytes_done: u64) -> bool {
let d = self.next_sleep(bytes_done);
if d == Duration::ZERO {
return false;
}
std::thread::sleep(d);
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_when_under_budget() {
let tb = TokenBucket::new(1_000_000);
assert_eq!(tb.next_sleep(0), Duration::ZERO);
}
#[test]
fn sleep_when_over_budget() {
let tb = TokenBucket::new(1_000_000);
let d = tb.next_sleep(2_000_000);
assert!(d >= Duration::from_millis(10));
assert!(d >= Duration::from_millis(500));
}
}