Coverage Report

Created: 2025-11-16 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/backoff-0.4.0/src/exponential.rs
Line
Count
Source
1
use instant::Instant;
2
use std::marker::PhantomData;
3
use std::time::Duration;
4
5
use crate::backoff::Backoff;
6
use crate::clock::Clock;
7
use crate::default;
8
9
#[derive(Debug)]
10
pub struct ExponentialBackoff<C> {
11
    /// The current retry interval.
12
    pub current_interval: Duration,
13
    /// The initial retry interval.
14
    pub initial_interval: Duration,
15
    /// The randomization factor to use for creating a range around the retry interval.
16
    ///
17
    /// A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
18
    /// above the retry interval.
19
    pub randomization_factor: f64,
20
    /// The value to multiply the current interval with for each retry attempt.
21
    pub multiplier: f64,
22
    /// The maximum value of the back off period. Once the retry interval reaches this
23
    /// value it stops increasing.
24
    pub max_interval: Duration,
25
    /// The system time. It is calculated when an [`ExponentialBackoff`](struct.ExponentialBackoff.html) instance is
26
    /// created and is reset when [`retry`](../trait.Operation.html#method.retry) is called.
27
    pub start_time: Instant,
28
    /// The maximum elapsed time after instantiating [`ExponentialBackfff`](struct.ExponentialBackoff.html) or calling
29
    /// [`reset`](trait.Backoff.html#method.reset) after which [`next_backoff`](../trait.Backoff.html#method.reset) returns `None`.
30
    pub max_elapsed_time: Option<Duration>,
31
    /// The clock used to get the current time.
32
    pub clock: C,
33
}
34
35
impl<C> Default for ExponentialBackoff<C>
36
where
37
    C: Clock + Default,
38
{
39
0
    fn default() -> ExponentialBackoff<C> {
40
0
        let mut eb = ExponentialBackoff {
41
0
            current_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
42
0
            initial_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
43
0
            randomization_factor: default::RANDOMIZATION_FACTOR,
44
0
            multiplier: default::MULTIPLIER,
45
0
            max_interval: Duration::from_millis(default::MAX_INTERVAL_MILLIS),
46
0
            max_elapsed_time: Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS)),
47
0
            clock: C::default(),
48
0
            start_time: Instant::now(),
49
0
        };
50
0
        eb.reset();
51
0
        eb
52
0
    }
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<backoff::clock::SystemClock> as core::default::Default>::default
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<_> as core::default::Default>::default
53
}
54
55
impl<C: Clock> ExponentialBackoff<C> {
56
    /// Returns the elapsed time since start_time.
57
0
    pub fn get_elapsed_time(&self) -> Duration {
58
0
        self.clock.now().duration_since(self.start_time)
59
0
    }
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<backoff::clock::SystemClock>>::get_elapsed_time
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<_>>::get_elapsed_time
60
61
0
    fn get_random_value_from_interval(
62
0
        randomization_factor: f64,
63
0
        random: f64,
64
0
        current_interval: Duration,
65
0
    ) -> Duration {
66
0
        let current_interval_nanos = duration_to_nanos(current_interval);
67
68
0
        let delta = randomization_factor * current_interval_nanos;
69
0
        let min_interval = current_interval_nanos - delta;
70
0
        let max_interval = current_interval_nanos + delta;
71
        // Get a random value from the range [minInterval, maxInterval].
72
        // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
73
        // we want a 33% chance for selecting either 1, 2 or 3.
74
0
        let diff = max_interval - min_interval;
75
0
        let nanos = min_interval + (random * (diff + 1.0));
76
0
        nanos_to_duration(nanos)
77
0
    }
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<backoff::clock::SystemClock>>::get_random_value_from_interval
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<_>>::get_random_value_from_interval
78
79
0
    fn increment_current_interval(&mut self) -> Duration {
80
0
        let current_interval_nanos = duration_to_nanos(self.current_interval);
81
0
        let max_interval_nanos = duration_to_nanos(self.max_interval);
82
        // Check for overflow, if overflow is detected set the current interval to the max interval.
83
0
        if current_interval_nanos >= max_interval_nanos / self.multiplier {
84
0
            self.max_interval
85
        } else {
86
0
            let nanos = current_interval_nanos * self.multiplier;
87
0
            nanos_to_duration(nanos)
88
        }
89
0
    }
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<backoff::clock::SystemClock>>::increment_current_interval
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<_>>::increment_current_interval
90
}
91
92
0
fn duration_to_nanos(d: Duration) -> f64 {
93
0
    d.as_secs() as f64 * 1_000_000_000.0 + f64::from(d.subsec_nanos())
94
0
}
95
96
0
fn nanos_to_duration(nanos: f64) -> Duration {
97
0
    let secs = nanos / 1_000_000_000.0;
98
0
    let nanos = nanos as u64 % 1_000_000_000;
99
0
    Duration::new(secs as u64, nanos as u32)
100
0
}
101
102
impl<C> Backoff for ExponentialBackoff<C>
103
where
104
    C: Clock,
