use futures::{FutureExt, future::BoxFuture};
use std::{num::NonZeroUsize, sync::Arc};
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use tracing::Level;
use crate::{
rch,
rtc::{DispatchDecision, DispatchGuard, Req, ReqEnum, ServerMonitor},
};
#[derive(Debug, Clone)]
pub struct ConcurrentLimitMonitor {
semaphore: Arc<Semaphore>,
log_level: Option<Level>,
}
impl ConcurrentLimitMonitor {
pub const DEFAULT_LOG_LEVEL: Option<Level> = Some(Level::TRACE);
pub fn new(concurrent_requests: NonZeroUsize) -> Self {
Self {
semaphore: Arc::new(Semaphore::new(concurrent_requests.get())),
log_level: Self::DEFAULT_LOG_LEVEL,
}
}
#[must_use]
pub fn log_level(mut self, level: Option<Level>) -> Self {
self.log_level = level;
self
}
}
#[allow(dead_code)]
struct ConcurrentGuard(OwnedSemaphorePermit);
impl DispatchGuard for ConcurrentGuard {}
impl<Value, Ref, RefMut> ServerMonitor<Value, Ref, RefMut> for ConcurrentLimitMonitor
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 trait_name = Req::<Value, Ref, RefMut>::trait_name();
let method_name = if let Ok(Some(req)) = req { Some(req.method_name()) } else { None };
async move {
let permit = match self.semaphore.clone().try_acquire_owned() {
Ok(permit) => permit,
Err(_) => {
let target = match method_name {
Some(method_name) => format!("{trait_name}::{method_name}"),
None => trait_name.to_string(),
};
log_at!(self.log_level, %target, "queueing call due to concurrent request limit");
self.semaphore.clone().acquire_owned().await.unwrap()
}
};
DispatchDecision::Guard(Box::new(ConcurrentGuard(permit)))
}
.boxed()
}
}