Skip to main content

kode_bridge/
retry.rs

1use crate::errors::KodeBridgeError;
2use rand::random_range;
3use std::time::{Duration, Instant};
4use tracing::{debug, warn};
5
6/// Type alias for complex retry function
7pub type RetryFn = Box<dyn Fn(&KodeBridgeError, usize) -> bool + Send + Sync>;
8
9/// Advanced retry configuration with adaptive strategies
10pub struct RetryConfig {
11    /// Maximum number of retry attempts
12    pub max_attempts: usize,
13    /// Base delay between retries
14    pub base_delay: Duration,
15    /// Maximum delay between retries (for exponential backoff)
16    pub max_delay: Duration,
17    /// Backoff strategy to use
18    pub backoff_strategy: BackoffStrategy,
19    /// Jitter strategy to avoid thundering herd
20    pub jitter_strategy: JitterStrategy,
21    /// Custom retry decision function (not cloneable, so we'll skip it in Clone)
22    pub should_retry_fn: Option<RetryFn>,
23}
24
25impl Clone for RetryConfig {
26    fn clone(&self) -> Self {
27        Self {
28            max_attempts: self.max_attempts,
29            base_delay: self.base_delay,
30            max_delay: self.max_delay,
31            backoff_strategy: self.backoff_strategy,
32            jitter_strategy: self.jitter_strategy,
33            should_retry_fn: None, // Skip cloning function pointer
34        }
35    }
36}
37
38#[derive(Debug, Clone, Copy)]
39pub enum BackoffStrategy {
40    /// Fixed delay between retries
41    Fixed,
42    /// Exponential backoff: delay *= multiplier
43    Exponential { multiplier: f64 },
44    /// Linear backoff: delay += increment
45    Linear { increment: Duration },
46}
47
48#[derive(Debug, Clone, Copy)]
49pub enum JitterStrategy {
50    /// No jitter
51    None,
52    /// Add random jitter up to 50% of delay
53    Full,
54    /// Add random jitter up to 25% of delay  
55    Partial,
56    /// Use decorrelated jitter for better distribution
57    Decorrelated,
58}
59
60impl Default for RetryConfig {
61    fn default() -> Self {
62        Self {
63            max_attempts: 3,
64            base_delay: Duration::from_millis(100),
65            max_delay: Duration::from_secs(30),
66            backoff_strategy: BackoffStrategy::Exponential { multiplier: 2.0 },
67            jitter_strategy: JitterStrategy::Partial,
68            should_retry_fn: None,
69        }
70    }
71}
72
73impl RetryConfig {
74    /// Create a new retry configuration
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Set maximum retry attempts
80    pub const fn max_attempts(mut self, max_attempts: usize) -> Self {
81        self.max_attempts = max_attempts;
82        self
83    }
84
85    /// Set base delay
86    pub const fn base_delay(mut self, delay: Duration) -> Self {
87        self.base_delay = delay;
88        self
89    }
90
91    /// Set maximum delay
92    pub const fn max_delay(mut self, delay: Duration) -> Self {
93        self.max_delay = delay;
94        self
95    }
96
97    /// Use exponential backoff strategy
98    pub const fn exponential_backoff(mut self, multiplier: f64) -> Self {
99        self.backoff_strategy = BackoffStrategy::Exponential { multiplier };
100        self
101    }
102
103    /// Use fixed backoff strategy
104    pub const fn fixed_backoff(mut self) -> Self {
105        self.backoff_strategy = BackoffStrategy::Fixed;
106        self
107    }
108
109    /// Use linear backoff strategy
110    pub const fn linear_backoff(mut self, increment: Duration) -> Self {
111        self.backoff_strategy = BackoffStrategy::Linear { increment };
112        self
113    }
114
115    /// Set jitter strategy
116    pub const fn jitter(mut self, strategy: JitterStrategy) -> Self {
117        self.jitter_strategy = strategy;
118        self
119    }
120
121    /// Set custom retry condition
122    pub fn should_retry<F>(mut self, f: F) -> Self
123    where
124        F: Fn(&KodeBridgeError, usize) -> bool + Send + Sync + 'static,
125    {
126        self.should_retry_fn = Some(Box::new(f));
127        self
128    }
129
130    /// Smart defaults for different scenarios
131    pub fn for_network_operations() -> Self {
132        Self::new()
133            .max_attempts(5)
134            .base_delay(Duration::from_millis(50))
135            .max_delay(Duration::from_secs(10))
136            .exponential_backoff(2.0)
137            .jitter(JitterStrategy::Full)
138    }
139
140    pub fn for_rate_limited_apis() -> Self {
141        Self::new()
142            .max_attempts(10)
143            .base_delay(Duration::from_secs(1))
144            .max_delay(Duration::from_secs(60))
145            .exponential_backoff(1.5)
146            .jitter(JitterStrategy::Decorrelated)
147    }
148
149    pub fn for_quick_operations() -> Self {
150        Self::new()
151            .max_attempts(2)
152            .base_delay(Duration::from_millis(10))
153            .max_delay(Duration::from_millis(100))
154            .fixed_backoff()
155            .jitter(JitterStrategy::None)
156    }
157
158    /// Optimized configuration for PUT requests
159    pub fn for_put_requests() -> Self {
160        Self::new()
161            .max_attempts(2) // 少重试,快速失败
162            .base_delay(Duration::from_millis(25))
163            .max_delay(Duration::from_millis(200))
164            .exponential_backoff(1.5) // 温和的退避
165            .jitter(JitterStrategy::Partial)
166    }
167
168    /// Configuration for large PUT requests
169    pub fn for_large_put_requests() -> Self {
170        Self::new()
171            .max_attempts(3) // 稍多重试,因为大请求更容易失败
172            .base_delay(Duration::from_millis(50))
173            .max_delay(Duration::from_millis(500))
174            .linear_backoff(Duration::from_millis(50))
175            .jitter(JitterStrategy::Partial)
176    }
177}
178
179/// Retry state tracking
180#[derive(Debug)]
181pub struct RetryState {
182    attempt: usize,
183    total_elapsed: Duration,
184    last_delay: Duration,
185}
186
187impl Default for RetryState {
188    fn default() -> Self {
189        Self {
190            attempt: 0,
191            total_elapsed: Duration::ZERO,
192            last_delay: Duration::ZERO,
193        }
194    }
195}
196
197impl RetryState {
198    pub fn new() -> Self {
199        Self::default()
200    }
201
202    pub const fn attempt(&self) -> usize {
203        self.attempt
204    }
205
206    pub const fn total_elapsed(&self) -> Duration {
207        self.total_elapsed
208    }
209
210    pub const fn last_delay(&self) -> Duration {
211        self.last_delay
212    }
213}
214
215/// Smart retry executor
216pub struct RetryExecutor {
217    config: RetryConfig,
218}
219
220impl RetryExecutor {
221    pub const fn new(config: RetryConfig) -> Self {
222        Self { config }
223    }
224
225    /// Execute operation with retry logic
226    pub async fn execute<F, Fut, T>(&self, mut operation: F) -> Result<T, KodeBridgeError>
227    where
228        F: FnMut() -> Fut + Send,
229        Fut: std::future::Future<Output = Result<T, KodeBridgeError>> + Send,
230        T: Send,
231    {
232        let mut state = RetryState::new();
233
234        loop {
235            state.attempt += 1;
236            let attempt_start = Instant::now();
237
238            debug!("Retry attempt {} starting", state.attempt);
239
240            match operation().await {
241                Ok(result) => {
242                    if state.attempt > 1 {
243                        debug!(
244                            "Operation succeeded on attempt {} after {}ms",
245                            state.attempt,
246                            state.total_elapsed.as_millis()
247                        );
248                    }
249                    return Ok(result);
250                }
251                Err(error) => {
252                    let attempt_duration = attempt_start.elapsed();
253                    state.total_elapsed += attempt_duration;
254
255                    // Check if we should retry this error
256                    let should_retry = if let Some(ref custom_fn) = self.config.should_retry_fn {
257                        custom_fn(&error, state.attempt)
258                    } else {
259                        self.default_should_retry(&error, state.attempt)
260                    };
261
262                    if !should_retry || state.attempt >= self.config.max_attempts {
263                        warn!(
264                            "Operation failed after {} attempts in {}ms: {}",
265                            state.attempt,
266                            state.total_elapsed.as_millis(),
267                            error
268                        );
269                        return Err(error);
270                    }
271
272                    // Calculate next delay
273                    let next_delay = self.calculate_delay(&mut state);
274
275                    debug!(
276                        "Retrying after {}ms (attempt {}/{}, error: {})",
277                        next_delay.as_millis(),
278                        state.attempt,
279                        self.config.max_attempts,
280                        error
281                    );
282
283                    tokio::time::sleep(next_delay).await;
284                }
285            }
286        }
287    }
288
289    /// Execute operation with context for better error reporting
290    pub async fn execute_with_context<F, Fut, T>(
291        &self,
292        operation_name: &str,
293        operation: F,
294    ) -> Result<T, KodeBridgeError>
295    where
296        F: FnMut() -> Fut + Send,
297        Fut: std::future::Future<Output = Result<T, KodeBridgeError>> + Send,
298        T: Send,
299    {
300        debug!("Starting retry execution for operation: {}", operation_name);
301
302        match self.execute(operation).await {
303            Ok(result) => {
304                debug!("Operation '{}' completed successfully", operation_name);
305                Ok(result)
306            }
307            Err(error) => {
308                warn!("Operation '{}' failed with error: {}", operation_name, error);
309                Err(KodeBridgeError::custom(format!(
310                    "Operation '{}' failed after retries: {}",
311                    operation_name, error
312                )))
313            }
314        }
315    }
316
317    /// Default retry logic based on error type
318    const fn default_should_retry(&self, error: &KodeBridgeError, attempt: usize) -> bool {
319        use KodeBridgeError::*;
320
321        match error {
322            // Always retry network-related errors
323            Io(_) | Connection { .. } | Timeout { .. } | StreamClosed => true,
324
325            // Retry server errors (5xx) but not client errors (4xx)
326            ServerError { status } => *status >= 500,
327            ClientError { .. } | InvalidRequest { .. } => false,
328
329            // Don't retry parsing or protocol errors
330            HttpParse(_) | Http(_) | Protocol { .. } => false,
331
332            // Don't retry configuration or validation errors
333            Configuration { .. } => false,
334
335            // Don't retry JSON errors (likely application issue)
336            Json(_) | JsonSerialize { .. } => false,
337
338            // Don't retry UTF-8 errors
339            Utf8(_) | FromUtf8(_) => false,
340
341            // Retry resource exhaustion with exponential backoff
342            PoolExhausted => attempt <= 5, // But limit attempts for pool exhaustion
343
344            // Custom errors - be conservative
345            Custom { .. } => false,
346
347            // HTTP status code errors need special handling
348            InvalidStatusCode(_) => false,
349        }
350    }
351
352    /// Calculate next retry delay with backoff and jitter
353    fn calculate_delay(&self, state: &mut RetryState) -> Duration {
354        let base_delay = match self.config.backoff_strategy {
355            BackoffStrategy::Fixed => self.config.base_delay,
356            BackoffStrategy::Exponential { multiplier } => {
357                if state.attempt == 1 {
358                    self.config.base_delay
359                } else {
360                    let exponential = (self.config.base_delay.as_millis() as f64
361                        * multiplier.powi((state.attempt - 1) as i32)) as u64;
362                    Duration::from_millis(exponential)
363                }
364            }
365            BackoffStrategy::Linear { increment } => self.config.base_delay + increment * (state.attempt as u32 - 1),
366        };
367
368        // Cap at maximum delay
369        let capped_delay = std::cmp::min(base_delay, self.config.max_delay);
370
371        // Apply jitter
372        let final_delay = match self.config.jitter_strategy {
373            JitterStrategy::None => capped_delay,
374            JitterStrategy::Full => {
375                let jitter = random_range(0..=capped_delay.as_millis() / 2) as u64;
376                capped_delay + Duration::from_millis(jitter)
377            }
378            JitterStrategy::Partial => {
379                let jitter = random_range(0..=capped_delay.as_millis() / 4) as u64;
380                capped_delay + Duration::from_millis(jitter)
381            }
382            JitterStrategy::Decorrelated => {
383                // Decorrelated jitter: next_delay = random_between(base_delay, last_delay * 3)
384                let min_delay = self.config.base_delay.as_millis() as u64;
385                let max_delay = std::cmp::min(
386                    (state.last_delay.as_millis() as u64 * 3).max(min_delay),
387                    self.config.max_delay.as_millis() as u64,
388                );
389                Duration::from_millis(random_range(min_delay..=max_delay))
390            }
391        };
392
393        state.last_delay = final_delay;
394        final_delay
395    }
396}
397
398/// Convenience function for simple retry operations
399pub async fn retry<F, Fut, T>(config: RetryConfig, operation: F) -> Result<T, KodeBridgeError>
400where
401    F: FnMut() -> Fut + Send,
402    Fut: std::future::Future<Output = Result<T, KodeBridgeError>> + Send,
403    T: Send,
404{
405    RetryExecutor::new(config).execute(operation).await
406}
407
408/// Convenience function with default configuration
409pub async fn retry_default<F, Fut, T>(operation: F) -> Result<T, KodeBridgeError>
410where
411    F: FnMut() -> Fut + Send,
412    Fut: std::future::Future<Output = Result<T, KodeBridgeError>> + Send,
413    T: Send,
414{
415    retry(RetryConfig::default(), operation).await
416}
417
418/// Circuit breaker pattern for failing services
419#[derive(Debug)]
420pub struct CircuitBreaker {
421    failure_threshold: usize,
422    recovery_timeout: Duration,
423    consecutive_failures: usize,
424    last_failure_time: Option<Instant>,
425    state: CircuitState,
426}
427
428#[derive(Debug, Clone, PartialEq)]
429enum CircuitState {
430    Closed,   // Normal operation
431    Open,     // Failing, reject requests
432    HalfOpen, // Testing if service recovered
433}
434
435impl CircuitBreaker {
436    pub const fn new(failure_threshold: usize, recovery_timeout: Duration) -> Self {
437        Self {
438            failure_threshold,
439            recovery_timeout,
440            consecutive_failures: 0,
441            last_failure_time: None,
442            state: CircuitState::Closed,
443        }
444    }
445
446    pub async fn execute<F, Fut, T>(&mut self, operation: F) -> Result<T, KodeBridgeError>
447    where
448        F: FnOnce() -> Fut + Send,
449        Fut: std::future::Future<Output = Result<T, KodeBridgeError>> + Send,
450        T: Send,
451    {
452        if self.state == CircuitState::Open {
453            if let Some(last_failure) = self.last_failure_time {
454                if last_failure.elapsed() >= self.recovery_timeout {
455                    debug!("Circuit breaker entering half-open state");
456                    self.state = CircuitState::HalfOpen;
457                } else {
458                    return Err(KodeBridgeError::custom("Circuit breaker is open"));
459                }
460            } else {
461                return Err(KodeBridgeError::custom("Circuit breaker is open"));
462            }
463        }
464
465        match operation().await {
466            Ok(result) => {
467                // Success - reset circuit breaker
468                if self.state == CircuitState::HalfOpen {
469                    debug!("Circuit breaker closing after successful operation");
470                }
471                self.consecutive_failures = 0;
472                self.last_failure_time = None;
473                self.state = CircuitState::Closed;
474                Ok(result)
475            }
476            Err(error) => {
477                // Failure - update circuit breaker state
478                self.consecutive_failures += 1;
479                self.last_failure_time = Some(Instant::now());
480
481                if self.consecutive_failures >= self.failure_threshold {
482                    debug!(
483                        "Circuit breaker opening after {} consecutive failures",
484                        self.consecutive_failures
485                    );
486                    self.state = CircuitState::Open;
487                }
488
489                Err(error)
490            }
491        }
492    }
493
494    pub const fn is_open(&self) -> bool {
495        matches!(self.state, CircuitState::Open)
496    }
497
498    pub const fn reset(&mut self) {
499        self.consecutive_failures = 0;
500        self.last_failure_time = None;
501        self.state = CircuitState::Closed;
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use std::sync::atomic::{AtomicUsize, Ordering};
509    use std::sync::Arc;
510
511    #[tokio::test]
512    async fn test_retry_success_on_first_attempt() {
513        let config = RetryConfig::new().max_attempts(3);
514        let executor = RetryExecutor::new(config);
515
516        let result = executor
517            .execute(|| async { Ok::<i32, KodeBridgeError>(42) })
518            .await;
519
520        assert_eq!(result.unwrap(), 42);
521    }
522
523    #[tokio::test]
524    async fn test_retry_success_after_failures() {
525        let config = RetryConfig::new()
526            .max_attempts(3)
527            .base_delay(Duration::from_millis(1));
528        let executor = RetryExecutor::new(config);
529        let attempt_count = Arc::new(AtomicUsize::new(0));
530
531        let result = executor
532            .execute(|| {
533                let count = Arc::clone(&attempt_count);
534                async move {
535                    let current = count.fetch_add(1, Ordering::SeqCst);
536                    if current < 2 {
537                        Err(KodeBridgeError::connection("Temporary failure"))
538                    } else {
539                        Ok(42)
540                    }
541                }
542            })
543            .await;
544
545        assert_eq!(result.unwrap(), 42);
546        assert_eq!(attempt_count.load(Ordering::SeqCst), 3);
547    }
548
549    #[tokio::test]
550    async fn test_retry_max_attempts_exceeded() {
551        let config = RetryConfig::new()
552            .max_attempts(2)
553            .base_delay(Duration::from_millis(1));
554        let executor = RetryExecutor::new(config);
555        let attempt_count = Arc::new(AtomicUsize::new(0));
556
557        let result = executor
558            .execute(|| {
559                let count = Arc::clone(&attempt_count);
560                async move {
561                    count.fetch_add(1, Ordering::SeqCst);
562                    Err::<i32, _>(KodeBridgeError::connection("Always fails"))
563                }
564            })
565            .await;
566
567        assert!(result.is_err());
568        assert_eq!(attempt_count.load(Ordering::SeqCst), 2);
569    }
570
571    #[tokio::test]
572    async fn test_retry_non_retriable_error() {
573        let config = RetryConfig::new()
574            .max_attempts(3)
575            .base_delay(Duration::from_millis(1));
576        let executor = RetryExecutor::new(config);
577        let attempt_count = Arc::new(AtomicUsize::new(0));
578
579        let result = executor
580            .execute(|| {
581                let count = Arc::clone(&attempt_count);
582                async move {
583                    count.fetch_add(1, Ordering::SeqCst);
584                    Err::<i32, _>(KodeBridgeError::ClientError { status: 400 })
585                }
586            })
587            .await;
588
589        assert!(result.is_err());
590        assert_eq!(attempt_count.load(Ordering::SeqCst), 1); // No retry for client error
591    }
592
593    #[tokio::test]
594    async fn test_circuit_breaker() {
595        let mut breaker = CircuitBreaker::new(2, Duration::from_millis(100));
596
597        // First failure
598        let result = breaker
599            .execute(|| async { Err::<i32, _>(KodeBridgeError::connection("Failure 1")) })
600            .await;
601        assert!(result.is_err());
602        assert!(!breaker.is_open());
603
604        // Second failure - should open circuit
605        let result = breaker
606            .execute(|| async { Err::<i32, _>(KodeBridgeError::connection("Failure 2")) })
607            .await;
608        assert!(result.is_err());
609        assert!(breaker.is_open());
610
611        // Third attempt should be rejected immediately
612        let result = breaker
613            .execute(|| async { Ok::<i32, KodeBridgeError>(42) })
614            .await;
615        assert!(result.is_err());
616        assert!(result
617            .unwrap_err()
618            .to_string()
619            .contains("Circuit breaker is open"));
620    }
621
622    #[test]
623    fn test_backoff_strategies() {
624        let mut state = RetryState::new();
625
626        // Test exponential backoff
627        let config = RetryConfig::new()
628            .exponential_backoff(2.0)
629            .base_delay(Duration::from_millis(100))
630            .jitter(JitterStrategy::None);
631        let executor = RetryExecutor::new(config);
632
633        state.attempt = 1;
634        let delay1 = executor.calculate_delay(&mut state);
635        assert_eq!(delay1, Duration::from_millis(100));
636
637        state.attempt = 2;
638        let delay2 = executor.calculate_delay(&mut state);
639        assert_eq!(delay2, Duration::from_millis(200));
640
641        state.attempt = 3;
642        let delay3 = executor.calculate_delay(&mut state);
643        assert_eq!(delay3, Duration::from_millis(400));
644    }
645
646    #[test]
647    fn test_retry_config_builder() {
648        let config = RetryConfig::for_network_operations();
649        assert_eq!(config.max_attempts, 5);
650        assert_eq!(config.base_delay, Duration::from_millis(50));
651
652        let config = RetryConfig::for_rate_limited_apis();
653        assert_eq!(config.max_attempts, 10);
654        assert_eq!(config.base_delay, Duration::from_secs(1));
655    }
656}