actr_runtime/transport/
backoff.rs1use std::time::Duration;
7
8#[derive(Debug, Clone)]
30pub struct ExponentialBackoff {
31 current: Duration,
33 max: Duration,
35 multiplier: f64,
37 retries: u32,
39 max_retries: Option<u32>,
41}
42
43impl ExponentialBackoff {
44 pub fn new(initial: Duration, max: Duration, max_retries: Option<u32>) -> Self {
64 Self {
65 current: initial,
66 max,
67 multiplier: 2.0,
68 retries: 0,
69 max_retries,
70 }
71 }
72
73 pub fn with_multiplier(
81 initial: Duration,
82 max: Duration,
83 max_retries: Option<u32>,
84 multiplier: f64,
85 ) -> Self {
86 Self {
87 current: initial,
88 max,
89 multiplier,
90 retries: 0,
91 max_retries,
92 }
93 }
94
95 pub fn retry_count(&self) -> u32 {
97 self.retries
98 }
99
100 pub fn reset(&mut self) {
102 self.retries = 0;
103 self.current = Duration::from_millis(100); }
105}
106
107impl Iterator for ExponentialBackoff {
108 type Item = Duration;
109
110 fn next(&mut self) -> Option<Duration> {
111 if let Some(max_retries) = self.max_retries {
113 if self.retries >= max_retries {
114 return None;
115 }
116 }
117
118 let delay = self.current;
120
121 let next_millis = (self.current.as_millis() as f64 * self.multiplier) as u64;
123 let next_duration = Duration::from_millis(next_millis);
124
125 self.current = if next_duration > self.max {
127 self.max
128 } else {
129 next_duration
130 };
131
132 self.retries += 1;
133
134 Some(delay)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_exponential_backoff_basic() {
144 let mut backoff =
145 ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(2), Some(4));
146
147 assert_eq!(backoff.next(), Some(Duration::from_millis(100)));
148 assert_eq!(backoff.next(), Some(Duration::from_millis(200)));
149 assert_eq!(backoff.next(), Some(Duration::from_millis(400)));
150 assert_eq!(backoff.next(), Some(Duration::from_millis(800)));
151 assert_eq!(backoff.next(), None); }
153
154 #[test]
155 fn test_exponential_backoff_with_cap() {
156 let mut backoff = ExponentialBackoff::new(
157 Duration::from_millis(100),
158 Duration::from_millis(500),
159 Some(5),
160 );
161
162 assert_eq!(backoff.next(), Some(Duration::from_millis(100)));
163 assert_eq!(backoff.next(), Some(Duration::from_millis(200)));
164 assert_eq!(backoff.next(), Some(Duration::from_millis(400)));
165 assert_eq!(backoff.next(), Some(Duration::from_millis(500))); assert_eq!(backoff.next(), Some(Duration::from_millis(500))); assert_eq!(backoff.next(), None);
168 }
169
170 #[test]
171 fn test_exponential_backoff_unlimited() {
172 let mut backoff = ExponentialBackoff::new(
173 Duration::from_millis(50),
174 Duration::from_secs(10),
175 None, );
177
178 for i in 0..20 {
179 let delay = backoff.next();
180 assert!(delay.is_some(), "Retry {i} should succeed");
181 }
182 }
183
184 #[test]
185 fn test_custom_multiplier() {
186 let mut backoff = ExponentialBackoff::with_multiplier(
187 Duration::from_millis(100),
188 Duration::from_secs(10),
189 Some(3),
190 1.5, );
192
193 assert_eq!(backoff.next(), Some(Duration::from_millis(100)));
194 assert_eq!(backoff.next(), Some(Duration::from_millis(150)));
195 assert_eq!(backoff.next(), Some(Duration::from_millis(225)));
196 assert_eq!(backoff.next(), None);
197 }
198
199 #[test]
200 fn test_retry_count() {
201 let mut backoff =
202 ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(1), None);
203
204 assert_eq!(backoff.retry_count(), 0);
205 backoff.next();
206 assert_eq!(backoff.retry_count(), 1);
207 backoff.next();
208 assert_eq!(backoff.retry_count(), 2);
209 }
210}