pub struct Hedge { /* private fields */ }Expand description
A policy that sends backup requests to reduce tail latency.
After a configured delay, if the primary request hasn’t completed, a hedge (backup) request is started. The first response wins.
§Warning
Only use with idempotent operations. Using hedging with non-idempotent operations (like payment processing) can cause duplicate effects.
§Examples
use do_over::{policy::Policy, hedge::Hedge, error::DoOverError};
use std::time::Duration;
// Good use case: read operations
let hedge = Hedge::new(Duration::from_millis(100));
// The hedge request starts if primary takes > 100ms
let result: Result<String, DoOverError<String>> = hedge.execute(|| async {
Ok("data".to_string())
}).await;Implementations§
Source§impl Hedge
impl Hedge
Sourcepub fn new(delay: Duration) -> Self
pub fn new(delay: Duration) -> Self
Create a new hedge policy.
§Arguments
delay- How long to wait before starting the hedge request
§Examples
use do_over::hedge::Hedge;
use std::time::Duration;
// Start hedge after 100ms
let hedge = Hedge::new(Duration::from_millis(100));Examples found in repository?
More examples
examples/hedge.rs (line 36)
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}Trait Implementations§
Auto Trait Implementations§
impl Freeze for Hedge
impl RefUnwindSafe for Hedge
impl Send for Hedge
impl Sync for Hedge
impl Unpin for Hedge
impl UnwindSafe for Hedge
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more