1#![forbid(unsafe_code)]
18
19use parking_lot::Mutex;
20use std::sync::atomic::AtomicBool;
21use std::sync::atomic::Ordering;
22use std::time::Instant;
23#[doc(hidden)]
24pub use tracing;
25
26const PERIOD_MS: u32 = 5000;
27const EVENTS_PER_PERIOD: u32 = 10;
28
29static DISABLE_RATE_LIMITING: AtomicBool = AtomicBool::new(false);
30
31pub fn disable_rate_limiting(disabled: bool) {
36 DISABLE_RATE_LIMITING.store(disabled, Ordering::Relaxed);
37}
38
39#[doc(hidden)]
40pub struct RateLimiter {
41 period_ms: u32,
42 events_per_period: u32,
43 state: Mutex<RateLimiterState>,
44}
45
46struct RateLimiterState {
47 start: Option<Instant>,
48 events: u32,
49 missed: u64,
50}
51
52#[doc(hidden)]
53pub struct RateLimited;
54
55impl RateLimiter {
56 pub const fn new_default() -> Self {
57 Self::new(PERIOD_MS, EVENTS_PER_PERIOD)
58 }
59
60 pub const fn new(period_ms: u32, events_per_period: u32) -> Self {
61 Self {
62 period_ms,
63 events_per_period,
64 state: Mutex::new(RateLimiterState {
65 start: None,
66 events: 0,
67 missed: 0,
68 }),
69 }
70 }
71
72 pub fn event(&self) -> Result<Option<u64>, RateLimited> {
77 if DISABLE_RATE_LIMITING.load(Ordering::Relaxed) {
78 return Ok(None);
79 }
80 let mut state = self.state.try_lock().ok_or(RateLimited)?;
81 let now = Instant::now();
82 let start = state.start.get_or_insert(now);
83 let elapsed = now.duration_since(*start);
84 if elapsed.as_millis() > self.period_ms as u128 {
85 *start = now;
86 state.events = 0;
87 }
88 if state.events >= self.events_per_period {
89 state.missed += 1;
90 return Err(RateLimited);
91 }
92 state.events += 1;
93 let missed = std::mem::take(&mut state.missed);
94 let missed = (missed != 0 || state.events == self.events_per_period).then_some(missed);
95 Ok(missed)
96 }
97}
98
99#[macro_export]
101macro_rules! error_ratelimited {
102 ($($rest:tt)*) => {
103 {
104 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
105 if let Ok(missed_events) = RATE_LIMITER.event() {
106 $crate::tracing::error!(dropped_ratelimited = missed_events, $($rest)*);
107 }
108 }
109 };
110}
111
112#[macro_export]
114macro_rules! warn_ratelimited {
115 ($($rest:tt)*) => {
116 {
117 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
118 if let Ok(missed_events) = RATE_LIMITER.event() {
119 $crate::tracing::warn!(dropped_ratelimited = missed_events, $($rest)*);
120 }
121 }
122 };
123}
124
125#[macro_export]
127macro_rules! info_ratelimited {
128 ($($rest:tt)*) => {
129 {
130 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
131 if let Ok(missed_events) = RATE_LIMITER.event() {
132 $crate::tracing::info!(dropped_ratelimited = missed_events, $($rest)*);
133 }
134 }
135 };
136}