use crate::backoff::Backoff;
use crate::error::{RetryError, StopReason};
use std::time::Duration;
pub(crate) enum Decision {
Retry(Duration),
Stop {
reason: StopReason,
elapsed: Option<Duration>,
},
}
pub(crate) fn should_retry_after<E, P, B>(
err: &E,
when: &P,
backoff: &mut B,
max_elapsed: Option<Duration>,
elapsed: impl FnOnce() -> Duration,
) -> Decision
where
P: Fn(&E) -> bool,
B: Backoff,
{
if !when(err) {
return Decision::Stop {
reason: StopReason::NotRetryable,
elapsed: None,
};
}
let Some(delay) = backoff.next_delay() else {
return Decision::Stop {
reason: StopReason::RetriesExhausted,
elapsed: None,
};
};
if let Some(budget) = max_elapsed {
let spent = elapsed();
if spent.saturating_add(delay) >= budget {
return Decision::Stop {
reason: StopReason::MaxElapsed,
elapsed: Some(spent),
};
}
}
Decision::Retry(delay)
}
pub(crate) fn give_up<E>(
error: E,
retries: u32,
reason: StopReason,
measured: Option<Duration>,
measure: impl FnOnce() -> Duration,
) -> RetryError<E> {
let elapsed = measured.unwrap_or_else(measure);
let attempts = retries.saturating_add(1);
if retries > 0 {
trace_give_up(attempts, elapsed, reason);
}
RetryError::new(error, attempts, elapsed, reason)
}
pub(crate) fn trace_retry<E: std::fmt::Debug>(attempt: u32, error: &E, delay: Duration) {
tracing::warn!(
target: "mettle::retry",
attempt,
delay_ms = delay.as_millis() as u64,
error = ?error,
"retrying after error"
);
}
fn trace_give_up(attempts: u32, elapsed: Duration, reason: StopReason) {
tracing::warn!(
target: "mettle::retry",
attempts,
elapsed_ms = elapsed.as_millis() as u64,
reason = reason.as_str(),
"gave up"
);
}