Skip to main content

hedge/
hedge.rs

1//! Hedge Policy Example
2//!
3//! This example demonstrates the Hedge policy for reducing tail latency.
4//! A hedge request is launched after a delay if the primary hasn't completed yet.
5//! Whichever request completes first wins.
6
7use do_over::{error::DoOverError, hedge::Hedge, policy::Policy};
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11
12#[tokio::main]
13async fn main() {
14    println!("=== Do-Over Hedge Policy Example ===\n");
15    println!("Configuration: hedge_delay=100ms");
16    println!("(A backup request is sent 100ms after the primary)\n");
17
18    // Scenario 1: Fast primary - wins before hedge starts
19    fast_primary_example().await;
20
21    println!("\n{}\n", "─".repeat(60));
22
23    // Scenario 2: Slow primary - hedge wins
24    slow_primary_example().await;
25
26    println!("\n{}\n", "─".repeat(60));
27
28    // Scenario 3: Both requests similar speed
29    race_example().await;
30}
31
32async fn fast_primary_example() {
33    println!("📌 Scenario 1: Fast Primary (completes before hedge starts)");
34    println!("   Primary latency: 50ms, Hedge delay: 100ms\n");
35
36    let hedge = Hedge::new(Duration::from_millis(100));
37    let request_count = Arc::new(AtomicUsize::new(0));
38    let start = Instant::now();
39
40    let result: Result<String, DoOverError<String>> = {
41        let rc = Arc::clone(&request_count);
42        hedge
43            .execute(|| {
44                let count = Arc::clone(&rc);
45                async move {
46                    let req_num = count.fetch_add(1, Ordering::SeqCst) + 1;
47                    let elapsed = start.elapsed().as_millis();
48                    println!(
49                        "   [+{:>4}ms] Request {} started ({})",
50                        elapsed,
51                        req_num,
52                        if req_num == 1 { "primary" } else { "hedge" }
53                    );
54
55                    // Primary is fast - 50ms
56                    tokio::time::sleep(Duration::from_millis(50)).await;
57
58                    let elapsed = start.elapsed().as_millis();
59                    println!(
60                        "   [+{:>4}ms] Request {} completed",
61                        elapsed, req_num
62                    );
63                    Ok(format!("Response from request {}", req_num))
64                }
65            })
66            .await
67    };
68
69    let total_time = start.elapsed().as_millis();
70    let total_requests = request_count.load(Ordering::SeqCst);
71
72    println!("\n   Result: {:?}", result.unwrap());
73    println!("   Total latency: {}ms", total_time);
74    println!("   Requests made: {}", total_requests);
75    println!("\n   💡 Primary completed in 50ms, before the 100ms hedge delay");
76}
77
78async fn slow_primary_example() {
79    println!("📌 Scenario 2: Slow Primary (hedge wins)");
80    println!("   Primary latency: 300ms, Hedge delay: 100ms, Hedge latency: 50ms\n");
81
82    let hedge = Hedge::new(Duration::from_millis(100));
83    let request_count = Arc::new(AtomicUsize::new(0));
84    let start = Instant::now();
85
86    let result: Result<String, DoOverError<String>> = {
87        let rc = Arc::clone(&request_count);
88        hedge
89            .execute(|| {
90                let count = Arc::clone(&rc);
91                async move {
92                    let req_num = count.fetch_add(1, Ordering::SeqCst) + 1;
93                    let elapsed = start.elapsed().as_millis();
94                    let is_primary = req_num == 1;
95
96                    println!(
97                        "   [+{:>4}ms] Request {} started ({})",
98                        elapsed,
99                        req_num,
100                        if is_primary { "primary" } else { "hedge" }
101                    );
102
103                    // Primary is slow (300ms), hedge is fast (50ms)
104                    let delay = if is_primary { 300 } else { 50 };
105                    tokio::time::sleep(Duration::from_millis(delay)).await;
106
107                    let elapsed = start.elapsed().as_millis();
108                    println!(
109                        "   [+{:>4}ms] Request {} completed",
110                        elapsed, req_num
111                    );
112                    Ok(format!("Response from request {}", req_num))
113                }
114            })
115            .await
116    };
117
118    let total_time = start.elapsed().as_millis();
119    let total_requests = request_count.load(Ordering::SeqCst);
120
121    println!("\n   Result: {:?}", result.unwrap());
122    println!("   Total latency: {}ms (would be 300ms without hedging)", total_time);
123    println!("   Requests made: {}", total_requests);
124    println!("\n   💡 Hedge started at 100ms, completed at 150ms, beating the slow primary");
125}
126
127async fn race_example() {
128    println!("📌 Scenario 3: Close Race (both requests similar speed)");
129    println!("   Primary latency: 120ms, Hedge delay: 100ms, Hedge latency: 30ms\n");
130
131    let hedge = Hedge::new(Duration::from_millis(100));
132    let request_count = Arc::new(AtomicUsize::new(0));
133    let start = Instant::now();
134
135    let result: Result<String, DoOverError<String>> = {
136        let rc = Arc::clone(&request_count);
137        hedge
138            .execute(|| {
139                let count = Arc::clone(&rc);
140                async move {
141                    let req_num = count.fetch_add(1, Ordering::SeqCst) + 1;
142                    let elapsed = start.elapsed().as_millis();
143                    let is_primary = req_num == 1;
144
145                    println!(
146                        "   [+{:>4}ms] Request {} started ({})",
147                        elapsed,
148                        req_num,
149                        if is_primary { "primary" } else { "hedge" }
150                    );
151
152                    // Primary: 120ms total, Hedge: starts at 100ms + 30ms = 130ms total
153                    // Primary should win by ~10ms
154                    let delay = if is_primary { 120 } else { 30 };
155                    tokio::time::sleep(Duration::from_millis(delay)).await;
156
157                    let elapsed = start.elapsed().as_millis();
158                    println!(
159                        "   [+{:>4}ms] Request {} completed",
160                        elapsed, req_num
161                    );
162                    Ok(format!("Response from request {} ({})", req_num, if is_primary { "primary" } else { "hedge" }))
163                }
164            })
165            .await
166    };
167
168    let total_time = start.elapsed().as_millis();
169    let total_requests = request_count.load(Ordering::SeqCst);
170
171    println!("\n   Result: {:?}", result.unwrap());
172    println!("   Total latency: {}ms", total_time);
173    println!("   Requests made: {}", total_requests);
174    println!("\n   💡 Primary wins at ~120ms, hedge (would finish at ~130ms) is cancelled");
175}