ironsbe_client/
reconnect.rs1use std::time::Duration;
4
5#[derive(Debug, Clone)]
7pub struct ReconnectConfig {
8 pub enabled: bool,
10 pub initial_delay: Duration,
12 pub max_delay: Duration,
14 pub backoff_multiplier: f64,
16 pub max_attempts: usize,
18}
19
20impl Default for ReconnectConfig {
21 fn default() -> Self {
22 Self {
23 enabled: true,
24 initial_delay: Duration::from_millis(100),
25 max_delay: Duration::from_secs(30),
26 backoff_multiplier: 2.0,
27 max_attempts: 10,
28 }
29 }
30}
31
32pub struct ReconnectState {
34 config: ReconnectConfig,
35 attempts: usize,
36 current_delay: Duration,
37}
38
39impl ReconnectState {
40 #[must_use]
42 pub fn new(config: ReconnectConfig) -> Self {
43 let initial_delay = config.initial_delay;
44 Self {
45 config,
46 attempts: 0,
47 current_delay: initial_delay,
48 }
49 }
50
51 pub fn on_failure(&mut self) -> Option<Duration> {
55 if !self.config.enabled {
56 return None;
57 }
58
59 self.attempts += 1;
60
61 if self.config.max_attempts > 0 && self.attempts >= self.config.max_attempts {
62 return None;
63 }
64
65 let delay = self.current_delay;
66
67 let next_delay = Duration::from_secs_f64(
69 self.current_delay.as_secs_f64() * self.config.backoff_multiplier,
70 );
71 self.current_delay = next_delay.min(self.config.max_delay);
72
73 Some(delay)
74 }
75
76 pub fn on_success(&mut self) {
78 self.attempts = 0;
79 self.current_delay = self.config.initial_delay;
80 }
81
82 #[must_use]
84 pub fn attempts(&self) -> usize {
85 self.attempts
86 }
87
88 #[must_use]
90 pub fn can_retry(&self) -> bool {
91 self.config.enabled
92 && (self.config.max_attempts == 0 || self.attempts < self.config.max_attempts)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_reconnect_backoff() {
102 let config = ReconnectConfig {
103 enabled: true,
104 initial_delay: Duration::from_millis(100),
105 max_delay: Duration::from_secs(10),
106 backoff_multiplier: 2.0,
107 max_attempts: 5,
108 };
109
110 let mut state = ReconnectState::new(config);
111
112 let delay = state.on_failure().unwrap();
114 assert_eq!(delay, Duration::from_millis(100));
115
116 let delay = state.on_failure().unwrap();
118 assert_eq!(delay, Duration::from_millis(200));
119
120 let delay = state.on_failure().unwrap();
122 assert_eq!(delay, Duration::from_millis(400));
123 }
124
125 #[test]
126 fn test_reconnect_max_attempts() {
127 let config = ReconnectConfig {
128 enabled: true,
129 initial_delay: Duration::from_millis(100),
130 max_delay: Duration::from_secs(10),
131 backoff_multiplier: 2.0,
132 max_attempts: 2,
133 };
134
135 let mut state = ReconnectState::new(config);
136
137 assert!(state.on_failure().is_some());
138 assert!(state.on_failure().is_none()); }
140
141 #[test]
142 fn test_reconnect_reset() {
143 let config = ReconnectConfig::default();
144 let mut state = ReconnectState::new(config);
145
146 state.on_failure();
147 state.on_failure();
148 assert_eq!(state.attempts(), 2);
149
150 state.on_success();
151 assert_eq!(state.attempts(), 0);
152 }
153
154 #[test]
155 fn test_reconnect_disabled() {
156 let config = ReconnectConfig {
157 enabled: false,
158 ..Default::default()
159 };
160
161 let mut state = ReconnectState::new(config);
162 assert!(state.on_failure().is_none());
163 }
164}