#![forbid(unsafe_code)]
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Instant;
#[doc(hidden)]
pub use tracing;
const PERIOD_MS: u32 = 5000;
const EVENTS_PER_PERIOD: u32 = 10;
static DISABLE_RATE_LIMITING: AtomicBool = AtomicBool::new(false);
pub fn disable_rate_limiting(disabled: bool) {
DISABLE_RATE_LIMITING.store(disabled, Ordering::Relaxed);
}
#[doc(hidden)]
pub struct RateLimiter {
period_ms: u32,
events_per_period: u32,
state: Mutex<RateLimiterState>,
}
struct RateLimiterState {
start: Option<Instant>,
events: u32,
missed: u64,
}
#[doc(hidden)]
pub struct RateLimited;
impl RateLimiter {
pub const fn new_default() -> Self {
Self::new(PERIOD_MS, EVENTS_PER_PERIOD)
}
pub const fn new(period_ms: u32, events_per_period: u32) -> Self {
Self {
period_ms,
events_per_period,
state: Mutex::new(RateLimiterState {
start: None,
events: 0,
missed: 0,
}),
}
}
pub fn event(&self) -> Result<Option<u64>, RateLimited> {
if DISABLE_RATE_LIMITING.load(Ordering::Relaxed) {
return Ok(None);
}
let mut state = self.state.try_lock().ok_or(RateLimited)?;
let now = Instant::now();
let start = state.start.get_or_insert(now);
let elapsed = now.duration_since(*start);
if elapsed.as_millis() > self.period_ms as u128 {
*start = now;
state.events = 0;
}
if state.events >= self.events_per_period {
state.missed += 1;
return Err(RateLimited);
}
state.events += 1;
let missed = std::mem::take(&mut state.missed);
let missed = (missed != 0 || state.events == self.events_per_period).then_some(missed);
Ok(missed)
}
}
#[macro_export]
macro_rules! error_ratelimited {
($($rest:tt)*) => {
{
static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
if let Ok(missed_events) = RATE_LIMITER.event() {
$crate::tracing::error!(dropped_ratelimited = missed_events, $($rest)*);
}
}
};
}
#[macro_export]
macro_rules! warn_ratelimited {
($($rest:tt)*) => {
{
static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
if let Ok(missed_events) = RATE_LIMITER.event() {
$crate::tracing::warn!(dropped_ratelimited = missed_events, $($rest)*);
}
}
};
}
#[macro_export]
macro_rules! info_ratelimited {
($($rest:tt)*) => {
{
static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
if let Ok(missed_events) = RATE_LIMITER.event() {
$crate::tracing::info!(dropped_ratelimited = missed_events, $($rest)*);
}
}
};
}