foundry_common/retry.rs
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
//! Retry utilities.
use eyre::{Error, Report, Result};
use std::{future::Future, time::Duration};
/// Error type for Retry.
#[derive(Debug, thiserror::Error)]
pub enum RetryError<E = Report> {
/// Keeps retrying operation.
Retry(E),
/// Stops retrying operation immediately.
Break(E),
}
/// A type that keeps track of attempts.
#[derive(Clone, Debug)]
pub struct Retry {
retries: u32,
delay: Duration,
}
impl Retry {
/// Creates a new `Retry` instance.
pub fn new(retries: u32, delay: Duration) -> Self {
Self { retries, delay }
}
/// Creates a new `Retry` instance with no delay between retries.
pub fn new_no_delay(retries: u32) -> Self {
Self::new(retries, Duration::ZERO)
}
/// Runs the given closure in a loop, retrying if it fails up to the specified number of times.
pub fn run<F: FnMut() -> Result<T>, T>(mut self, mut callback: F) -> Result<T> {
loop {
match callback() {
Err(e) if self.retries > 0 => {
self.handle_err(e);
if !self.delay.is_zero() {
std::thread::sleep(self.delay);
}
}
res => return res,
}
}
}
/// Runs the given async closure in a loop, retrying if it fails up to the specified number of
/// times.
pub async fn run_async<F, Fut, T>(mut self, mut callback: F) -> Result<T>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T>>,
{
loop {
match callback().await {
Err(e) if self.retries > 0 => {
self.handle_err(e);
if !self.delay.is_zero() {
tokio::time::sleep(self.delay).await;
}
}
res => return res,
};
}
}
/// Runs the given async closure in a loop, retrying if it fails up to the specified number of
/// times or immediately returning an error if the closure returned [`RetryError::Break`].
pub async fn run_async_until_break<F, Fut, T>(mut self, mut callback: F) -> Result<T>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T, RetryError>>,
{
loop {
match callback().await {
Err(RetryError::Retry(e)) if self.retries > 0 => {
self.handle_err(e);
if !self.delay.is_zero() {
tokio::time::sleep(self.delay).await;
}
}
Err(RetryError::Retry(e) | RetryError::Break(e)) => return Err(e),
Ok(t) => return Ok(t),
};
}
}
fn handle_err(&mut self, err: Error) {
debug_assert!(self.retries > 0);
self.retries -= 1;
let _ = sh_warn!(
"{msg}{delay} ({retries} tries remaining)",
msg = crate::errors::display_chain(&err),
delay = if self.delay.is_zero() {
String::new()
} else {
format!("; waiting {} seconds before trying again", self.delay.as_secs())
},
retries = self.retries,
);
}
}