futures_retrying/
backoff.rs1use rand::{thread_rng, Rng};
2use std::time::{Duration, Instant};
3
4pub fn instant() -> impl Backoff + Sized {
6 Duration::from_secs(0)
7}
8
9pub fn constant(duration: Duration) -> impl Backoff + Sized {
11 duration
12}
13
14pub trait Backoff: Send {
15 fn next_retry(&mut self) -> Option<Duration>;
17
18 fn exponential(self) -> Exponential<Self>
20 where
21 Self: Sized,
22 {
23 Exponential {
24 factor: 1,
25 inner: self,
26 }
27 }
28
29 fn max_backoff(self, max: Duration) -> Max<Self>
31 where
32 Self: Sized,
33 {
34 Max { max, inner: self }
35 }
36
37 fn min_backoff(self, min: Duration) -> Min<Self>
39 where
40 Self: Sized,
41 {
42 Min { min, inner: self }
43 }
44
45 fn jitter(self, scale: f64) -> Jitter<Self>
50 where
51 Self: Sized,
52 {
53 assert!(scale > 0.0, "scale must be larger than zero");
54 assert!(scale <= 1.0, "scale must be smaller or equal to one");
55 Jitter { scale, inner: self }
56 }
57
58 fn num_attempts(self, num: u32) -> MaxAttempts<Self>
59 where
60 Self: Sized,
61 {
62 assert!(num > 0, "num must be larger than zero");
63 let num_attempts_left = num - 1;
64 MaxAttempts {
65 num_attempts_left,
66 inner: self,
67 }
68 }
69
70 fn deadline(self, deadline: Instant) -> Deadline<Self>
71 where
72 Self: Sized,
73 {
74 Deadline {
75 deadline,
76 inner: self,
77 }
78 }
79}
80
81impl Backoff for Duration {
82 fn next_retry(&mut self) -> Option<Duration> {
83 Some(*self)
84 }
85}
86
87pub struct Exponential<S>
88where
89 S: Backoff,
90{
91 inner: S,
92 factor: u32,
93}
94
95impl<S> Backoff for Exponential<S>
96where
97 S: Backoff,
98{
99 fn next_retry(&mut self) -> Option<Duration> {
100 let dur = self.inner.next_retry().map(|dur| dur * (self.factor as _));
101 self.factor *= 2;
102 dur
103 }
104}
105
106pub struct Max<S>
107where
108 S: Backoff,
109{
110 inner: S,
111 max: Duration,
112}
113
114impl<S> Backoff for Max<S>
115where
116 S: Backoff,
117{
118 fn next_retry(&mut self) -> Option<Duration> {
119 self.inner
120 .next_retry()
121 .map(|dur| std::cmp::min(self.max, dur))
122 }
123}
124
125pub struct Min<S>
126where
127 S: Backoff,
128{
129 inner: S,
130 min: Duration,
131}
132
133impl<S> Backoff for Min<S>
134where
135 S: Backoff,
136{
137 fn next_retry(&mut self) -> Option<Duration> {
138 self.inner
139 .next_retry()
140 .map(|dur| std::cmp::max(self.min, dur))
141 }
142}
143
144pub struct Jitter<S>
145where
146 S: Backoff,
147{
148 inner: S,
149 scale: f64,
150}
151
152impl<S> Backoff for Jitter<S>
153where
154 S: Backoff,
155{
156 fn next_retry(&mut self) -> Option<Duration> {
157 self.inner.next_retry().map(|dur| {
158 let margin = Duration::from_secs_f64(dur.as_secs_f64() * self.scale);
159 thread_rng().gen_range(dur - margin, dur)
160 })
161 }
162}
163
164pub struct MaxAttempts<S>
165where
166 S: Backoff,
167{
168 inner: S,
169 num_attempts_left: u32,
170}
171
172impl<S> Backoff for MaxAttempts<S>
173where
174 S: Backoff,
175{
176 fn next_retry(&mut self) -> Option<Duration> {
177 if self.num_attempts_left > 0 {
178 self.num_attempts_left -= 1;
179 self.inner.next_retry()
180 } else {
181 None
182 }
183 }
184}
185
186pub struct Deadline<S>
187where
188 S: Backoff,
189{
190 inner: S,
191 deadline: Instant,
192}
193
194impl<S> Backoff for Deadline<S>
195where
196 S: Backoff,
197{
198 fn next_retry(&mut self) -> Option<Duration> {
199 if self.deadline < Instant::now() {
200 None
201 } else {
202 self.inner.next_retry()
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_instant() {
213 let mut bo = instant();
214 assert_eq!(bo.next_retry(), Some(Duration::from_secs(0)));
215 assert_eq!(bo.next_retry(), Some(Duration::from_secs(0)));
216 }
217
218 #[test]
219 fn test_constant() {
220 let mut bo = constant(Duration::from_secs(5));
221 assert_eq!(bo.next_retry(), Some(Duration::from_secs(5)));
222 assert_eq!(bo.next_retry(), Some(Duration::from_secs(5)));
223 }
224
225 #[test]
226 fn test_min_backoff() {
227 let mut bo = constant(Duration::from_secs(5)).min_backoff(Duration::from_secs(10));
228 assert_eq!(bo.next_retry(), Some(Duration::from_secs(10)));
229 assert_eq!(bo.next_retry(), Some(Duration::from_secs(10)));
230
231 let mut bo = constant(Duration::from_secs(5)).min_backoff(Duration::from_secs(3));
232 assert_eq!(bo.next_retry(), Some(Duration::from_secs(5)));
233 assert_eq!(bo.next_retry(), Some(Duration::from_secs(5)));
234 }
235
236 #[test]
237 fn test_max_backoff() {
238 let mut bo = constant(Duration::from_secs(5)).max_backoff(Duration::from_secs(10));
239 assert_eq!(bo.next_retry(), Some(Duration::from_secs(5)));
240 assert_eq!(bo.next_retry(), Some(Duration::from_secs(5)));
241
242 let mut bo = constant(Duration::from_secs(5)).max_backoff(Duration::from_secs(3));
243 assert_eq!(bo.next_retry(), Some(Duration::from_secs(3)));
244 assert_eq!(bo.next_retry(), Some(Duration::from_secs(3)));
245 }
246
247 #[test]
248 fn test_exponential() {
249 let mut bo = constant(Duration::from_secs(1)).exponential();
250 assert_eq!(bo.next_retry(), Some(Duration::from_secs(1)));
251 assert_eq!(bo.next_retry(), Some(Duration::from_secs(2)));
252 assert_eq!(bo.next_retry(), Some(Duration::from_secs(4)));
253 assert_eq!(bo.next_retry(), Some(Duration::from_secs(8)));
254 }
255
256 #[test]
257 fn test_jitter() {
258 let mut bo = constant(Duration::from_secs(1)).jitter(0.1);
259 let range = Duration::from_millis(900)..=Duration::from_secs(1);
260 for _i in 0..100_000 {
261 let dur = bo.next_retry().unwrap();
262 assert!(range.contains(&dur));
263 }
264 }
265
266 #[test]
267 fn test_num_attempts() {
268 let mut bo = constant(Duration::from_secs(1)).num_attempts(3);
269 assert_eq!(bo.next_retry(), Some(Duration::from_secs(1)));
270 assert_eq!(bo.next_retry(), Some(Duration::from_secs(1)));
271 assert_eq!(bo.next_retry(), None);
272 assert_eq!(bo.next_retry(), None);
273 }
274
275 #[test]
276 fn deadline() {
277 let mut bo =
278 constant(Duration::from_secs(1)).deadline(Instant::now() + Duration::from_millis(20));
279 assert_eq!(bo.next_retry(), Some(Duration::from_secs(1)));
280 assert_eq!(bo.next_retry(), Some(Duration::from_secs(1)));
281 std::thread::sleep(Duration::from_millis(21));
282 assert_eq!(bo.next_retry(), None);
283 assert_eq!(bo.next_retry(), None);
284 }
285}