1use crate::errors::KodeBridgeError;
2use rand::random_range;
3use std::time::{Duration, Instant};
4use tracing::{debug, warn};
5
6pub type RetryFn = Box<dyn Fn(&KodeBridgeError, usize) -> bool + Send + Sync>;
8
9pub struct RetryConfig {
11 pub max_attempts: usize,
13 pub base_delay: Duration,
15 pub max_delay: Duration,
17 pub backoff_strategy: BackoffStrategy,
19 pub jitter_strategy: JitterStrategy,
21 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, }
35 }
36}
37
38#[derive(Debug, Clone, Copy)]
39pub enum BackoffStrategy {
40 Fixed,
42 Exponential { multiplier: f64 },
44 Linear { increment: Duration },
46}
47
48#[derive(Debug, Clone, Copy)]
49pub enum JitterStrategy {
50 None,
52 Full,
54 Partial,
56 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 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub const fn max_attempts(mut self, max_attempts: usize) -> Self {
81 self.max_attempts = max_attempts;
82 self
83 }
84
85 pub const fn base_delay(mut self, delay: Duration) -> Self {
87 self.base_delay = delay;
88 self
89 }
90
91 pub const fn max_delay(mut self, delay: Duration) -> Self {
93 self.max_delay = delay;
94 self
95 }
96
97 pub const fn exponential_backoff(mut self, multiplier: f64) -> Self {
99 self.backoff_strategy = BackoffStrategy::Exponential { multiplier };
100 self
101 }
102
103 pub const fn fixed_backoff(mut self) -> Self {
105 self.backoff_strategy = BackoffStrategy::Fixed;
106 self
107 }
108
109 pub const fn linear_backoff(mut self, increment: Duration) -> Self {
111 self.backoff_strategy = BackoffStrategy::Linear { increment };
112 self
113 }
114
115 pub const fn jitter(mut self, strategy: JitterStrategy) -> Self {
117 self.jitter_strategy = strategy;
118 self
119 }
120
121 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 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 pub fn for_put_requests() -> Self {
160 Self::new()
161 .max_attempts(2) .base_delay(Duration::from_millis(25))
163 .max_delay(Duration::from_millis(200))
164 .exponential_backoff(1.5) .jitter(JitterStrategy::Partial)
166 }
167
168 pub fn for_large_put_requests() -> Self {
170 Self::new()
171 .max_attempts(3) .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#[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
215pub struct RetryExecutor {
217 config: RetryConfig,
218}
219
220impl RetryExecutor {
221 pub const fn new(config: RetryConfig) -> Self {
222 Self { config }
223 }
224
225 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 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 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 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 const fn default_should_retry(&self, error: &KodeBridgeError, attempt: usize) -> bool {
319 use KodeBridgeError::*;
320
321 match error {
322 Io(_) | Connection { .. } | Timeout { .. } | StreamClosed => true,
324
325 ServerError { status } => *status >= 500,
327 ClientError { .. } | InvalidRequest { .. } => false,
328
329 HttpParse(_) | Http(_) | Protocol { .. } => false,
331
332 Configuration { .. } => false,
334
335 Json(_) | JsonSerialize { .. } => false,
337
338 Utf8(_) | FromUtf8(_) => false,
340
341 PoolExhausted => attempt <= 5, Custom { .. } => false,
346
347 InvalidStatusCode(_) => false,
349 }
350 }
351
352 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 let capped_delay = std::cmp::min(base_delay, self.config.max_delay);
370
371 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 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
398pub 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
408pub 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#[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, Open, HalfOpen, }
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 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 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); }
592
593 #[tokio::test]
594 async fn test_circuit_breaker() {
595 let mut breaker = CircuitBreaker::new(2, Duration::from_millis(100));
596
597 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 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 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 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}