Skip to main content

wasmtime/runtime/component/concurrent/
abort.rs

1use crate::sync::Mutex;
2use alloc::sync::Arc;
3use core::mem::{self, ManuallyDrop};
4use core::pin::Pin;
5use core::task::{Context, Poll, Waker};
6
7/// Handle to a task which may be used to join on the result of executing it.
8///
9/// This represents a handle to a running task which can be cancelled with
10/// [`JoinHandle::abort`]. The final result and drop of the task can be
11/// determined by `await`-ing this handle.
12///
13/// Note that dropping this handle does not affect the running task it's
14/// connected to. A manual invocation of [`JoinHandle::abort`] is required to
15/// affect the task.
16pub struct JoinHandle {
17    // Note that this handle is `Send` and `Sync` and may be used from any
18    // thread, concurrently with the store's event loop polling or dropping the
19    // task on another thread. This lock is only ever held for short critical
20    // sections which don't run any external code (e.g. wakers are always
21    // invoked after the lock is released).
22    state: Arc<Mutex<JoinState>>,
23}
24
25enum JoinState {
26    /// The task this is connected to is still running and has not completed or
27    /// been dropped.
28    Running {
29        /// The waker that the running task has registered which is signaled
30        /// upon abort.
31        waiting_for_abort_signal: Option<Waker>,
32
33        /// The waker that the `JoinHandle` has registered to await
34        /// destruction of the running task itself.
35        waiting_for_abort_to_complete: Option<Waker>,
36    },
37
38    /// An abort as been requested through an `JoinHandle`. The task specified
39    /// here is used for `Future for JoinHandle`.
40    AbortRequested {
41        waiting_for_abort_to_complete: Option<Waker>,
42    },
43
44    /// The running task has completed, so no need to abort it and nothing else
45    /// needs to wait.
46    Complete,
47}
48
49impl JoinHandle {
50    /// Abort the task.
51    ///
52    /// This flags the connected task should abort in the near future, but note
53    /// that if this is called while the future is being polled then that call
54    /// will still complete.
55    ///
56    /// Note that this `JoinHandle` is itself a `Future` and can be used to
57    /// await the result and destruction of the task that this is associated
58    /// with.
59    pub fn abort(&self) {
60        let task = {
61            let mut state = self.state.lock();
62
63            match &mut *state {
64                // If this task is still running, then transition it into the
65                // `AbortRequested` state. If present the waker for the running
66                // task is notified, after the lock is released, to indicate
67                // that an abort signal has been received.
68                JoinState::Running {
69                    waiting_for_abort_signal,
70                    waiting_for_abort_to_complete,
71                } => {
72                    let task = waiting_for_abort_signal.take();
73                    *state = JoinState::AbortRequested {
74                        waiting_for_abort_to_complete: waiting_for_abort_to_complete.take(),
75                    };
76                    task
77                }
78
79                // If this task has already been aborted or has completed,
80                // nothing is left to do.
81                JoinState::AbortRequested { .. } | JoinState::Complete => None,
82            }
83        };
84
85        if let Some(task) = task {
86            task.wake();
87        }
88    }
89
90    /// Wraps the `future` provided in a new future which is "abortable" where
91    /// if the returned `JoinHandle` is flagged then the future will resolve
92    /// ASAP with `None` and drop the provided `future`.
93    pub(crate) fn run<F>(future: F) -> (JoinHandle, impl Future<Output = Option<F::Output>>)
94    where
95        F: Future,
96    {
97        let handle = JoinHandle {
98            state: Arc::new(Mutex::new(JoinState::Running {
99                waiting_for_abort_signal: None,
100                waiting_for_abort_to_complete: None,
101            })),
102        };
103        let future = JoinHandleFuture {
104            future: ManuallyDrop::new(future),
105            state: handle.state.clone(),
106        };
107        (handle, future)
108    }
109}
110
111impl Future for JoinHandle {
112    type Output = ();
113
114    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
115        let mut state = self.state.lock();
116        match &mut *state {
117            // If this task is running or still only has requested an abort,
118            // wait further for the task to get dropped.
119            JoinState::Running {
120                waiting_for_abort_to_complete,
121                ..
122            }
123            | JoinState::AbortRequested {
124                waiting_for_abort_to_complete,
125            } => {
126                // Note that the previous waker, if any, is dropped after the
127                // lock is released to avoid running arbitrary code while the
128                // lock is held.
129                let prev = waiting_for_abort_to_complete.replace(cx.waker().clone());
130                drop(state);
131                drop(prev);
132                Poll::Pending
133            }
134
135            // The task is dropped, done!
136            JoinState::Complete => Poll::Ready(()),
137        }
138    }
139}
140
141struct JoinHandleFuture<F> {
142    future: ManuallyDrop<F>,
143    state: Arc<Mutex<JoinState>>,
144}
145
146impl<F> Future for JoinHandleFuture<F>
147where
148    F: Future,
149{
150    type Output = Option<F::Output>;
151
152    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
153        // SAFETY: this is a pin-projection from `Self` to the state and `Pin`
154        // of the internal future. This is the exclusive access of these fields
155        // apart from the destructor and should be safe.
156        let (state, future) = unsafe {
157            let me = self.get_unchecked_mut();
158            (&me.state, Pin::new_unchecked(&mut *me.future))
159        };
160
161        // First, before polling the future, check to see if we've been
162        // aborted. If not register our task as awaiting such an abort.
163        {
164            let mut state = state.lock();
165            match &mut *state {
166                JoinState::Running {
167                    waiting_for_abort_signal,
168                    ..
169                } => {
170                    let prev = waiting_for_abort_signal.replace(cx.waker().clone());
171                    drop(state);
172                    drop(prev);
173                }
174                JoinState::AbortRequested { .. } | JoinState::Complete => {
175                    return Poll::Ready(None);
176                }
177            }
178        }
179
180        future.poll(cx).map(Some)
181    }
182}
183
184impl<F> Drop for JoinHandleFuture<F> {
185    fn drop(&mut self) {
186        // SAFETY: this is the exclusive owner of this future and it's safe to
187        // drop here during the owning destructor.
188        //
189        // Note that this explicitly happens before notifying the abort handle
190        // that the task completed so that when the notification goes through
191        // it's guaranteed that the future has been destroyed.
192        unsafe {
193            ManuallyDrop::drop(&mut self.future);
194        }
195
196        // After the future dropped see if there was a task awaiting its
197        // destruction. Simultaneously flag this state as complete. Note that
198        // the previous state, and its wakers, are dropped/invoked after the
199        // lock is released.
200        let prev = mem::replace(&mut *self.state.lock(), JoinState::Complete);
201        let task = match prev {
202            JoinState::Running {
203                waiting_for_abort_to_complete,
204                ..
205            }
206            | JoinState::AbortRequested {
207                waiting_for_abort_to_complete,
208            } => waiting_for_abort_to_complete,
209            JoinState::Complete => None,
210        };
211        if let Some(task) = task {
212            task.wake();
213        }
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::JoinHandle;
220    use std::pin::{Pin, pin};
221    use std::task::{Context, Poll, Waker};
222    use tokio::sync::oneshot;
223
224    fn is_ready<F>(future: Pin<&mut F>) -> bool
225    where
226        F: Future,
227    {
228        match future.poll(&mut Context::from_waker(Waker::noop())) {
229            Poll::Ready(_) => true,
230            Poll::Pending => false,
231        }
232    }
233
234    #[tokio::test]
235    async fn abort_in_progress() {
236        let (tx, rx) = oneshot::channel::<()>();
237        let (mut handle, future) = JoinHandle::run(rx);
238        let mut handle = Pin::new(&mut handle);
239        {
240            let mut future = pin!(future);
241            assert!(!is_ready(future.as_mut()));
242            assert!(!is_ready(handle.as_mut()));
243            handle.abort();
244            assert!(is_ready(future.as_mut()));
245            assert!(!is_ready(handle.as_mut()));
246            assert!(!tx.is_closed());
247        }
248        assert!(is_ready(handle.as_mut()));
249        assert!(tx.is_closed());
250    }
251
252    #[tokio::test]
253    async fn abort_complete() {
254        let (tx, rx) = oneshot::channel::<()>();
255        let (mut handle, future) = JoinHandle::run(rx);
256        let mut handle = Pin::new(&mut handle);
257        tx.send(()).unwrap();
258        assert!(!is_ready(handle.as_mut()));
259        {
260            let mut future = pin!(future);
261            assert!(is_ready(future.as_mut()));
262            assert!(!is_ready(handle.as_mut()));
263        }
264        assert!(is_ready(handle.as_mut()));
265        handle.abort();
266        assert!(is_ready(handle.as_mut()));
267    }
268
269    #[tokio::test]
270    async fn abort_dropped() {
271        let (tx, rx) = oneshot::channel::<()>();
272        let (mut handle, future) = JoinHandle::run(rx);
273        let mut handle = Pin::new(&mut handle);
274        drop(future);
275        assert!(is_ready(handle.as_mut()));
276        handle.abort();
277        assert!(is_ready(handle.as_mut()));
278        assert!(tx.is_closed());
279    }
280
281    #[tokio::test]
282    async fn await_completion() {
283        let (tx, rx) = oneshot::channel::<()>();
284        tx.send(()).unwrap();
285        let (handle, future) = JoinHandle::run(rx);
286        let task = tokio::task::spawn(future);
287        handle.await;
288        task.await.unwrap();
289    }
290
291    #[tokio::test]
292    async fn await_abort() {
293        let (tx, rx) = oneshot::channel::<()>();
294        tx.send(()).unwrap();
295        let (handle, future) = JoinHandle::run(rx);
296        handle.abort();
297        let task = tokio::task::spawn(future);
298        handle.await;
299        task.await.unwrap();
300    }
301}