1#[cfg(feature = "server")]
8use std::time::Duration;
9
10#[cfg(feature = "server")]
12const BASE_BACKOFF: Duration = Duration::from_millis(200);
13
14#[cfg(feature = "server")]
16pub const MAX_RETRIES: u32 = 3;
17
18#[cfg(feature = "server")]
23pub fn backoff_for(attempt: u32, jitter_ratio: f64) -> Duration {
24 debug_assert!((0.0..=1.0).contains(&jitter_ratio));
25 let exp = u32::checked_shl(1, attempt).unwrap_or(1 << 30);
26 let base_ms = BASE_BACKOFF.as_millis() as u64;
27 let core = base_ms.saturating_mul(exp as u64);
28 let jitter = (base_ms as f64 * jitter_ratio) as u64;
29 Duration::from_millis(core.saturating_add(jitter))
30}
31
32#[cfg(all(test, feature = "server"))]
33mod tests {
34 use super::*;
35
36 #[test]
37 fn backoff_grows_exponentially_without_jitter() {
38 assert_eq!(backoff_for(0, 0.0), Duration::from_millis(200));
40 assert_eq!(backoff_for(1, 0.0), Duration::from_millis(400));
41 assert_eq!(backoff_for(2, 0.0), Duration::from_millis(800));
42 }
43
44 #[test]
45 fn backoff_includes_jitter_within_base_range() {
46 assert_eq!(backoff_for(0, 0.5), Duration::from_millis(300));
48 assert_eq!(backoff_for(1, 0.5), Duration::from_millis(500));
49 }
50
51 #[test]
52 fn backoff_clamps_large_attempt() {
53 let d = backoff_for(40, 0.0);
55 assert!(d.as_millis() > 0);
56 }
57}