use std::str::FromStr;
use std::time::Duration;
use parse_display::{Display, FromStr};
use rand::RngExt;
use serde::{Deserialize, Serialize};
use super::retry_delay_duration_format::RetryDelayDurationFormat;
use crate::constants::DEFAULT_RETRY_DELAY;
#[derive(Debug, Clone, PartialEq, Display, FromStr, Serialize, Deserialize)]
pub enum RetryDelay {
#[display("none")]
None,
#[display("fixed({0})")]
Fixed(
#[display(with = RetryDelayDurationFormat)]
#[serde(with = "qubit_common::serde::duration_millis")]
Duration,
),
#[display("random({min}..={max})")]
Random {
#[display(with = RetryDelayDurationFormat)]
#[serde(with = "qubit_common::serde::duration_millis")]
min: Duration,
#[display(with = RetryDelayDurationFormat)]
#[serde(with = "qubit_common::serde::duration_millis")]
max: Duration,
},
#[display("exponential(initial={initial}, max={max}, multiplier={multiplier})")]
Exponential {
#[display(with = RetryDelayDurationFormat)]
#[serde(with = "qubit_common::serde::duration_millis")]
initial: Duration,
#[display(with = RetryDelayDurationFormat)]
#[serde(with = "qubit_common::serde::duration_millis")]
max: Duration,
multiplier: f64,
},
}
impl RetryDelay {
#[inline]
pub fn none() -> Self {
Self::None
}
#[inline]
pub fn fixed(delay: Duration) -> Self {
Self::Fixed(delay)
}
#[inline]
pub fn random(min: Duration, max: Duration) -> Self {
Self::Random { min, max }
}
#[inline]
pub fn exponential(initial: Duration, max: Duration, multiplier: f64) -> Self {
Self::Exponential {
initial,
max,
multiplier,
}
}
pub fn base_delay(&self, attempt: u32) -> Duration {
match self {
Self::None => Duration::ZERO,
Self::Fixed(delay) => *delay,
Self::Random { min, max } => {
if min >= max {
return *min;
}
let mut rng = rand::rng();
let min_nanos = Self::duration_to_nanos_u64(*min);
let max_nanos = Self::duration_to_nanos_u64(*max);
Duration::from_nanos(rng.random_range(min_nanos..=max_nanos))
}
Self::Exponential {
initial,
max,
multiplier,
} => Self::exponential_delay(*initial, *max, *multiplier, attempt),
}
}
fn duration_to_nanos_u64(duration: Duration) -> u64 {
duration.as_nanos().min(u64::MAX as u128) as u64
}
fn exponential_delay(
initial: Duration,
max: Duration,
multiplier: f64,
attempt: u32,
) -> Duration {
let power = attempt.saturating_sub(1);
let factor = multiplier.powi(power.min(i32::MAX as u32) as i32);
if !factor.is_finite() {
return max;
}
let secs = initial.as_secs_f64() * factor;
if !secs.is_finite() || secs >= max.as_secs_f64() {
return max;
}
Duration::try_from_secs_f64(secs).map_or(max, |delay| delay.min(max))
}
pub fn validate(&self) -> Result<(), String> {
match self {
Self::None => Ok(()),
Self::Fixed(delay) => {
if delay.is_zero() {
Err("fixed delay cannot be zero".to_string())
} else {
Ok(())
}
}
Self::Random { min, max } => {
if min.is_zero() {
Err("random delay minimum cannot be zero".to_string())
} else if min > max {
Err("random delay minimum cannot be greater than maximum".to_string())
} else {
Ok(())
}
}
Self::Exponential {
initial,
max,
multiplier,
} => {
if initial.is_zero() {
Err("exponential delay initial value cannot be zero".to_string())
} else if max < initial {
Err("exponential delay maximum cannot be smaller than initial".to_string())
} else if !multiplier.is_finite() || *multiplier <= 1.0 {
Err(
"exponential delay multiplier must be finite and greater than 1.0"
.to_string(),
)
} else {
Ok(())
}
}
}
}
}
impl Default for RetryDelay {
#[inline]
fn default() -> Self {
Self::from_str(DEFAULT_RETRY_DELAY)
.expect("DEFAULT_RETRY_DELAY must be a valid RetryDelay string")
}
}