telemetry_kit/sync/
retry.rs

1//! Retry strategy with exponential backoff
2
3use rand::Rng;
4use std::time::Duration;
5
6/// Retry strategy for sync operations
7#[derive(Debug, Clone)]
8pub struct RetryStrategy {
9    max_retries: u32,
10    base_delay_ms: u64,
11}
12
13impl RetryStrategy {
14    /// Create a new retry strategy
15    ///
16    /// # Arguments
17    /// * `max_retries` - Maximum number of retry attempts
18    /// * `base_delay_ms` - Base delay in milliseconds (default: 1000)
19    pub fn new(max_retries: u32, base_delay_ms: u64) -> Self {
20        Self {
21            max_retries,
22            base_delay_ms,
23        }
24    }
25
26    /// Create default retry strategy (5 retries, 1 second base delay)
27    pub fn default_strategy() -> Self {
28        Self::new(5, 1000)
29    }
30
31    /// Calculate delay for a given retry attempt
32    ///
33    /// Uses exponential backoff with jitter:
34    /// `delay = base_delay * (2 ^ retry_count) + jitter`
35    ///
36    /// # Arguments
37    /// * `retry_count` - Current retry attempt (0-indexed)
38    ///
39    /// # Returns
40    /// Duration to wait before retrying
41    pub fn delay_for(&self, retry_count: u32) -> Duration {
42        let exponential_delay = self.base_delay_ms * 2_u64.pow(retry_count);
43        let jitter = rand::thread_rng().gen_range(0..1000); // 0-1000ms jitter
44
45        Duration::from_millis(exponential_delay + jitter)
46    }
47
48    /// Check if we should retry
49    pub fn should_retry(&self, retry_count: u32) -> bool {
50        retry_count < self.max_retries
51    }
52
53    /// Get maximum retries
54    pub fn max_retries(&self) -> u32 {
55        self.max_retries
56    }
57}
58
59impl Default for RetryStrategy {
60    fn default() -> Self {
61        Self::default_strategy()
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_retry_delay() {
71        let strategy = RetryStrategy::new(5, 1000);
72
73        // First retry: ~1-2 seconds (1000ms base + jitter)
74        let delay0 = strategy.delay_for(0);
75        assert!(delay0.as_millis() >= 1000 && delay0.as_millis() < 2000);
76
77        // Second retry: ~2-3 seconds (2000ms base + jitter)
78        let delay1 = strategy.delay_for(1);
79        assert!(delay1.as_millis() >= 2000 && delay1.as_millis() < 3000);
80
81        // Third retry: ~4-5 seconds (4000ms base + jitter)
82        let delay2 = strategy.delay_for(2);
83        assert!(delay2.as_millis() >= 4000 && delay2.as_millis() < 5000);
84    }
85
86    #[test]
87    fn test_should_retry() {
88        let strategy = RetryStrategy::new(3, 1000);
89
90        assert!(strategy.should_retry(0));
91        assert!(strategy.should_retry(1));
92        assert!(strategy.should_retry(2));
93        assert!(!strategy.should_retry(3));
94        assert!(!strategy.should_retry(4));
95    }
96
97    #[test]
98    fn test_default_strategy() {
99        let strategy = RetryStrategy::default();
100        assert_eq!(strategy.max_retries(), 5);
101    }
102}