use rand::Rng;
use std::time::Duration;
use paladin_core::platform::container::battalion::RetryPolicy;
pub fn calculate_retry_delay(policy: &RetryPolicy, attempt: u32) -> Duration {
let base_delay = policy.base_delay;
let delay = if policy.exponential_backoff {
let multiplier = 2u32.pow(attempt);
base_delay * multiplier
} else {
base_delay
};
let delay = delay.min(policy.max_delay);
if policy.jitter {
add_jitter(delay)
} else {
delay
}
}
fn add_jitter(duration: Duration) -> Duration {
let mut rng = rand::thread_rng();
let jitter_factor = rng.gen_range(0.5..=1.0);
duration.mul_f64(jitter_factor)
}
pub fn should_retry(policy: &RetryPolicy, attempt: u32) -> bool {
attempt < policy.max_attempts
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_calculate_retry_delay_linear() {
let policy = RetryPolicy {
exponential_backoff: false,
jitter: false,
base_delay: Duration::from_millis(100),
..Default::default()
};
assert_eq!(
calculate_retry_delay(&policy, 0),
Duration::from_millis(100)
);
assert_eq!(
calculate_retry_delay(&policy, 1),
Duration::from_millis(100)
);
assert_eq!(
calculate_retry_delay(&policy, 2),
Duration::from_millis(100)
);
}
#[test]
fn test_calculate_retry_delay_exponential() {
let policy = RetryPolicy {
exponential_backoff: true,
jitter: false,
base_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(10),
..Default::default()
};
assert_eq!(
calculate_retry_delay(&policy, 0),
Duration::from_millis(100)
); assert_eq!(
calculate_retry_delay(&policy, 1),
Duration::from_millis(200)
); assert_eq!(
calculate_retry_delay(&policy, 2),
Duration::from_millis(400)
); assert_eq!(
calculate_retry_delay(&policy, 3),
Duration::from_millis(800)
); }
#[test]
fn test_calculate_retry_delay_max_cap() {
let policy = RetryPolicy {
exponential_backoff: true,
jitter: false,
base_delay: Duration::from_millis(100),
max_delay: Duration::from_millis(500),
..Default::default()
};
assert_eq!(
calculate_retry_delay(&policy, 10),
Duration::from_millis(500)
);
}
#[test]
fn test_calculate_retry_delay_with_jitter() {
let policy = RetryPolicy {
exponential_backoff: false,
jitter: true,
base_delay: Duration::from_millis(100),
..Default::default()
};
let delay = calculate_retry_delay(&policy, 0);
assert!(delay >= Duration::from_millis(50));
assert!(delay <= Duration::from_millis(100));
}
#[test]
fn test_should_retry_within_limit() {
let policy = RetryPolicy::default();
assert!(should_retry(&policy, 0)); assert!(should_retry(&policy, 1)); assert!(should_retry(&policy, 2)); }
#[test]
fn test_should_retry_exceeds_limit() {
let policy = RetryPolicy::default();
assert!(!should_retry(&policy, 3)); assert!(!should_retry(&policy, 4)); }
#[test]
fn test_should_retry_custom_limit() {
let policy = RetryPolicy {
max_attempts: 5,
..Default::default()
};
assert!(should_retry(&policy, 4)); assert!(!should_retry(&policy, 5)); }
#[test]
fn test_add_jitter_range() {
let duration = Duration::from_millis(1000);
for _ in 0..10 {
let jittered = add_jitter(duration);
assert!(jittered >= Duration::from_millis(500)); assert!(jittered <= Duration::from_millis(1000)); }
}
#[test]
fn test_exponential_backoff_sequence() {
let policy = RetryPolicy {
exponential_backoff: true,
jitter: false,
base_delay: Duration::from_millis(10),
max_delay: Duration::from_secs(60),
..Default::default()
};
let delays: Vec<Duration> = (0..5).map(|i| calculate_retry_delay(&policy, i)).collect();
assert_eq!(delays[0], Duration::from_millis(10)); assert_eq!(delays[1], Duration::from_millis(20)); assert_eq!(delays[2], Duration::from_millis(40)); assert_eq!(delays[3], Duration::from_millis(80)); assert_eq!(delays[4], Duration::from_millis(160)); }
}