Skip to main content

async_drop/
lib.rs

1//! Async drop support for Rust.
2//!
3//! This crate provides [`AsyncDrop`], a trait for types that need to perform
4//! asynchronous cleanup, and [`Dropper`], a wrapper that automatically runs
5//! the async cleanup when the value is dropped.
6//!
7//! # Example
8//!
9//! ```
10//! use async_drop::{AsyncDrop, AsyncDropFuture, Dropper};
11//!
12//! struct DbConnection;
13//!
14//! impl AsyncDrop for DbConnection {
15//!     fn async_drop(&mut self) -> AsyncDropFuture<'_> {
16//!         Box::pin(async {
17//!             // Close the connection gracefully...
18//!             Ok(())
19//!         })
20//!     }
21//! }
22//!
23//! let conn = Dropper::new(DbConnection);
24//! // Use `conn` as if it were a `DbConnection` (via Deref).
25//! // When `conn` goes out of scope, `async_drop` runs automatically.
26//! ```
27
28use std::{
29    ops::{Deref, DerefMut},
30    pin::Pin,
31};
32
33/// The future type returned by [`AsyncDrop::async_drop`].
34pub type AsyncDropFuture<'a> = Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>>;
35
36/// A trait for types that require asynchronous cleanup.
37///
38/// Implement this trait to define async teardown logic (e.g. flushing buffers,
39/// closing network connections, or releasing distributed locks). Use
40/// [`Dropper`] to ensure `async_drop` is called automatically when the value
41/// goes out of scope.
42pub trait AsyncDrop {
43    /// Performs asynchronous cleanup of this value.
44    ///
45    /// Returns `Ok(())` on success or `Err` with a message that will be
46    /// printed to stderr.
47    fn async_drop(&mut self) -> AsyncDropFuture<'_>;
48}
49
50/// A wrapper that calls [`AsyncDrop::async_drop`] when dropped.
51///
52/// `Dropper<T>` dereferences to `T`, so the wrapped value can be used
53/// transparently. On drop, it spawns a dedicated thread with a short-lived
54/// Tokio runtime to drive the cleanup future to completion. This avoids
55/// deadlocks when the `Dropper` is dropped inside a single-threaded async
56/// executor.
57///
58/// # Panics
59///
60/// Panics if the internal Tokio runtime cannot be created.
61pub struct Dropper<T>
62where
63    T: AsyncDrop,
64{
65    inner: Option<T>,
66}
67
68impl<T> Dropper<T>
69where
70    T: AsyncDrop,
71{
72    /// Wraps `inner` so that its [`AsyncDrop::async_drop`] implementation is
73    /// called automatically when this `Dropper` is dropped.
74    pub fn new(inner: T) -> Self {
75        Self { inner: Some(inner) }
76    }
77}
78
79impl<T> Drop for Dropper<T>
80where
81    T: AsyncDrop,
82{
83    fn drop(&mut self) {
84        let Some(mut inner) = self.inner.take() else {
85            return;
86        };
87
88        let future = inner.async_drop();
89
90        // Spawn a dedicated thread with its own tokio runtime so we don't
91        // deadlock when dropped inside a single-threaded tokio executor.
92        // Use catch_unwind to handle panics in the async drop and propagate
93        // them to the parent thread.
94        let result = std::thread::scope(|s| {
95            s.spawn(move || {
96                let rt = tokio::runtime::Builder::new_current_thread()
97                    .enable_all()
98                    .build()
99                    .expect("failed to build async-drop runtime");
100
101                let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
102                    rt.block_on(future)
103                }));
104
105                match result {
106                    Ok(inner) => inner.map_err(|e| format!("Async drop error: {}", e)),
107                    Err(_) => Err("async_drop panicked".to_owned()),
108                }
109            })
110            .join()
111            .unwrap_or_else(|_| Err("async_drop thread panicked".to_owned()))
112        });
113
114        if let Err(e) = result {
115            panic!("{}", e);
116        }
117    }
118}
119
120impl<T> Deref for Dropper<T>
121where
122    T: AsyncDrop + 'static,
123{
124    type Target = T;
125    fn deref(&self) -> &Self::Target {
126        self.inner
127            .as_ref()
128            .expect("Dropper value has already been dropped")
129    }
130}
131
132impl<T> DerefMut for Dropper<T>
133where
134    T: AsyncDrop + 'static,
135{
136    fn deref_mut(&mut self) -> &mut Self::Target {
137        self.inner
138            .as_mut()
139            .expect("Dropper value has already been dropped")
140    }
141}