use do_over::{error::DoOverError, hedge::Hedge, policy::Policy};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
#[tokio::main]
async fn main() {
println!("=== Do-Over Hedge Policy Example ===\n");
println!("Configuration: hedge_delay=100ms");
println!("(A backup request is sent 100ms after the primary)\n");
fast_primary_example().await;
println!("\n{}\n", "─".repeat(60));
slow_primary_example().await;
println!("\n{}\n", "─".repeat(60));
race_example().await;
}
async fn fast_primary_example() {
println!("📌 Scenario 1: Fast Primary (completes before hedge starts)");
println!(" Primary latency: 50ms, Hedge delay: 100ms\n");
let hedge = Hedge::new(Duration::from_millis(100));
let request_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let result: Result<String, DoOverError<String>> = {
let rc = Arc::clone(&request_count);
hedge
.execute(|| {
let count = Arc::clone(&rc);
async move {
let req_num = count.fetch_add(1, Ordering::SeqCst) + 1;
let elapsed = start.elapsed().as_millis();
println!(
" [+{:>4}ms] Request {} started ({})",
elapsed,
req_num,
if req_num == 1 { "primary" } else { "hedge" }
);
tokio::time::sleep(Duration::from_millis(50)).await;
let elapsed = start.elapsed().as_millis();
println!(
" [+{:>4}ms] Request {} completed",
elapsed, req_num
);
Ok(format!("Response from request {}", req_num))
}
})
.await
};
let total_time = start.elapsed().as_millis();
let total_requests = request_count.load(Ordering::SeqCst);
println!("\n Result: {:?}", result.unwrap());
println!(" Total latency: {}ms", total_time);
println!(" Requests made: {}", total_requests);
println!("\n 💡 Primary completed in 50ms, before the 100ms hedge delay");
}
async fn slow_primary_example() {
println!("📌 Scenario 2: Slow Primary (hedge wins)");
println!(" Primary latency: 300ms, Hedge delay: 100ms, Hedge latency: 50ms\n");
let hedge = Hedge::new(Duration::from_millis(100));
let request_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let result: Result<String, DoOverError<String>> = {
let rc = Arc::clone(&request_count);
hedge
.execute(|| {
let count = Arc::clone(&rc);
async move {
let req_num = count.fetch_add(1, Ordering::SeqCst) + 1;
let elapsed = start.elapsed().as_millis();
let is_primary = req_num == 1;
println!(
" [+{:>4}ms] Request {} started ({})",
elapsed,
req_num,
if is_primary { "primary" } else { "hedge" }
);
let delay = if is_primary { 300 } else { 50 };
tokio::time::sleep(Duration::from_millis(delay)).await;
let elapsed = start.elapsed().as_millis();
println!(
" [+{:>4}ms] Request {} completed",
elapsed, req_num
);
Ok(format!("Response from request {}", req_num))
}
})
.await
};
let total_time = start.elapsed().as_millis();
let total_requests = request_count.load(Ordering::SeqCst);
println!("\n Result: {:?}", result.unwrap());
println!(" Total latency: {}ms (would be 300ms without hedging)", total_time);
println!(" Requests made: {}", total_requests);
println!("\n 💡 Hedge started at 100ms, completed at 150ms, beating the slow primary");
}
async fn race_example() {
println!("📌 Scenario 3: Close Race (both requests similar speed)");
println!(" Primary latency: 120ms, Hedge delay: 100ms, Hedge latency: 30ms\n");
let hedge = Hedge::new(Duration::from_millis(100));
let request_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let result: Result<String, DoOverError<String>> = {
let rc = Arc::clone(&request_count);
hedge
.execute(|| {
let count = Arc::clone(&rc);
async move {
let req_num = count.fetch_add(1, Ordering::SeqCst) + 1;
let elapsed = start.elapsed().as_millis();
let is_primary = req_num == 1;
println!(
" [+{:>4}ms] Request {} started ({})",
elapsed,
req_num,
if is_primary { "primary" } else { "hedge" }
);
let delay = if is_primary { 120 } else { 30 };
tokio::time::sleep(Duration::from_millis(delay)).await;
let elapsed = start.elapsed().as_millis();
println!(
" [+{:>4}ms] Request {} completed",
elapsed, req_num
);
Ok(format!("Response from request {} ({})", req_num, if is_primary { "primary" } else { "hedge" }))
}
})
.await
};
let total_time = start.elapsed().as_millis();
let total_requests = request_count.load(Ordering::SeqCst);
println!("\n Result: {:?}", result.unwrap());
println!(" Total latency: {}ms", total_time);
println!(" Requests made: {}", total_requests);
println!("\n 💡 Primary wins at ~120ms, hedge (would finish at ~130ms) is cancelled");
}