execution_policy/retry/
backoff.rs1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Backoff {
6 Fixed(Duration),
8 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 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}