cloud_lite_core_rs/
retry.rs1use std::time::Duration;
4
5#[derive(Debug, Clone)]
7pub struct RetryConfig {
8 pub max_retries: u32,
10
11 pub initial_backoff: Duration,
13
14 pub max_backoff: Duration,
16
17 pub backoff_multiplier: f64,
19
20 pub jitter: bool,
22
23 pub retry_on_401: bool,
25}
26
27impl Default for RetryConfig {
28 fn default() -> Self {
29 Self {
30 max_retries: 3,
31 initial_backoff: Duration::from_secs(1),
32 max_backoff: Duration::from_secs(30),
33 backoff_multiplier: 2.0,
34 jitter: true,
35 retry_on_401: true,
36 }
37 }
38}
39
40impl RetryConfig {
41 pub fn compute_backoff(
52 &self,
53 current_backoff: Duration,
54 retry_after: Option<Duration>,
55 ) -> Duration {
56 if let Some(retry_after) = retry_after {
58 return retry_after;
59 }
60
61 let backoff = current_backoff.min(self.max_backoff);
63
64 if self.jitter {
66 self.add_jitter(backoff)
67 } else {
68 backoff
69 }
70 }
71
72 fn add_jitter(&self, duration: Duration) -> Duration {
73 use std::collections::hash_map::RandomState;
74 use std::hash::BuildHasher;
75
76 let millis = duration.as_millis() as f64;
78 let jitter_range = millis * 0.2;
79
80 let random =
82 (RandomState::new().hash_one(std::time::SystemTime::now()) % 100) as f64 / 100.0;
83
84 let jittered = millis + (jitter_range * (random * 2.0 - 1.0));
85 Duration::from_millis(jittered.max(0.0) as u64)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn jitter_stays_within_bounds() {
95 let config = RetryConfig::default();
96 let base = Duration::from_secs(10);
97
98 for _ in 0..100 {
99 let jittered = config.add_jitter(base);
100 let millis = jittered.as_millis();
101
102 assert!(millis >= 8000);
104 assert!(millis <= 12000);
105 }
106 }
107}