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
impl TimeoutPolicy
Sourcepub fn new(timeout: Duration) -> Self
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
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
impl Clone for TimeoutPolicy
Source§fn clone(&self) -> TimeoutPolicy
fn clone(&self) -> TimeoutPolicy
Returns a duplicate of the value. Read more
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl<E> Policy<DoOverError<E>> for TimeoutPolicy
impl<E> Policy<DoOverError<E>> for TimeoutPolicy
Auto Trait Implementations§
impl Freeze for TimeoutPolicy
impl RefUnwindSafe for TimeoutPolicy
impl Send for TimeoutPolicy
impl Sync for TimeoutPolicy
impl Unpin for TimeoutPolicy
impl UnwindSafe for TimeoutPolicy
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