use futures::{FutureExt, future::BoxFuture};
use std::{collections::VecDeque, num::NonZeroUsize, sync::Arc, time::Duration};
use tokio::sync::Mutex;
use tracing::Level;
use crate::{
exec::time::{Instant, sleep},
rch,
rtc::{DispatchDecision, RecvDecision, Req, ReqEnum, ReqReceiverMonitor, ServerMonitor},
};
#[derive(Debug, Clone)]
pub struct RateLimitMonitor {
requests: NonZeroUsize,
window: Duration,
history: Arc<Mutex<VecDeque<Instant>>>,
log_level: Option<Level>,
}
impl RateLimitMonitor {
pub const DEFAULT_LOG_LEVEL: Option<Level> = Some(Level::TRACE);
pub fn new(requests: NonZeroUsize, window: Duration) -> Self {
Self {
requests,
window,
history: Arc::new(Mutex::new(VecDeque::new())),
log_level: Self::DEFAULT_LOG_LEVEL,
}
}
#[must_use]
pub fn log_level(mut self, level: Option<Level>) -> Self {
self.log_level = level;
self
}
async fn wait_for_slot(&self, target: impl Fn() -> String) {
let mut history = self.history.lock().await;
loop {
while let Some(front) = history.front()
&& front.elapsed() >= self.window
{
history.pop_front();
}
if history.len() < self.requests.get() {
break;
}
log_at!(self.log_level, target =% target(), "delaying this and possibly subsequent calls due to rate limiting");
let front = history.front().unwrap();
sleep(self.window - front.elapsed()).await;
}
history.push_back(Instant::now());
}
fn req_target_str<Value, Ref, RefMut>(
req: &Result<Option<Req<Value, Ref, RefMut>>, rch::mpsc::RecvError>,
) -> impl Fn() -> String
where
Value: ReqEnum,
Ref: ReqEnum,
RefMut: ReqEnum,
{
let trait_name = Req::<Value, Ref, RefMut>::trait_name();
let method_name = if let Ok(Some(req)) = req { Some(req.method_name()) } else { None };
move || match method_name {
Some(method_name) => format!("{trait_name}::{method_name}"),
None => trait_name.to_string(),
}
}
}
impl<Value, Ref, RefMut> ServerMonitor<Value, Ref, RefMut> for RateLimitMonitor
where
Value: ReqEnum,
Ref: ReqEnum,
RefMut: ReqEnum,
{
fn pre_dispatch<'a>(
&'a mut self, req: &'a Result<Option<Req<Value, Ref, RefMut>>, rch::mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let target = Self::req_target_str(req);
async move {
self.wait_for_slot(target).await;
DispatchDecision::Pass
}
.boxed()
}
}
impl<Value, Ref, RefMut> ReqReceiverMonitor<Value, Ref, RefMut> for RateLimitMonitor
where
Value: ReqEnum,
Ref: ReqEnum,
RefMut: ReqEnum,
{
fn pre_recv<'a>(
&'a mut self, req: &'a Result<Option<Req<Value, Ref, RefMut>>, rch::mpsc::RecvError>,
) -> BoxFuture<'a, RecvDecision> {
let target = Self::req_target_str(req);
async move {
self.wait_for_slot(target).await;
RecvDecision::Pass
}
.boxed()
}
}