use std::time::Duration;
#[derive(Clone, Debug)]
pub struct RetryPolicy {
pub max_attempts: u32,
pub initial_backoff: Duration,
pub backoff: Backoff,
pub max_backoff: Duration,
pub retryable: RetryableCodes,
}
#[derive(Clone, Copy, Debug)]
pub enum Backoff {
Exponential { multiplier: f64 },
Fixed,
}
#[derive(Clone, Debug)]
pub struct RetryableCodes(pub Vec<tonic::Code>);
impl Default for RetryableCodes {
fn default() -> Self {
Self(vec![
tonic::Code::Unavailable,
tonic::Code::DeadlineExceeded,
])
}
}
impl RetryPolicy {
pub fn none() -> Self {
Self {
max_attempts: 1,
initial_backoff: Duration::from_millis(0),
backoff: Backoff::Fixed,
max_backoff: Duration::from_secs(1),
retryable: RetryableCodes::default(),
}
}
pub fn exponential(max_attempts: u32) -> Self {
Self {
max_attempts,
initial_backoff: Duration::from_millis(50),
backoff: Backoff::Exponential { multiplier: 2.0 },
max_backoff: Duration::from_secs(2),
retryable: RetryableCodes::default(),
}
}
pub fn fixed(max_attempts: u32, delay: Duration) -> Self {
Self {
max_attempts,
initial_backoff: delay,
backoff: Backoff::Fixed,
max_backoff: delay,
retryable: RetryableCodes::default(),
}
}
}
impl Default for RetryPolicy {
fn default() -> Self {
Self::none()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_no_retry() {
assert_eq!(RetryPolicy::default().max_attempts, 1);
}
#[test]
fn exponential_defaults_have_reasonable_values() {
let p = RetryPolicy::exponential(3);
assert_eq!(p.max_attempts, 3);
assert!(matches!(p.backoff, Backoff::Exponential { multiplier } if multiplier > 1.0));
}
}