use parking_lot::Mutex;
use std::time::{Duration, Instant};
const MAX_SLEEP_SECS: f64 = 60.0;
pub struct RateLimiter {
inner: Mutex<RateLimiterInner>,
}
struct RateLimiterInner {
rate_bytes_per_sec: u64,
available: f64,
last_refill: Instant,
}
impl RateLimiter {
pub fn new(rate_bytes_per_sec: u64) -> Self {
Self {
inner: Mutex::new(RateLimiterInner {
rate_bytes_per_sec,
available: rate_bytes_per_sec as f64,
last_refill: Instant::now(),
}),
}
}
pub fn request(&self, bytes: usize) {
let mut inner = self.inner.lock();
if inner.rate_bytes_per_sec == 0 {
return;
}
let now = Instant::now();
let elapsed = now.duration_since(inner.last_refill).as_secs_f64();
inner.available += elapsed * inner.rate_bytes_per_sec as f64;
inner.available = inner.available.min(inner.rate_bytes_per_sec as f64 * 2.0); inner.last_refill = now;
let needed = bytes as f64;
if inner.available >= needed {
inner.available -= needed;
return;
}
let deficit = needed - inner.available;
inner.available -= needed;
let wait_secs = deficit / inner.rate_bytes_per_sec as f64;
drop(inner);
std::thread::sleep(Duration::from_secs_f64(wait_secs.min(MAX_SLEEP_SECS)));
}
}
impl RateLimiter {
#[cfg(test)]
pub(crate) fn is_enabled(&self) -> bool {
self.inner.lock().rate_bytes_per_sec > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_limiter_disabled() {
let rl = RateLimiter::new(0);
assert!(!rl.is_enabled());
rl.request(1_000_000);
}
#[test]
fn test_rate_limiter_basic() {
let rl = RateLimiter::new(1_000_000); assert!(rl.is_enabled());
rl.request(100);
}
}