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

Async Cancellation and Resource Cleanup

You will ensure system resources are released when asynchronous tasks are dropped before completion.

In this lesson, you will learn how to ensure that asynchronous tasks clean up their resources properly when they are cancelled midway through execution.

The Nature of Async Cancellation

In Rust, the primary mechanism for cancelling an asynchronous task is simply dropping its Future. Because an async block is a state machine, when the Future is dropped, the state machine is destroyed. Any local variables held within that state machine are also dropped automatically.

However, a naive implementation might leave external resources—like a file handle left open, a network socket in an inconsistent state, or a held mutex—in a "dangling" or locked state.

Using RAII for Resource Management

The idiomatic way to handle cleanup in Rust is the RAII (Resource Acquisition Is Initialization) pattern. Since the compiler guarantees that drop will be called on local variables when the Future is destroyed, you should encapsulate your resources within custom types that implement the Drop trait.

Consider this scenario where you must ensure a "lock" is released even if the task is cancelled:

struct AsyncLockGuard<'a> {
    lock: &'a Mutex<()>,
}

impl<'a> Drop for AsyncLockGuard<'a> {
    fn drop(&mut self) {
        // This runs automatically if the future is dropped
        // while the task is awaiting.
        self.lock.unlock();
    }
}

async fn perform_work(lock: &Mutex<()>) {
    let _guard = AsyncLockGuard { lock };
    // If the task is cancelled here, the _guard's drop 
    // implementation ensures the lock is released.
    do_something_async().await;
}

The Pitfall of Cancellation Points

It is vital to remember that a task can only be cancelled at an .await point. If your cleanup logic performs its own await (e.g., waiting for a network flush), you encounter a challenge: the Drop trait is synchronous and cannot perform await.

If your resource requires an asynchronous teardown, you should avoid simple Drop logic and instead design a "cancellation-aware" wrapper that uses a CancellationToken or a structured scope. By checking for cancellation before or after your await points, you can manually trigger the necessary cleanup steps while the runtime is still capable of executing code.

Key Takeaway: Use RAII types with Drop to guarantee cleanup of synchronous resources, but rely on explicit cancellation flags for resources requiring asynchronous teardown.