titan_client/tcp/
reconnection.rs1use rand;
2use std::time::Duration;
3use tracing::info;
4
5use super::{tcp_client, tcp_client_blocking};
6
7#[derive(Debug, Clone)]
9pub struct ReconnectionConfig {
10 pub base_interval: Duration,
12 pub max_interval: Duration,
14 pub max_attempts: Option<u32>,
17 pub use_jitter: bool,
19}
20
21impl Default for ReconnectionConfig {
22 fn default() -> Self {
23 Self {
24 base_interval: Duration::from_secs(1),
25 max_interval: Duration::from_secs(60),
26 max_attempts: None,
27 use_jitter: true,
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct ReconnectionManager {
35 config: ReconnectionConfig,
36 current_attempt: u32,
37}
38
39impl ReconnectionManager {
40 pub fn new(config: ReconnectionConfig) -> Self {
42 Self {
43 config,
44 current_attempt: 0,
45 }
46 }
47
48 pub fn new_default() -> Self {
50 Self::new(ReconnectionConfig::default())
51 }
52
53 pub fn current_attempt(&self) -> u32 {
55 self.current_attempt
56 }
57
58 pub fn reset(&mut self) {
60 self.current_attempt = 0;
61 }
62
63 pub fn is_max_attempts_reached(&self) -> bool {
67 if let Some(max) = self.config.max_attempts {
68 self.current_attempt > max
69 } else {
70 false
71 }
72 }
73
74 pub fn next_delay(&mut self) -> Option<Duration> {
78 self.current_attempt += 1;
80
81 if let Some(max) = self.config.max_attempts {
83 if self.current_attempt > max {
84 return None;
85 }
86 }
87
88 let exponent = std::cmp::min(self.current_attempt, 10); let backoff_secs = std::cmp::min(
91 self.config.base_interval.as_secs() * (1 << exponent),
92 self.config.max_interval.as_secs(),
93 );
94
95 let final_secs = if self.config.use_jitter {
97 let jitter = rand::random::<u64>() % (backoff_secs / 4 + 1);
98 backoff_secs + jitter
99 } else {
100 backoff_secs
101 };
102
103 let wait_time = Duration::from_secs(final_secs);
104
105 info!(
106 "Reconnection attempt {}/{:?} scheduled in {:?}",
107 self.current_attempt, self.config.max_attempts, wait_time
108 );
109
110 Some(wait_time)
111 }
112
113 pub fn config(&self) -> &ReconnectionConfig {
115 &self.config
116 }
117
118 pub fn set_config(&mut self, config: ReconnectionConfig) {
120 self.config = config;
121 }
122}
123
124pub fn from_tcp_client_config(config: &tcp_client_blocking::TcpClientConfig) -> ReconnectionConfig {
126 ReconnectionConfig {
127 base_interval: config.base_reconnect_interval,
128 max_interval: config.max_reconnect_interval,
129 max_attempts: config.max_reconnect_attempts,
130 use_jitter: true,
131 }
132}
133
134pub fn from_async_reconnect_settings(settings: &tcp_client::Config) -> ReconnectionConfig {
136 ReconnectionConfig {
137 base_interval: settings.retry_delay,
138 max_interval: settings.retry_delay, max_attempts: settings.max_retries,
140 use_jitter: false, }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_exponential_backoff() {
150 let config = ReconnectionConfig {
151 base_interval: Duration::from_secs(1),
152 max_interval: Duration::from_secs(60),
153 max_attempts: Some(10),
154 use_jitter: false, };
156
157 let mut manager = ReconnectionManager::new(config);
158
159 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(2));
161
162 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(4));
164
165 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(8));
167
168 manager.reset();
170 assert_eq!(manager.current_attempt, 0);
171
172 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(2));
174 }
175
176 #[test]
177 fn test_max_attempts() {
178 let config = ReconnectionConfig {
179 base_interval: Duration::from_secs(1),
180 max_interval: Duration::from_secs(60),
181 max_attempts: Some(3),
182 use_jitter: false,
183 };
184
185 let mut manager = ReconnectionManager::new(config);
186
187 assert!(manager.next_delay().is_some()); assert!(manager.next_delay().is_some()); assert!(manager.next_delay().is_some()); assert!(manager.next_delay().is_none());
194 }
195
196 #[test]
197 fn test_unlimited_attempts() {
198 let config = ReconnectionConfig {
199 base_interval: Duration::from_secs(1),
200 max_interval: Duration::from_secs(60),
201 max_attempts: None,
202 use_jitter: false,
203 };
204
205 let mut manager = ReconnectionManager::new(config);
206
207 for _ in 0..100 {
209 assert!(manager.next_delay().is_some());
210 }
211 }
212
213 #[test]
214 fn test_max_interval() {
215 let config = ReconnectionConfig {
216 base_interval: Duration::from_secs(1),
217 max_interval: Duration::from_secs(8),
218 max_attempts: None,
219 use_jitter: false,
220 };
221
222 let mut manager = ReconnectionManager::new(config);
223
224 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(2));
226
227 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(4));
229
230 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(8));
232
233 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(8));
235
236 assert_eq!(manager.next_delay().unwrap(), Duration::from_secs(8));
238 }
239}