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 Error::Unsupported(_) => false,
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use crate::error::{ConnectionFailureReason, DeviceNotFoundReason};
384 use std::sync::Arc;
385 use std::sync::atomic::{AtomicU32, Ordering};
386
387 #[test]
388 fn test_retry_config_default() {
389 let config = RetryConfig::default();
390 assert_eq!(config.max_retries, 3);
391 assert!(config.jitter);
392 }
393
394 #[test]
395 fn test_retry_config_none() {
396 let config = RetryConfig::none();
397 assert_eq!(config.max_retries, 0);
398 }
399
400 #[test]
401 fn test_delay_calculation() {
402 let config = RetryConfig {
403 initial_delay: Duration::from_millis(100),
404 backoff_multiplier: 2.0,
405 max_delay: Duration::from_secs(10),
406 jitter: false,
407 max_retries: 5,
408 };
409
410 assert_eq!(config.delay_for_attempt(0), Duration::from_millis(100));
411 assert_eq!(config.delay_for_attempt(1), Duration::from_millis(200));
412 assert_eq!(config.delay_for_attempt(2), Duration::from_millis(400));
413 }
414
415 #[test]
416 fn test_is_retryable() {
417 assert!(is_retryable(&Error::Timeout {
418 operation: "test".to_string(),
419 duration: Duration::from_secs(1),
420 }));
421 assert!(is_retryable(&Error::ConnectionFailed {
422 device_id: None,
423 reason: ConnectionFailureReason::Other("test".to_string()),
424 }));
425 assert!(is_retryable(&Error::NotConnected));
426 assert!(!is_retryable(&Error::InvalidData("test".to_string())));
427 assert!(!is_retryable(&Error::DeviceNotFound(
428 DeviceNotFoundReason::NotFound {
429 identifier: "test".to_string()
430 }
431 )));
432 }
433
434 #[tokio::test]
435 async fn test_with_retry_immediate_success() {
436 let config = RetryConfig::new(3);
437 let result = with_retry(&config, "test", || async { Ok::<_, Error>(42) }).await;
438 assert_eq!(result.unwrap(), 42);
439 }
440
441 #[tokio::test]
442 async fn test_with_retry_eventual_success() {
443 let config = RetryConfig {
444 max_retries: 3,
445 initial_delay: Duration::from_millis(1),
446 jitter: false,
447 ..Default::default()
448 };
449
450 let attempts = Arc::new(AtomicU32::new(0));
451 let attempts_clone = Arc::clone(&attempts);
452
453 let result: Result<i32> = with_retry(&config, "test", || {
454 let attempts = Arc::clone(&attempts_clone);
455 async move {
456 let count = attempts.fetch_add(1, Ordering::SeqCst);
457 if count < 2 {
458 Err(Error::ConnectionFailed {
459 device_id: None,
460 reason: ConnectionFailureReason::Other("transient error".to_string()),
461 })
462 } else {
463 Ok(42)
464 }
465 }
466 })
467 .await;
468
469 assert_eq!(result.unwrap(), 42);
470 assert_eq!(attempts.load(Ordering::SeqCst), 3);
471 }
472
473 #[tokio::test]
474 async fn test_with_retry_all_fail() {
475 let config = RetryConfig {
476 max_retries: 2,
477 initial_delay: Duration::from_millis(1),
478 jitter: false,
479 ..Default::default()
480 };
481
482 let attempts = Arc::new(AtomicU32::new(0));
483 let attempts_clone = Arc::clone(&attempts);
484
485 let result: Result<i32> = with_retry(&config, "test", || {
486 let attempts = Arc::clone(&attempts_clone);
487 async move {
488 attempts.fetch_add(1, Ordering::SeqCst);
489 Err::<i32, _>(Error::ConnectionFailed {
490 device_id: None,
491 reason: ConnectionFailureReason::Other("persistent error".to_string()),
492 })
493 }
494 })
495 .await;
496
497 assert!(result.is_err());
498 assert_eq!(attempts.load(Ordering::SeqCst), 3); }
500
501 #[tokio::test]
502 async fn test_with_retry_non_retryable_error() {
503 let config = RetryConfig::new(3);
504 let attempts = Arc::new(AtomicU32::new(0));
505 let attempts_clone = Arc::clone(&attempts);
506
507 let result: Result<i32> = with_retry(&config, "test", || {
508 let attempts = Arc::clone(&attempts_clone);
509 async move {
510 attempts.fetch_add(1, Ordering::SeqCst);
511 Err::<i32, _>(Error::InvalidData("not retryable".to_string()))
512 }
513 })
514 .await;
515
516 assert!(result.is_err());
517 assert_eq!(attempts.load(Ordering::SeqCst), 1); }
519}