radion_sdk/realtime/
reconnect.rs1use std::time::Duration;
4
5#[derive(Debug, Clone, Copy)]
7pub struct ReconnectOptions {
8 pub initial_delay: Duration,
10 pub max_delay: Duration,
12 pub factor: f64,
14 pub jitter: f64,
16}
17
18impl Default for ReconnectOptions {
19 fn default() -> Self {
20 Self {
21 initial_delay: Duration::from_millis(500),
22 max_delay: Duration::from_secs(30),
23 factor: 2.0,
24 jitter: 0.2,
25 }
26 }
27}
28
29#[derive(Debug)]
32pub(crate) struct ReconnectPolicy {
33 options: ReconnectOptions,
34 attempt: u32,
35}
36
37impl ReconnectPolicy {
38 pub(crate) fn new(options: ReconnectOptions) -> Self {
39 Self {
40 options,
41 attempt: 0,
42 }
43 }
44
45 pub(crate) fn attempts(&self) -> u32 {
47 self.attempt
48 }
49
50 pub(crate) fn next_delay(&mut self) -> Duration {
52 let initial = self.options.initial_delay.as_secs_f64();
53 let max = self.options.max_delay.as_secs_f64();
54 let base = (initial * self.options.factor.powi(self.attempt as i32)).min(max);
55 self.attempt = self.attempt.saturating_add(1);
56 let spread = base * self.options.jitter;
58 let delay = base - spread / 2.0 + spread * self.pseudo_jitter();
59 Duration::from_secs_f64(delay.max(0.0))
60 }
61
62 pub(crate) fn reset(&mut self) {
64 self.attempt = 0;
65 }
66
67 fn pseudo_jitter(&self) -> f64 {
68 let x = (f64::from(self.attempt) * 12.9898).sin() * 43758.5453;
70 x - x.floor()
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn backoff_grows_and_caps_then_resets() {
80 let mut policy = ReconnectPolicy::new(ReconnectOptions::default());
81 let first = policy.next_delay();
82 let second = policy.next_delay();
83 assert!(second >= first, "delay should grow");
84 for _ in 0..20 {
85 let delay = policy.next_delay();
86 assert!(delay <= Duration::from_secs(35));
88 }
89 policy.reset();
90 assert_eq!(policy.attempts(), 0);
91 }
92}