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}