Skip to main content

async_task/
task.rs

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