use std::time::Duration;
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ExponentialBackoffConfig {
pub max_retries: u32,
pub t_wait: Duration,
pub backoff: f64,
pub t_wait_max: Option<Duration>,
pub backoff_max: Option<Duration>,
}
impl ExponentialBackoffConfig {
pub fn get_backoff_duration(&self, attempt: u32) -> Duration {
self.t_wait
.mul_f64(self.backoff.powi(attempt as i32))
.min(self.backoff_max.unwrap_or(Duration::MAX))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
#[test]
fn test_simple_deserialization() {
let raw = r#"{"max_retries": 3, "t_wait": {"secs": 5,"nanos": 0}, "backoff": 2}"#;
let expected_config = ExponentialBackoffConfig {
max_retries: 3,
t_wait: Duration::from_secs(5),
backoff: 2.0,
t_wait_max: None,
backoff_max: None,
};
let config: ExponentialBackoffConfig = serde_json::from_str(raw).unwrap();
assert_eq!(expected_config, config);
}
#[cfg(feature = "serde")]
#[test]
fn test_deserialization_with_optionals() {
let raw = r#"{
"max_retries": 3,
"t_wait": {"secs": 5,"nanos": 0},
"backoff": 2,
"t_wait_max": {"secs": 120,"nanos": 0},
"backoff_max": {"secs": 15,"nanos": 0}
}"#;
let expected_config = ExponentialBackoffConfig {
max_retries: 3,
t_wait: Duration::from_secs(5),
backoff: 2.0,
t_wait_max: Some(Duration::from_secs(120)),
backoff_max: Some(Duration::from_secs(15)),
};
let config: ExponentialBackoffConfig = serde_json::from_str(raw).unwrap();
assert_eq!(expected_config, config);
}
#[test]
fn test_backoff_duration() {
let backoff = ExponentialBackoffConfig {
max_retries: 3,
t_wait: Duration::from_secs(1),
backoff: 2.0,
t_wait_max: None,
backoff_max: None,
};
assert_eq!(Duration::from_secs(1), backoff.get_backoff_duration(0));
assert_eq!(Duration::from_secs(2), backoff.get_backoff_duration(1));
assert_eq!(Duration::from_secs(4), backoff.get_backoff_duration(2));
}
#[test]
fn test_linear_backoff_duration() {
let backoff = ExponentialBackoffConfig {
max_retries: 3,
t_wait: Duration::from_secs(2),
backoff: 1.0,
t_wait_max: None,
backoff_max: None,
};
assert_eq!(Duration::from_secs(2), backoff.get_backoff_duration(0));
assert_eq!(Duration::from_secs(2), backoff.get_backoff_duration(1));
assert_eq!(Duration::from_secs(2), backoff.get_backoff_duration(2));
}
#[test]
fn test_backoff_duration_exponent() {
let backoff = ExponentialBackoffConfig {
max_retries: 3,
t_wait: Duration::from_secs(1),
backoff: 3.0,
t_wait_max: None,
backoff_max: None,
};
assert_eq!(Duration::from_secs(1), backoff.get_backoff_duration(0));
assert_eq!(Duration::from_secs(3), backoff.get_backoff_duration(1));
assert_eq!(Duration::from_secs(9), backoff.get_backoff_duration(2));
}
#[test]
fn test_backoff_duration_max_wait() {
let backoff = ExponentialBackoffConfig {
max_retries: 3,
t_wait: Duration::from_secs(1),
backoff: 2.0,
t_wait_max: None,
backoff_max: Some(Duration::from_secs(3)),
};
assert_eq!(Duration::from_secs(3), backoff.get_backoff_duration(2));
}
}