backon/
blocking_retry.rs

1use core::time::Duration;
2
3use crate::backoff::BackoffBuilder;
4use crate::blocking_sleep::MaybeBlockingSleeper;
5use crate::Backoff;
6use crate::BlockingSleeper;
7use crate::DefaultBlockingSleeper;
8
9/// BlockingRetryable adds retry support for blocking functions.
10///
11/// For example:
12///
13/// - Functions without extra args:
14///
15/// ```ignore
16/// fn fetch() -> Result<String> {
17///     Ok("hello, world!".to_string())
18/// }
19/// ```
20///
21/// - Closures
22///
23/// ```ignore
24/// || {
25///     Ok("hello, world!".to_string())
26/// }
27/// ```
28///
29/// # Example
30///
31/// ```no_run
32/// use anyhow::Result;
33/// use backon::BlockingRetryable;
34/// use backon::ExponentialBuilder;
35///
36/// fn fetch() -> Result<String> {
37///     Ok("hello, world!".to_string())
38/// }
39///
40/// fn main() -> Result<()> {
41///     let content = fetch.retry(ExponentialBuilder::default()).call()?;
42///     println!("fetch succeeded: {}", content);
43///
44///     Ok(())
45/// }
46/// ```
47pub trait BlockingRetryable<B: BackoffBuilder, T, E, F: FnMut() -> Result<T, E>> {
48    /// Generate a new retry.
49    fn retry(self, builder: B) -> BlockingRetry<B::Backoff, T, E, F>;
50}
51
52impl<B, T, E, F> BlockingRetryable<B, T, E, F> for F
53where
54    B: BackoffBuilder,
55    F: FnMut() -> Result<T, E>,
56{
57    fn retry(self, builder: B) -> BlockingRetry<B::Backoff, T, E, F> {
58        BlockingRetry::new(self, builder.build())
59    }
60}
61
62/// Retry structure generated by [`BlockingRetryable`].
63pub struct BlockingRetry<
64    B: Backoff,
65    T,
66    E,
67    F: FnMut() -> Result<T, E>,
68    SF: MaybeBlockingSleeper = DefaultBlockingSleeper,
69    RF = fn(&E) -> bool,
70    NF = fn(&E, Duration),
71> {
72    backoff: B,
73    retryable: RF,
74    notify: NF,
75    f: F,
76    sleep_fn: SF,
77}
78
79impl<B, T, E, F> BlockingRetry<B, T, E, F>
80where
81    B: Backoff,
82    F: FnMut() -> Result<T, E>,
83{
84    /// Create a new retry.
85    fn new(f: F, backoff: B) -> Self {
86        BlockingRetry {
87            backoff,
88            retryable: |_: &E| true,
89            notify: |_: &E, _: Duration| {},
90            sleep_fn: DefaultBlockingSleeper::default(),
91            f,
92        }
93    }
94}
95
96impl<B, T, E, F, SF, RF, NF> BlockingRetry<B, T, E, F, SF, RF, NF>
97where
98    B: Backoff,
99    F: FnMut() -> Result<T, E>,
100    SF: MaybeBlockingSleeper,
101    RF: FnMut(&E) -> bool,
102    NF: FnMut(&E, Duration),
103{
104    /// Set the sleeper for retrying.
105    ///
106    /// The sleeper should implement the [`BlockingSleeper`] trait. The simplest way is to use a closure like  `Fn(Duration)`.
107    ///
108    /// If not specified, we use the [`DefaultBlockingSleeper`].
109    ///
110    /// # Examples
111    ///
112    /// ```no_run
113    /// use anyhow::Result;
114    /// use backon::BlockingRetryable;
115    /// use backon::ExponentialBuilder;
116    ///
117    /// fn fetch() -> Result<String> {
118    ///     Ok("hello, world!".to_string())
119    /// }
120    ///
121    /// fn main() -> Result<()> {
122    ///     let retry = fetch
123    ///         .retry(ExponentialBuilder::default())
124    ///         .sleep(std::thread::sleep);
125    ///     let content = retry.call()?;
126    ///     println!("fetch succeeded: {}", content);
127    ///
128    ///     Ok(())
129    /// }
130    /// ```
131    pub fn sleep<SN: BlockingSleeper>(self, sleep_fn: SN) -> BlockingRetry<B, T, E, F, SN, RF, NF> {
132        BlockingRetry {
133            backoff: self.backoff,
134            retryable: self.retryable,
135            notify: self.notify,
136            f: self.f,
137            sleep_fn,
138        }
139    }
140
141    /// Set the conditions for retrying.
142    ///
143    /// If not specified, all errors are considered retryable.
144    ///
145    /// # Examples
146    ///
147    /// ```no_run
148    /// use anyhow::Result;
149    /// use backon::BlockingRetryable;
150    /// use backon::ExponentialBuilder;
151    ///
152    /// fn fetch() -> Result<String> {
153    ///     Ok("hello, world!".to_string())
154    /// }
155    ///
156    /// fn main() -> Result<()> {
157    ///     let retry = fetch
158    ///         .retry(ExponentialBuilder::default())
159    ///         .when(|e| e.to_string() == "EOF");
160    ///     let content = retry.call()?;
161    ///     println!("fetch succeeded: {}", content);
162    ///
163    ///     Ok(())
164    /// }
165    /// ```
166    pub fn when<RN: FnMut(&E) -> bool>(
167        self,
168        retryable: RN,
169    ) -> BlockingRetry<B, T, E, F, SF, RN, NF> {
170        BlockingRetry {
171            backoff: self.backoff,
172            retryable,
173            notify: self.notify,
174            f: self.f,
175            sleep_fn: self.sleep_fn,
176        }
177    }
178
179    /// Set to notify for all retry attempts.
180    ///
181    /// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing.
182    ///
183    /// If not specified, this operation does nothing.
184    ///
185    /// # Examples
186    ///
187    /// ```no_run
188    /// use core::time::Duration;
189    ///
190    /// use anyhow::Result;
191    /// use backon::BlockingRetryable;
192    /// use backon::ExponentialBuilder;
193    ///
194    /// fn fetch() -> Result<String> {
195    ///     Ok("hello, world!".to_string())
196    /// }
197    ///
198    /// fn main() -> Result<()> {
199    ///     let retry = fetch.retry(ExponentialBuilder::default()).notify(
200    ///         |err: &anyhow::Error, dur: Duration| {
201    ///             println!("retrying error {:?} with sleeping {:?}", err, dur);
202    ///         },
203    ///     );
204    ///     let content = retry.call()?;
205    ///     println!("fetch succeeded: {}", content);
206    ///
207    ///     Ok(())
208    /// }
209    /// ```
210    pub fn notify<NN: FnMut(&E, Duration)>(
211        self,
212        notify: NN,
213    ) -> BlockingRetry<B, T, E, F, SF, RF, NN> {
214        BlockingRetry {
215            backoff: self.backoff,
216            retryable: self.retryable,
217            notify,
218            f: self.f,
219            sleep_fn: self.sleep_fn,
220        }
221    }
222}
223
224impl<B, T, E, F, SF, RF, NF> BlockingRetry<B, T, E, F, SF, RF, NF>
225where
226    B: Backoff,
227    F: FnMut() -> Result<T, E>,
228    SF: BlockingSleeper,
229    RF: FnMut(&E) -> bool,
230    NF: FnMut(&E, Duration),
231{
232    /// Call the retried function.
233    ///
234    /// TODO: implement [`FnOnce`] after it stable.
235    pub fn call(mut self) -> Result<T, E> {
236        loop {
237            let result = (self.f)();
238
239            match result {
240                Ok(v) => return Ok(v),
241                Err(err) => {
242                    if !(self.retryable)(&err) {
243                        return Err(err);
244                    }
245
246                    match self.backoff.next() {
247                        None => return Err(err),
248                        Some(dur) => {
249                            (self.notify)(&err, dur);
250                            self.sleep_fn.sleep(dur);
251                        }
252                    }
253                }
254            }
255        }
256    }
257}
258#[cfg(test)]
259mod tests {
260    extern crate alloc;
261
262    use alloc::string::ToString;
263    use alloc::vec;
264    use alloc::vec::Vec;
265    use core::time::Duration;
266
267    use spin::Mutex;
268
269    use super::*;
270    use crate::ExponentialBuilder;
271
272    fn always_error() -> anyhow::Result<()> {
273        Err(anyhow::anyhow!("test_query meets error"))
274    }
275
276    #[test]
277    fn test_retry() -> anyhow::Result<()> {
278        let result = always_error
279            .retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)))
280            .call();
281
282        assert!(result.is_err());
283        assert_eq!("test_query meets error", result.unwrap_err().to_string());
284        Ok(())
285    }
286
287    #[test]
288    fn test_retry_with_not_retryable_error() -> anyhow::Result<()> {
289        let error_times = Mutex::new(0);
290
291        let f = || {
292            let mut x = error_times.lock();
293            *x += 1;
294            Err::<(), anyhow::Error>(anyhow::anyhow!("not retryable"))
295        };
296
297        let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
298        let result = f
299            .retry(backoff)
300            // Only retry If error message is `retryable`
301            .when(|e| e.to_string() == "retryable")
302            .call();
303
304        assert!(result.is_err());
305        assert_eq!("not retryable", result.unwrap_err().to_string());
306        // `f` always returns error "not retryable", so it should be executed
307        // only once.
308        assert_eq!(*error_times.lock(), 1);
309        Ok(())
310    }
311
312    #[test]
313    fn test_retry_with_retryable_error() -> anyhow::Result<()> {
314        let error_times = Mutex::new(0);
315
316        let f = || {
317            // println!("I have been called!");
318            let mut x = error_times.lock();
319            *x += 1;
320            Err::<(), anyhow::Error>(anyhow::anyhow!("retryable"))
321        };
322
323        let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
324        let result = f
325            .retry(backoff)
326            // Only retry If error message is `retryable`
327            .when(|e| e.to_string() == "retryable")
328            .call();
329
330        assert!(result.is_err());
331        assert_eq!("retryable", result.unwrap_err().to_string());
332        // `f` always returns error "retryable", so it should be executed
333        // 4 times (retry 3 times).
334        assert_eq!(*error_times.lock(), 4);
335        Ok(())
336    }
337
338    #[test]
339    fn test_fn_mut_when_and_notify() -> anyhow::Result<()> {
340        let mut calls_retryable: Vec<()> = vec![];
341        let mut calls_notify: Vec<()> = vec![];
342
343        let f = || Err::<(), anyhow::Error>(anyhow::anyhow!("retryable"));
344
345        let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
346        let result = f
347            .retry(backoff)
348            .when(|_| {
349                calls_retryable.push(());
350                true
351            })
352            .notify(|_, _| {
353                calls_notify.push(());
354            })
355            .call();
356
357        assert!(result.is_err());
358        assert_eq!("retryable", result.unwrap_err().to_string());
359        // `f` always returns error "retryable", so it should be executed
360        // 4 times (retry 3 times).
361        assert_eq!(calls_retryable.len(), 4);
362        assert_eq!(calls_notify.len(), 3);
363        Ok(())
364    }
365}