Skip to main content

async_task/
task.rs

1use core::fmt;
2use core::future::Future;
3use core::marker::PhantomData;
4use core::mem;
5use core::pin::Pin;
6use core::ptr::NonNull;
7use core::sync::atomic::Ordering;
8use core::task::{Context, Poll};
9
10use crate::header::{Header, HeaderWithMetadata};
11use crate::raw::{OpenSchedule, Panic};
12use crate::state::*;
13use crate::ScheduleInfo;
14
15/// A spawned task.
16///
17/// A [`Task`] can be awaited to retrieve the output of its future.
18///
19/// Dropping a [`Task`] cancels it, which means its future won't be polled again. To drop the
20/// [`Task`] handle without canceling it, use [`detach()`][`Task::detach()`] instead. To cancel a
21/// task gracefully and wait until it is fully destroyed, use the [`cancel()`][Task::cancel()]
22/// method.
23///
24/// Note that canceling a task actually wakes it and reschedules one last time. Then, the executor
25/// can destroy the task by simply dropping its [`Runnable`][`super::Runnable`] or by invoking
26/// [`run()`][`super::Runnable::run()`].
27///
28/// # Examples
29///
30/// ```
31/// use smol::{future, Executor};
32/// use std::thread;
33///
34/// let ex = Executor::new();
35///
36/// // Spawn a future onto the executor.
37/// let task = ex.spawn(async {
38///     println!("Hello from a task!");
39///     1 + 2
40/// });
41///
42/// // Run an executor thread.
43/// thread::spawn(move || future::block_on(ex.run(future::pending::<()>())));
44///
45/// // Wait for the task's output.
46/// assert_eq!(future::block_on(task), 3);
47/// ```
48#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
49pub struct Task<T, M = ()> {
50    /// A raw task pointer.
51    pub(crate) ptr: NonNull<()>,
52
53    /// A marker capturing generic types `T` and `M`.
54    pub(crate) _marker: PhantomData<(T, M)>,
55}
56
57unsafe impl<T: Send, M: Send + Sync> Send for Task<T, M> {}
58unsafe impl<T, M: Send + Sync> Sync for Task<T, M> {}
59
60impl<T, M> Unpin for Task<T, M> {}
61
62#[cfg(feature = "std")]
63impl<T, M> std::panic::UnwindSafe for Task<T, M> {}
64#[cfg(feature = "std")]
65impl<T, M> std::panic::RefUnwindSafe for Task<T, M> {}
66
67impl<T, M> Task<T, M> {
68    /// Detaches the task to let it keep running in the background.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use smol::{Executor, Timer};
74    /// use std::time::Duration;
75    ///
76    /// let ex = Executor::new();
77    ///
78    /// // Spawn a daemon future.
79    /// ex.spawn(async {
80    ///     loop {
81    ///         println!("I'm a daemon task looping forever.");
82    ///         Timer::after(Duration::from_secs(1)).await;
83    ///     }
84    /// })
85    /// .detach();
86    /// ```
87    pub fn detach(self) {
88        let this = self;
89        let _out = set_detached::<T>(this.ptr.as_ptr());
90        mem::forget(this);
91    }
92
93    /// Cancels the task and waits for it to stop running.
94    ///
95    /// Returns the task's output if it was completed just before it got canceled, or [`None`] if
96    /// it didn't complete.
97    ///
98    /// While it's possible to simply drop the [`Task`] to cancel it, this is a cleaner way of
99    /// canceling because it also waits for the task to stop running.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// # if cfg!(miri) { return; } // Miri does not support epoll
105    /// use smol::{future, Executor, Timer};
106    /// use std::thread;
107    /// use std::time::Duration;
108    ///
109    /// let ex = Executor::new();
110    ///
111    /// // Spawn a daemon future.
112    /// let task = ex.spawn(async {
113    ///     loop {
114    ///         println!("Even though I'm in an infinite loop, you can still cancel me!");
115    ///         Timer::after(Duration::from_secs(1)).await;
116    ///     }
117    /// });
118    ///
119    /// // Run an executor thread.
120    /// thread::spawn(move || future::block_on(ex.run(future::pending::<()>())));
121    ///
122    /// future::block_on(async {
123    ///     Timer::after(Duration::from_secs(3)).await;
124    ///     task.cancel().await;
125    /// });
126    /// ```
127    pub async fn cancel(self) -> Option<T> {
128        let this = self;
129        set_canceled(this.ptr.as_ptr());
130        this.fallible().await
131    }
132
133    /// Converts this task into a [`FallibleTask`].
134    ///
135    /// Like [`Task`], a fallible task will poll the task's output until it is
136    /// completed or cancelled due to its [`Runnable`][`super::Runnable`] being
137    /// dropped without being run. Resolves to the task's output when completed,
138    /// or [`None`] if it didn't complete.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use smol::{future, Executor};
144    /// use std::thread;
145    ///
146    /// let ex = Executor::new();
147    ///
148    /// // Spawn a future onto the executor.
149    /// let task = ex.spawn(async {
150    ///     println!("Hello from a task!");
151    ///     1 + 2
152    /// })
153    /// .fallible();
154    ///
155    /// // Run an executor thread.
156    /// thread::spawn(move || future::block_on(ex.run(future::pending::<()>())));
157    ///
158    /// // Wait for the task's output.
159    /// assert_eq!(future::block_on(task), Some(3));
160    /// ```
161    ///
162    /// ```
163    /// use smol::future;
164    ///
165    /// // Schedule function which drops the runnable without running it.
166    /// let schedule = move |runnable| drop(runnable);
167    ///
168    /// // Create a task with the future and the schedule function.
169    /// let (runnable, task) = async_task::spawn(async {
170    ///     println!("Hello from a task!");
171    ///     1 + 2
172    /// }, schedule);
173    /// runnable.schedule();
174    ///
175    /// // Wait for the task's output.
176    /// assert_eq!(future::block_on(task.fallible()), None);
177    /// ```
178    pub fn fallible(self) -> FallibleTask<T, M> {
179        FallibleTask { task: self }
180    }
181
182    fn header_with_metadata(&self) -> &HeaderWithMetadata<M> {
183        let ptr = self.ptr.as_ptr();
184        let header = ptr as *const HeaderWithMetadata<M>;
185        unsafe { &*header }
186    }
187
188    /// Returns `true` if the current task is finished.
189    ///
190    /// Note that in a multithreaded environment, this task can change finish immediately after calling this function.
191    pub fn is_finished(&self) -> bool {
192        let ptr = self.ptr.as_ptr();
193        let header = ptr as *const Header;
194
195        unsafe {
196            let state = (*header).state.load(Ordering::Acquire);
197            state & (CLOSED | COMPLETED) != 0
198        }
199    }
200
201    /// Get the metadata associated with this task.
202    ///
203    /// Tasks can be created with a metadata object associated with them; by default, this
204    /// is a `()` value. See the [`Builder::metadata()`] method for more information.
205    pub fn metadata(&self) -> &M {
206        let ptr = self.ptr.as_ptr();
207        let header = ptr as *const HeaderWithMetadata<M>;
208        &unsafe { &*header }.metadata
209    }
210}
211
212/// Puts the task in detached state.
213#[inline(never)]
214fn set_detached<T>(ptr: *const ()) -> Option<Result<T, Panic>> {
215    let header = ptr as *const Header;
216
217    unsafe {
218        // A place where the output will be stored in case it needs to be dropped.
219        let mut output = None;
220
221        // Optimistically assume the `Task` is being detached just after creating the task.
222        // This is a common case so if the `Task` is detached, the overhead of it is only one
223        // compare-exchange operation.
224        if let Err(mut state) = (*header).state.compare_exchange_weak(
225            SCHEDULED | TASK | REFERENCE,
226            SCHEDULED | REFERENCE,
227            Ordering::AcqRel,
228            Ordering::Acquire,
229        ) {
230            loop {
231                // If the task has been completed but not yet closed, that means its output
232                // must be dropped.
233                if state & COMPLETED != 0 && state & CLOSED == 0 {
234                    // Mark the task as closed in order to grab its output.
235                    match (*header).state.compare_exchange_weak(
236                        state,
237                        state | CLOSED,
238                        Ordering::AcqRel,
239                        Ordering::Acquire,
240                    ) {
241                        Ok(_) => {
242                            // Read the output.
243                            output = Some(
244                                ((*header).vtable.get_output(ptr) as *mut Result<T, Panic>).read(),
245                            );
246
247                            // Update the state variable because we're continuing the loop.
248                            state |= CLOSED;
249                        }
250                        Err(s) => state = s,
251                    }
252                } else {
253                    // If this is the last reference to the task and it's not closed, then
254                    // close it and schedule one more time so that its future gets dropped by
255                    // the executor.
256                    let new = if state & (!(REFERENCE - 1) | CLOSED) == 0 {
257                        SCHEDULED | CLOSED | REFERENCE
258                    } else {
259                        state & !TASK
260                    };
261
262                    // A last reference that is still open has to be scheduled so the
263                    // executor drops the future. Count that publication before the swap.
264                    let schedules = state & (!(REFERENCE - 1) | CLOSED) == 0;
265                    if schedules {
266                        ((*header).vtable.begin_schedule)(ptr);
267                    }
268
269                    match (*header).state.compare_exchange_weak(
270                        state,
271                        new,
272                        Ordering::AcqRel,
273                        Ordering::Acquire,
274                    ) {
275                        Ok(_) => {
276                            // If this is the last reference to the task, we need to either
277                            // schedule dropping its future or destroy it.
278                            if schedules {
279                                let _held = OpenSchedule::after_begin(ptr);
280                                ((*header).vtable.before_enqueue)(ptr);
281                                ((*header).vtable.enqueue)(ptr, ScheduleInfo::new(false));
282                            } else if state & !(REFERENCE - 1) == 0 {
283                                ((*header).vtable.destroy)(ptr);
284                            }
285
286                            break;
287                        }
288                        Err(s) => {
289                            if schedules {
290                                ((*header).vtable.end_schedule)(ptr);
291                            }
292                            state = s;
293                        }
294                    }
295                }
296            }
297        }
298
299        output
300    }
301}
302
303/// Puts the task in canceled state.
304#[inline(never)]
305fn set_canceled(ptr: *const ()) {
306    let header = ptr as *const Header;
307
308    unsafe {
309        let mut state = (*header).state.load(Ordering::Acquire);
310
311        loop {
312            // If the task has been completed or closed, it can't be canceled.
313            if state & (COMPLETED | CLOSED) != 0 {
314                break;
315            }
316
317            // If the task is not scheduled nor running, we'll need to schedule it.
318            let schedules = state & (SCHEDULED | RUNNING) == 0;
319            let new = if schedules {
320                (state | SCHEDULED | CLOSED) + REFERENCE
321            } else {
322                state | CLOSED
323            };
324
325            // Count the publication before the swap that commits to enqueueing.
326            if schedules {
327                ((*header).vtable.begin_schedule)(ptr);
328            }
329
330            // Mark the task as closed.
331            match (*header).state.compare_exchange_weak(
332                state,
333                new,
334                Ordering::AcqRel,
335                Ordering::Acquire,
336            ) {
337                Ok(_) => {
338                    // If the task is not scheduled nor running, schedule it one more time so
339                    // that its future gets dropped by the executor.
340                    if schedules {
341                        let _held = OpenSchedule::after_begin(ptr);
342                        ((*header).vtable.before_enqueue)(ptr);
343                        ((*header).vtable.enqueue)(ptr, ScheduleInfo::new(false));
344                    }
345
346                    // Notify the awaiter that the task has been closed.
347                    if state & AWAITER != 0 {
348                        (*header).notify(None);
349                    }
350
351                    break;
352                }
353                Err(s) => {
354                    if schedules {
355                        ((*header).vtable.end_schedule)(ptr);
356                    }
357                    state = s;
358                }
359            }
360        }
361    }
362}
363
364/// Polls the task to retrieve its output.
365///
366/// Returns `Some` if the task has completed or `None` if it was closed.
367///
368/// A task becomes closed in the following cases:
369///
370/// 1. It gets canceled by `Runnable::drop()`, `Task::drop()`, or `Task::cancel()`.
371/// 2. Its output gets awaited by the `Task`.
372/// 3. It panics while polling the future.
373/// 4. It is completed and the `Task` gets dropped.
374fn poll_task<T>(ptr: *const (), cx: &mut Context<'_>) -> Poll<Option<T>> {
375    let header = ptr as *const Header;
376
377    unsafe {
378        let mut state = (*header).state.load(Ordering::Acquire);
379
380        loop {
381            // If the task has been closed, notify the awaiter and return `None`.
382            if state & CLOSED != 0 {
383                // If the task is scheduled or running, we need to wait until its future is
384                // dropped.
385                if state & (SCHEDULED | RUNNING) != 0 {
386                    // Replace the waker with one associated with the current task.
387                    (*header).register(cx.waker());
388
389                    // Reload the state after registering. It is possible changes occurred just
390                    // before registration so we need to check for that.
391                    state = (*header).state.load(Ordering::Acquire);
392
393                    // If the task is still scheduled or running, we need to wait because its
394                    // future is not dropped yet.
395                    if state & (SCHEDULED | RUNNING) != 0 {
396                        return Poll::Pending;
397                    }
398                }
399
400                // Even though the awaiter is most likely the current task, it could also be
401                // another task.
402                (*header).notify(Some(cx.waker()));
403                return Poll::Ready(None);
404            }
405
406            // If the task is not completed, register the current task.
407            if state & COMPLETED == 0 {
408                // Replace the waker with one associated with the current task.
409                (*header).register(cx.waker());
410
411                // Reload the state after registering. It is possible that the task became
412                // completed or closed just before registration so we need to check for that.
413                state = (*header).state.load(Ordering::Acquire);
414
415                // If the task has been closed, restart.
416                if state & CLOSED != 0 {
417                    continue;
418                }
419
420                // If the task is still not completed, we're blocked on it.
421                if state & COMPLETED == 0 {
422                    return Poll::Pending;
423                }
424            }
425
426            // Since the task is now completed, mark it as closed in order to grab its output.
427            match (*header).state.compare_exchange(
428                state,
429                state | CLOSED,
430                Ordering::AcqRel,
431                Ordering::Acquire,
432            ) {
433                Ok(_) => {
434                    // Notify the awaiter. Even though the awaiter is most likely the current
435                    // task, it could also be another task.
436                    if state & AWAITER != 0 {
437                        (*header).notify(Some(cx.waker()));
438                    }
439
440                    // Take the output from the task.
441                    let output = (*header).vtable.get_output(ptr) as *mut Result<T, Panic>;
442                    let output = output.read();
443
444                    // Propagate the panic if the task panicked.
445                    let output = match output {
446                        Ok(output) => output,
447                        Err(panic) => {
448                            #[cfg(feature = "std")]
449                            std::panic::resume_unwind(panic);
450
451                            #[cfg(not(feature = "std"))]
452                            match panic {}
453                        }
454                    };
455
456                    return Poll::Ready(Some(output));
457                }
458                Err(s) => state = s,
459            }
460        }
461    }
462}
463
464impl<T, M> Drop for Task<T, M> {
465    fn drop(&mut self) {
466        let ptr = self.ptr.as_ptr();
467        set_canceled(ptr);
468        set_detached::<T>(ptr);
469    }
470}
471
472impl<T, M> Future for Task<T, M> {
473    type Output = T;
474
475    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
476        match poll_task::<T>(self.ptr.as_ptr(), cx) {
477            Poll::Ready(t) => Poll::Ready(t.expect("Task polled after completion")),
478            Poll::Pending => Poll::Pending,
479        }
480    }
481}
482
483impl<T, M: fmt::Debug> fmt::Debug for Task<T, M> {
484    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485        f.debug_struct("Task")
486            .field("header", self.header_with_metadata())
487            .finish()
488    }
489}
490
491/// A spawned task with a fallible response.
492///
493/// This type behaves like [`Task`], however it produces an `Option<T>` when
494/// polled and will return `None` if the executor dropped its
495/// [`Runnable`][`super::Runnable`] without being run.
496///
497/// This can be useful to avoid the panic produced when polling the `Task`
498/// future if the executor dropped its `Runnable`.
499#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
500pub struct FallibleTask<T, M = ()> {
501    task: Task<T, M>,
502}
503
504impl<T, M> FallibleTask<T, M> {
505    /// Detaches the task to let it keep running in the background.
506    ///
507    /// # Examples
508    ///
509    /// ```
510    /// use smol::{Executor, Timer};
511    /// use std::time::Duration;
512    ///
513    /// let ex = Executor::new();
514    ///
515    /// // Spawn a daemon future.
516    /// ex.spawn(async {
517    ///     loop {
518    ///         println!("I'm a daemon task looping forever.");
519    ///         Timer::after(Duration::from_secs(1)).await;
520    ///     }
521    /// })
522    /// .fallible()
523    /// .detach();
524    /// ```
525    pub fn detach(self) {
526        self.task.detach()
527    }
528
529    /// Cancels the task and waits for it to stop running.
530    ///
531    /// Returns the task's output if it was completed just before it got canceled, or [`None`] if
532    /// it didn't complete.
533    ///
534    /// While it's possible to simply drop the [`Task`] to cancel it, this is a cleaner way of
535    /// canceling because it also waits for the task to stop running.
536    ///
537    /// # Examples
538    ///
539    /// ```
540    /// # if cfg!(miri) { return; } // Miri does not support epoll
541    /// use smol::{future, Executor, Timer};
542    /// use std::thread;
543    /// use std::time::Duration;
544    ///
545    /// let ex = Executor::new();
546    ///
547    /// // Spawn a daemon future.
548    /// let task = ex.spawn(async {
549    ///     loop {
550    ///         println!("Even though I'm in an infinite loop, you can still cancel me!");
551    ///         Timer::after(Duration::from_secs(1)).await;
552    ///     }
553    /// })
554    /// .fallible();
555    ///
556    /// // Run an executor thread.
557    /// thread::spawn(move || future::block_on(ex.run(future::pending::<()>())));
558    ///
559    /// future::block_on(async {
560    ///     Timer::after(Duration::from_secs(3)).await;
561    ///     task.cancel().await;
562    /// });
563    /// ```
564    pub async fn cancel(self) -> Option<T> {
565        self.task.cancel().await
566    }
567
568    /// Returns `true` if the current task is finished.
569    ///
570    /// Note that in a multithreaded environment, this task can change finish immediately after calling this function.
571    pub fn is_finished(&self) -> bool {
572        self.task.is_finished()
573    }
574}
575
576impl<T, M> Future for FallibleTask<T, M> {
577    type Output = Option<T>;
578
579    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
580        poll_task::<T>(self.task.ptr.as_ptr(), cx)
581    }
582}
583
584impl<T, M: fmt::Debug> fmt::Debug for FallibleTask<T, M> {
585    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586        f.debug_struct("FallibleTask")
587            .field("header", self.task.header_with_metadata())
588            .finish()
589    }
590}