pistonite_cu/co/handle.rs
1use std::sync::Arc;
2use std::sync::atomic::{AtomicU8, Ordering};
3
4use tokio::task::{JoinError, JoinHandle};
5
6use crate::co::{co_util, runtime};
7
8/// Join handle for async task
9///
10/// This is a wrapper around `tokio`'s `JoinHandle` type.
11pub struct Handle<T>(pub(crate) JoinHandle<T>);
12impl<T> Handle<T> {
13 /// Convert this handle into a [`RobustHandle`].
14 /// with a more robust mechanism for aborting.
15 pub fn into_robust(self) -> RobustHandle<T> {
16 self.into()
17 }
18
19 /// Test if the task has finished and is ready to be joined
20 pub fn is_finished(&self) -> bool {
21 self.0.is_finished()
22 }
23
24 /// Abort the task, trying to `join` or `co_join` an aborted
25 /// task (if it's not already completed) will return an error indicating
26 /// it's already aborted.
27 ///
28 /// If the task was completed before joining, however, it may not
29 /// see this abort call. If you always want `join` to indicate
30 /// aborted, see [`RobustHandle`]
31 pub fn abort(&self) {
32 self.0.abort();
33 }
34
35 /// Return a handle to remotely abort the task
36 pub fn abort_handle(&self) -> AbortHandle {
37 self.0.abort_handle()
38 }
39
40 /// Block the current thread to join the task
41 ///
42 /// Panics are caught by the runtime, and will be returned as an Err.
43 ///
44 /// Will error if the task was aborted or panicked.
45 /// If you want to handle the abort, use [`join_maybe_aborted`](Self::join_maybe_aborted)
46 ///
47 /// # Blocking
48 /// **Do not use this in an async context**, since it will block the runtime,
49 /// and will panic if the thread is currently driving IO.
50 /// Use [`co_join().await`](`Self::co_join`) instead.
51 #[inline]
52 pub fn join(self) -> crate::Result<T> {
53 Self::handle_error(runtime::foreground().block_on(self.0))
54 }
55
56 /// Wait for the task asynchronously
57 ///
58 /// Panics are caught by the runtime, and will be returned as an Err.
59 ///
60 /// Will error if the task was aborted or panicked.
61 /// If you want to handle the abort, use [`co_join_maybe_aborted`](Self::join_maybe_aborted)
62 #[inline]
63 pub async fn co_join(self) -> crate::Result<T> {
64 Self::handle_error(self.0.await)
65 }
66
67 /// Like [`join`](Self::join), but returns `None` if the task was aborted.
68 ///
69 /// Panics are caught by the runtime, and will be returned as an Err.
70 ///
71 /// # Blocking
72 /// **Do not use this in an async context**, since it will block the runtime,
73 /// and will panic if the thread is currently driving IO.
74 /// Use [`co_join_maybe_aborted().await`](`Self::co_join_maybe_aborted`) instead.
75 #[inline]
76 pub fn join_maybe_aborted(self) -> crate::Result<Option<T>> {
77 Self::handle_error_maybe_aborted(runtime::foreground().block_on(self.0))
78 }
79
80 /// Like [`co_join`](Self::co_join), but returns `None` if the task was aborted.
81 #[inline]
82 pub async fn co_join_maybe_aborted(self) -> crate::Result<Option<T>> {
83 Self::handle_error_maybe_aborted(self.0.await)
84 }
85
86 #[inline]
87 fn handle_error(e: Result<T, JoinError>) -> crate::Result<T> {
88 match Self::handle_error_maybe_aborted(e) {
89 Ok(Some(x)) => Ok(x),
90 Ok(None) => crate::bail!("aborted"),
91 Err(e) => Err(e),
92 }
93 }
94
95 fn handle_error_maybe_aborted(e: Result<T, JoinError>) -> crate::Result<Option<T>> {
96 let e = match e {
97 Ok(x) => return Ok(Some(x)),
98 Err(e) => e,
99 };
100 co_util::handle_join_error(e)?;
101 Ok(None)
102 }
103}
104
105pub type AbortHandle = tokio::task::AbortHandle;
106
107/// Join handle for async task, like [`Handle`], with a more robust mechanism for aborting.
108///
109/// This is more robust than [`Handle::abort`], that aborting
110/// a completed task is possible as long as it's not joined yet
111/// (even if `join` has already been called)
112pub struct RobustHandle<T> {
113 inner: Handle<T>,
114 aborted: Arc<AtomicU8>,
115}
116impl<T> From<Handle<T>> for RobustHandle<T> {
117 fn from(value: Handle<T>) -> Self {
118 Self {
119 inner: value,
120 aborted: Arc::new(AtomicU8::new(0)),
121 }
122 }
123}
124
125impl<T> RobustHandle<T> {
126 /// Abort the [`RobustHandle`] pointed to by this handle.
127 ///
128 /// Returns if the abort was successful. If `false` is returned,
129 /// that means the task has already been joined.
130 ///
131 /// See documentation [above](RobustHandle) for more detail
132 pub fn abort(&self) -> bool {
133 let ok = RobustAbortHandle::abort_internal(&self.aborted);
134 if ok {
135 self.inner.abort();
136 }
137 ok
138 }
139
140 /// Return a handle to remotely and robustly abort the task
141 pub fn abort_handle(&self) -> RobustAbortHandle {
142 RobustAbortHandle {
143 inner: self.inner.abort_handle(),
144 aborted: Arc::clone(&self.aborted),
145 }
146 }
147
148 /// Block the current thread to join the task
149 ///
150 /// Panics are caught by the runtime, and will be returned as an Err.
151 ///
152 /// Will error if the task was aborted or panicked.
153 /// If you want to handle the abort, use [`join_maybe_aborted`](Self::join_maybe_aborted)
154 ///
155 /// # Blocking
156 /// **Do not use this in an async context**, since it will block the runtime,
157 /// and will panic if the thread is currently driving IO.
158 /// Use [`co_join().await`](`Self::co_join`) instead.
159 #[inline]
160 pub fn join(self) -> crate::Result<T> {
161 match self.join_maybe_aborted()? {
162 Some(x) => Ok(x),
163 None => crate::bail!("aborted"),
164 }
165 }
166
167 /// Wait for the task asynchronously
168 ///
169 /// Panics are caught by the runtime, and will be returned as an Err.
170 ///
171 /// Will error if the task was aborted or panicked.
172 /// If you want to handle the abort, use [`co_join_maybe_aborted`](Self::join_maybe_aborted)
173 #[inline]
174 pub async fn co_join(self) -> crate::Result<T> {
175 match self.co_join_maybe_aborted().await? {
176 Some(x) => Ok(x),
177 None => crate::bail!("aborted"),
178 }
179 }
180
181 /// Like [`join`](Self::join), but returns `None` if the task was aborted.
182 ///
183 /// Panics are caught by the runtime, and will be returned as an Err.
184 ///
185 /// The task may still be completed with a value if it was aborted.
186 /// If you want to access it, see [`join_maybe_aborted_robust`](Self::join_maybe_aborted_robust)
187 ///
188 /// # Blocking
189 /// **Do not use this in an async context**, since it will block the runtime,
190 /// and will panic if the thread is currently driving IO.
191 /// Use [`co_join_maybe_aborted().await`](`Self::co_join_maybe_aborted`) instead.
192 #[inline]
193 pub fn join_maybe_aborted(self) -> crate::Result<Option<T>> {
194 match self.join_maybe_aborted_robust()? {
195 Ok(x) => Ok(Some(x)),
196 Err(_) => Ok(None),
197 }
198 }
199
200 /// Like [`join`](Self::join), but returns `None` if the task was aborted.
201 ///
202 /// Panics are caught by the runtime, and will be returned as an Err.
203 ///
204 /// The task may still be completed with a value if it was aborted.
205 /// If you want to access it, see [`co_join_maybe_aborted_robust`](Self::co_join_maybe_aborted_robust)
206 #[inline]
207 pub async fn co_join_maybe_aborted(self) -> crate::Result<Option<T>> {
208 match self.co_join_maybe_aborted_robust().await? {
209 Ok(x) => Ok(Some(x)),
210 Err(_) => Ok(None),
211 }
212 }
213
214 /// Block the current thread to join the task
215 ///
216 /// Panics are caught by the runtime, and will be returned as an Err.
217 ///
218 /// # Return Value
219 /// The outer `Result` checks if joining is successful, and the inner
220 /// indicates if the task was aborted. If the task was completed before
221 /// it was aborted, the value is also returned.
222 ///
223 /// - Returns `Ok(Ok(T))` if join is successful and task is not aborted
224 /// - Returns `Ok(Err(None))` if join is successful, and task is aborted
225 /// without finishing
226 /// - Returns `Ok(Err(Some(T)))` if join is successful, and task is aborted,
227 /// but it was finished anyway.
228 /// - Returns `Err` if join fails.
229 ///
230 /// ```rust
231 /// # use pistonite_cu as cu;
232 /// use std::time::Duration;
233 ///
234 /// let handle = cu::co::spawn(async move {
235 /// tokio::time::sleep(Duration::from_millis(100)).await;
236 /// 42
237 /// }).into_robust();
238 ///
239 /// std::thread::sleep(Duration::from_millis(200));
240 /// assert!(handle.abort(), "task is not joined yet, so abort is possible");
241 /// match handle.join_maybe_aborted_robust() {
242 /// Err(e) => panic!("join failed: {e}"),
243 /// Ok(Ok(x)) => {
244 /// assert!(false, "abort was called, so it can't return Ok(Ok)")
245 /// }
246 /// Ok(Err(Some(x))) => {
247 /// assert_eq!(x, 42, "abort was called after task has already produced value 42");
248 /// }
249 /// Ok(Err(None)) => {
250 /// assert!(false, "abort was not called when the task was still running ")
251 /// }
252 /// }
253 /// ```
254 ///
255 /// # Blocking
256 /// Use [`co_join_maybe_aborted_robust().await`](`Self::co_join_maybe_aborted_robust`) instead.
257 pub fn join_maybe_aborted_robust(self) -> crate::Result<Result<T, Option<T>>> {
258 Self::handle_error_maybe_aborted_robust(
259 runtime::foreground().block_on(self.inner.0),
260 &self.aborted,
261 )
262 }
263
264 /// Wait for the task asynchronously
265 ///
266 /// Panics are caught by the runtime, and will be returned as an Err.
267 ///
268 /// # Return Value
269 /// See [`join_maybe_aborted_robust`](Self::join_maybe_aborted_robust)
270 pub async fn co_join_maybe_aborted_robust(self) -> crate::Result<Result<T, Option<T>>> {
271 Self::handle_error_maybe_aborted_robust(self.inner.0.await, &self.aborted)
272 }
273
274 fn handle_error_maybe_aborted_robust(
275 e: Result<T, JoinError>,
276 aborted: &AtomicU8,
277 ) -> crate::Result<Result<T, Option<T>>> {
278 let e = match e {
279 Ok(x) => {
280 if Self::check_aborted(aborted) {
281 return Ok(Err(Some(x)));
282 } else {
283 return Ok(Ok(x));
284 }
285 }
286 Err(e) => e,
287 };
288 if Self::check_aborted(aborted) {
289 return Ok(Err(None));
290 }
291 co_util::handle_join_error(e)?;
292 Ok(Err(None))
293 }
294
295 fn check_aborted(aborted: &AtomicU8) -> bool {
296 loop {
297 let status = aborted.load(Ordering::Relaxed);
298 // aborted
299 if status == 1 {
300 return true;
301 }
302 debug_assert_eq!(0, status, "only the join handle can set the status to 2");
303 if aborted
304 .compare_exchange_weak(0, 2, Ordering::Acquire, Ordering::Relaxed)
305 .is_ok()
306 {
307 return false;
308 }
309 std::hint::spin_loop();
310 }
311 }
312}
313
314pub struct RobustAbortHandle {
315 inner: AbortHandle,
316 aborted: Arc<AtomicU8>,
317}
318impl RobustAbortHandle {
319 /// Abort the [`RobustHandle`] pointed to by this handle.
320 ///
321 /// See documentation for [`RobustHandle`] for more details.
322 ///
323 /// Returns if the abort was successful. If `false` is returned,
324 /// that means the task has already been joined.
325 pub fn abort(&self) -> bool {
326 let ok = Self::abort_internal(&self.aborted);
327 if ok {
328 self.inner.abort();
329 }
330 ok
331 }
332
333 fn abort_internal(aborted: &AtomicU8) -> bool {
334 loop {
335 let status = aborted.load(Ordering::Relaxed);
336 // already joined
337 if status == 2 {
338 return false;
339 }
340 // aborted
341 if status != 0 {
342 debug_assert_eq!(status, 1);
343 return true;
344 }
345 if aborted
346 .compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
347 .is_ok()
348 {
349 return true;
350 }
351 std::hint::spin_loop();
352 }
353 }
354}