Skip to main content

retry/
delay.rs

1use std::time::Duration;
2
3/// A retry delay strategy.
4///
5/// Implementations determine how long to wait before each retry attempt.
6pub trait Delay: Send {
7    /// Returns the delay before the next retry attempt.
8    fn next_delay(&mut self) -> Duration;
9}
10
11/// Fixed delay between retries.
12///
13/// Every retry attempt waits the same amount of time.
14///
15/// # Example
16///
17/// ```
18/// use std::time::Duration;
19/// use retry::delay::{Delay, Fixed};
20///
21/// let mut d = Fixed::new(Duration::from_secs(2));
22/// assert_eq!(d.next_delay(), Duration::from_secs(2));
23/// assert_eq!(d.next_delay(), Duration::from_secs(2));
24/// ```
25#[derive(Debug, Clone, Copy)]
26pub struct Fixed {
27    duration: Duration,
28}
29
30impl Fixed {
31    pub const fn new(duration: Duration) -> Self {
32        Self {
33            duration,
34        }
35    }
36}
37
38impl Delay for Fixed {
39    fn next_delay(&mut self) -> Duration {
40        self.duration
41    }
42}
43
44/// Exponential backoff delay.
45///
46/// Each retry multiplies the delay by `multiplier` (default 2),
47/// capped at `max` (default 60s). Optional jitter applies
48/// ±25% random variation to each delay value.
49///
50/// # Example
51///
52/// ```
53/// use std::time::Duration;
54/// use retry::delay::{Delay, Exponential};
55///
56/// let mut d = Exponential::new(Duration::from_millis(100))
57///     .with_max(Duration::from_secs(2))
58///     .with_multiplier(2);
59///
60/// assert_eq!(d.next_delay(), Duration::from_millis(100));
61/// assert_eq!(d.next_delay(), Duration::from_millis(200));
62/// assert_eq!(d.next_delay(), Duration::from_millis(400));
63/// assert_eq!(d.next_delay(), Duration::from_millis(800));
64/// assert_eq!(d.next_delay(), Duration::from_millis(1600));
65/// // capped at max
66/// assert_eq!(d.next_delay(), Duration::from_secs(2));
67/// assert_eq!(d.next_delay(), Duration::from_secs(2));
68/// ```
69#[derive(Debug, Clone)]
70pub struct Exponential {
71    current: Duration,
72    max: Duration,
73    multiplier: u32,
74    jitter: bool,
75    rng_state: u64,
76}
77
78impl Exponential {
79    /// Creates a new exponential backoff starting with `initial` delay.
80    pub fn new(initial: Duration) -> Self {
81        Self {
82            current: initial,
83            max: Duration::from_secs(60),
84            multiplier: 2,
85            jitter: false,
86            rng_state: initial.as_nanos() as u64 ^ 0x9e3779b97f4a7c15,
87        }
88    }
89
90    /// Sets the maximum delay cap (default 60s).
91    pub fn with_max(mut self, max: Duration) -> Self {
92        self.max = max;
93        self
94    }
95
96    /// Sets the multiplier applied to the delay after each retry (default 2).
97    pub fn with_multiplier(mut self, multiplier: u32) -> Self {
98        self.multiplier = multiplier;
99        self
100    }
101
102    /// Enables ±25% random jitter on each delay value.
103    ///
104    /// The actual delay will be uniformly distributed in the range
105    /// `[0.75 * delay, 1.25 * delay]`.
106    pub fn with_jitter(mut self) -> Self {
107        self.jitter = true;
108        self.rng_state = random_seed();
109        self
110    }
111
112    fn jitter(&mut self, delay: Duration) -> Duration {
113        if delay == Duration::ZERO {
114            return Duration::ZERO;
115        }
116
117        // xorshift64
118        self.rng_state ^= self.rng_state << 13;
119        self.rng_state ^= self.rng_state >> 7;
120        self.rng_state ^= self.rng_state << 17;
121
122        let nanos = delay.as_nanos();
123        // ±25% → multiplier in range [7500, 12500] per 10000
124        let offset = (self.rng_state % 5001) as u128;
125        let multiplier = 7500 + offset;
126        Duration::from_nanos((nanos * multiplier / 10000) as u64)
127    }
128}
129
130impl Delay for Exponential {
131    fn next_delay(&mut self) -> Duration {
132        let delay = self.current;
133
134        // Grow current for next call, capped at max
135        let current_nanos = self.current.as_nanos();
136        let next = current_nanos.saturating_mul(self.multiplier as u128);
137        let max_nanos = self.max.as_nanos();
138        self.current = Duration::from_nanos(std::cmp::min(next, max_nanos) as u64);
139
140        if self.jitter { self.jitter(delay) } else { delay }
141    }
142}
143
144fn random_seed() -> u64 {
145    std::time::SystemTime::now()
146        .duration_since(std::time::UNIX_EPOCH)
147        .map(|d| d.as_nanos() as u64)
148        .unwrap_or(0x9e3779b97f4a7c15)
149}