1use std::time::Duration;
6
7use tokio::time::sleep;
8
9#[derive(Debug, Clone)]
11pub struct RetryConfig {
12 pub max_retries: u32,
14 pub initial_delay: Duration,
16 pub max_delay: Duration,
18 pub backoff_multiplier: f64,
20}
21
22impl Default for RetryConfig {
23 fn default() -> Self {
24 Self {
25 max_retries: 3,
26 initial_delay: Duration::from_millis(100),
27 max_delay: Duration::from_secs(10),
28 backoff_multiplier: 2.0,
29 }
30 }
31}
32
33pub fn default_local_retry_config() -> RetryConfig {
35 RetryConfig::new(2, Duration::from_millis(10), Duration::from_millis(500))
36}
37
38pub fn default_remote_retry_config() -> RetryConfig {
40 RetryConfig::new(3, Duration::from_millis(50), Duration::from_secs(5))
41}
42
43impl RetryConfig {
44 pub fn new(max_retries: u32, initial_delay: Duration, max_delay: Duration) -> Self {
45 Self {
46 max_retries,
47 initial_delay,
48 max_delay,
49 backoff_multiplier: 2.0,
50 }
51 }
52
53 pub fn with_max_retries(mut self, max_retries: u32) -> Self {
54 self.max_retries = max_retries;
55 self
56 }
57
58 pub fn with_initial_delay(mut self, initial_delay: Duration) -> Self {
59 self.initial_delay = initial_delay;
60 self
61 }
62
63 pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
64 self.max_delay = max_delay;
65 self
66 }
67
68 pub fn with_backoff_multiplier(mut self, multiplier: f64) -> Self {
69 self.backoff_multiplier = multiplier;
70 self
71 }
72}
73
74#[derive(Debug)]
76pub enum RetryResult<T, E> {
77 Success(T),
79 Failed(E),
81 DoNotRetry(E),
83}
84
85pub async fn with_retry<F, Fut, T, E>(func: F, config: &RetryConfig) -> Result<T, E>
87where
88 F: Fn() -> Fut,
89 Fut: std::future::Future<Output = Result<T, E>>,
90 E: std::fmt::Debug,
91{
92 let mut delay = config.initial_delay;
93 let mut last_error: Option<E> = None;
94
95 for attempt in 0..=config.max_retries {
96 match func().await {
97 Ok(result) => return Ok(result),
98 Err(e) => {
99 last_error = Some(e);
100
101 if attempt < config.max_retries {
102 log::warn!(
103 "Operation failed (attempt {}), retrying in {:?}...",
104 attempt + 1,
105 delay
106 );
107 sleep(delay).await;
108
109 delay =
111 std::cmp::min(delay.mul_f64(config.backoff_multiplier), config.max_delay);
112 }
113 }
114 }
115 }
116
117 Err(last_error.unwrap())
118}
119
120pub async fn with_retry_and_handler<F, Fut, T, E, H>(
122 func: F,
123 config: &RetryConfig,
124 error_handler: H,
125) -> Result<T, E>
126where
127 F: Fn() -> Fut,
128 Fut: std::future::Future<Output = Result<T, E>>,
129 E: std::fmt::Debug + Clone,
130 H: Fn(&E, u32) -> bool, {
132 let mut delay = config.initial_delay;
133 let mut last_error: Option<E> = None;
134
135 for attempt in 0..=config.max_retries {
136 match func().await {
137 Ok(result) => return Ok(result),
138 Err(e) => {
139 if !error_handler(&e, attempt + 1) {
141 return Err(e);
142 }
143
144 last_error = Some(e);
145
146 if attempt < config.max_retries {
147 log::warn!(
148 "Operation failed (attempt {}), retrying in {:?}...",
149 attempt + 1,
150 delay
151 );
152 sleep(delay).await;
153
154 delay =
156 std::cmp::min(delay.mul_f64(config.backoff_multiplier), config.max_delay);
157 }
158 }
159 }
160 }
161
162 Err(last_error.unwrap())
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[tokio::test]
170 async fn test_retry_success_on_first_attempt() {
171 let config = RetryConfig::default();
172 let attempts = std::sync::Arc::new(tokio::sync::Mutex::new(0));
173 let attempts_clone = attempts.clone();
174
175 let result = with_retry(
176 move || {
177 let attempts = attempts_clone.clone();
178 async move {
179 let mut count = attempts.lock().await;
180 *count += 1;
181 Ok::<_, String>("success".to_string())
182 }
183 },
184 &config,
185 )
186 .await;
187
188 assert!(result.is_ok());
189 assert_eq!(*attempts.lock().await, 1);
190 }
191
192 #[tokio::test]
193 async fn test_retry_eventual_success() {
194 let config = RetryConfig::default();
195 let attempts = std::sync::Arc::new(tokio::sync::Mutex::new(0));
196 let attempts_clone = attempts.clone();
197
198 let result = with_retry(
199 move || {
200 let attempts = attempts_clone.clone();
201 async move {
202 let mut count = attempts.lock().await;
203 *count += 1;
204 if *count < 3 {
205 Err::<String, _>("temporary error".to_string())
206 } else {
207 Ok::<_, String>("success".to_string())
208 }
209 }
210 },
211 &config,
212 )
213 .await;
214
215 assert!(result.is_ok());
216 assert_eq!(*attempts.lock().await, 3);
217 }
218
219 #[tokio::test]
220 async fn test_retry_exhausted() {
221 let config = RetryConfig::default().with_max_retries(2);
222 let attempts = std::sync::Arc::new(tokio::sync::Mutex::new(0));
223 let attempts_clone = attempts.clone();
224
225 let result = with_retry(
226 move || {
227 let attempts = attempts_clone.clone();
228 async move {
229 let mut count = attempts.lock().await;
230 *count += 1;
231 Err::<String, _>("persistent error".to_string())
232 }
233 },
234 &config,
235 )
236 .await;
237
238 assert!(result.is_err());
239 assert_eq!(*attempts.lock().await, 3); }
241}