use do_over::{error::DoOverError, policy::Policy, retry::RetryPolicy};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
#[tokio::main]
async fn main() {
println!("=== Do-Over Retry Policy Example ===\n");
fixed_backoff_example().await;
println!("\n{}\n", "─".repeat(60));
exponential_backoff_example().await;
println!("\n{}\n", "─".repeat(60));
exhausted_retries_example().await;
}
async fn fixed_backoff_example() {
println!("📌 Scenario 1: Fixed Backoff (fails twice, succeeds on third)");
println!(" Configuration: max_retries=2, delay=100ms\n");
let policy = RetryPolicy::fixed(2, Duration::from_millis(100));
let attempt_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let result: Result<String, DoOverError<String>> = policy
.execute(|| {
let attempts = Arc::clone(&attempt_count);
async move {
let attempt = attempts.fetch_add(1, Ordering::SeqCst) + 1;
let elapsed = start.elapsed().as_millis();
if attempt < 3 {
println!(
" [+{:>4}ms] Attempt {}: ❌ Simulated failure",
elapsed, attempt
);
Err(DoOverError::Inner(format!("Transient error on attempt {}", attempt)))
} else {
println!(
" [+{:>4}ms] Attempt {}: ✅ Success!",
elapsed, attempt
);
Ok("Operation completed successfully".to_string())
}
}
})
.await;
let total_time = start.elapsed().as_millis();
println!("\n Result: {:?}", result.unwrap());
println!(" Total time: {}ms (expected ~200ms for 2 retries)", total_time);
}
async fn exponential_backoff_example() {
println!("📌 Scenario 2: Exponential Backoff (show increasing delays)");
println!(" Configuration: max_retries=3, base=100ms, factor=2.0\n");
let policy = RetryPolicy::exponential(3, Duration::from_millis(100), 2.0);
let attempt_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let result: Result<String, DoOverError<String>> = policy
.execute(|| {
let attempts = Arc::clone(&attempt_count);
async move {
let attempt = attempts.fetch_add(1, Ordering::SeqCst) + 1;
let elapsed = start.elapsed().as_millis();
if attempt < 4 {
println!(
" [+{:>4}ms] Attempt {}: ❌ Simulated failure (next delay: {}ms)",
elapsed,
attempt,
100 * 2_i32.pow(attempt as u32)
);
Err(DoOverError::Inner(format!("Transient error")))
} else {
println!(
" [+{:>4}ms] Attempt {}: ✅ Success!",
elapsed, attempt
);
Ok("Operation completed with exponential backoff".to_string())
}
}
})
.await;
let total_time = start.elapsed().as_millis();
println!("\n Result: {:?}", result.unwrap());
println!(
" Total time: {}ms (expected ~1400ms: 200+400+800)",
total_time
);
println!(" Delays: 200ms → 400ms → 800ms (exponential growth)");
}
async fn exhausted_retries_example() {
println!("📌 Scenario 3: Exhausted Retries (all attempts fail)");
println!(" Configuration: max_retries=2, delay=50ms\n");
let policy = RetryPolicy::fixed(2, Duration::from_millis(50));
let attempt_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let result: Result<String, DoOverError<String>> = policy
.execute(|| {
let attempts = Arc::clone(&attempt_count);
async move {
let attempt = attempts.fetch_add(1, Ordering::SeqCst) + 1;
let elapsed = start.elapsed().as_millis();
println!(
" [+{:>4}ms] Attempt {}: ❌ Permanent failure",
elapsed, attempt
);
Err(DoOverError::Inner(format!(
"Service unavailable (attempt {})",
attempt
)))
}
})
.await;
let total_time = start.elapsed().as_millis();
println!("\n Result: {:?}", result.unwrap_err());
println!(
" Total time: {}ms (3 attempts, 2 retries exhausted)",
total_time
);
}