#![allow(clippy::type_complexity)]
#[cfg(not(any(feature = "tokio", feature = "async-std")))]
compile_error!("At least on of 'tokio' or 'async-std' feature must be enabled");
pub mod backoff;
pub mod jitter;
use std::{marker::PhantomData, time::Duration};
pub use backoff::{Backoff, Exponential, Fixed, Linear};
pub use jitter::{Decorrelated, Equal, Full, Jitter, NoJitter};
pub fn until_ok<T, E>() -> Mulligan<'static, T, E, impl Fn(&Result<T, E>) -> bool, NoJitter, Fixed>
{
until::<T, E, _>(|result: &Result<T, E>| result.is_ok())
}
pub fn until<T, E, Cond>(f: Cond) -> Mulligan<'static, T, E, Cond, NoJitter, Fixed>
where
Cond: Fn(&Result<T, E>) -> bool,
{
Mulligan {
stop_after: None,
until: f,
backoff: Fixed::base(Duration::from_secs(0)),
jitterable: jitter::NoJitter,
max: None,
before_attempt: None,
after_attempt: None,
_phantom: PhantomData,
}
}
pub struct Mulligan<'a, T, E, Cond, Jit, Back>
where
Cond: Fn(&Result<T, E>) -> bool,
Jit: jitter::Jitter,
Back: backoff::Backoff,
{
stop_after: Option<u32>,
until: Cond,
backoff: Back,
jitterable: Jit,
max: Option<Duration>,
before_attempt: Option<Box<dyn Fn(u32) + Send + Sync + 'a>>,
after_attempt: Option<Box<dyn Fn(&Result<T, E>, u32) + Send + Sync + 'a>>,
_phantom: PhantomData<(T, E)>,
}
impl<'a, T, E, Cond, Jit, Back> Mulligan<'a, T, E, Cond, Jit, Back>
where
Cond: Fn(&Result<T, E>) -> bool,
Jit: jitter::Jitter,
Back: backoff::Backoff,
{
pub async fn execute<F>(mut self, f: F) -> Result<T, E>
where
F: AsyncFn() -> Result<T, E>,
{
let mut attempt: u32 = 0;
loop {
if let Some(before_attempt) = &self.before_attempt {
before_attempt(attempt);
}
let res = f().await;
if self.stop_after.is_some_and(|max| attempt >= max) | (self.until)(&res) {
return res;
}
let delay = self.calculate_delay(attempt);
Self::sleep(delay).await;
if let Some(after_attempt) = &self.after_attempt {
after_attempt(&res, attempt);
}
attempt += 1;
}
}
pub fn execute_sync<F>(mut self, f: F) -> Result<T, E>
where
F: Fn() -> Result<T, E>,
{
let mut attempt: u32 = 0;
loop {
if let Some(before_attempt) = &self.before_attempt {
before_attempt(attempt);
}
let res = f();
if self.stop_after.is_some_and(|max| attempt >= max) | (self.until)(&res) {
return res;
}
let delay = self.calculate_delay(attempt);
std::thread::sleep(delay);
if let Some(after_attempt) = &self.after_attempt {
after_attempt(&res, attempt);
}
attempt += 1;
}
}
pub fn before_attempt<F>(mut self, before_attempt: F) -> Self
where
F: Fn(u32) + Send + Sync + 'a,
{
self.before_attempt = Some(Box::new(before_attempt));
self
}
pub fn after_attempt<F>(mut self, after_attempt: F) -> Self
where
F: Fn(&Result<T, E>, u32) + Send + Sync + 'a,
{
self.after_attempt = Some(Box::new(after_attempt));
self
}
pub fn stop_after(mut self, attempts: u32) -> Self {
self.stop_after = Some(attempts);
self
}
fn calculate_delay(&mut self, attempt: u32) -> Duration {
let delay = self.backoff.delay(attempt);
self.jitterable.jitter(delay, self.max)
}
pub fn jitter<J>(self, jitter: J) -> Mulligan<'a, T, E, Cond, J, Back>
where
J: jitter::Jitter,
{
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: self.backoff,
jitterable: jitter,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn full_jitter(self) -> Mulligan<'a, T, E, Cond, jitter::Full, Back> {
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: self.backoff,
jitterable: jitter::Full,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn equal_jitter(self) -> Mulligan<'a, T, E, Cond, jitter::Equal, Back> {
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: self.backoff,
jitterable: jitter::Equal,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn decorrelated_jitter(
self,
base: Duration,
) -> Mulligan<'a, T, E, Cond, jitter::Decorrelated, Back> {
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: self.backoff,
jitterable: jitter::Decorrelated::base(base),
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn backoff<B>(self, backoff: B) -> Mulligan<'a, T, E, Cond, Jit, B>
where
B: Backoff,
{
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff,
jitterable: self.jitterable,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn fixed(self, dur: Duration) -> Mulligan<'a, T, E, Cond, Jit, Fixed> {
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: Fixed::base(dur),
jitterable: self.jitterable,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn linear(self, dur: Duration) -> Mulligan<'a, T, E, Cond, Jit, Linear> {
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: Linear::base(dur),
jitterable: self.jitterable,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn exponential(self, dur: Duration) -> Mulligan<'a, T, E, Cond, Jit, Exponential> {
Mulligan {
stop_after: self.stop_after,
until: self.until,
backoff: Exponential::base(dur),
jitterable: self.jitterable,
max: self.max,
before_attempt: self.before_attempt,
after_attempt: self.after_attempt,
_phantom: PhantomData,
}
}
pub fn max_delay(mut self, dur: Duration) -> Self {
self.max = Some(dur);
self
}
#[cfg(feature = "tokio")]
async fn sleep(dur: Duration) {
tokio::time::sleep(dur).await;
}
#[cfg(all(feature = "async-std", not(feature = "tokio")))]
async fn sleep(dur: Duration) {
async_std::future::sleep(dur).await;
}
}