backoff_rs/
lib.rs

1//! # Backoff
2//!
3//! Backoff provides the base components for implementing backoff and retry operations.
4//!
5//! ```rust
6//! use backoff_rs::ExponentialBackoffBuilder;
7//! use std::time::Duration;
8//!
9//! fn main() {
10//!     let bo = ExponentialBackoffBuilder::default()
11//!         .factor(1.75)
12//!         .interval(Duration::from_millis(500))
13//!         .jitter(Duration::from_millis(150))
14//!         .max(Duration::from_secs(5))
15//!         .build();
16//!
17//!     for attempt in 0..=5 {
18//!         println!("{:?}", bo.duration(attempt));
19//!     }
20//! }
21//! ```
22
23use rand::Rng;
24use std::time::Duration;
25
26/// Configures an ExponentialBackoff instance for use.
27pub struct ExponentialBackoffBuilder {
28    factor: f64,
29    interval: Duration,
30    jitter: Duration,
31    max: Option<Duration>,
32}
33
34impl Default for ExponentialBackoffBuilder {
35    #[inline]
36    fn default() -> Self {
37        Self {
38            factor: 1.75,
39            interval: Duration::from_millis(500),
40            jitter: Duration::from_millis(150),
41            max: None,
42        }
43    }
44}
45
46impl ExponentialBackoffBuilder {
47    /// Factor sets a backoff factor for the backoff algorithm.
48    #[inline]
49    pub const fn factor(mut self, factor: f64) -> Self {
50        self.factor = factor;
51        self
52    }
53
54    /// Interval sets base wait interval for the backoff algorithm.
55    pub const fn interval(mut self, interval: Duration) -> Self {
56        self.interval = interval;
57        self
58    }
59
60    /// Jitter sets the maximum jitter for the backoff algorithm.
61    #[inline]
62    pub const fn jitter(mut self, jitter: Duration) -> Self {
63        self.jitter = jitter;
64        self
65    }
66
67    /// Max sets the maximum timeout despite the number of attempts. none/zero is the default.
68    #[inline]
69    pub const fn max(mut self, max: Duration) -> Self {
70        self.max = Some(max);
71        self
72    }
73
74    /// finalizes the configuration and returns a usable [Exponential] instance.
75    #[inline]
76    pub const fn build(self) -> Exponential {
77        Exponential {
78            factor: self.factor,
79            interval: self.interval.as_nanos() as f64,
80            jitter: self.jitter.as_nanos() as f64,
81            max: match self.max {
82                Some(d) => Some(d.as_nanos() as u64),
83                None => None,
84            },
85        }
86    }
87}
88
89/// An Exponential Backoff instance for calculating backoff durations.
90pub struct Exponential {
91    factor: f64,
92    interval: f64,
93    jitter: f64,
94    max: Option<u64>,
95}
96
97impl Exponential {
98    /// returns the calculated backoff duration for backoff and retries based on the attempt.
99    pub fn duration(&self, attempt: usize) -> Duration {
100        let nanoseconds = (self.factor.powi(attempt as i32) * self.interval
101            + rand::thread_rng().gen_range(0.0..=self.jitter)) as u64;
102        match self.max {
103            Some(max) if nanoseconds > max => Duration::from_nanos(max),
104            _ => Duration::from_nanos(nanoseconds),
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111
112    use super::*;
113
114    #[test]
115    fn no_jitter() {
116        let bo = ExponentialBackoffBuilder::default()
117            .jitter(Duration::default())
118            .max(Duration::from_secs(5))
119            .build();
120
121        assert_eq!(bo.duration(0), Duration::from_millis(500));
122        assert_eq!(bo.duration(1), Duration::from_millis(875));
123        assert_eq!(bo.duration(2), Duration::from_nanos(1531250000));
124        assert_eq!(bo.duration(3), Duration::from_nanos(2679687500));
125        assert_eq!(bo.duration(4), Duration::from_nanos(4689453125));
126        assert_eq!(bo.duration(5), Duration::from_secs(5));
127    }
128
129    #[test]
130    fn with_jitter() {
131        let bo = ExponentialBackoffBuilder::default()
132            .jitter(Duration::default())
133            .max(Duration::from_secs(5))
134            .build();
135
136        assert!(bo.duration(0) <= Duration::from_millis(500));
137        assert!(bo.duration(1) <= Duration::from_millis(875));
138        assert!(bo.duration(2) <= Duration::from_nanos(1531250000));
139        assert!(bo.duration(3) <= Duration::from_nanos(2679687500));
140        assert!(bo.duration(4) <= Duration::from_nanos(4689453125));
141        assert!(bo.duration(5) <= Duration::from_secs(5));
142    }
143}