backoff/
retry.rs

1use std::thread;
2use std::time::Duration;
3
4use crate::backoff::Backoff;
5use crate::error::Error;
6
7/// Retries this operation according to the backoff policy.
8/// backoff is reset before it is used.
9///
10/// # Examples
11///
12/// ```rust
13/// # use backoff::{ExponentialBackoff, Error, retry};
14/// let f = || -> Result<(), Error<&str>> {
15///     // Business logic...
16///     // Give up.
17///     Err(Error::Permanent("error"))
18/// };
19///
20/// let backoff = ExponentialBackoff::default();
21/// let _ = retry(backoff, f).err().unwrap();
22/// ```
23pub fn retry<F, B, T, E>(backoff: B, op: F) -> Result<T, Error<E>>
24where
25    F: FnMut() -> Result<T, Error<E>>,
26    B: Backoff,
27{
28    let mut retry = Retry {
29        backoff,
30        notify: NoopNotify,
31        sleep: ThreadSleep,
32    };
33
34    retry.retry_notify(op)
35}
36
37/// Retries this operation according to the backoff policy.
38/// Calls notify on failed attempts (in case of transient errors).
39/// backoff is reset before it is used.
40///
41/// # Examples
42///
43/// ```rust
44/// # use backoff::{Error, retry_notify};
45/// # use backoff::backoff::Stop;
46/// # use std::time::Duration;
47/// let notify = |err, dur| { println!("Error happened at {:?}: {}", dur, err); };
48/// let f = || -> Result<(), Error<&str>> {
49///     // Business logic...
50///     Err(Error::transient("error"))
51/// };
52///
53/// let backoff = Stop{};
54/// let _ = retry_notify(backoff, f, notify).err().unwrap();
55/// ```
56pub fn retry_notify<F, B, N, T, E>(backoff: B, op: F, notify: N) -> Result<T, Error<E>>
57where
58    F: FnMut() -> Result<T, Error<E>>,
59    B: Backoff,
60    N: Notify<E>,
61{
62    let mut retry = Retry {
63        backoff,
64        notify,
65        sleep: ThreadSleep,
66    };
67
68    retry.retry_notify(op)
69}
70
71struct Retry<B, N, S> {
72    backoff: B,
73    notify: N,
74    sleep: S,
75}
76
77impl<B, N, S> Retry<B, N, S> {
78    pub fn retry_notify<F, T, E>(&mut self, mut op: F) -> Result<T, Error<E>>
79    where
80        F: FnMut() -> Result<T, Error<E>>,
81        B: Backoff,
82        N: Notify<E>,
83        S: Sleep,
84    {
85        self.backoff.reset();
86
87        loop {
88            let err = match op() {
89                Ok(v) => return Ok(v),
90                Err(err) => err,
91            };
92
93            let (err, next) = match err {
94                Error::Permanent(err) => return Err(Error::Permanent(err)),
95                Error::Transient { err, retry_after } => {
96                    match retry_after.or_else(|| self.backoff.next_backoff()) {
97                        Some(next) => (err, next),
98                        None => return Err(Error::transient(err)),
99                    }
100                }
101            };
102
103            self.notify.notify(err, next);
104
105            self.sleep.sleep(next);
106        }
107    }
108}
109
110trait Sleep {
111    fn sleep(&mut self, dur: Duration);
112}
113
114struct ThreadSleep;
115
116impl Sleep for ThreadSleep {
117    fn sleep(&mut self, dur: Duration) {
118        thread::sleep(dur);
119    }
120}
121
122/// Notify is called in [`retry_notify`](trait.Operation.html#method.retry_notify) in case of errors.
123pub trait Notify<E> {
124    fn notify(&mut self, err: E, duration: Duration);
125}
126
127impl<E, F> Notify<E> for F
128where
129    F: FnMut(E, Duration),
130{
131    fn notify(&mut self, err: E, duration: Duration) {
132        self(err, duration)
133    }
134}
135
136/// No-op implementation of [`Notify`]. Literally does nothing.
137#[derive(Debug, Clone, Copy)]
138pub struct NoopNotify;
139
140impl<E> Notify<E> for NoopNotify {
141    fn notify(&mut self, _: E, _: Duration) {}
142}