/rust/registry/src/index.crates.io-6f17d22bba15001f/backon-1.5.0/src/backoff/constant.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use core::time::Duration; |
2 | | |
3 | | use crate::backoff::BackoffBuilder; |
4 | | |
5 | | /// ConstantBuilder is used to create a [`ConstantBackoff`], providing a steady delay with a fixed number of retries. |
6 | | /// |
7 | | /// # Default |
8 | | /// |
9 | | /// - delay: 1s |
10 | | /// - max_times: 3 |
11 | | /// |
12 | | /// # Examples |
13 | | /// |
14 | | /// ```no_run |
15 | | /// use anyhow::Result; |
16 | | /// use backon::ConstantBuilder; |
17 | | /// use backon::Retryable; |
18 | | /// |
19 | | /// async fn fetch() -> Result<String> { |
20 | | /// Ok(reqwest::get("https://www.rust-lang.org") |
21 | | /// .await? |
22 | | /// .text() |
23 | | /// .await?) |
24 | | /// } |
25 | | /// |
26 | | /// #[tokio::main(flavor = "current_thread")] |
27 | | /// async fn main() -> Result<()> { |
28 | | /// let content = fetch.retry(ConstantBuilder::default()).await?; |
29 | | /// println!("fetch succeeded: {}", content); |
30 | | /// |
31 | | /// Ok(()) |
32 | | /// } |
33 | | /// ``` |
34 | | #[derive(Debug, Clone, Copy)] |
35 | | pub struct ConstantBuilder { |
36 | | delay: Duration, |
37 | | max_times: Option<usize>, |
38 | | jitter: bool, |
39 | | seed: Option<u64>, |
40 | | } |
41 | | |
42 | | impl Default for ConstantBuilder { |
43 | 0 | fn default() -> Self { |
44 | 0 | Self::new() |
45 | 0 | } |
46 | | } |
47 | | |
48 | | impl ConstantBuilder { |
49 | | /// Create a new `ConstantBuilder` with default values. |
50 | 0 | pub const fn new() -> Self { |
51 | 0 | Self { |
52 | 0 | delay: Duration::from_secs(1), |
53 | 0 | max_times: Some(3), |
54 | 0 | jitter: false, |
55 | 0 | seed: None, |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | | /// Set the delay for the backoff. |
60 | 0 | pub const fn with_delay(mut self, delay: Duration) -> Self { |
61 | 0 | self.delay = delay; |
62 | 0 | self |
63 | 0 | } |
64 | | |
65 | | /// Set the maximum number of attempts to be made. |
66 | 0 | pub const fn with_max_times(mut self, max_times: usize) -> Self { |
67 | 0 | self.max_times = Some(max_times); |
68 | 0 | self |
69 | 0 | } |
70 | | |
71 | | /// Enable jitter for the backoff. |
72 | | /// |
73 | | /// Jitter is a random value added to the delay to prevent a thundering herd problem. |
74 | 0 | pub const fn with_jitter(mut self) -> Self { |
75 | 0 | self.jitter = true; |
76 | 0 | self |
77 | 0 | } |
78 | | |
79 | | /// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std. |
80 | 0 | pub fn with_jitter_seed(mut self, seed: u64) -> Self { |
81 | 0 | self.seed = Some(seed); |
82 | 0 | self |
83 | 0 | } |
84 | | |
85 | | /// Set no max times for the backoff. |
86 | | /// |
87 | | /// The backoff will not stop by itself. |
88 | | /// |
89 | | /// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._ |
90 | 0 | pub const fn without_max_times(mut self) -> Self { |
91 | 0 | self.max_times = None; |
92 | 0 | self |
93 | 0 | } |
94 | | } |
95 | | |
96 | | impl BackoffBuilder for ConstantBuilder { |
97 | | type Backoff = ConstantBackoff; |
98 | | |
99 | 0 | fn build(self) -> Self::Backoff { |
100 | 0 | ConstantBackoff { |
101 | 0 | delay: self.delay, |
102 | 0 | max_times: self.max_times, |
103 | 0 |
|
104 | 0 | attempts: 0, |
105 | 0 | jitter: self.jitter, |
106 | 0 | rng: if let Some(seed) = self.seed { |
107 | 0 | fastrand::Rng::with_seed(seed) |
108 | | } else { |
109 | | #[cfg(feature = "std")] |
110 | 0 | let rng = fastrand::Rng::new(); |
111 | 0 |
|
112 | 0 | #[cfg(not(feature = "std"))] |
113 | 0 | let rng = fastrand::Rng::with_seed(super::RANDOM_SEED); |
114 | 0 |
|
115 | 0 | rng |
116 | | }, |
117 | | } |
118 | 0 | } |
119 | | } |
120 | | |
121 | | impl BackoffBuilder for &ConstantBuilder { |
122 | | type Backoff = ConstantBackoff; |
123 | | |
124 | 0 | fn build(self) -> Self::Backoff { |
125 | 0 | (*self).build() |
126 | 0 | } |
127 | | } |
128 | | |
129 | | /// ConstantBackoff offers a consistent delay with a limited number of retries. |
130 | | /// |
131 | | /// This backoff strategy is constructed by [`ConstantBuilder`]. |
132 | | #[doc(hidden)] |
133 | | #[derive(Debug)] |
134 | | pub struct ConstantBackoff { |
135 | | delay: Duration, |
136 | | max_times: Option<usize>, |
137 | | |
138 | | attempts: usize, |
139 | | jitter: bool, |
140 | | rng: fastrand::Rng, |
141 | | } |
142 | | |
143 | | impl Iterator for ConstantBackoff { |
144 | | type Item = Duration; |
145 | | |
146 | 0 | fn next(&mut self) -> Option<Self::Item> { |
147 | 0 | let mut delay = || match self.jitter { |
148 | 0 | true => self.delay + self.delay.mul_f32(self.rng.f32()), |
149 | 0 | false => self.delay, |
150 | 0 | }; |
151 | 0 | match self.max_times { |
152 | 0 | None => Some(delay()), |
153 | 0 | Some(max_times) => { |
154 | 0 | if self.attempts >= max_times { |
155 | 0 | None |
156 | | } else { |
157 | 0 | self.attempts += 1; |
158 | 0 | Some(delay()) |
159 | | } |
160 | | } |
161 | | } |
162 | 0 | } |
163 | | } |
164 | | |
165 | | #[cfg(test)] |
166 | | mod tests { |
167 | | use core::time::Duration; |
168 | | |
169 | | #[cfg(target_arch = "wasm32")] |
170 | | use wasm_bindgen_test::wasm_bindgen_test as test; |
171 | | |
172 | | use super::*; |
173 | | |
174 | | const TEST_BUILDER: ConstantBuilder = ConstantBuilder::new() |
175 | | .with_delay(Duration::from_secs(2)) |
176 | | .with_max_times(5) |
177 | | .with_jitter(); |
178 | | |
179 | | #[test] |
180 | | fn test_constant_default() { |
181 | | let mut it = ConstantBuilder::default().build(); |
182 | | |
183 | | assert_eq!(Some(Duration::from_secs(1)), it.next()); |
184 | | assert_eq!(Some(Duration::from_secs(1)), it.next()); |
185 | | assert_eq!(Some(Duration::from_secs(1)), it.next()); |
186 | | assert_eq!(None, it.next()); |
187 | | } |
188 | | |
189 | | #[test] |
190 | | fn test_constant_with_delay() { |
191 | | let mut it = ConstantBuilder::default() |
192 | | .with_delay(Duration::from_secs(2)) |
193 | | .build(); |
194 | | |
195 | | assert_eq!(Some(Duration::from_secs(2)), it.next()); |
196 | | assert_eq!(Some(Duration::from_secs(2)), it.next()); |
197 | | assert_eq!(Some(Duration::from_secs(2)), it.next()); |
198 | | assert_eq!(None, it.next()); |
199 | | } |
200 | | |
201 | | #[test] |
202 | | fn test_constant_with_times() { |
203 | | let mut it = ConstantBuilder::default().with_max_times(1).build(); |
204 | | |
205 | | assert_eq!(Some(Duration::from_secs(1)), it.next()); |
206 | | assert_eq!(None, it.next()); |
207 | | } |
208 | | |
209 | | #[test] |
210 | | fn test_constant_with_jitter() { |
211 | | let mut it = ConstantBuilder::default().with_jitter().build(); |
212 | | |
213 | | let dur = it.next().unwrap(); |
214 | | fastrand::seed(7); |
215 | | assert!(dur > Duration::from_secs(1)); |
216 | | } |
217 | | |
218 | | #[test] |
219 | | fn test_constant_without_max_times() { |
220 | | let mut it = ConstantBuilder::default().without_max_times().build(); |
221 | | |
222 | | for _ in 0..10_000 { |
223 | | assert_eq!(Some(Duration::from_secs(1)), it.next()); |
224 | | } |
225 | | } |
226 | | |
227 | | // allow assertions on constants because they are not optimized out by unit tests |
228 | | #[allow(clippy::assertions_on_constants)] |
229 | | #[test] |
230 | | fn test_constant_const_builder() { |
231 | | assert_eq!(TEST_BUILDER.delay, Duration::from_secs(2)); |
232 | | assert_eq!(TEST_BUILDER.max_times, Some(5)); |
233 | | assert!(TEST_BUILDER.jitter); |
234 | | } |
235 | | } |