use std::collections::HashMap;
use std::num::NonZeroU32;
use std::sync::{Arc, Mutex, PoisonError};
use rypsok_core::limits::Limits;
use governor::clock::{Clock, DefaultClock};
use governor::{Quota, RateLimiter};
pub struct ToolLimiter {
limiter: RateLimiter<governor::state::NotKeyed, governor::state::InMemoryState, DefaultClock>,
clock: DefaultClock,
}
impl std::fmt::Debug for ToolLimiter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ToolLimiter")
}
}
impl ToolLimiter {
pub fn per_minute(per_minute: u32) -> Self {
let n = NonZeroU32::new(per_minute.max(1)).expect("at least one");
let clock = DefaultClock::default();
ToolLimiter {
limiter: RateLimiter::direct_with_clock(Quota::per_minute(n), clock.clone()),
clock,
}
}
pub fn admit(&self) -> Result<(), u64> {
self.limiter.check().map_err(|not_until| {
let wait = not_until.wait_time_from(self.clock.now());
(wait.as_millis() as u64).max(1)
})
}
}
pub use rypsok_core::model::CallerId;
#[derive(Debug)]
pub struct ToolLimiters {
pub search: ToolLimiter,
pub fetch: ToolLimiter,
pub status: ToolLimiter,
}
#[derive(Debug)]
pub struct CallerLimiters {
per_minute: (u32, u32, u32),
callers: Mutex<HashMap<CallerId, Arc<ToolLimiters>>>,
}
impl CallerLimiters {
pub fn new(limits: &Limits) -> Self {
CallerLimiters {
per_minute: (
limits.web_search_per_minute,
limits.web_fetch_per_minute,
limits.engine_status_per_minute,
),
callers: Mutex::new(HashMap::new()),
}
}
pub fn for_caller(&self, caller: &CallerId) -> Arc<ToolLimiters> {
let mut callers = self.callers.lock().unwrap_or_else(PoisonError::into_inner);
Arc::clone(callers.entry(caller.clone()).or_insert_with(|| {
Arc::new(ToolLimiters {
search: ToolLimiter::per_minute(self.per_minute.0),
fetch: ToolLimiter::per_minute(self.per_minute.1),
status: ToolLimiter::per_minute(self.per_minute.2),
})
}))
}
}
#[cfg(test)]
mod tests;