Coverage Report

Created: 2025-05-07 06:59

/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
}