105
{
106
0
    fn reset(&mut self) {
107
0
        self.current_interval = self.initial_interval;
108
0
        self.start_time = self.clock.now();
109
0
    }
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<backoff::clock::SystemClock> as backoff::backoff::Backoff>::reset
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<_> as backoff::backoff::Backoff>::reset
110
111
0
    fn next_backoff(&mut self) -> Option<Duration> {
112
0
        let elapsed_time = self.get_elapsed_time();
113
114
0
        match self.max_elapsed_time {
115
0
            Some(v) if elapsed_time > v => None,
116
            _ => {
117
0
                let random = rand::random::<f64>();
118
0
                let randomized_interval = Self::get_random_value_from_interval(
119
0
                    self.randomization_factor,
120
0
                    random,
121
0
                    self.current_interval,
122
                );
123
0
                self.current_interval = self.increment_current_interval();
124
125
0
                if let Some(max_elapsed_time) = self.max_elapsed_time {
126
0
                    if elapsed_time + randomized_interval <= max_elapsed_time {
127
0
                        Some(randomized_interval)
128
                    } else {
129
0
                        None
130
                    }
131
                } else {
132
0
                    Some(randomized_interval)
133
                }
134
            }
135
        }
136
0
    }
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<backoff::clock::SystemClock> as backoff::backoff::Backoff>::next_backoff
Unexecuted instantiation: <backoff::exponential::ExponentialBackoff<_> as backoff::backoff::Backoff>::next_backoff
137
}
138
139
impl<C> Clone for ExponentialBackoff<C>
140
where
141
    C: Clone,
142
{
143
0
    fn clone(&self) -> Self {
144
0
        let clock = self.clock.clone();
145
0
        ExponentialBackoff { clock, ..*self }
146
0
    }
147
}
148
149
/// Builder for [`ExponentialBackoff`](type.ExponentialBackoff.html).
150
///
151
/// TODO: Example
152
#[derive(Debug)]
153
pub struct ExponentialBackoffBuilder<C> {
154
    initial_interval: Duration,
155
    randomization_factor: f64,
156
    multiplier: f64,
157
    max_interval: Duration,
158
    max_elapsed_time: Option<Duration>,
159
    _clock: PhantomData<C>,
160
}
161
162
impl<C> Default for ExponentialBackoffBuilder<C> {
163
0
    fn default() -> Self {
164
0
        Self {
165
0
            initial_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
166
0
            randomization_factor: default::RANDOMIZATION_FACTOR,
167
0
            multiplier: default::MULTIPLIER,
168
0
            max_interval: Duration::from_millis(default::MAX_INTERVAL_MILLIS),
169
0
            max_elapsed_time: Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS)),
170
0
            _clock: PhantomData,
171
0
        }
172
0
    }
173
}
174
175
impl<C> ExponentialBackoffBuilder<C>
176
where
177
    C: Clock + Default,
