Skip to main content

execution_policy/retry/
backoff.rs

1use std::time::Duration;
2
3/// Delay schedule between attempts.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Backoff {
6    /// Constant delay.
7    Fixed(Duration),
8    /// `base * 2^(attempt-1)`, clamped to `max`.
9    Exponential { base: Duration, max: Duration },
10}
11
12impl Backoff {
13    pub fn fixed(d: Duration) -> Self {
14        Backoff::Fixed(d)
15    }
16    pub fn exponential(base: Duration, max: Duration) -> Self {
17        Backoff::Exponential { base, max }
18    }
19
20    /// Raw (pre-jitter) delay to wait *after* `attempt` (1-based) before the next try.
21    pub(crate) fn raw_delay(&self, attempt: u32) -> Duration {
22        match self {
23            Backoff::Fixed(d) => *d,
24            Backoff::Exponential { base, max } => {
25                let shift = attempt.saturating_sub(1).min(63);
26                let factor = 1u64.checked_shl(shift).unwrap_or(u64::MAX);
27                let scaled = base
28                    .checked_mul(u32::try_from(factor).unwrap_or(u32::MAX))
29                    .unwrap_or(*max);
30                scaled.min(*max)
31            }
32        }
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn fixed_is_constant() {
42        let b = Backoff::fixed(Duration::from_millis(50));
43        assert_eq!(b.raw_delay(1), Duration::from_millis(50));
44        assert_eq!(b.raw_delay(5), Duration::from_millis(50));
45    }
46
47    #[test]
48    fn exponential_doubles_then_clamps() {
49        let b = Backoff::exponential(Duration::from_millis(50), Duration::from_millis(400));
50        assert_eq!(b.raw_delay(1), Duration::from_millis(50));
51        assert_eq!(b.raw_delay(2), Duration::from_millis(100));
52        assert_eq!(b.raw_delay(3), Duration::from_millis(200));
53        assert_eq!(b.raw_delay(4), Duration::from_millis(400));
54        assert_eq!(b.raw_delay(5), Duration::from_millis(400));
55        assert_eq!(b.raw_delay(40), Duration::from_millis(400));
56    }
57}