use std::{thread::sleep, time::Duration};
pub fn retry<T, E, F>(times: usize, delay: Duration, mut op: F) -> Result<T, E>
where
F: FnMut() -> Result<T, E>,
{
let mut attempts = 0;
let mut current_delay = delay;
loop {
match op() {
Ok(res) => return Ok(res),
Err(e) if attempts + 1 >= times => return Err(e),
Err(_) => {
attempts += 1;
sleep(current_delay);
current_delay *= 2; }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_retries_and_succeeds() {
let mut tries = 0;
let res = retry(3, Duration::from_millis(1), || {
tries += 1;
if tries == 3 {
Ok("done")
} else {
Err("fail")
}
});
assert_eq!(res, Ok("done"));
}
#[test]
fn it_fails_after_retries() {
let res: Result<(), &str> = retry(2, Duration::from_millis(1), || Err("nope"));
assert_eq!(res, Err("nope"));
}
}
pub trait Retry<T, E> {
fn retry<F>(self, times: usize, delay: Duration, op: F) -> Result<T, E>
where
F: FnMut() -> Result<T, E>;
}
impl<T, E> Retry<T, E> for Result<T, E> {
fn retry<F>(self, times: usize, delay: Duration, op: F) -> Result<T, E>
where
F: FnMut() -> Result<T, E>,
{
retry(times, delay, op)
}
}
#[test]
fn retry_should_eventually_succeed() {
use super::*;
use std::time::Duration;
let mut counter = 0;
let result = Ok(()).retry(5, Duration::from_millis(1), || {
counter += 1;
if counter < 3 {
Err("fail")
} else {
Ok(())
}
});
assert_eq!(result, Ok(()));
}
#[test]
fn test_retry_ext_failure() {
let mut counter = 0;
let result = Err::<(), &str>("initial error").retry(3, Duration::from_millis(1), || {
counter += 1;
Err("still failing")
});
assert_eq!(result, Err("still failing"));
assert_eq!(counter, 3); }