truffle_core/session/
reconnect.rs1use std::time::{Duration, Instant};
4
5#[derive(Debug)]
8pub struct ReconnectBackoff {
9 attempt: u32,
10 base: Duration,
11 max: Duration,
12 last_attempt: Option<Instant>,
13}
14
15impl ReconnectBackoff {
16 pub fn new() -> Self {
19 Self {
20 attempt: 0,
21 base: Duration::from_millis(100),
22 max: Duration::from_secs(30),
23 last_attempt: None,
24 }
25 }
26
27 pub fn should_retry(&self) -> Option<Duration> {
32 if self.attempt == 0 {
33 return Some(Duration::ZERO);
34 }
35
36 let delay = self.current_delay();
37
38 match self.last_attempt {
39 Some(last) => {
40 let elapsed = last.elapsed();
41 if elapsed >= delay {
42 Some(Duration::ZERO)
43 } else {
44 None
46 }
47 }
48 None => Some(Duration::ZERO),
50 }
51 }
52
53 pub fn retry_after(&self) -> Duration {
56 if self.attempt == 0 {
57 return Duration::ZERO;
58 }
59
60 let delay = self.current_delay();
61
62 match self.last_attempt {
63 Some(last) => {
64 let elapsed = last.elapsed();
65 if elapsed >= delay {
66 Duration::ZERO
67 } else {
68 delay - elapsed
69 }
70 }
71 None => Duration::ZERO,
72 }
73 }
74
75 pub fn success(&mut self) {
77 self.attempt = 0;
78 self.last_attempt = None;
79 }
80
81 pub fn failure(&mut self) {
83 self.last_attempt = Some(Instant::now());
84 self.attempt = self.attempt.saturating_add(1);
85 }
86
87 fn current_delay(&self) -> Duration {
89 let shift = (self.attempt.saturating_sub(1)).min(31);
91 let delay_ms = self.base.as_millis() as u64 * (1u64 << shift);
92 let delay = Duration::from_millis(delay_ms);
93 delay.min(self.max)
94 }
95}
96
97impl Default for ReconnectBackoff {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_initial_state_allows_retry() {
109 let backoff = ReconnectBackoff::new();
110 assert!(backoff.should_retry().is_some());
111 assert_eq!(backoff.retry_after(), Duration::ZERO);
112 }
113
114 #[test]
115 fn test_failure_increases_delay() {
116 let mut backoff = ReconnectBackoff::new();
117
118 backoff.failure(); assert_eq!(backoff.current_delay(), Duration::from_millis(100));
120
121 backoff.last_attempt = Some(Instant::now() - Duration::from_secs(60));
122 backoff.failure(); assert_eq!(backoff.current_delay(), Duration::from_millis(200));
124
125 backoff.last_attempt = Some(Instant::now() - Duration::from_secs(60));
126 backoff.failure(); assert_eq!(backoff.current_delay(), Duration::from_millis(400));
128 }
129
130 #[test]
131 fn test_delay_caps_at_max() {
132 let mut backoff = ReconnectBackoff::new();
133 for _ in 0..30 {
134 backoff.last_attempt = Some(Instant::now() - Duration::from_secs(60));
135 backoff.failure();
136 }
137 assert!(backoff.current_delay() <= Duration::from_secs(30));
138 }
139
140 #[test]
141 fn test_success_resets() {
142 let mut backoff = ReconnectBackoff::new();
143 backoff.failure();
144 backoff.failure();
145 backoff.failure();
146
147 backoff.success();
148 assert_eq!(backoff.attempt, 0);
149 assert!(backoff.last_attempt.is_none());
150 assert!(backoff.should_retry().is_some());
151 }
152
153 #[test]
154 fn test_should_retry_blocks_during_backoff() {
155 let mut backoff = ReconnectBackoff::new();
156 backoff.failure(); assert!(backoff.should_retry().is_none());
160 }
161}