Skip to main content

TimeoutPolicy

Struct TimeoutPolicy 

Source
pub struct TimeoutPolicy { /* private fields */ }
Expand description

A policy that enforces a maximum duration for operations.

If the operation doesn’t complete within the timeout, it is cancelled and DoOverError::Timeout is returned.

§Examples

use do_over::{policy::Policy, timeout::TimeoutPolicy, error::DoOverError};
use std::time::Duration;

let policy = TimeoutPolicy::new(Duration::from_secs(10));

let result: Result<String, DoOverError<String>> = policy.execute(|| async {
    // Long-running operation
    Ok("done".to_string())
}).await;

Implementations§

Source§

impl TimeoutPolicy

Source

pub fn new(timeout: Duration) -> Self

Create a new timeout policy.

§Arguments
  • timeout - Maximum duration to wait for the operation
§Examples
use do_over::timeout::TimeoutPolicy;
use std::time::Duration;

// 5 second timeout
let policy = TimeoutPolicy::new(Duration::from_secs(5));
Examples found in repository?
examples/comprehensive.rs (line 58)
53fn create_inventory_policy() -> InventoryPolicy {
54    Wrap::new(
55        RateLimiter::new(3, Duration::from_secs(1)), // 3 requests per second
56        Wrap::new(
57            RetryPolicy::fixed(2, Duration::from_millis(100)),
58            TimeoutPolicy::new(Duration::from_millis(500)),
59        ),
60    )
61}
62
63fn create_payment_policy() -> PaymentPolicy {
64    Wrap::new(
65        CircuitBreaker::new(3, Duration::from_secs(5)), // Opens after 3 failures
66        Wrap::new(
67            RetryPolicy::fixed(2, Duration::from_millis(200)),
68            TimeoutPolicy::new(Duration::from_secs(1)),
69        ),
70    )
71}
72
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/http_service.rs (line 14)
11async fn main() {
12    let policy = Wrap::new(
13        RetryPolicy::fixed(2, Duration::from_millis(50)),
14        TimeoutPolicy::new(Duration::from_secs(1)),
15    );
16
17    let app = Router::new().route(
18        "/",
19        get(|| async move {
20            let result: Result<&str, DoOverError<()>> =
21                policy.execute(|| async { Ok("hello from do-over") }).await;
22            result.unwrap()
23        }),
24    );
25
26    println!("Starting server on http://0.0.0.0:3000");
27    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
28    axum::serve(listener, app).await.unwrap();
29}
examples/timeout.rs (line 31)
27async fn fast_operation_example() {
28    println!("📌 Scenario 1: Fast Operation (completes within timeout)");
29    println!("   Configuration: timeout=500ms, operation_time=100ms\n");
30
31    let policy = TimeoutPolicy::new(Duration::from_millis(500));
32    let start = Instant::now();
33
34    let result: Result<String, DoOverError<String>> = policy
35        .execute(|| async {
36            println!("   → Operation started...");
37            tokio::time::sleep(Duration::from_millis(100)).await;
38            println!("   → Operation completed!");
39            Ok("Fast operation result".to_string())
40        })
41        .await;
42
43    let elapsed = start.elapsed();
44    println!("\n   Result: {:?}", result.unwrap());
45    println!("   Elapsed: {:?} (well under 500ms timeout)", elapsed);
46}
47
48async fn slow_operation_example() {
49    println!("📌 Scenario 2: Slow Operation (exceeds timeout)");
50    println!("   Configuration: timeout=200ms, operation_time=500ms\n");
51
52    let policy = TimeoutPolicy::new(Duration::from_millis(200));
53    let start = Instant::now();
54
55    let result: Result<String, DoOverError<String>> = policy
56        .execute(|| async {
57            println!("   → Operation started (will take 500ms)...");
58            tokio::time::sleep(Duration::from_millis(500)).await;
59            // This line should not be reached
60            println!("   → This should not print!");
61            Ok("Slow operation result".to_string())
62        })
63        .await;
64
65    let elapsed = start.elapsed();
66
67    match result {
68        Err(DoOverError::Timeout) => {
69            println!("   → Operation cancelled due to timeout!");
70            println!("\n   Result: DoOverError::Timeout");
71        }
72        _ => println!("   Unexpected result: {:?}", result),
73    }
74
75    println!("   Elapsed: {:?} (stopped at ~200ms timeout)", elapsed);
76    println!("\n   💡 The operation was cancelled - no resources wasted!");
77}
78
79async fn edge_case_example() {
80    println!("📌 Scenario 3: Edge Case (operation completes just in time)");
81    println!("   Configuration: timeout=300ms, operation_time=250ms\n");
82
83    let policy = TimeoutPolicy::new(Duration::from_millis(300));
84    let start = Instant::now();
85
86    let result: Result<String, DoOverError<String>> = policy
87        .execute(|| async {
88            println!("   → Operation started (will take 250ms)...");
89            tokio::time::sleep(Duration::from_millis(250)).await;
90            println!("   → Operation completed just in time!");
91            Ok("Completed with 50ms to spare".to_string())
92        })
93        .await;
94
95    let elapsed = start.elapsed();
96
97    match result {
98        Ok(msg) => {
99            println!("\n   Result: ✅ {}", msg);
100        }
101        Err(DoOverError::Timeout) => {
102            println!("\n   Result: ❌ Timeout (race condition)");
103        }
104        Err(e) => {
105            println!("\n   Result: {:?}", e);
106        }
107    }
108
109    println!("   Elapsed: {:?}", elapsed);
110}
examples/composition.rs (line 44)
37async fn pattern1_retry_timeout() {
38    println!("📌 Pattern 1: Retry wrapping Timeout");
39    println!("   Wrap::new(Retry(3, 100ms), Timeout(200ms))");
40    println!("   → Each retry attempt gets its own 200ms timeout\n");
41
42    let policy = Wrap::new(
43        RetryPolicy::fixed(3, Duration::from_millis(100)),
44        TimeoutPolicy::new(Duration::from_millis(200)),
45    );
46
47    let attempt_count = Arc::new(AtomicUsize::new(0));
48    let start = Instant::now();
49
50    // Scenario: First 2 attempts timeout, third succeeds quickly
51    let result: Result<String, DoOverError<String>> = {
52        let ac = Arc::clone(&attempt_count);
53        policy
54            .execute(|| {
55                let count = Arc::clone(&ac);
56                async move {
57                    let attempt = count.fetch_add(1, Ordering::SeqCst) + 1;
58                    let elapsed = start.elapsed().as_millis();
59                    println!(
60                        "   [+{:>4}ms] Attempt {}: Started",
61                        elapsed, attempt
62                    );
63
64                    if attempt < 3 {
65                        // First two attempts are slow - will timeout
66                        println!(
67                            "   [+{:>4}ms] Attempt {}: Processing slowly (will timeout)...",
68                            elapsed, attempt
69                        );
70                        tokio::time::sleep(Duration::from_millis(500)).await;
71                        Ok("Should not reach here".to_string())
72                    } else {
73                        // Third attempt is fast
74                        tokio::time::sleep(Duration::from_millis(50)).await;
75                        let elapsed = start.elapsed().as_millis();
76                        println!(
77                            "   [+{:>4}ms] Attempt {}: ✅ Completed quickly!",
78                            elapsed, attempt
79                        );
80                        Ok("Success on attempt 3".to_string())
81                    }
82                }
83            })
84            .await
85    };
86
87    let total_time = start.elapsed().as_millis();
88    println!("\n   Result: {:?}", result.unwrap());
89    println!("   Total time: {}ms", total_time);
90    println!("   Total attempts: {}", attempt_count.load(Ordering::SeqCst));
91    println!("\n   💡 Each attempt had its own timeout; operation succeeded on 3rd try");
92}
93
94async fn pattern2_circuit_retry() {
95    println!("📌 Pattern 2: CircuitBreaker wrapping Retry");
96    println!("   Wrap::new(CircuitBreaker(3, 1s), Retry(2, 50ms))");
97    println!("   → Retries happen inside the circuit breaker\n");
98
99    let policy = Wrap::new(
100        CircuitBreaker::new(3, Duration::from_secs(1)),
101        RetryPolicy::fixed(2, Duration::from_millis(50)),
102    );
103
104    let failure_count = Arc::new(AtomicUsize::new(0));
105    let start = Instant::now();
106
107    // Make several calls that fail to open the circuit
108    println!("   Phase 1: Failing calls to open circuit");
109    for i in 1..=4 {
110        let fc = Arc::clone(&failure_count);
111        let s = start;
112        let result: Result<String, DoOverError<String>> = policy
113            .execute(|| {
114                let count = Arc::clone(&fc);
115                async move {
116                    count.fetch_add(1, Ordering::SeqCst);
117                    let elapsed = s.elapsed().as_millis();
118                    println!(
119                        "   [+{:>4}ms] Call {}: Inner operation failing...",
120                        elapsed, i
121                    );
122                    Err(DoOverError::Inner(format!("Service error")))
123                }
124            })
125            .await;
126
127        let elapsed = start.elapsed().as_millis();
128        match &result {
129            Err(DoOverError::Inner(_)) => {
130                println!(
131                    "   [+{:>4}ms] Call {}: ❌ Failed after retries",
132                    elapsed, i
133                );
134            }
135            Err(DoOverError::CircuitOpen) => {
136                println!(
137                    "   [+{:>4}ms] Call {}: 🚫 Circuit is OPEN",
138                    elapsed, i
139                );
140            }
141            _ => {}
142        }
143    }
144
145    let inner_calls = failure_count.load(Ordering::SeqCst);
146    println!("\n   Summary:");
147    println!("   - Inner operation calls: {} (includes retries)", inner_calls);
148    println!("   - Circuit opened after threshold reached");
149    println!("\n   💡 Retries happened inside circuit breaker; failures accumulated to open circuit");
150}
151
152async fn pattern3_full_stack() {
153    println!("📌 Pattern 3: Full Resilience Stack");
154    println!("   Bulkhead(2) → Retry(2, 100ms) → Timeout(500ms)");
155    println!("   Recommended ordering for production systems\n");
156
157    // Note: Using Bulkhead + Retry + Timeout for this example to avoid
158    // CircuitBreaker clone issues in concurrent async context
159    let policy = Arc::new(Wrap::new(
160        Bulkhead::new(2),
161        Wrap::new(
162            RetryPolicy::fixed(2, Duration::from_millis(100)),
163            TimeoutPolicy::new(Duration::from_millis(500)),
164        ),
165    ));
166
167    let call_count = Arc::new(AtomicUsize::new(0));
168    let start = Instant::now();
169
170    println!("   Launching 4 concurrent requests (bulkhead limit is 2)...\n");
171
172    let mut handles = vec![];
173    for i in 1..=4 {
174        let p = Arc::clone(&policy);
175        let cc = Arc::clone(&call_count);
176        let s = start;
177
178        let handle = tokio::spawn(async move {
179            let result: Result<String, DoOverError<String>> = p
180                .execute(|| {
181                    let count = Arc::clone(&cc);
182                    async move {
183                        let call = count.fetch_add(1, Ordering::SeqCst) + 1;
184                        let elapsed = s.elapsed().as_millis();
185                        println!(
186                            "   [+{:>4}ms] Request {}, call {}: Processing...",
187                            elapsed, i, call
188                        );
189                        tokio::time::sleep(Duration::from_millis(200)).await;
190                        let elapsed = s.elapsed().as_millis();
191                        println!(
192                            "   [+{:>4}ms] Request {}, call {}: ✅ Done",
193                            elapsed, i, call
194                        );
195                        Ok(format!("Response {}", i))
196                    }
197                })
198                .await;
199
200            let elapsed = s.elapsed().as_millis();
201            match &result {
202                Ok(msg) => println!("   [+{:>4}ms] Request {}: ✅ {}", elapsed, i, msg),
203                Err(DoOverError::BulkheadFull) => {
204                    println!("   [+{:>4}ms] Request {}: 🚫 Rejected by bulkhead", elapsed, i)
205                }
206                Err(e) => println!("   [+{:>4}ms] Request {}: ❌ {:?}", elapsed, i, e),
207            }
208        });
209        handles.push(handle);
210
211        tokio::time::sleep(Duration::from_millis(10)).await;
212    }
213
214    for handle in handles {
215        handle.await.unwrap();
216    }
217
218    let total_time = start.elapsed().as_millis();
219    let total_calls = call_count.load(Ordering::SeqCst);
220
221    println!("\n   Summary:");
222    println!("   - Total inner operation calls: {}", total_calls);
223    println!("   - Total time: {}ms", total_time);
224    println!("\n   Policy execution order:");
225    println!("   1. Bulkhead: Limits to 2 concurrent executions");
226    println!("   2. Retry: Retries transient failures");
227    println!("   3. Timeout: Each attempt bounded to 500ms");
228}

Trait Implementations§

Source§

impl Clone for TimeoutPolicy

Source§

fn clone(&self) -> TimeoutPolicy

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 TimeoutPolicy
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§

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.