Skip to main content

Hedge

Struct Hedge 

Source
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

Source

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?
examples/comprehensive.rs (line 75)
73fn create_notification_policy() -> NotificationPolicy {
74    Wrap::new(
75        Hedge::new(Duration::from_millis(100)), // Start hedge after 100ms
76        TimeoutPolicy::new(Duration::from_millis(500)),
77    )
78}
More examples
Hide additional 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§

Source§

impl Clone for Hedge

Source§

fn clone(&self) -> Hedge

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<E> Policy<DoOverError<E>> for Hedge
where E: Send + Sync,

Source§

fn execute<'life0, 'async_trait, F, Fut, T>( &'life0 self, f: F, ) -> Pin<Box<dyn Future<Output = Result<T, DoOverError<E>>> + Send + 'async_trait>>
where F: Fn() -> Fut + Send + Sync + 'async_trait, Fut: Future<Output = Result<T, DoOverError<E>>> + Send + 'async_trait, T: Send + 'async_trait, Self: 'async_trait, 'life0: 'async_trait,

Execute an async operation with this policy’s resilience behavior. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.