Skip to main content

scheduler/
executor.rs

1use crate::{Instant, Priority, RunnableMeta, Scheduler, SessionId, Timer};
2use async_task::Runnable;
3use std::{
4    any::Any,
5    future::Future,
6    marker::PhantomData,
7    mem::ManuallyDrop,
8    panic::Location,
9    pin::Pin,
10    rc::Rc,
11    sync::Arc,
12    task::{Context, Poll, Waker},
13    thread::{self, ThreadId},
14    time::Duration,
15};
16
17/// A `!Send` executor pinned to a single session. Tasks spawned on it run in
18/// order on whichever thread drains the dispatch destination supplied at
19/// construction time — typically the main thread for the default session, or
20/// a dedicated OS thread for sessions created by `spawn_dedicated_thread`.
21#[derive(Clone)]
22pub struct LocalExecutor {
23    session_id: SessionId,
24    scheduler: Arc<dyn Scheduler>,
25    // Spawned tasks' schedule callbacks each hold an `Arc` clone of this
26    // closure, so the destination it captures stays alive as long as work
27    // could still land on it.
28    dispatch: Arc<dyn Fn(Runnable<RunnableMeta>) + Send + Sync>,
29    not_send: PhantomData<Rc<()>>,
30}
31
32impl LocalExecutor {
33    /// Constructs a local executor that runs spawned tasks by sending their
34    /// runnables through `dispatch`. The `scheduler` is retained for access to
35    /// clocks, timers, and other scheduler-level services.
36    ///
37    /// For the common case of routing runnables through
38    /// `Scheduler::schedule_local`, callers pass a closure that does exactly
39    /// that. `spawn_dedicated_thread` instead passes a closure that sends to
40    /// the dedicated thread's channel.
41    pub fn new(
42        session_id: SessionId,
43        scheduler: Arc<dyn Scheduler>,
44        dispatch: impl Fn(Runnable<RunnableMeta>) + Send + Sync + 'static,
45    ) -> Self {
46        Self {
47            session_id,
48            scheduler,
49            dispatch: Arc::new(dispatch),
50            not_send: PhantomData,
51        }
52    }
53
54    /// Creates a thread-safe handle that submits `Send` futures to this executor's dispatch destination.
55    pub fn to_send(&self) -> SendLocalExecutor {
56        SendLocalExecutor {
57            _scheduler: self.scheduler.clone(),
58            dispatch: self.dispatch.clone(),
59        }
60    }
61
62    pub fn session_id(&self) -> SessionId {
63        self.session_id
64    }
65
66    pub fn scheduler(&self) -> &Arc<dyn Scheduler> {
67        &self.scheduler
68    }
69
70    /// Whether this executor runs on a [`TestScheduler`](crate::TestScheduler).
71    pub fn is_test(&self) -> bool {
72        self.scheduler.as_test().is_some()
73    }
74
75    #[track_caller]
76    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
77    where
78        F: Future + 'static,
79        F::Output: 'static,
80    {
81        let schedule = self.schedule();
82        let location = Location::caller();
83        let (runnable, task) = spawn_local_with_source_location(
84            future,
85            schedule,
86            RunnableMeta {
87                location,
88                spawned: crate::SpawnTime(Instant::now()),
89            },
90        );
91        runnable.schedule();
92        Task(TaskState::Spawned(task))
93    }
94
95    /// Like [`Self::spawn`], but routes the task's runnables through the
96    /// given dispatch destination instead of this executor's own. The
97    /// destination must deliver runnables to this executor's thread: the
98    /// future is polled and dropped on the spawning thread.
99    #[track_caller]
100    pub fn spawn_with_dispatch<F>(
101        &self,
102        future: F,
103        dispatch: impl Fn(Runnable<RunnableMeta>) + Send + Sync + 'static,
104    ) -> Task<F::Output>
105    where
106        F: Future + 'static,
107        F::Output: 'static,
108    {
109        let location = Location::caller();
110        let (runnable, task) = spawn_local_with_source_location(
111            future,
112            dispatch,
113            RunnableMeta {
114                location,
115                spawned: crate::SpawnTime(Instant::now()),
116            },
117        );
118        runnable.schedule();
119        Task(TaskState::Spawned(task))
120    }
121
122    #[cfg(not(target_family = "wasm"))]
123    pub fn block_on<Fut: Future>(&self, future: Fut) -> Fut::Output {
124        use std::cell::Cell;
125
126        let output = Cell::new(None);
127        let future = async {
128            output.set(Some(future.await));
129        };
130        let mut future = std::pin::pin!(future);
131
132        self.scheduler
133            .block(Some(self.session_id), future.as_mut(), None);
134
135        output.take().expect("block_on future did not complete")
136    }
137
138    /// Block until the future completes or timeout occurs.
139    /// Returns Ok(output) if completed, Err(future) if timed out.
140    #[cfg(not(target_family = "wasm"))]
141    pub fn block_with_timeout<Fut: Future>(
142        &self,
143        timeout: Duration,
144        future: Fut,
145    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
146        use std::cell::Cell;
147
148        let output = Cell::new(None);
149        let mut future = Box::pin(future);
150
151        {
152            let future_ref = &mut future;
153            let wrapper = async {
154                output.set(Some(future_ref.await));
155            };
156            let mut wrapper = std::pin::pin!(wrapper);
157
158            self.scheduler
159                .block(Some(self.session_id), wrapper.as_mut(), Some(timeout));
160        }
161
162        match output.take() {
163            Some(value) => Ok(value),
164            None => Err(future),
165        }
166    }
167
168    #[track_caller]
169    pub fn timer(&self, duration: Duration) -> Timer {
170        self.scheduler.timer(duration)
171    }
172
173    pub fn now(&self) -> Instant {
174        self.scheduler.clock().now()
175    }
176
177    /// Spawn a closure on a fresh session pinned to its own [`LocalExecutor`].
178    /// The closure runs on a new OS thread under `PlatformScheduler`, or on
179    /// the test scheduler's loop under `TestScheduler`.
180    ///
181    /// The returned `Task` represents the dedicated work: dropping it cancels
182    /// the dedicated closure, `.await`ing it yields the closure's return
183    /// value, `.detach()`ing it lets the dedicated work run independently of
184    /// the caller.
185    #[track_caller]
186    pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
187    where
188        F: FnOnce(LocalExecutor) -> Fut + Send + 'static,
189        Fut: Future + 'static,
190        Fut::Output: Send + Sync + 'static,
191    {
192        self.scheduler
193            .clone()
194            .spawn_dedicated(box_dedicated(f))
195            .downcast::<Fut::Output>()
196    }
197
198    fn schedule(&self) -> impl Fn(Runnable<RunnableMeta>) + Send + Sync + 'static {
199        let dispatch = self.dispatch.clone();
200        move |runnable| dispatch(runnable)
201    }
202}
203
204/// Submits `Send` futures from any thread to a [`LocalExecutor`]'s dispatch destination.
205///
206/// Retains the scheduler and dispatch destination independently of the original executor.
207#[derive(Clone)]
208pub struct SendLocalExecutor {
209    _scheduler: Arc<dyn Scheduler>,
210    dispatch: Arc<dyn Fn(Runnable<RunnableMeta>) + Send + Sync>,
211}
212
213impl SendLocalExecutor {
214    /// Spawns a future whose polls run on the original executor's dispatch thread.
215    #[track_caller]
216    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
217    where
218        F: Future + Send + 'static,
219        F::Output: Send + 'static,
220    {
221        let dispatch = self.dispatch.clone();
222        let location = Location::caller();
223        let (runnable, task) = async_task::Builder::new()
224            .metadata(RunnableMeta {
225                location,
226                spawned: crate::SpawnTime(Instant::now()),
227            })
228            .spawn(move |_| future, move |runnable| dispatch(runnable));
229        runnable.schedule();
230        Task(TaskState::Spawned(task))
231    }
232}
233
234/// Boxes the user-supplied dedicated closure into the type-erased shape
235/// expected by [`Scheduler::spawn_dedicated`]. The user's `Fut::Output` is
236/// boxed as `Box<dyn Any + Send + Sync>` on the dedicated side and downcast
237/// back to `Fut::Output` by [`Task::downcast`] in the wrapper.
238fn box_dedicated<F, Fut>(
239    f: F,
240) -> Box<
241    dyn FnOnce(LocalExecutor) -> Pin<Box<dyn Future<Output = Box<dyn Any + Send + Sync>> + 'static>>
242        + Send
243        + 'static,
244>
245where
246    F: FnOnce(LocalExecutor) -> Fut + Send + 'static,
247    Fut: Future + 'static,
248    Fut::Output: Send + Sync + 'static,
249{
250    Box::new(move |executor| {
251        Box::pin(async move { Box::new(f(executor).await) as Box<dyn Any + Send + Sync> })
252    })
253}
254
255#[derive(Clone)]
256pub struct BackgroundExecutor {
257    scheduler: Arc<dyn Scheduler>,
258}
259
260impl BackgroundExecutor {
261    pub fn new(scheduler: Arc<dyn Scheduler>) -> Self {
262        Self { scheduler }
263    }
264
265    #[track_caller]
266    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
267    where
268        F: Future + Send + 'static,
269        F::Output: Send + 'static,
270    {
271        self.spawn_with_priority(Priority::default(), future)
272    }
273
274    #[track_caller]
275    pub fn spawn_with_priority<F>(&self, priority: Priority, future: F) -> Task<F::Output>
276    where
277        F: Future + Send + 'static,
278        F::Output: Send + 'static,
279    {
280        let schedule = self.schedule_with_priority(priority);
281        let location = Location::caller();
282        let (runnable, task) = async_task::Builder::new()
283            .metadata(RunnableMeta {
284                location,
285                spawned: crate::SpawnTime(Instant::now()),
286            })
287            .spawn(move |_| future, schedule);
288        runnable.schedule();
289        Task(TaskState::Spawned(task))
290    }
291
292    /// Spawns a future on a dedicated realtime thread for audio processing.
293    #[track_caller]
294    pub fn spawn_realtime<F>(&self, future: F) -> Task<F::Output>
295    where
296        F: Future + Send + 'static,
297        F::Output: Send + 'static,
298    {
299        let location = Location::caller();
300        let (tx, rx) = flume::bounded::<async_task::Runnable<RunnableMeta>>(1);
301
302        self.scheduler.spawn_realtime(Box::new(move || {
303            while let Ok(runnable) = rx.recv() {
304                runnable.run();
305            }
306        }));
307
308        let (runnable, task) = async_task::Builder::new()
309            .metadata(RunnableMeta {
310                location,
311                spawned: crate::SpawnTime(Instant::now()),
312            })
313            .spawn(
314                move |_| future,
315                move |runnable| {
316                    let _ = tx.send(runnable);
317                },
318            );
319        runnable.schedule();
320        Task(TaskState::Spawned(task))
321    }
322
323    #[track_caller]
324    pub fn timer(&self, duration: Duration) -> Timer {
325        self.scheduler.timer(duration)
326    }
327
328    pub fn now(&self) -> Instant {
329        self.scheduler.clock().now()
330    }
331
332    pub fn scheduler(&self) -> &Arc<dyn Scheduler> {
333        &self.scheduler
334    }
335
336    /// Whether this executor runs on a [`TestScheduler`](crate::TestScheduler).
337    pub fn is_test(&self) -> bool {
338        self.scheduler.as_test().is_some()
339    }
340
341    /// Spawn a closure on a fresh session pinned to its own [`LocalExecutor`].
342    /// The closure runs on a new OS thread under `PlatformScheduler`, or on
343    /// the test scheduler's loop under `TestScheduler`.
344    ///
345    /// The returned `Task` represents the dedicated work: dropping it cancels
346    /// the dedicated closure, `.await`ing it yields the closure's return
347    /// value, `.detach()`ing it lets the dedicated work run independently of
348    /// the caller.
349    #[track_caller]
350    pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
351    where
352        F: FnOnce(LocalExecutor) -> Fut + Send + 'static,
353        Fut: Future + 'static,
354        Fut::Output: Send + Sync + 'static,
355    {
356        self.scheduler
357            .clone()
358            .spawn_dedicated(box_dedicated(f))
359            .downcast::<Fut::Output>()
360    }
361
362    fn schedule_with_priority(
363        &self,
364        priority: Priority,
365    ) -> impl Fn(Runnable<RunnableMeta>) + Send + Sync + 'static {
366        let scheduler = Arc::downgrade(&self.scheduler);
367        move |runnable| {
368            if let Some(scheduler) = scheduler.upgrade() {
369                scheduler.schedule_background_with_priority(runnable, priority);
370            }
371        }
372    }
373}
374
375/// A long-lived handle to one dedicated session: every future spawned on it
376/// runs on that session's single thread, never on the background pool.
377///
378/// Use this when tasks rely on thread-local state being coherent across
379/// spawns, or when the per-thread setup cost of the work is high enough that
380/// it should be paid once. Creating the session costs a thread (on the web, a
381/// worker — expensive); each spawn afterwards is just a channel send.
382///
383/// Dropping the handle cancels the session: queued and future spawns resolve
384/// to cancelled tasks.
385pub struct DedicatedExecutor {
386    sender: flume::Sender<Runnable<RunnableMeta>>,
387    _session: Task<()>,
388}
389
390impl DedicatedExecutor {
391    /// Starts a dedicated session on `executor` and returns a handle that
392    /// spawns futures onto it. Under `TestScheduler` the session runs on the
393    /// deterministic test loop; no real thread is created.
394    #[track_caller]
395    pub fn new(executor: &BackgroundExecutor) -> Self {
396        let (sender, receiver) = flume::unbounded::<Runnable<RunnableMeta>>();
397        let session = executor.spawn_dedicated(move |_executor| async move {
398            while let Ok(runnable) = receiver.recv_async().await {
399                runnable.run();
400            }
401        });
402        Self {
403            sender,
404            _session: session,
405        }
406    }
407
408    /// Spawns a future onto the dedicated session's thread.
409    ///
410    /// The returned task has the usual semantics: dropping it cancels the
411    /// future, `.await`ing it yields the output, `.detach()`ing it lets it
412    /// run to completion on its own.
413    #[track_caller]
414    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
415    where
416        F: Future + Send + 'static,
417        F::Output: Send + 'static,
418    {
419        let sender = self.sender.clone();
420        let (runnable, task) = async_task::Builder::new()
421            .metadata(RunnableMeta::new_with_callers_location())
422            .spawn(
423                move |_| future,
424                move |runnable| {
425                    let _ = sender.send(runnable);
426                },
427            );
428        runnable.schedule();
429        Task(TaskState::Spawned(task))
430    }
431}
432
433/// Task is a primitive that allows work to happen in the background.
434///
435/// It implements [`Future`] so you can `.await` on it.
436///
437/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
438/// the task to continue running, but with no way to return a value.
439#[must_use]
440pub struct Task<T>(TaskState<T>);
441
442enum TaskState<T> {
443    /// A task that is ready to return a value
444    Ready(Option<T>),
445
446    /// A task that is currently running.
447    Spawned(async_task::Task<T, RunnableMeta>),
448
449    /// A typed view of a [`Task<Box<dyn Any + Send + Sync>>`] obtained via
450    /// [`Task::downcast`]. The inner task drives the actual work; the
451    /// downcast layer just unwraps the `Box<dyn Any + Send + Sync>` on poll.
452    Downcast {
453        inner: Box<Task<Box<dyn Any + Send + Sync>>>,
454        marker: PhantomData<fn() -> T>,
455    },
456
457    /// A task whose real handle is delivered later by another thread (see
458    /// [`Task::rendezvous`]). Once delivered, polling replaces this state
459    /// with the delivered task's state.
460    Rendezvous(RendezvousReceiver<T>),
461}
462
463/// State shared between the two halves of a [`Task::rendezvous`] pair.
464enum RendezvousState<T> {
465    /// No task delivered yet; holds the consumer's waker if it polled.
466    Pending(Option<Waker>),
467    /// The producer delivered before the consumer consumed the task.
468    Delivered(Task<T>),
469    /// The consumer was dropped before delivery; delivery cancels the task.
470    Cancelled,
471    /// The consumer was detached before delivery; delivery detaches the task.
472    Detached,
473    /// The consumer took the delivered task.
474    Taken,
475}
476
477pub(crate) struct RendezvousReceiver<T> {
478    shared: Arc<parking_lot::Mutex<RendezvousState<T>>>,
479}
480
481impl<T> RendezvousReceiver<T> {
482    fn poll_take(&self, cx: &mut Context) -> Poll<Task<T>> {
483        let mut state = self.shared.lock();
484        match std::mem::replace(&mut *state, RendezvousState::Taken) {
485            RendezvousState::Delivered(task) => Poll::Ready(task),
486            RendezvousState::Pending(_) => {
487                *state = RendezvousState::Pending(Some(cx.waker().clone()));
488                Poll::Pending
489            }
490            RendezvousState::Cancelled | RendezvousState::Detached | RendezvousState::Taken => {
491                unreachable!("a rendezvous task was polled after its receiver was consumed")
492            }
493        }
494    }
495
496    fn is_ready(&self) -> bool {
497        match &*self.shared.lock() {
498            RendezvousState::Delivered(task) => task.is_ready(),
499            _ => false,
500        }
501    }
502
503    fn detach(self) {
504        let mut state = self.shared.lock();
505        match std::mem::replace(&mut *state, RendezvousState::Detached) {
506            RendezvousState::Delivered(task) => {
507                *state = RendezvousState::Taken;
508                drop(state);
509                task.detach();
510            }
511            // The producer applies the detached disposition on delivery.
512            RendezvousState::Pending(_) => {}
513            RendezvousState::Cancelled | RendezvousState::Detached | RendezvousState::Taken => {
514                unreachable!("a rendezvous task was detached after its receiver was consumed")
515            }
516        }
517    }
518}
519
520impl<T> Drop for RendezvousReceiver<T> {
521    fn drop(&mut self) {
522        let mut state = self.shared.lock();
523        match &*state {
524            RendezvousState::Pending(_) => *state = RendezvousState::Cancelled,
525            RendezvousState::Delivered(_) => {
526                let delivered = std::mem::replace(&mut *state, RendezvousState::Cancelled);
527                drop(state);
528                drop(delivered);
529            }
530            // `Taken`, `Detached`, and `Cancelled` record dispositions that
531            // this drop must not overwrite: the receiver is also dropped as a
532            // normal side effect of taking or detaching.
533            _ => {}
534        }
535    }
536}
537
538pub(crate) struct RendezvousSender<T> {
539    shared: Arc<parking_lot::Mutex<RendezvousState<T>>>,
540}
541
542impl<T> RendezvousSender<T> {
543    /// Hands the real task to the rendezvous, applying the consumer's
544    /// disposition if it was dropped or detached before delivery.
545    pub(crate) fn deliver(self, task: Task<T>) {
546        let mut state = self.shared.lock();
547        match std::mem::replace(&mut *state, RendezvousState::Delivered(task)) {
548            RendezvousState::Pending(waker) => {
549                drop(state);
550                if let Some(waker) = waker {
551                    waker.wake();
552                }
553            }
554            RendezvousState::Cancelled => {
555                let delivered = std::mem::replace(&mut *state, RendezvousState::Cancelled);
556                drop(state);
557                drop(delivered);
558            }
559            RendezvousState::Detached => {
560                let RendezvousState::Delivered(task) =
561                    std::mem::replace(&mut *state, RendezvousState::Detached)
562                else {
563                    unreachable!("the delivered task was just stored");
564                };
565                drop(state);
566                task.detach();
567            }
568            RendezvousState::Delivered(_) | RendezvousState::Taken => {
569                unreachable!("a rendezvous task was delivered twice")
570            }
571        }
572    }
573}
574
575impl<T> Task<T> {
576    /// Creates a new task that will resolve with the value
577    pub fn ready(val: T) -> Self {
578        Task(TaskState::Ready(Some(val)))
579    }
580
581    /// Creates a task whose real handle arrives later through the returned
582    /// sender, typically from another thread. Until delivery the task is
583    /// pending; afterwards it behaves exactly like the delivered task.
584    /// Dropping or detaching the task before delivery is honored on delivery.
585    pub(crate) fn rendezvous() -> (Self, RendezvousSender<T>) {
586        let shared = Arc::new(parking_lot::Mutex::new(RendezvousState::Pending(None)));
587        (
588            Task(TaskState::Rendezvous(RendezvousReceiver {
589                shared: shared.clone(),
590            })),
591            RendezvousSender { shared },
592        )
593    }
594
595    /// Creates a Task from an async_task::Task
596    pub fn from_async_task(task: async_task::Task<T, RunnableMeta>) -> Self {
597        Task(TaskState::Spawned(task))
598    }
599
600    pub fn is_ready(&self) -> bool {
601        match &self.0 {
602            TaskState::Ready(_) => true,
603            TaskState::Spawned(task) => task.is_finished(),
604            TaskState::Downcast { inner, .. } => inner.is_ready(),
605            TaskState::Rendezvous(receiver) => receiver.is_ready(),
606        }
607    }
608
609    /// Detaching a task runs it to completion in the background
610    pub fn detach(self) {
611        match self {
612            Task(TaskState::Ready(_)) => {}
613            Task(TaskState::Spawned(task)) => task.detach(),
614            Task(TaskState::Downcast { inner, .. }) => inner.detach(),
615            Task(TaskState::Rendezvous(receiver)) => receiver.detach(),
616        }
617    }
618
619    /// Converts this task into a fallible task that returns `Option<T>`.
620    pub fn fallible(self) -> FallibleTask<T> {
621        FallibleTask(match self.0 {
622            TaskState::Ready(val) => FallibleTaskState::Ready(val),
623            TaskState::Spawned(task) => FallibleTaskState::Spawned(task.fallible()),
624            TaskState::Downcast { inner, .. } => FallibleTaskState::Downcast {
625                inner: Box::new(inner.fallible()),
626                marker: PhantomData,
627            },
628            TaskState::Rendezvous(receiver) => FallibleTaskState::Rendezvous(receiver),
629        })
630    }
631}
632
633impl Task<Box<dyn Any + Send + Sync>> {
634    /// Reinterprets the boxed output as a concrete `T` via downcast on
635    /// completion. Used by [`LocalExecutor::spawn_dedicated`] and
636    /// [`BackgroundExecutor::spawn_dedicated`] to recover the user closure's
637    /// `Fut::Output` from the dyn-safe [`Scheduler::spawn_dedicated`].
638    ///
639    /// Panics on poll if the inner output is not in fact a `T` -- a logic
640    /// error in whatever produced the inner task, since the downcast type is
641    /// chosen by the caller of `downcast`.
642    pub fn downcast<T: Send + Sync + 'static>(self) -> Task<T> {
643        Task(TaskState::Downcast {
644            inner: Box::new(self),
645            marker: PhantomData,
646        })
647    }
648}
649
650impl<T> std::fmt::Debug for Task<T> {
651    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652        match &self.0 {
653            TaskState::Ready(_) => f.debug_tuple("Task::Ready").finish(),
654            TaskState::Spawned(task) => f.debug_tuple("Task::Spawned").field(task).finish(),
655            TaskState::Downcast { inner, .. } => {
656                f.debug_tuple("Task::Downcast").field(inner).finish()
657            }
658            TaskState::Rendezvous(_) => f.debug_tuple("Task::Rendezvous").finish(),
659        }
660    }
661}
662
663/// A task that returns `Option<T>` instead of panicking when cancelled.
664#[must_use]
665pub struct FallibleTask<T>(FallibleTaskState<T>);
666
667enum FallibleTaskState<T> {
668    /// A task that is ready to return a value
669    Ready(Option<T>),
670
671    /// A task that is currently running (wraps async_task::FallibleTask).
672    Spawned(async_task::FallibleTask<T, RunnableMeta>),
673
674    /// Mirror of [`TaskState::Downcast`] for fallible tasks.
675    Downcast {
676        inner: Box<FallibleTask<Box<dyn Any + Send + Sync>>>,
677        marker: PhantomData<fn() -> T>,
678    },
679
680    /// Mirror of [`TaskState::Rendezvous`] for fallible tasks.
681    Rendezvous(RendezvousReceiver<T>),
682}
683
684impl<T> FallibleTask<T> {
685    /// Creates a new fallible task that will resolve with the value.
686    pub fn ready(val: T) -> Self {
687        FallibleTask(FallibleTaskState::Ready(Some(val)))
688    }
689
690    /// Detaching a task runs it to completion in the background.
691    pub fn detach(self) {
692        match self.0 {
693            FallibleTaskState::Ready(_) => {}
694            FallibleTaskState::Spawned(task) => task.detach(),
695            FallibleTaskState::Downcast { inner, .. } => inner.detach(),
696            FallibleTaskState::Rendezvous(receiver) => receiver.detach(),
697        }
698    }
699}
700
701impl<T: 'static> Future for FallibleTask<T> {
702    type Output = Option<T>;
703
704    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
705        let this = unsafe { self.get_unchecked_mut() };
706        loop {
707            match &mut this.0 {
708                FallibleTaskState::Ready(val) => return Poll::Ready(val.take()),
709                FallibleTaskState::Spawned(task) => return Pin::new(task).poll(cx),
710                FallibleTaskState::Downcast { inner, .. } => {
711                    return match Pin::new(inner.as_mut()).poll(cx) {
712                        Poll::Ready(Some(boxed_any)) => Poll::Ready(Some(
713                            *boxed_any
714                                .downcast::<T>()
715                                .expect("FallibleTask::poll: downcast type mismatch"),
716                        )),
717                        Poll::Ready(None) => Poll::Ready(None),
718                        Poll::Pending => Poll::Pending,
719                    };
720                }
721                FallibleTaskState::Rendezvous(receiver) => match receiver.poll_take(cx) {
722                    Poll::Ready(task) => {
723                        this.0 = task.fallible().0;
724                        continue;
725                    }
726                    Poll::Pending => return Poll::Pending,
727                },
728            }
729        }
730    }
731}
732
733impl<T> std::fmt::Debug for FallibleTask<T> {
734    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
735        match &self.0 {
736            FallibleTaskState::Ready(_) => f.debug_tuple("FallibleTask::Ready").finish(),
737            FallibleTaskState::Spawned(task) => {
738                f.debug_tuple("FallibleTask::Spawned").field(task).finish()
739            }
740            FallibleTaskState::Downcast { inner, .. } => f
741                .debug_tuple("FallibleTask::Downcast")
742                .field(inner)
743                .finish(),
744            FallibleTaskState::Rendezvous(_) => f.debug_tuple("FallibleTask::Rendezvous").finish(),
745        }
746    }
747}
748
749impl<T: 'static> Future for Task<T> {
750    type Output = T;
751
752    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
753        let this = unsafe { self.get_unchecked_mut() };
754        loop {
755            match &mut this.0 {
756                TaskState::Ready(val) => return Poll::Ready(val.take().unwrap()),
757                TaskState::Spawned(task) => return Pin::new(task).poll(cx),
758                TaskState::Downcast { inner, .. } => {
759                    return match Pin::new(inner.as_mut()).poll(cx) {
760                        Poll::Ready(boxed_any) => Poll::Ready(
761                            *boxed_any
762                                .downcast::<T>()
763                                .expect("Task::poll: downcast type mismatch"),
764                        ),
765                        Poll::Pending => Poll::Pending,
766                    };
767                }
768                TaskState::Rendezvous(receiver) => match receiver.poll_take(cx) {
769                    Poll::Ready(task) => {
770                        this.0 = task.0;
771                        continue;
772                    }
773                    Poll::Pending => return Poll::Pending,
774                },
775            }
776        }
777    }
778}
779
780/// Variant of `async_task::spawn_local` that includes the source location of the spawn in panics.
781#[track_caller]
782fn spawn_local_with_source_location<Fut, S>(
783    future: Fut,
784    schedule: S,
785    metadata: RunnableMeta,
786) -> (
787    async_task::Runnable<RunnableMeta>,
788    async_task::Task<Fut::Output, RunnableMeta>,
789)
790where
791    Fut: Future + 'static,
792    Fut::Output: 'static,
793    S: async_task::Schedule<RunnableMeta> + Send + Sync + 'static,
794{
795    #[inline]
796    fn thread_id() -> ThreadId {
797        std::thread_local! {
798            static ID: ThreadId = thread::current().id();
799        }
800        ID.try_with(|id| *id)
801            .unwrap_or_else(|_| thread::current().id())
802    }
803
804    struct Checked<F> {
805        id: ThreadId,
806        inner: ManuallyDrop<F>,
807        location: &'static Location<'static>,
808    }
809
810    impl<F> Drop for Checked<F> {
811        fn drop(&mut self) {
812            assert_eq!(
813                self.id,
814                thread_id(),
815                "local task dropped by a thread that didn't spawn it. Task spawned at {}",
816                self.location
817            );
818            // SAFETY: `inner` is wrapped in `ManuallyDrop`, so this is the only
819            // place it is dropped. The thread check above ensures local futures
820            // are dropped on the thread that created them.
821            unsafe { ManuallyDrop::drop(&mut self.inner) };
822        }
823    }
824
825    impl<F: Future> Future for Checked<F> {
826        type Output = F::Output;
827
828        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
829            // SAFETY: We don't move any fields out of `self`; this mutable
830            // reference is only used to check metadata and to project the pin to
831            // `inner` below.
832            let this = unsafe { self.get_unchecked_mut() };
833            assert!(
834                this.id == thread_id(),
835                "local task polled by a thread that didn't spawn it. Task spawned at {}",
836                this.location
837            );
838            // SAFETY: `inner` is structurally pinned by `Checked`; after
839            // `Checked` is pinned, `inner` is never moved. The thread check
840            // above ensures the local future is only polled by its spawning
841            // thread.
842            unsafe { Pin::new_unchecked(&mut *this.inner).poll(cx) }
843        }
844    }
845
846    let location = metadata.location;
847
848    let future = move |_| Checked {
849        id: thread_id(),
850        inner: ManuallyDrop::new(future),
851        location,
852    };
853
854    let builder = async_task::Builder::new().metadata(metadata);
855    // SAFETY: `Checked` enforces the invariants required by `spawn_unchecked`:
856    // the non-`Send` future is only polled and dropped on the thread that
857    // spawned it.
858    unsafe { builder.spawn_unchecked(future, schedule) }
859}