1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Exponential backoff generator. Serves as a building block to implement custom
//! retry functions.
//!
//! # Why?
//! When an network requests times out, often the best way to solve it is to try
//! again. But trying again straight away might at best cause some network overhead,
//! and at worst a full fledged DDOS. So we have to be responsible about it.
//!
//! A good explanation of retry strategies can be found on the [Stripe
//! blog](https://stripe.com/blog/idempotency).
//!
//! # Usage
//! Here we try and read a file from disk, and try again if it fails. A more
//! realistic scenario would probably to perform an HTTP request, but the approach
//! should be similar.
//!
//! ```rust
//! # fn retry() -> std::io::Result<()> {
//! use exponential_backoff::Backoff;
//! use std::{fs, thread, time::Duration};
//!
//! let retries = 8;
//! let min = Duration::from_millis(100);
//! let max = Duration::from_secs(10);
//! let backoff = Backoff::new(retries, min, max);
//!
//! for duration in &backoff {
//!     match fs::read_to_string("README.md") {
//!         Ok(s) => {
//!             println!("{}", s);
//!             break;
//!         }
//!         Err(err) => thread::sleep(duration),
//!     }
//! }
//! # Ok(()) }
//! ```

use std::iter;
use std::time::Duration;

pub use iterator::Iter;

mod iterator;

/// Exponential backoff type.
#[derive(Debug, Clone)]
pub struct Backoff {
    retries: u32,
    min: Duration,
    max: Option<Duration>,
    jitter: f32,
    factor: u32,
}

impl Backoff {
    /// Create a new instance.
    ///
    /// # Panics
    ///
    /// This method panics if the retry count is set to 0.
    #[inline]
    pub fn new(retries: u32, min: Duration, max: impl Into<Option<Duration>>) -> Self {
        assert!(
            retries >= 1,
            "<exponential-backoff>: retries should be 1 or higher."
        );

        Self {
            retries,
            min,
            max: max.into(),
            jitter: 0.3,
            factor: 2,
        }
    }

    /// Set the min duration.
    #[inline]
    pub fn set_min(&mut self, min: Duration) {
        self.min = min;
    }

    /// Set the max duration.
    #[inline]
    pub fn set_max(&mut self, max: Option<Duration>) {
        self.max = max;
    }

    /// Set the amount of jitter per backoff.
    ///
    /// ## Panics
    /// This method panics if a number smaller than `0` or larger than `1` is
    /// provided.
    #[inline]
    pub fn set_jitter(&mut self, jitter: f32) {
        assert!(
            jitter > 0f32 && jitter < 1f32,
            "<exponential-backoff>: jitter must be between 0 and 1."
        );
        self.jitter = jitter;
    }

    /// Set the growth factor for each iteration of the backoff.
    #[inline]
    pub fn set_factor(&mut self, factor: u32) {
        self.factor = factor;
    }

    /// Get the next value for the retry count.
    pub fn next(&self, retry_attempt: u32) -> Option<Duration> {
        Iter::with_count(self, retry_attempt).next()
    }

    /// Create an iterator.
    #[inline]
    pub fn iter(&self) -> Iter {
        Iter::new(self)
    }
}

impl<'b> iter::IntoIterator for &'b Backoff {
    type Item = Duration;
    type IntoIter = Iter<'b>;

    fn into_iter(self) -> Self::IntoIter {
        Self::IntoIter::new(self)
    }
}