use std::time::Instant;
pub struct Throttler {
limit: f64,
available: f64,
refill_amount: f64,
last_refill: Instant,
}
impl Throttler {
pub fn new(limit: f64) -> Self {
let mut t = Self {
limit: 0.0,
available: 0.0,
refill_amount: 0.0,
last_refill: Instant::now(),
};
t.reset(limit);
t
}
pub fn reset(&mut self, limit: f64) {
self.limit = limit;
self.available = limit;
self.refill_amount = limit / 1000.0;
self.last_refill = Instant::now();
}
fn refill(&mut self) -> f64 {
let now = Instant::now();
let refill_count = (now.duration_since(self.last_refill).as_secs_f64() * 1000.0) as i64;
if refill_count != 0 {
self.available = self
.limit
.min(self.available + refill_count as f64 * self.refill_amount);
self.last_refill = now;
}
self.available
}
pub fn try_take(&mut self, tokens: f64) -> bool {
if self.limit == 0.0 {
return true;
}
if tokens > self.refill() {
return false;
}
self.available -= tokens;
true
}
}
impl Default for Throttler {
fn default() -> Self {
Self::new(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_disabled_when_zero_limit() {
let mut t = Throttler::new(0.0);
assert!(t.try_take(1_000_000_000.0));
assert!(t.try_take(1_000_000_000.0));
}
#[test]
fn test_bucket_drains_then_refuses() {
let mut t = Throttler::new(1000.0);
assert!(t.try_take(1000.0)); assert!(!t.try_take(500.0));
}
#[test]
fn test_refill_over_time() {
let mut t = Throttler::new(1000.0);
assert!(t.try_take(1000.0)); std::thread::sleep(Duration::from_millis(200));
assert!(t.try_take(150.0));
}
#[test]
fn test_reset_refills_to_full() {
let mut t = Throttler::new(1000.0);
assert!(t.try_take(1000.0)); t.reset(2000.0);
assert!(t.try_take(2000.0));
}
}