use async_trait::async_trait;
use tokio::time::Duration;
#[derive(Debug, Clone)]
pub struct RetryConfig {
pub max_retries: u32,
pub min_delay: Duration,
pub max_delay: Duration,
pub jitter: bool,
pub retry_on_status: Vec<u16>,
}
pub struct RetryOperator {
config: RetryConfig,
}
impl RetryOperator {
pub fn new(config: RetryConfig) -> Self {
Self { config }
}
fn backoff(&self, attempt: u32) -> Duration {
let base = self.config.min_delay.as_millis() as u64;
let cap = self.config.max_delay.as_millis() as u64;
let mut delay = base.saturating_mul(1u64 << attempt);
if delay > cap {
delay = cap;
}
let duration = Duration::from_millis(delay);
if self.config.jitter {
duration
} else {
duration
}
}
}
#[async_trait]
pub trait ResiliencePolicy: Send + Sync {
async fn should_retry(&self, attempt: u32, error: &crate::Error) -> Option<Duration>;
}
#[async_trait]
impl ResiliencePolicy for RetryOperator {
async fn should_retry(&self, attempt: u32, error: &crate::Error) -> Option<Duration> {
if attempt >= self.config.max_retries {
return None;
}
if let Some(ctx) = error.context() {
if let Some(retryable) = ctx.retryable {
if retryable {
return Some(self.backoff(attempt));
} else {
return None;
}
}
}
if let crate::Error::Remote {
retryable: true, ..
} = error
{
return Some(self.backoff(attempt));
}
if matches!(error, crate::Error::Runtime { .. })
|| matches!(error, crate::Error::Transport(_))
{
return Some(self.backoff(attempt));
}
None
}
}