178
{
179
0
    pub fn new() -> Self {
180
0
        Default::default()
181
0
    }
182
183
    /// The initial retry interval.
184
0
    pub fn with_initial_interval(&mut self, initial_interval: Duration) -> &mut Self {
185
0
        self.initial_interval = initial_interval;
186
0
        self
187
0
    }
188
189
    /// The randomization factor to use for creating a range around the retry interval.
190
    ///
191
    /// A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
192
    /// above the retry interval.
193
0
    pub fn with_randomization_factor(&mut self, randomization_factor: f64) -> &mut Self {
194
0
        self.randomization_factor = randomization_factor;
195
0
        self
196
0
    }
197
198
    /// The value to multiply the current interval with for each retry attempt.
199
0
    pub fn with_multiplier(&mut self, multiplier: f64) -> &mut Self {
200
0
        self.multiplier = multiplier;
201
0
        self
202
0
    }
203
204
    /// The maximum value of the back off period. Once the retry interval reaches this
205
    /// value it stops increasing.
206
0
    pub fn with_max_interval(&mut self, max_interval: Duration) -> &mut Self {
207
0
        self.max_interval = max_interval;
208
0
        self
209
0
    }
210
211
    /// The maximum elapsed time after instantiating [`ExponentialBackfff`](struct.ExponentialBackoff.html) or calling
212
    /// [`reset`](trait.Backoff.html#method.reset) after which [`next_backoff`](../trait.Backoff.html#method.reset) returns `None`.
213
0
    pub fn with_max_elapsed_time(&mut self, max_elapsed_time: Option<Duration>) -> &mut Self {
214
0
        self.max_elapsed_time = max_elapsed_time;
215
0
        self
216
0
    }
217
218
0
    pub fn build(&self) -> ExponentialBackoff<C> {
219
0
        ExponentialBackoff {
220
0
            current_interval: self.initial_interval,
221
0
            initial_interval: self.initial_interval,
222
0
            randomization_factor: self.randomization_factor,
223
0
            multiplier: self.multiplier,
224
0
            max_interval: self.max_interval,
225
0
            max_elapsed_time: self.max_elapsed_time,
226
0
            clock: C::default(),
227
0
            start_time: Instant::now(),
228
0
        }
229
0
    }
230
}
231
232
#[cfg(test)]
233
use crate::clock::SystemClock;
234
235
#[test]
236
fn get_randomized_interval() {
237
    // 33% chance of being 1.
238
    let f = ExponentialBackoff::<SystemClock>::get_random_value_from_interval;
239
    assert_eq!(Duration::new(0, 1), f(0.5, 0.0, Duration::new(0, 2)));
240
    assert_eq!(Duration::new(0, 1), f(0.5, 0.33, Duration::new(0, 2)));
241
    // 33% chance of being 2.
242
    assert_eq!(Duration::new(0, 2), f(0.5, 0.34, Duration::new(0, 2)));
243
    assert_eq!(Duration::new(0, 2), f(0.5, 0.66, Duration::new(0, 2)));
244
    // 33% chance of being 3.
245
    assert_eq!(Duration::new(0, 3), f(0.5, 0.67, Duration::new(0, 2)));
246
    assert_eq!(Duration::new(0, 3), f(0.5, 0.99, Duration::new(0, 2)));
247
}
248
249
#[test]
250
fn exponential_backoff_builder() {
251
    let initial_interval = Duration::from_secs(1);
252
    let max_interval = Duration::from_secs(2);
253
    let multiplier = 3.0;
254
    let randomization_factor = 4.0;
255
    let backoff: ExponentialBackoff<SystemClock> = ExponentialBackoffBuilder::new()
256
        .with_initial_interval(initial_interval)
257
        .with_multiplier(multiplier)
258
        .with_randomization_factor(randomization_factor)
259
        .with_max_interval(max_interval)
260
        .with_max_elapsed_time(None)
261
        .build();
262
    assert_eq!(backoff.initial_interval, initial_interval);
263
    assert_eq!(backoff.current_interval, initial_interval);
264
    assert_eq!(backoff.multiplier, multiplier);
265
    assert_eq!(backoff.randomization_factor, randomization_factor);
266
    assert_eq!(backoff.max_interval, max_interval);
267
    assert_eq!(backoff.max_elapsed_time, None);
268
}
269
270
#[test]
271
fn exponential_backoff_default_builder() {
272
    let backoff: ExponentialBackoff<SystemClock> = ExponentialBackoffBuilder::new().build();
273
    assert_eq!(
274
        backoff.initial_interval,
275
        Duration::from_millis(default::INITIAL_INTERVAL_MILLIS)
276
    );
277
    assert_eq!(
278
        backoff.current_interval,
279
        Duration::from_millis(default::INITIAL_INTERVAL_MILLIS)
280
    );
281
    assert_eq!(backoff.multiplier, default::MULTIPLIER);
282
    assert_eq!(backoff.randomization_factor, default::RANDOMIZATION_FACTOR);
283
    assert_eq!(
284
        backoff.max_interval,
285
        Duration::from_millis(default::MAX_INTERVAL_MILLIS)
286
    );
287
    assert_eq!(
288
        backoff.max_elapsed_time,
289
        Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS))
290
    );
291
}