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 state: Mutex<RateLimiterState>,
42}
43
44struct RateLimiterState {
45 period_ms: u32,
46 events_per_period: u32,
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 state: Mutex::new(RateLimiterState {
63 period_ms,
64 events_per_period,
65 start: None,
66 events: 0,
67 missed: 0,
68 }),
69 }
70 }
71
72 pub fn event(&self) -> Result<Option<u64>, RateLimited> {
77 self.event_with_config(None, None)
78 }
79
80 #[cold]
86 pub fn event_with_config(
87 &self,
88 period_ms: Option<u32>,
89 events_per_period: Option<u32>,
90 ) -> Result<Option<u64>, RateLimited> {
91 if DISABLE_RATE_LIMITING.load(Ordering::Relaxed) {
92 return Ok(None);
93 }
94
95 let mut state = self.state.try_lock().ok_or(RateLimited)?;
96
97 let mut reset_state = false;
99 if let Some(new_period) = period_ms {
100 if state.period_ms != new_period {
101 state.period_ms = new_period;
102 reset_state = true;
103 }
104 }
105 if let Some(new_events_per_period) = events_per_period {
106 if state.events_per_period != new_events_per_period {
107 state.events_per_period = new_events_per_period;
108 reset_state = true;
109 }
110 }
111
112 if reset_state {
114 state.start = None;
115 state.events = 0;
116 state.missed = 0;
117 }
118
119 let now = Instant::now();
120 let period_ms = state.period_ms;
121 let start = state.start.get_or_insert(now);
122 let elapsed = now.duration_since(*start);
123 if elapsed.as_millis() > period_ms as u128 {
124 *start = now;
125 state.events = 0;
126 }
127 if state.events >= state.events_per_period {
128 state.missed += 1;
129 return Err(RateLimited);
130 }
131 state.events += 1;
132 let missed = std::mem::take(&mut state.missed);
133 let missed = (missed != 0 || state.events == state.events_per_period).then_some(missed);
134 Ok(missed)
135 }
136}
137
138#[macro_export]
153macro_rules! error_ratelimited {
154 (period: $period:expr, limit: $limit:expr, $($rest:tt)*) => {
156 {
157 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
158 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), Some($limit)) {
159 $crate::tracing::error!(dropped_ratelimited = missed_events, $($rest)*);
160 }
161 }
162 };
163 (period: $period:expr, $($rest:tt)*) => {
165 {
166 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
167 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), None) {
168 $crate::tracing::error!(dropped_ratelimited = missed_events, $($rest)*);
169 }
170 }
171 };
172 (limit: $limit:expr, $($rest:tt)*) => {
174 {
175 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
176 if let Ok(missed_events) = RATE_LIMITER.event_with_config(None, Some($limit)) {
177 $crate::tracing::error!(dropped_ratelimited = missed_events, $($rest)*);
178 }
179 }
180 };
181 ($($rest:tt)*) => {
183 {
184 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
185 if let Ok(missed_events) = RATE_LIMITER.event() {
186 $crate::tracing::error!(dropped_ratelimited = missed_events, $($rest)*);
187 }
188 }
189 };
190}
191
192#[macro_export]
207macro_rules! warn_ratelimited {
208 (period: $period:expr, limit: $limit:expr, $($rest:tt)*) => {
210 {
211 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
212 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), Some($limit)) {
213 $crate::tracing::warn!(dropped_ratelimited = missed_events, $($rest)*);
214 }
215 }
216 };
217 (period: $period:expr, $($rest:tt)*) => {
219 {
220 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
221 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), None) {
222 $crate::tracing::warn!(dropped_ratelimited = missed_events, $($rest)*);
223 }
224 }
225 };
226 (limit: $limit:expr, $($rest:tt)*) => {
228 {
229 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
230 if let Ok(missed_events) = RATE_LIMITER.event_with_config(None, Some($limit)) {
231 $crate::tracing::warn!(dropped_ratelimited = missed_events, $($rest)*);
232 }
233 }
234 };
235 ($($rest:tt)*) => {
237 {
238 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
239 if let Ok(missed_events) = RATE_LIMITER.event() {
240 $crate::tracing::warn!(dropped_ratelimited = missed_events, $($rest)*);
241 }
242 }
243 };
244}
245
246#[macro_export]
261macro_rules! info_ratelimited {
262 (period: $period:expr, limit: $limit:expr, $($rest:tt)*) => {
264 {
265 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
266 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), Some($limit)) {
267 $crate::tracing::info!(dropped_ratelimited = missed_events, $($rest)*);
268 }
269 }
270 };
271 (period: $period:expr, $($rest:tt)*) => {
273 {
274 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
275 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), None) {
276 $crate::tracing::info!(dropped_ratelimited = missed_events, $($rest)*);
277 }
278 }
279 };
280 (limit: $limit:expr, $($rest:tt)*) => {
282 {
283 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
284 if let Ok(missed_events) = RATE_LIMITER.event_with_config(None, Some($limit)) {
285 $crate::tracing::info!(dropped_ratelimited = missed_events, $($rest)*);
286 }
287 }
288 };
289 ($($rest:tt)*) => {
291 {
292 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
293 if let Ok(missed_events) = RATE_LIMITER.event() {
294 $crate::tracing::info!(dropped_ratelimited = missed_events, $($rest)*);
295 }
296 }
297 };
298}
299
300#[macro_export]
308macro_rules! event_ratelimited_static {
309 (level: $level:ident, period: $period:expr, limit: $limit:expr, $($rest:tt)*) => {{
311 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
312 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), Some($limit)) {
313 $crate::tracing::event!(
314 $crate::tracing::Level::$level,
315 dropped_ratelimited = missed_events,
316 $($rest)*
317 );
318 }
319 }};
320 (level: $level:ident, period: $period:expr, $($rest:tt)*) => {{
322 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
323 if let Ok(missed_events) = RATE_LIMITER.event_with_config(Some($period), None) {
324 $crate::tracing::event!(
325 $crate::tracing::Level::$level,
326 dropped_ratelimited = missed_events,
327 $($rest)*
328 );
329 }
330 }};
331 (level: $level:ident, limit: $limit:expr, $($rest:tt)*) => {{
333 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
334 if let Ok(missed_events) = RATE_LIMITER.event_with_config(None, Some($limit)) {
335 $crate::tracing::event!(
336 $crate::tracing::Level::$level,
337 dropped_ratelimited = missed_events,
338 $($rest)*
339 );
340 }
341 }};
342 (level: $level:ident, $($rest:tt)*) => {{
344 static RATE_LIMITER: $crate::RateLimiter = $crate::RateLimiter::new_default();
345 if let Ok(missed_events) = RATE_LIMITER.event() {
346 $crate::tracing::event!(
347 $crate::tracing::Level::$level,
348 dropped_ratelimited = missed_events,
349 $($rest)*
350 );
351 }
352 }};
353}
354
355#[macro_export]
370macro_rules! event_ratelimited {
371 ($level:expr, period: $period:expr, limit: $limit:expr, $($rest:tt)*) => {
373 match $level {
374 $crate::tracing::Level::ERROR => {
375 $crate::event_ratelimited_static!(level: ERROR, period: $period, limit: $limit, $($rest)*);
376 }
377 $crate::tracing::Level::WARN => {
378 $crate::event_ratelimited_static!(level: WARN, period: $period, limit: $limit, $($rest)*);
379 }
380 $crate::tracing::Level::INFO => {
381 $crate::event_ratelimited_static!(level: INFO, period: $period, limit: $limit, $($rest)*);
382 }
383 $crate::tracing::Level::DEBUG => {
384 $crate::event_ratelimited_static!(level: DEBUG, period: $period, limit: $limit, $($rest)*);
385 }
386 $crate::tracing::Level::TRACE => {
387 $crate::event_ratelimited_static!(level: TRACE, period: $period, limit: $limit, $($rest)*);
388 }
389 }
390 };
391 ($level:expr, period: $period:expr, $($rest:tt)*) => {
393 match $level {
394 $crate::tracing::Level::ERROR => {
395 $crate::event_ratelimited_static!(level: ERROR, period: $period, $($rest)*);
396 }
397 $crate::tracing::Level::WARN => {
398 $crate::event_ratelimited_static!(level: WARN, period: $period, $($rest)*);
399 }
400 $crate::tracing::Level::INFO => {
401 $crate::event_ratelimited_static!(level: INFO, period: $period, $($rest)*);
402 }
403 $crate::tracing::Level::DEBUG => {
404 $crate::event_ratelimited_static!(level: DEBUG, period: $period, $($rest)*);
405 }
406 $crate::tracing::Level::TRACE => {
407 $crate::event_ratelimited_static!(level: TRACE, period: $period, $($rest)*);
408 }
409 }
410 };
411 ($level:expr, limit: $limit:expr, $($rest:tt)*) => {
413 match $level {
414 $crate::tracing::Level::ERROR => {
415 $crate::event_ratelimited_static!(level: ERROR, limit: $limit, $($rest)*);
416 }
417 $crate::tracing::Level::WARN => {
418 $crate::event_ratelimited_static!(level: WARN, limit: $limit, $($rest)*);
419 }
420 $crate::tracing::Level::INFO => {
421 $crate::event_ratelimited_static!(level: INFO, limit: $limit, $($rest)*);
422 }
423 $crate::tracing::Level::DEBUG => {
424 $crate::event_ratelimited_static!(level: DEBUG, limit: $limit, $($rest)*);
425 }
426 $crate::tracing::Level::TRACE => {
427 $crate::event_ratelimited_static!(level: TRACE, limit: $limit, $($rest)*);
428 }
429 }
430 };
431 ($level:expr, $($rest:tt)*) => {
433 match $level {
434 $crate::tracing::Level::ERROR => {
435 $crate::event_ratelimited_static!(level: ERROR, $($rest)*);
436 }
437 $crate::tracing::Level::WARN => {
438 $crate::event_ratelimited_static!(level: WARN, $($rest)*);
439 }
440 $crate::tracing::Level::INFO => {
441 $crate::event_ratelimited_static!(level: INFO, $($rest)*);
442 }
443 $crate::tracing::Level::DEBUG => {
444 $crate::event_ratelimited_static!(level: DEBUG, $($rest)*);
445 }
446 $crate::tracing::Level::TRACE => {
447 $crate::event_ratelimited_static!(level: TRACE, $($rest)*);
448 }
449 }
450 };
451}