Skip to main content

async_safe_defer/
async_scope.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::{
3    fmt,
4    future::Future,
5    pin::Pin,
6    task::{Context, Poll},
7};
8
9type LocalFuture<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
10type SendFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
11
12fn poll_tasks<F>(tasks: &mut Vec<Pin<Box<F>>>, context: &mut Context<'_>) -> Poll<()>
13where
14    F: Future<Output = ()> + ?Sized,
15{
16    loop {
17        let Some(mut future) = tasks.pop() else {
18            return Poll::Ready(());
19        };
20
21        match future.as_mut().poll(context) {
22            Poll::Pending => {
23                tasks.push(future);
24                return Poll::Pending;
25            }
26            Poll::Ready(()) => {}
27        }
28    }
29}
30
31/// A LIFO stack of local asynchronous cleanup actions.
32///
33/// Each registration stores one boxed wrapper future. The cleanup factory is
34/// invoked only when that wrapper is first polled, and actions are awaited
35/// sequentially.
36///
37/// Dropping a pending [`LocalRun`] leaves its current action in the stack. A
38/// later run resumes it after any newer LIFO entries. Dropping the scope itself
39/// drops all remaining actions without polling them further.
40#[must_use = "call run().await or finish().await to execute registered cleanup actions"]
41pub struct LocalAsyncScope<'a> {
42    tasks: Vec<LocalFuture<'a>>,
43}
44
45/// Drains a [`LocalAsyncScope`] without consuming it.
46#[must_use = "futures do nothing unless polled or awaited"]
47pub struct LocalRun<'scope, 'task> {
48    scope: &'scope mut LocalAsyncScope<'task>,
49}
50
51impl Future for LocalRun<'_, '_> {
52    type Output = ();
53
54    fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
55        poll_tasks(&mut self.scope.tasks, context)
56    }
57}
58
59impl<'a> LocalAsyncScope<'a> {
60    /// Creates an empty scope.
61    #[inline]
62    pub const fn new() -> Self {
63        Self { tasks: Vec::new() }
64    }
65
66    /// Creates an empty scope with space for at least `capacity` actions.
67    #[inline]
68    pub fn with_capacity(capacity: usize) -> Self {
69        Self {
70            tasks: Vec::with_capacity(capacity),
71        }
72    }
73
74    /// Queues a cleanup factory without invoking it.
75    #[inline]
76    pub fn defer<F, Fut>(&mut self, action: F)
77    where
78        F: FnOnce() -> Fut + 'a,
79        Fut: Future<Output = ()> + 'a,
80    {
81        self.tasks.push(Box::pin(async move { action().await }));
82    }
83
84    /// Returns the number of cleanup actions that have not completed.
85    #[inline]
86    pub fn len(&self) -> usize {
87        self.tasks.len()
88    }
89
90    /// Returns the number of actions the scope can hold without reallocating.
91    #[inline]
92    pub fn capacity(&self) -> usize {
93        self.tasks.capacity()
94    }
95
96    /// Returns `true` when no cleanup actions remain.
97    #[inline]
98    pub fn is_empty(&self) -> bool {
99        self.tasks.is_empty()
100    }
101
102    /// Drops all queued actions without polling them further.
103    #[inline]
104    pub fn clear(&mut self) {
105        self.tasks.clear();
106    }
107
108    /// Borrows the scope and drains its actions in LIFO order.
109    #[inline]
110    pub fn run(&mut self) -> LocalRun<'_, 'a> {
111        LocalRun { scope: self }
112    }
113
114    /// Consumes the scope and runs its actions in LIFO order.
115    ///
116    /// Dropping the returned future also drops every remaining action.
117    #[inline]
118    pub async fn finish(mut self) {
119        self.run().await;
120    }
121}
122
123impl Default for LocalAsyncScope<'_> {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl fmt::Debug for LocalAsyncScope<'_> {
130    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
131        formatter
132            .debug_struct("LocalAsyncScope")
133            .field("pending", &self.tasks.len())
134            .finish()
135    }
136}
137
138/// Convenience name for [`LocalAsyncScope`].
139///
140/// Use [`SendAsyncScope`] when the scope must cross thread boundaries.
141pub use LocalAsyncScope as AsyncScope;
142
143/// A LIFO stack of `Send` asynchronous cleanup actions.
144///
145/// Deferred closures and the futures they return must implement [`Send`].
146/// Execution and cancellation behavior match [`LocalAsyncScope`].
147#[must_use = "call run().await or finish().await to execute registered cleanup actions"]
148pub struct SendAsyncScope<'a> {
149    tasks: Vec<SendFuture<'a>>,
150}
151
152/// The `Send` counterpart to [`LocalRun`].
153#[must_use = "futures do nothing unless polled or awaited"]
154pub struct SendRun<'scope, 'task> {
155    scope: &'scope mut SendAsyncScope<'task>,
156}
157
158impl Future for SendRun<'_, '_> {
159    type Output = ();
160
161    fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
162        poll_tasks(&mut self.scope.tasks, context)
163    }
164}
165
166impl<'a> SendAsyncScope<'a> {
167    /// Creates an empty scope.
168    #[inline]
169    pub const fn new() -> Self {
170        Self { tasks: Vec::new() }
171    }
172
173    /// Creates an empty scope with space for at least `capacity` actions.
174    #[inline]
175    pub fn with_capacity(capacity: usize) -> Self {
176        Self {
177            tasks: Vec::with_capacity(capacity),
178        }
179    }
180
181    /// Queues a `Send` cleanup closure without invoking it.
182    #[inline]
183    pub fn defer<F, Fut>(&mut self, action: F)
184    where
185        F: FnOnce() -> Fut + Send + 'a,
186        Fut: Future<Output = ()> + Send + 'a,
187    {
188        self.tasks.push(Box::pin(async move { action().await }));
189    }
190
191    /// Returns the number of cleanup actions that have not completed.
192    #[inline]
193    pub fn len(&self) -> usize {
194        self.tasks.len()
195    }
196
197    /// Returns the number of actions the scope can hold without reallocating.
198    #[inline]
199    pub fn capacity(&self) -> usize {
200        self.tasks.capacity()
201    }
202
203    /// Returns `true` when no cleanup actions remain.
204    #[inline]
205    pub fn is_empty(&self) -> bool {
206        self.tasks.is_empty()
207    }
208
209    /// Drops all queued actions without polling them further.
210    #[inline]
211    pub fn clear(&mut self) {
212        self.tasks.clear();
213    }
214
215    /// Borrows the scope and drains its actions in LIFO order.
216    #[inline]
217    pub fn run(&mut self) -> SendRun<'_, 'a> {
218        SendRun { scope: self }
219    }
220
221    /// Consumes the scope and runs its actions in LIFO order.
222    ///
223    /// Dropping the returned future also drops every remaining action.
224    #[inline]
225    pub async fn finish(mut self) {
226        self.run().await;
227    }
228}
229
230impl Default for SendAsyncScope<'_> {
231    fn default() -> Self {
232        Self::new()
233    }
234}
235
236impl fmt::Debug for SendAsyncScope<'_> {
237    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
238        formatter
239            .debug_struct("SendAsyncScope")
240            .field("pending", &self.tasks.len())
241            .finish()
242    }
243}