canlink_hal/monitor/
reconnect.rs1use std::time::Duration;
4
5#[derive(Debug, Clone)]
23pub struct ReconnectConfig {
24 pub max_retries: u32,
28
29 pub retry_interval: Duration,
31
32 pub backoff_multiplier: f32,
37}
38
39impl Default for ReconnectConfig {
40 fn default() -> Self {
41 Self {
42 max_retries: 3,
43 retry_interval: Duration::from_secs(1),
44 backoff_multiplier: 2.0,
45 }
46 }
47}
48
49impl ReconnectConfig {
50 #[must_use]
52 pub fn new() -> Self {
53 Self::default()
54 }
55
56 #[must_use]
58 pub fn fixed_interval(max_retries: u32, interval: Duration) -> Self {
59 Self {
60 max_retries,
61 retry_interval: interval,
62 backoff_multiplier: 1.0,
63 }
64 }
65
66 #[must_use]
68 pub fn exponential_backoff(
69 max_retries: u32,
70 initial_interval: Duration,
71 multiplier: f32,
72 ) -> Self {
73 Self {
74 max_retries,
75 retry_interval: initial_interval,
76 backoff_multiplier: multiplier,
77 }
78 }
79
80 #[must_use]
82 #[allow(clippy::cast_possible_wrap)]
83 pub fn interval_for_attempt(&self, attempt: u32) -> Duration {
84 if attempt == 0 {
85 return self.retry_interval;
86 }
87
88 let multiplier = self.backoff_multiplier.powi(attempt as i32);
89 Duration::from_secs_f32(self.retry_interval.as_secs_f32() * multiplier)
90 }
91
92 #[must_use]
94 pub fn should_retry(&self, current_attempt: u32) -> bool {
95 self.max_retries == 0 || current_attempt < self.max_retries
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_default_config() {
105 let config = ReconnectConfig::default();
106 assert_eq!(config.max_retries, 3);
107 assert_eq!(config.retry_interval, Duration::from_secs(1));
108 assert!((config.backoff_multiplier - 2.0).abs() < f32::EPSILON);
109 }
110
111 #[test]
112 fn test_fixed_interval() {
113 let config = ReconnectConfig::fixed_interval(5, Duration::from_millis(500));
114 assert_eq!(config.max_retries, 5);
115 assert!((config.backoff_multiplier - 1.0).abs() < f32::EPSILON);
116
117 assert_eq!(
119 config.interval_for_attempt(0),
120 config.interval_for_attempt(3)
121 );
122 }
123
124 #[test]
125 fn test_exponential_backoff() {
126 let config = ReconnectConfig::exponential_backoff(5, Duration::from_secs(1), 2.0);
127
128 assert_eq!(config.interval_for_attempt(0), Duration::from_secs(1));
129 assert_eq!(config.interval_for_attempt(1), Duration::from_secs(2));
130 assert_eq!(config.interval_for_attempt(2), Duration::from_secs(4));
131 }
132
133 #[test]
134 fn test_should_retry() {
135 let config = ReconnectConfig {
136 max_retries: 3,
137 ..Default::default()
138 };
139
140 assert!(config.should_retry(0));
141 assert!(config.should_retry(2));
142 assert!(!config.should_retry(3));
143 }
144
145 #[test]
146 fn test_unlimited_retries() {
147 let config = ReconnectConfig {
148 max_retries: 0, ..Default::default()
150 };
151
152 assert!(config.should_retry(100));
153 assert!(config.should_retry(1000));
154 }
155}