Skip to main content

isb_apps/notify/
rate.rs

1//! Retry backoff and per-channel rate limiting.
2
3use std::collections::VecDeque;
4use std::time::{Duration, Instant};
5
6/// The longest wait between attempts (Retry-After included).
7pub const BACKOFF_MAX: Duration = Duration::from_secs(300);
8
9/// The wait before attempt `attempt + 1` (attempt counts from 1): the base
10/// doubling per attempt, at least what the server asked for, capped.
11pub fn backoff(base: Duration, attempt: u32, retry_after: Option<Duration>) -> Duration {
12    let exp = base.saturating_mul(1u32 << attempt.saturating_sub(1).min(16));
13    exp.max(retry_after.unwrap_or_default()).min(BACKOFF_MAX)
14}
15
16/// Sliding one-minute window of sends.
17#[derive(Debug, Default)]
18pub struct RateLimit {
19    sent: VecDeque<Instant>,
20}
21
22impl RateLimit {
23    /// How long to wait before the next send may go, at `now`.
24    pub fn wait(&mut self, now: Instant, per_minute: usize) -> Duration {
25        while self
26            .sent
27            .front()
28            .is_some_and(|t| now.duration_since(*t) >= Duration::from_secs(60))
29        {
30            self.sent.pop_front();
31        }
32        if self.sent.len() < per_minute {
33            return Duration::ZERO;
34        }
35        (self.sent[0] + Duration::from_secs(60)).saturating_duration_since(now)
36    }
37
38    pub fn record(&mut self, now: Instant) {
39        self.sent.push_back(now);
40    }
41}