use std::time::Duration;
#[derive(Clone, Debug)]
pub struct RetryPolicy {
pub max_retries: u32,
pub base_delay: Duration,
pub max_delay: Duration,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: 2,
base_delay: Duration::from_millis(250),
max_delay: Duration::from_secs(10),
}
}
}
impl RetryPolicy {
pub fn disabled() -> Self {
Self {
max_retries: 0,
..Self::default()
}
}
}
pub(crate) trait Transient {
fn transient(&self, idempotent: bool) -> bool;
}
pub(crate) async fn with_retry<F, Fut, R, E>(
policy: &RetryPolicy,
idempotent: bool,
mut f: F,
) -> Result<R, E>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<R, E>>,
E: Transient,
{
let mut attempt: u32 = 0;
loop {
match f().await {
Ok(r) => return Ok(r),
Err(e) => {
if attempt >= policy.max_retries || !e.transient(idempotent) {
return Err(e);
}
let factor = 1u32.checked_shl(attempt.min(16)).unwrap_or(u32::MAX);
let delay = policy
.base_delay
.saturating_mul(factor)
.min(policy.max_delay);
tokio::time::sleep(delay).await;
attempt += 1;
}
}
}
}