use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Semaphore;
use tokio::task::JoinHandle;
#[derive(Debug)]
pub(crate) struct RateLimiter {
semaphore: Arc<Semaphore>,
refill_task: JoinHandle<()>,
}
impl RateLimiter {
pub fn new(rps: f64) -> Self {
assert!(rps > 0.0, "requests_per_second must be positive");
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "rps is validated positive and ceil() fits in usize"
)]
let capacity = rps.ceil() as usize;
let semaphore = Arc::new(Semaphore::new(capacity));
let refill_interval = Duration::from_secs_f64(1.0 / rps);
let sem = Arc::clone(&semaphore);
let refill_task = tokio::spawn(async move {
let mut interval = tokio::time::interval(refill_interval);
loop {
interval.tick().await;
if sem.available_permits() < capacity {
sem.add_permits(1);
}
}
});
Self {
semaphore,
refill_task,
}
}
pub async fn acquire(&self) {
let permit = self
.semaphore
.acquire()
.await
.expect("rate limiter semaphore closed unexpectedly");
permit.forget(); }
}
impl Drop for RateLimiter {
fn drop(&mut self) {
self.refill_task.abort();
}
}