1use std::future::Future;
28use std::time::Duration;
29
30use rand::Rng;
31use tokio::time::sleep;
32use tracing::{debug, warn};
33
34use crate::error::{Error, Result};
35
36#[derive(Debug, Clone)]
38pub struct RetryConfig {
39 pub max_retries: u32,
41 pub initial_delay: Duration,
43 pub max_delay: Duration,
45 pub backoff_multiplier: f64,
47 pub jitter: bool,
49}
50
51impl Default for RetryConfig {
52 fn default() -> Self {
53 Self {
54 max_retries: 3,
55 initial_delay: Duration::from_millis(100),
56 max_delay: Duration::from_secs(5),
57 backoff_multiplier: 2.0,
58 jitter: true,
59 }
60 }
61}
62
63impl RetryConfig {
64 pub fn new(max_retries: u32) -> Self {
66 Self {
67 max_retries,
68 ..Default::default()
69 }
70 }
71
72 pub fn none() -> Self {
74 Self {
75 max_retries: 0,
76 ..Default::default()
77 }
78 }
79
80 pub fn aggressive() -> Self {
82 Self {
83 max_retries: 5,
84 initial_delay: Duration::from_millis(50),
85 max_delay: Duration::from_secs(10),
86 backoff_multiplier: 1.5,
87 jitter: true,
88 }
89 }
90
91 pub fn for_scan() -> Self {
111 Self {
112 max_retries: 5,
113 initial_delay: Duration::from_millis(200),
114 max_delay: Duration::from_secs(2),
115 backoff_multiplier: 1.5,
116 jitter: true,
117 }
118 }
119
120 pub fn for_connect() -> Self {
129 Self {
130 max_retries: 3,
131 initial_delay: Duration::from_secs(1),
132 max_delay: Duration::from_secs(10),
133 backoff_multiplier: 2.0,
134 jitter: true,
135 }
136 }
137
138 pub fn for_read() -> Self {
147 Self {
148 max_retries: 3,
149 initial_delay: Duration::from_millis(100),
150 max_delay: Duration::from_secs(2),
151 backoff_multiplier: 2.0,
152 jitter: true,
153 }
154 }
155
156 pub fn for_write() -> Self {
165 Self {
166 max_retries: 2,
167 initial_delay: Duration::from_millis(200),
168 max_delay: Duration::from_secs(3),
169 backoff_multiplier: 2.0,
170 jitter: true,
171 }
172 }
173
174 pub fn for_history() -> Self {
184 Self {
185 max_retries: 5,
186 initial_delay: Duration::from_millis(500),
187 max_delay: Duration::from_secs(15),
188 backoff_multiplier: 2.0,
189 jitter: true,
190 }
191 }
192
193 pub fn for_reconnect() -> Self {
202 Self {
203 max_retries: 5,
204 initial_delay: Duration::from_secs(2),
205 max_delay: Duration::from_secs(30),
206 backoff_multiplier: 2.0,
207 jitter: true,
208 }
209 }
210
211 pub fn quick() -> Self {
216 Self {
217 max_retries: 2,
218 initial_delay: Duration::from_millis(50),
219 max_delay: Duration::from_millis(500),
220 backoff_multiplier: 2.0,
221 jitter: false,
222 }
223 }
224
225 #[must_use]
229 pub fn max_retries(mut self, retries: u32) -> Self {
230 self.max_retries = retries;
231 self
232 }
233
234 #[must_use]
236 pub fn initial_delay(mut self, delay: Duration) -> Self {
237 self.initial_delay = delay;
238 self
239 }
240
241 #[must_use]
243 pub fn max_delay(mut self, delay: Duration) -> Self {
244 self.max_delay = delay;
245 self
246 }
247
248 #[must_use]
250 pub fn backoff_multiplier(mut self, multiplier: f64) -> Self {
251 self.backoff_multiplier = multiplier;
252 self
253 }
254
255 #[must_use]
257 pub fn jitter(mut self, enabled: bool) -> Self {
258 self.jitter = enabled;
259 self
260 }
261
262 fn delay_for_attempt(&self, attempt: u32) -> Duration {
264 let base_delay =
265 self.initial_delay.as_secs_f64() * self.backoff_multiplier.powi(attempt as i32);
266 let capped_delay = base_delay.min(self.max_delay.as_secs_f64());
267
268 let final_delay = if self.jitter {
269 let jitter_factor = 1.0 + (rand::rng().random::<f64>() * 0.25);
271 capped_delay * jitter_factor
272 } else {
273 capped_delay
274 };
275
276 Duration::from_secs_f64(final_delay)
277 }
278}
279
280pub async fn with_retry<F, Fut, T>(
292 config: &RetryConfig,
293 operation_name: &str,
294 operation: F,
295) -> Result<T>
296where
297 F: Fn() -> Fut,
298 Fut: Future<Output = Result<T>>,
299{
300 let mut last_error = None;
301
302 for attempt in 0..=config.max_retries {
303 match operation().await {
304 Ok(result) => {
305 if attempt > 0 {
306 debug!("{} succeeded after {} retries", operation_name, attempt);
307 }
308 return Ok(result);
309 }
310 Err(e) => {
311 if !is_retryable(&e) {
312 return Err(e);
313 }
314
315 last_error = Some(e);
316
317 if attempt < config.max_retries {
318 let delay = config.delay_for_attempt(attempt);
319 warn!(
320 "{} failed (attempt {}/{}), retrying in {:?}",
321 operation_name,
322 attempt + 1,
323 config.max_retries + 1,
324 delay
325 );
326 sleep(delay).await;
327 }
328 }
329 }
330 }
331
332 Err(last_error
333 .unwrap_or_else(|| Error::InvalidData("Operation failed with no error".to_string())))
334}
335
336fn is_retryable(error: &Error) -> bool {
338 use crate::error::ConnectionFailureReason;
339
340 match error {
341 Error::Timeout { .. } => true,
343 Error::Bluetooth(_) => true,
345 Error::ConnectionFailed { reason, .. } => {
347 matches!(
348 reason,
349 ConnectionFailureReason::OutOfRange
350 | ConnectionFailureReason::Timeout
351 | ConnectionFailureReason::BleError(_)
352 | ConnectionFailureReason::Other(_)
353 )
354 }
355 Error::NotConnected => true,
357 Error::WriteFailed { .. } => true,
359 Error::InvalidData(_) => false,
361 Error::InvalidHistoryData { .. } => false,
363 Error::InvalidReadingFormat { .. } => false,
365 Error::DeviceNotFound(_) => false,
367 Error::CharacteristicNotFound { .. } => false,
369 Error::Cancelled => false,
371 Error::Io(_) => true,
373 Error::InvalidConfig(_) => false,
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use crate::error::{ConnectionFailureReason, DeviceNotFoundReason};
382 use std::sync::Arc;
383 use std::sync::atomic::{AtomicU32, Ordering};
384
385 #[test]
386 fn test_retry_config_default() {
387 let config = RetryConfig::default();
388 assert_eq!(config.max_retries, 3);
389 assert!(config.jitter);
390 }
391
392 #[test]
393 fn test_retry_config_none() {
394 let config = RetryConfig::none();
395 assert_eq!(config.max_retries, 0);
396 }
397
398 #[test]
399 fn test_delay_calculation() {
400 let config = RetryConfig {
401 initial_delay: Duration::from_millis(100),
402 backoff_multiplier: 2.0,
403 max_delay: Duration::from_secs(10),
404 jitter: false,
405 max_retries: 5,
406 };
407
408 assert_eq!(config.delay_for_attempt(0), Duration::from_millis(100));
409 assert_eq!(config.delay_for_attempt(1), Duration::from_millis(200));
410 assert_eq!(config.delay_for_attempt(2), Duration::from_millis(400));
411 }
412
413 #[test]
414 fn test_is_retryable() {
415 assert!(is_retryable(&Error::Timeout {
416 operation: "test".to_string(),
417 duration: Duration::from_secs(1),
418 }));
419 assert!(is_retryable(&Error::ConnectionFailed {
420 device_id: None,
421 reason: ConnectionFailureReason::Other("test".to_string()),
422 }));
423 assert!(is_retryable(&Error::NotConnected));
424 assert!(!is_retryable(&Error::InvalidData("test".to_string())));
425 assert!(!is_retryable(&Error::DeviceNotFound(
426 DeviceNotFoundReason::NotFound {
427 identifier: "test".to_string()
428 }
429 )));
430 }
431
432 #[tokio::test]
433 async fn test_with_retry_immediate_success() {
434 let config = RetryConfig::new(3);
435 let result = with_retry(&config, "test", || async { Ok::<_, Error>(42) }).await;
436 assert_eq!(result.unwrap(), 42);
437 }
438
439 #[tokio::test]
440 async fn test_with_retry_eventual_success() {
441 let config = RetryConfig {
442 max_retries: 3,
443 initial_delay: Duration::from_millis(1),
444 jitter: false,
445 ..Default::default()
446 };
447
448 let attempts = Arc::new(AtomicU32::new(0));
449 let attempts_clone = Arc::clone(&attempts);
450
451 let result: Result<i32> = with_retry(&config, "test", || {
452 let attempts = Arc::clone(&attempts_clone);
453 async move {
454 let count = attempts.fetch_add(1, Ordering::SeqCst);
455 if count < 2 {
456 Err(Error::ConnectionFailed {
457 device_id: None,
458 reason: ConnectionFailureReason::Other("transient error".to_string()),
459 })
460 } else {
461 Ok(42)
462 }
463 }
464 })
465 .await;
466
467 assert_eq!(result.unwrap(), 42);
468 assert_eq!(attempts.load(Ordering::SeqCst), 3);
469 }
470
471 #[tokio::test]
472 async fn test_with_retry_all_fail() {
473 let config = RetryConfig {
474 max_retries: 2,
475 initial_delay: Duration::from_millis(1),
476 jitter: false,
477 ..Default::default()
478 };
479
480 let attempts = Arc::new(AtomicU32::new(0));
481 let attempts_clone = Arc::clone(&attempts);
482
483 let result: Result<i32> = with_retry(&config, "test", || {
484 let attempts = Arc::clone(&attempts_clone);
485 async move {
486 attempts.fetch_add(1, Ordering::SeqCst);
487 Err::<i32, _>(Error::ConnectionFailed {
488 device_id: None,
489 reason: ConnectionFailureReason::Other("persistent error".to_string()),
490 })
491 }
492 })
493 .await;
494
495 assert!(result.is_err());
496 assert_eq!(attempts.load(Ordering::SeqCst), 3); }
498
499 #[tokio::test]
500 async fn test_with_retry_non_retryable_error() {
501 let config = RetryConfig::new(3);
502 let attempts = Arc::new(AtomicU32::new(0));
503 let attempts_clone = Arc::clone(&attempts);
504
505 let result: Result<i32> = with_retry(&config, "test", || {
506 let attempts = Arc::clone(&attempts_clone);
507 async move {
508 attempts.fetch_add(1, Ordering::SeqCst);
509 Err::<i32, _>(Error::InvalidData("not retryable".to_string()))
510 }
511 })
512 .await;
513
514 assert!(result.is_err());
515 assert_eq!(attempts.load(Ordering::SeqCst), 1); }
517}