Course contentsShow
← Rust
Lesson 1167 of 1,91010. Concurrency PrimitivesFree lesson

Timeout Patterns in Async Operations

You will implement robust failure handling for long-running network or I/O operations.

In this lesson, you will learn how to prevent your asynchronous applications from hanging indefinitely by implementing robust timeout patterns for I/O-bound operations.

The Problem of Hanging Futures

In asynchronous Rust, a Future represents an operation that may not complete immediately. If a network request is sent to a server that is unresponsive or experiencing high latency, the Future might remain in a "pending" state forever. Since async tasks are cooperative, an infinite hang can starve your executor's resources, leading to cascading failures across your application.

Implementing Timeouts with tokio::time::timeout

The idiomatic way to handle this is by wrapping your potentially slow operation in a timeout future. When you use tokio::time::timeout, you provide a Duration and a Future. If the Future completes before the duration elapses, you get the result. If not, the timeout triggers, and the inner Future is dropped.

Crucially, because Rust Futures are lazy and cancellation-safe, dropping the future is the primary mechanism for cleanup. When the timeout drops the task, any resources held by that task (like an open socket or a buffer) are cleaned up via their Drop implementation.

Practical Implementation

use tokio::time::{timeout, Duration};

async fn fetch_data_with_deadline() -> Result<String, String> {
    let operation = perform_network_call();
    
    // We race the operation against a timer
    match timeout(Duration::from_secs(5), operation).await {
        Ok(result) => Ok(result),
        Err(_) => Err("Operation timed out after 5 seconds".to_string()),
    }
}

Considerations for Cancellation

Not every operation is safely cancellable. If your Future performs a side effect that is not atomic (e.g., writing half of a packet to a database), dropping the future might leave the system in an inconsistent state. When designing these systems, always ensure that your underlying async tasks reach a clean state upon cancellation, or use tokio::select! to manage specific shutdown signals if more complex coordination is required.

Key Takeaway: Use timeout wrappers to race futures against a clock, ensuring that long-running I/O never blocks your executor's progress indefinitely.