Skip to main content

retry/
retry.rs

1//! Retry Policy Example
2//!
3//! This example demonstrates the RetryPolicy with both fixed and exponential backoff strategies.
4//! It shows how retries work with simulated transient failures.
5
6use do_over::{error::DoOverError, policy::Policy, retry::RetryPolicy};
7use std::sync::atomic::{AtomicUsize, Ordering};
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11#[tokio::main]
12async fn main() {
13    println!("=== Do-Over Retry Policy Example ===\n");
14
15    // Scenario 1: Fixed backoff - fails twice, succeeds on third attempt
16    fixed_backoff_example().await;
17
18    println!("\n{}\n", "─".repeat(60));
19
20    // Scenario 2: Exponential backoff - show increasing delays
21    exponential_backoff_example().await;
22
23    println!("\n{}\n", "─".repeat(60));
24
25    // Scenario 3: Exhausts all retries
26    exhausted_retries_example().await;
27}
28
29async fn fixed_backoff_example() {
30    println!("📌 Scenario 1: Fixed Backoff (fails twice, succeeds on third)");
31    println!("   Configuration: max_retries=2, delay=100ms\n");
32
33    let policy = RetryPolicy::fixed(2, Duration::from_millis(100));
34    let attempt_count = Arc::new(AtomicUsize::new(0));
35    let start = Instant::now();
36
37    let result: Result<String, DoOverError<String>> = policy
38        .execute(|| {
39            let attempts = Arc::clone(&attempt_count);
40            async move {
41                let attempt = attempts.fetch_add(1, Ordering::SeqCst) + 1;
42                let elapsed = start.elapsed().as_millis();
43
44                if attempt < 3 {
45                    println!(
46                        "   [+{:>4}ms] Attempt {}: ❌ Simulated failure",
47                        elapsed, attempt
48                    );
49                    Err(DoOverError::Inner(format!("Transient error on attempt {}", attempt)))
50                } else {
51                    println!(
52                        "   [+{:>4}ms] Attempt {}: ✅ Success!",
53                        elapsed, attempt
54                    );
55                    Ok("Operation completed successfully".to_string())
56                }
57            }
58        })
59        .await;
60
61    let total_time = start.elapsed().as_millis();
62    println!("\n   Result: {:?}", result.unwrap());
63    println!("   Total time: {}ms (expected ~200ms for 2 retries)", total_time);
64}
65
66async fn exponential_backoff_example() {
67    println!("📌 Scenario 2: Exponential Backoff (show increasing delays)");
68    println!("   Configuration: max_retries=3, base=100ms, factor=2.0\n");
69
70    let policy = RetryPolicy::exponential(3, Duration::from_millis(100), 2.0);
71    let attempt_count = Arc::new(AtomicUsize::new(0));
72    let start = Instant::now();
73
74    let result: Result<String, DoOverError<String>> = policy
75        .execute(|| {
76            let attempts = Arc::clone(&attempt_count);
77            async move {
78                let attempt = attempts.fetch_add(1, Ordering::SeqCst) + 1;
79                let elapsed = start.elapsed().as_millis();
80
81                if attempt < 4 {
82                    println!(
83                        "   [+{:>4}ms] Attempt {}: ❌ Simulated failure (next delay: {}ms)",
84                        elapsed,
85                        attempt,
86                        100 * 2_i32.pow(attempt as u32)
87                    );
88                    Err(DoOverError::Inner(format!("Transient error")))
89                } else {
90                    println!(
91                        "   [+{:>4}ms] Attempt {}: ✅ Success!",
92                        elapsed, attempt
93                    );
94                    Ok("Operation completed with exponential backoff".to_string())
95                }
96            }
97        })
98        .await;
99
100    let total_time = start.elapsed().as_millis();
101    println!("\n   Result: {:?}", result.unwrap());
102    println!(
103        "   Total time: {}ms (expected ~1400ms: 200+400+800)",
104        total_time
105    );
106    println!("   Delays: 200ms → 400ms → 800ms (exponential growth)");
107}
108
109async fn exhausted_retries_example() {
110    println!("📌 Scenario 3: Exhausted Retries (all attempts fail)");
111    println!("   Configuration: max_retries=2, delay=50ms\n");
112
113    let policy = RetryPolicy::fixed(2, Duration::from_millis(50));
114    let attempt_count = Arc::new(AtomicUsize::new(0));
115    let start = Instant::now();
116
117    let result: Result<String, DoOverError<String>> = policy
118        .execute(|| {
119            let attempts = Arc::clone(&attempt_count);
120            async move {
121                let attempt = attempts.fetch_add(1, Ordering::SeqCst) + 1;
122                let elapsed = start.elapsed().as_millis();
123                println!(
124                    "   [+{:>4}ms] Attempt {}: ❌ Permanent failure",
125                    elapsed, attempt
126                );
127                Err(DoOverError::Inner(format!(
128                    "Service unavailable (attempt {})",
129                    attempt
130                )))
131            }
132        })
133        .await;
134
135    let total_time = start.elapsed().as_millis();
136    println!("\n   Result: {:?}", result.unwrap_err());
137    println!(
138        "   Total time: {}ms (3 attempts, 2 retries exhausted)",
139        total_time
140    );
141}