use std::sync::{Arc, Mutex};
use tokio::time::{Duration, Instant, sleep_until};
#[derive(Debug, Clone)]
pub struct RateLimiter {
next_allowed: Arc<Mutex<Instant>>,
interval: Duration,
}
impl RateLimiter {
pub fn new(qps: f64) -> Self {
let safe_qps = if qps <= 0.0 { 1.0 } else { qps };
let interval_micros = (1_000_000.0 / safe_qps) as u64;
Self {
next_allowed: Arc::new(Mutex::new(Instant::now())),
interval: Duration::from_micros(interval_micros),
}
}
pub async fn acquire(&self) {
let target_time = {
let mut next = self.next_allowed.lock().unwrap();
let now = Instant::now();
if *next < now {
*next = now;
}
let target = *next;
*next += self.interval;
target
};
sleep_until(target_time).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::Instant;
#[tokio::test]
async fn test_rate_limiter_spacing() {
let limiter = RateLimiter::new(10.0);
let start = Instant::now();
limiter.acquire().await;
limiter.acquire().await;
limiter.acquire().await;
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() >= 190,
"Elapsed: {}ms",
elapsed.as_millis()
);
}
}