/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tower-0.5.2/src/retry/backoff.rs
Line | Count | Source |
1 | | //! This module contains generic [backoff] utilities to be used with the retry |
2 | | //! layer. |
3 | | //! |
4 | | //! The [`Backoff`] trait is a generic way to represent backoffs that can use |
5 | | //! any timer type. |
6 | | //! |
7 | | //! [`ExponentialBackoffMaker`] implements the maker type for |
8 | | //! [`ExponentialBackoff`] which implements the [`Backoff`] trait and provides |
9 | | //! a batteries included exponential backoff and jitter strategy. |
10 | | //! |
11 | | //! [backoff]: https://en.wikipedia.org/wiki/Exponential_backoff |
12 | | |
13 | | use std::fmt::Display; |
14 | | use std::future::Future; |
15 | | use std::time::Duration; |
16 | | use tokio::time; |
17 | | |
18 | | use crate::util::rng::{HasherRng, Rng}; |
19 | | |
20 | | /// Trait used to construct [`Backoff`] trait implementors. |
21 | | pub trait MakeBackoff { |
22 | | /// The backoff type produced by this maker. |
23 | | type Backoff: Backoff; |
24 | | |
25 | | /// Constructs a new backoff type. |
26 | | fn make_backoff(&mut self) -> Self::Backoff; |
27 | | } |
28 | | |
29 | | /// A backoff trait where a single mutable reference represents a single |
30 | | /// backoff session. Implementors must also implement [`Clone`] which will |
31 | | /// reset the backoff back to the default state for the next session. |
32 | | pub trait Backoff { |
33 | | /// The future associated with each backoff. This usually will be some sort |
34 | | /// of timer. |
35 | | type Future: Future<Output = ()>; |
36 | | |
37 | | /// Initiate the next backoff in the sequence. |
38 | | fn next_backoff(&mut self) -> Self::Future; |
39 | | } |
40 | | |
41 | | /// A maker type for [`ExponentialBackoff`]. |
42 | | #[derive(Debug, Clone)] |
43 | | pub struct ExponentialBackoffMaker<R = HasherRng> { |
44 | | /// The minimum amount of time to wait before resuming an operation. |
45 | | min: time::Duration, |
46 | | /// The maximum amount of time to wait before resuming an operation. |
47 | | max: time::Duration, |
48 | | /// The ratio of the base timeout that may be randomly added to a backoff. |
49 | | /// |
50 | | /// Must be greater than or equal to 0.0. |
51 | | jitter: f64, |
52 | | rng: R, |
53 | | } |
54 | | |
55 | | /// A jittered [exponential backoff] strategy. |
56 | | /// |
57 | | /// The backoff duration will increase exponentially for every subsequent |
58 | | /// backoff, up to a maximum duration. A small amount of [random jitter] is |
59 | | /// added to each backoff duration, in order to avoid retry spikes. |
60 | | /// |
61 | | /// [exponential backoff]: https://en.wikipedia.org/wiki/Exponential_backoff |
62 | | /// [random jitter]: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ |
63 | | #[derive(Debug, Clone)] |
64 | | pub struct ExponentialBackoff<R = HasherRng> { |
65 | | min: time::Duration, |
66 | | max: time::Duration, |
67 | | jitter: f64, |
68 | | rng: R, |
69 | | iterations: u32, |
70 | | } |
71 | | |
72 | | impl<R> ExponentialBackoffMaker<R> |
73 | | where |
74 | | R: Rng, |
75 | | { |
76 | | /// Create a new `ExponentialBackoff`. |
77 | | /// |
78 | | /// # Error |
79 | | /// |
80 | | /// Returns a config validation error if: |
81 | | /// - `min` > `max` |
82 | | /// - `max` > 0 |
83 | | /// - `jitter` >= `0.0` |
84 | | /// - `jitter` < `100.0` |
85 | | /// - `jitter` is finite |
86 | 0 | pub fn new( |
87 | 0 | min: time::Duration, |
88 | 0 | max: time::Duration, |
89 | 0 | jitter: f64, |
90 | 0 | rng: R, |
91 | 0 | ) -> Result<Self, InvalidBackoff> { |
92 | 0 | if min > max { |
93 | 0 | return Err(InvalidBackoff("maximum must not be less than minimum")); |
94 | 0 | } |
95 | 0 | if max == time::Duration::from_millis(0) { |
96 | 0 | return Err(InvalidBackoff("maximum must be non-zero")); |
97 | 0 | } |
98 | 0 | if jitter < 0.0 { |
99 | 0 | return Err(InvalidBackoff("jitter must not be negative")); |
100 | 0 | } |
101 | 0 | if jitter > 100.0 { |
102 | 0 | return Err(InvalidBackoff("jitter must not be greater than 100")); |
103 | 0 | } |
104 | 0 | if !jitter.is_finite() { |
105 | 0 | return Err(InvalidBackoff("jitter must be finite")); |
106 | 0 | } |
107 | | |
108 | 0 | Ok(ExponentialBackoffMaker { |
109 | 0 | min, |
110 | 0 | max, |
111 | 0 | jitter, |
112 | 0 | rng, |
113 | 0 | }) |
114 | 0 | } |
115 | | } |
116 | | |
117 | | impl<R> MakeBackoff for ExponentialBackoffMaker<R> |
118 | | where |
119 | | R: Rng + Clone, |
120 | | { |
121 | | type Backoff = ExponentialBackoff<R>; |
122 | | |
123 | 0 | fn make_backoff(&mut self) -> Self::Backoff { |
124 | 0 | ExponentialBackoff { |
125 | 0 | max: self.max, |
126 | 0 | min: self.min, |
127 | 0 | jitter: self.jitter, |
128 | 0 | rng: self.rng.clone(), |
129 | 0 | iterations: 0, |
130 | 0 | } |
131 | 0 | } |
132 | | } |
133 | | |
134 | | impl<R: Rng> ExponentialBackoff<R> { |
135 | 0 | fn base(&self) -> time::Duration { |
136 | 0 | debug_assert!( |
137 | 0 | self.min <= self.max, |
138 | | "maximum backoff must not be less than minimum backoff" |
139 | | ); |
140 | 0 | debug_assert!( |
141 | 0 | self.max > time::Duration::from_millis(0), |
142 | | "Maximum backoff must be non-zero" |
143 | | ); |
144 | 0 | self.min |
145 | 0 | .checked_mul(2_u32.saturating_pow(self.iterations)) |
146 | 0 | .unwrap_or(self.max) |
147 | 0 | .min(self.max) |
148 | 0 | } |
149 | | |
150 | | /// Returns a random, uniform duration on `[0, base*self.jitter]` no greater |
151 | | /// than `self.max`. |
152 | 0 | fn jitter(&mut self, base: time::Duration) -> time::Duration { |
153 | 0 | if self.jitter == 0.0 { |
154 | 0 | time::Duration::default() |
155 | | } else { |
156 | 0 | let jitter_factor = self.rng.next_f64(); |
157 | 0 | debug_assert!( |
158 | 0 | jitter_factor > 0.0, |
159 | | "rng returns values between 0.0 and 1.0" |
160 | | ); |
161 | 0 | let rand_jitter = jitter_factor * self.jitter; |
162 | 0 | let secs = (base.as_secs() as f64) * rand_jitter; |
163 | 0 | let nanos = (base.subsec_nanos() as f64) * rand_jitter; |
164 | 0 | let remaining = self.max - base; |
165 | 0 | time::Duration::new(secs as u64, nanos as u32).min(remaining) |
166 | | } |
167 | 0 | } |
168 | | } |
169 | | |
170 | | impl<R> Backoff for ExponentialBackoff<R> |
171 | | where |
172 | | R: Rng, |
173 | | { |
174 | | type Future = tokio::time::Sleep; |
175 | | |
176 | 0 | fn next_backoff(&mut self) -> Self::Future { |
177 | 0 | let base = self.base(); |
178 | 0 | let next = base + self.jitter(base); |
179 | | |
180 | 0 | self.iterations += 1; |
181 | | |
182 | 0 | tokio::time::sleep(next) |
183 | 0 | } |
184 | | } |
185 | | |
186 | | impl Default for ExponentialBackoffMaker { |
187 | 0 | fn default() -> Self { |
188 | 0 | ExponentialBackoffMaker::new( |
189 | 0 | Duration::from_millis(50), |
190 | 0 | Duration::from_millis(u64::MAX), |
191 | | 0.99, |
192 | 0 | HasherRng::default(), |
193 | | ) |
194 | 0 | .expect("Unable to create ExponentialBackoff") |
195 | 0 | } |
196 | | } |
197 | | |
198 | | /// Backoff validation error. |
199 | | #[derive(Debug)] |
200 | | pub struct InvalidBackoff(&'static str); |
201 | | |
202 | | impl Display for InvalidBackoff { |
203 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
204 | 0 | write!(f, "invalid backoff: {}", self.0) |
205 | 0 | } |
206 | | } |
207 | | |
208 | | impl std::error::Error for InvalidBackoff {} |
209 | | |
210 | | #[cfg(test)] |
211 | | mod tests { |
212 | | use super::*; |
213 | | use quickcheck::*; |
214 | | |
215 | | quickcheck! { |
216 | | fn backoff_base_first(min_ms: u64, max_ms: u64) -> TestResult { |
217 | | let min = time::Duration::from_millis(min_ms); |
218 | | let max = time::Duration::from_millis(max_ms); |
219 | | let rng = HasherRng::default(); |
220 | | let mut backoff = match ExponentialBackoffMaker::new(min, max, 0.0, rng) { |
221 | | Err(_) => return TestResult::discard(), |
222 | | Ok(backoff) => backoff, |
223 | | }; |
224 | | let backoff = backoff.make_backoff(); |
225 | | |
226 | | let delay = backoff.base(); |
227 | | TestResult::from_bool(min == delay) |
228 | | } |
229 | | |
230 | | fn backoff_base(min_ms: u64, max_ms: u64, iterations: u32) -> TestResult { |
231 | | let min = time::Duration::from_millis(min_ms); |
232 | | let max = time::Duration::from_millis(max_ms); |
233 | | let rng = HasherRng::default(); |
234 | | let mut backoff = match ExponentialBackoffMaker::new(min, max, 0.0, rng) { |
235 | | Err(_) => return TestResult::discard(), |
236 | | Ok(backoff) => backoff, |
237 | | }; |
238 | | let mut backoff = backoff.make_backoff(); |
239 | | |
240 | | backoff.iterations = iterations; |
241 | | let delay = backoff.base(); |
242 | | TestResult::from_bool(min <= delay && delay <= max) |
243 | | } |
244 | | |
245 | | fn backoff_jitter(base_ms: u64, max_ms: u64, jitter: f64) -> TestResult { |
246 | | let base = time::Duration::from_millis(base_ms); |
247 | | let max = time::Duration::from_millis(max_ms); |
248 | | let rng = HasherRng::default(); |
249 | | let mut backoff = match ExponentialBackoffMaker::new(base, max, jitter, rng) { |
250 | | Err(_) => return TestResult::discard(), |
251 | | Ok(backoff) => backoff, |
252 | | }; |
253 | | let mut backoff = backoff.make_backoff(); |
254 | | |
255 | | let j = backoff.jitter(base); |
256 | | if jitter == 0.0 || base_ms == 0 || max_ms == base_ms { |
257 | | TestResult::from_bool(j == time::Duration::default()) |
258 | | } else { |
259 | | TestResult::from_bool(j > time::Duration::default()) |
260 | | } |
261 | | } |
262 | | } |
263 | | } |