1use 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 fixed_backoff_example().await;
17
18 println!("\n{}\n", "─".repeat(60));
19
20 exponential_backoff_example().await;
22
23 println!("\n{}\n", "─".repeat(60));
24
25 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}