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}