use futures::{FutureExt, future::BoxFuture};
use std::{error::Error, fmt, future, time::Duration};
use tracing::Level;
use crate::{
exec::time::Instant,
rch::mpsc,
rtc::{DispatchDecision, RecvDecision, Req, ReqEnum, ReqReceiverMonitor, ServerMonitor},
};
#[derive(Debug, Clone)]
pub struct IncompatibleClientLimitExceeded {
pub limit: usize,
pub window: Duration,
}
impl fmt::Display for IncompatibleClientLimitExceeded {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "more than {} requests failed to receive within {} s", self.limit, self.window.as_secs_f32())
}
}
impl Error for IncompatibleClientLimitExceeded {}
#[derive(Debug)]
pub struct IncompatibleClientMonitor {
log_level: Option<Level>,
limit: Option<usize>,
window: Duration,
window_start: Option<Instant>,
count: usize,
}
impl IncompatibleClientMonitor {
pub const DEFAULT_LOG_LEVEL: Option<Level> = Some(Level::WARN);
pub const DEFAULT_LIMIT: Option<usize> = Some(20);
pub const DEFAULT_WINDOW: Duration = Duration::from_secs(10);
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn log_level(mut self, level: Option<Level>) -> Self {
self.log_level = level;
self
}
#[must_use]
pub fn limit(mut self, limit: Option<usize>) -> Self {
self.limit = limit;
self
}
#[must_use]
pub fn window(mut self, window: Duration) -> Self {
self.window = window;
self
}
fn count_failure(&mut self, limit: usize) -> bool {
match self.window_start {
Some(start) if start.elapsed() < self.window => self.count += 1,
_ => {
self.window_start = Some(Instant::now());
self.count = 1;
}
}
self.count > limit
}
fn on_non_final_recv_error(&mut self, error: &mpsc::RecvError) -> DispatchDecision {
log_at!(self.log_level, %error, "failed to receive request");
let Some(limit) = self.limit else {
return DispatchDecision::Drop;
};
if self.count_failure(limit) {
DispatchDecision::Error(Box::new(IncompatibleClientLimitExceeded { limit, window: self.window }))
} else {
DispatchDecision::Drop
}
}
fn on_non_final_req_recv_error(&mut self, error: &mpsc::RecvError) -> RecvDecision {
log_at!(self.log_level, %error, "failed to receive request");
let Some(limit) = self.limit else {
return RecvDecision::Drop;
};
if self.count_failure(limit) { RecvDecision::Pass } else { RecvDecision::Drop }
}
}
impl Default for IncompatibleClientMonitor {
fn default() -> Self {
Self {
log_level: Self::DEFAULT_LOG_LEVEL,
limit: Self::DEFAULT_LIMIT,
window: Self::DEFAULT_WINDOW,
window_start: None,
count: 0,
}
}
}
impl<Value, Ref, RefMut> ServerMonitor<Value, Ref, RefMut> for IncompatibleClientMonitor
where
Value: ReqEnum,
Ref: ReqEnum,
RefMut: ReqEnum,
{
fn pre_dispatch<'a>(
&mut self, req: &'a Result<Option<Req<Value, Ref, RefMut>>, mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let decision = match req {
Err(err) if !err.is_final() => self.on_non_final_recv_error(err),
_ => DispatchDecision::Pass,
};
future::ready(decision).boxed()
}
}
impl<Value, Ref, RefMut> ReqReceiverMonitor<Value, Ref, RefMut> for IncompatibleClientMonitor
where
Value: ReqEnum,
Ref: ReqEnum,
RefMut: ReqEnum,
{
fn pre_recv<'a>(
&mut self, req: &'a Result<Option<Req<Value, Ref, RefMut>>, mpsc::RecvError>,
) -> BoxFuture<'a, RecvDecision> {
let decision = match req {
Err(err) if !err.is_final() => self.on_non_final_req_recv_error(err),
_ => RecvDecision::Pass,
};
future::ready(decision).boxed()
}
}