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},
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    pub fn session_id(&self) -> SessionId {
55        self.session_id
56    }
57
58    pub fn scheduler(&self) -> &Arc<dyn Scheduler> {
59        &self.scheduler
60    }
61
62    #[track_caller]
63    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
64    where
65        F: Future + 'static,
66        F::Output: 'static,
67    {
68        let dispatch = self.dispatch.clone();
69        let location = Location::caller();
70        let (runnable, task) = spawn_local_with_source_location(
71            future,
72            move |runnable| dispatch(runnable),
73            RunnableMeta {
74                location,
75                spawned: crate::SpawnTime(Instant::now()),
76            },
77        );
78        runnable.schedule();
79        Task(TaskState::Spawned(task))
80    }
81
82    /// Like [`Self::spawn`], but routes the task's runnables through the
83    /// given dispatch destination instead of this executor's own. The
84    /// destination must deliver runnables to this executor's thread: the
85    /// future is polled and dropped on the spawning thread.
86    #[track_caller]
87    pub fn spawn_with_dispatch<F>(
88        &self,
89        future: F,
90        dispatch: impl Fn(Runnable<RunnableMeta>) + Send + Sync + 'static,
91    ) -> Task<F::Output>
92    where
93        F: Future + 'static,
94        F::Output: 'static,
95    {
96        let location = Location::caller();
97        let (runnable, task) = spawn_local_with_source_location(
98            future,
99            dispatch,
100            RunnableMeta {
101                location,
102                spawned: crate::SpawnTime(Instant::now()),
103            },
104        );
105        runnable.schedule();
106        Task(TaskState::Spawned(task))
107    }
108
109    pub fn block_on<Fut: Future>(&self, future: Fut) -> Fut::Output {
110        use std::cell::Cell;
111
112        let output = Cell::new(None);
113        let future = async {
114            output.set(Some(future.await));
115        };
116        let mut future = std::pin::pin!(future);
117
118        self.scheduler
119            .block(Some(self.session_id), future.as_mut(), None);
120
121        output.take().expect("block_on future did not complete")
122    }
123
124    /// Block until the future completes or timeout occurs.
125    /// Returns Ok(output) if completed, Err(future) if timed out.
126    pub fn block_with_timeout<Fut: Future>(
127        &self,
128        timeout: Duration,
129        future: Fut,
130    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
131        use std::cell::Cell;
132
133        let output = Cell::new(None);
134        let mut future = Box::pin(future);
135
136        {
137            let future_ref = &mut future;
138            let wrapper = async {
139                output.set(Some(future_ref.await));
140            };
141            let mut wrapper = std::pin::pin!(wrapper);
142
143            self.scheduler
144                .block(Some(self.session_id), wrapper.as_mut(), Some(timeout));
145        }
146
147        match output.take() {
148            Some(value) => Ok(value),
149            None => Err(future),
150        }
151    }
152
153    #[track_caller]
154    pub fn timer(&self, duration: Duration) -> Timer {
155        self.scheduler.timer(duration)
156    }
157
158    pub fn now(&self) -> Instant {
159        self.scheduler.clock().now()
160    }
161
162    /// Spawn a closure on a fresh session pinned to its own [`LocalExecutor`].
163    /// The closure runs on a new OS thread under `PlatformScheduler`, or on
164    /// the test scheduler's loop under `TestScheduler`.
165    ///
166    /// The returned `Task` represents the dedicated work: dropping it cancels
167    /// the dedicated closure, `.await`ing it yields the closure's return
168    /// value, `.detach()`ing it lets the dedicated work run independently of
169    /// the caller.
170    #[track_caller]
171    pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
172    where
173        F: FnOnce(LocalExecutor) -> Fut + Send + 'static,
174        Fut: Future + 'static,
175        Fut::Output: Send + Sync + 'static,
176    {
177        self.scheduler
178            .clone()
179            .spawn_dedicated(box_dedicated(f))
180            .downcast::<Fut::Output>()
181    }
182}
183
184/// Boxes the user-supplied dedicated closure into the type-erased shape
185/// expected by [`Scheduler::spawn_dedicated`]. The user's `Fut::Output` is
186/// boxed as `Box<dyn Any + Send + Sync>` on the dedicated side and downcast
187/// back to `Fut::Output` by [`Task::downcast`] in the wrapper.
188fn box_dedicated<F, Fut>(
189    f: F,
190) -> Box<
191    dyn FnOnce(LocalExecutor) -> Pin<Box<dyn Future<Output = Box<dyn Any + Send + Sync>> + 'static>>
192        + Send
193        + 'static,
194>
195where
196    F: FnOnce(LocalExecutor) -> Fut + Send + 'static,
197    Fut: Future + 'static,
198    Fut::Output: Send + Sync + 'static,
199{
200    Box::new(move |executor| {
201        Box::pin(async move { Box::new(f(executor).await) as Box<dyn Any + Send + Sync> })
202    })
203}
204
205#[derive(Clone)]
206pub struct BackgroundExecutor {
207    scheduler: Arc<dyn Scheduler>,
208}
209
210impl BackgroundExecutor {
211    pub fn new(scheduler: Arc<dyn Scheduler>) -> Self {
212        Self { scheduler }
213    }
214
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        self.spawn_with_priority(Priority::default(), future)
222    }
223
224    #[track_caller]
225    pub fn spawn_with_priority<F>(&self, priority: Priority, future: F) -> Task<F::Output>
226    where
227        F: Future + Send + 'static,
228        F::Output: Send + 'static,
229    {
230        let scheduler = Arc::downgrade(&self.scheduler);
231        let location = Location::caller();
232        let (runnable, task) = async_task::Builder::new()
233            .metadata(RunnableMeta {
234                location,
235                spawned: crate::SpawnTime(Instant::now()),
236            })
237            .spawn(
238                move |_| future,
239                move |runnable| {
240                    if let Some(scheduler) = scheduler.upgrade() {
241                        scheduler.schedule_background_with_priority(runnable, priority);
242                    }
243                },
244            );
245        runnable.schedule();
246        Task(TaskState::Spawned(task))
247    }
248
249    /// Spawns a future on a dedicated realtime thread for audio processing.
250    #[track_caller]
251    pub fn spawn_realtime<F>(&self, future: F) -> Task<F::Output>
252    where
253        F: Future + Send + 'static,
254        F::Output: Send + 'static,
255    {
256        let location = Location::caller();
257        let (tx, rx) = flume::bounded::<async_task::Runnable<RunnableMeta>>(1);
258
259        self.scheduler.spawn_realtime(Box::new(move || {
260            while let Ok(runnable) = rx.recv() {
261                runnable.run();
262            }
263        }));
264
265        let (runnable, task) = async_task::Builder::new()
266            .metadata(RunnableMeta {
267                location,
268                spawned: crate::SpawnTime(Instant::now()),
269            })
270            .spawn(
271                move |_| future,
272                move |runnable| {
273                    let _ = tx.send(runnable);
274                },
275            );
276        runnable.schedule();
277        Task(TaskState::Spawned(task))
278    }
279
280    #[track_caller]
281    pub fn timer(&self, duration: Duration) -> Timer {
282        self.scheduler.timer(duration)
283    }
284
285    pub fn now(&self) -> Instant {
286        self.scheduler.clock().now()
287    }
288
289    pub fn scheduler(&self) -> &Arc<dyn Scheduler> {
290        &self.scheduler
291    }
292
293    /// Spawn a closure on a fresh session pinned to its own [`LocalExecutor`].
294    /// The closure runs on a new OS thread under `PlatformScheduler`, or on
295    /// the test scheduler's loop under `TestScheduler`.
296    ///
297    /// The returned `Task` represents the dedicated work: dropping it cancels
298    /// the dedicated closure, `.await`ing it yields the closure's return
299    /// value, `.detach()`ing it lets the dedicated work run independently of
300    /// the caller.
301    #[track_caller]
302    pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
303    where
304        F: FnOnce(LocalExecutor) -> Fut + Send + 'static,
305        Fut: Future + 'static,
306        Fut::Output: Send + Sync + 'static,
307    {
308        self.scheduler
309            .clone()
310            .spawn_dedicated(box_dedicated(f))
311            .downcast::<Fut::Output>()
312    }
313}
314
315/// Task is a primitive that allows work to happen in the background.
316///
317/// It implements [`Future`] so you can `.await` on it.
318///
319/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
320/// the task to continue running, but with no way to return a value.
321#[must_use]
322pub struct Task<T>(TaskState<T>);
323
324enum TaskState<T> {
325    /// A task that is ready to return a value
326    Ready(Option<T>),
327
328    /// A task that is currently running.
329    Spawned(async_task::Task<T, RunnableMeta>),
330
331    /// A typed view of a [`Task<Box<dyn Any + Send + Sync>>`] obtained via
332    /// [`Task::downcast`]. The inner task drives the actual work; the
333    /// downcast layer just unwraps the `Box<dyn Any + Send + Sync>` on poll.
334    Downcast {
335        inner: Box<Task<Box<dyn Any + Send + Sync>>>,
336        marker: PhantomData<fn() -> T>,
337    },
338}
339
340impl<T> Task<T> {
341    /// Creates a new task that will resolve with the value
342    pub fn ready(val: T) -> Self {
343        Task(TaskState::Ready(Some(val)))
344    }
345
346    /// Creates a Task from an async_task::Task
347    pub fn from_async_task(task: async_task::Task<T, RunnableMeta>) -> Self {
348        Task(TaskState::Spawned(task))
349    }
350
351    pub fn is_ready(&self) -> bool {
352        match &self.0 {
353            TaskState::Ready(_) => true,
354            TaskState::Spawned(task) => task.is_finished(),
355            TaskState::Downcast { inner, .. } => inner.is_ready(),
356        }
357    }
358
359    /// Detaching a task runs it to completion in the background
360    pub fn detach(self) {
361        match self {
362            Task(TaskState::Ready(_)) => {}
363            Task(TaskState::Spawned(task)) => task.detach(),
364            Task(TaskState::Downcast { inner, .. }) => inner.detach(),
365        }
366    }
367
368    /// Converts this task into a fallible task that returns `Option<T>`.
369    pub fn fallible(self) -> FallibleTask<T> {
370        FallibleTask(match self.0 {
371            TaskState::Ready(val) => FallibleTaskState::Ready(val),
372            TaskState::Spawned(task) => FallibleTaskState::Spawned(task.fallible()),
373            TaskState::Downcast { inner, .. } => FallibleTaskState::Downcast {
374                inner: Box::new(inner.fallible()),
375                marker: PhantomData,
376            },
377        })
378    }
379}
380
381impl Task<Box<dyn Any + Send + Sync>> {
382    /// Reinterprets the boxed output as a concrete `T` via downcast on
383    /// completion. Used by [`LocalExecutor::spawn_dedicated`] and
384    /// [`BackgroundExecutor::spawn_dedicated`] to recover the user closure's
385    /// `Fut::Output` from the dyn-safe [`Scheduler::spawn_dedicated`].
386    ///
387    /// Panics on poll if the inner output is not in fact a `T` -- a logic
388    /// error in whatever produced the inner task, since the downcast type is
389    /// chosen by the caller of `downcast`.
390    pub fn downcast<T: Send + Sync + 'static>(self) -> Task<T> {
391        Task(TaskState::Downcast {
392            inner: Box::new(self),
393            marker: PhantomData,
394        })
395    }
396}
397
398impl<T> std::fmt::Debug for Task<T> {
399    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400        match &self.0 {
401            TaskState::Ready(_) => f.debug_tuple("Task::Ready").finish(),
402            TaskState::Spawned(task) => f.debug_tuple("Task::Spawned").field(task).finish(),
403            TaskState::Downcast { inner, .. } => {
404                f.debug_tuple("Task::Downcast").field(inner).finish()
405            }
406        }
407    }
408}
409
410/// A task that returns `Option<T>` instead of panicking when cancelled.
411#[must_use]
412pub struct FallibleTask<T>(FallibleTaskState<T>);
413
414enum FallibleTaskState<T> {
415    /// A task that is ready to return a value
416    Ready(Option<T>),
417
418    /// A task that is currently running (wraps async_task::FallibleTask).
419    Spawned(async_task::FallibleTask<T, RunnableMeta>),
420
421    /// Mirror of [`TaskState::Downcast`] for fallible tasks.
422    Downcast {
423        inner: Box<FallibleTask<Box<dyn Any + Send + Sync>>>,
424        marker: PhantomData<fn() -> T>,
425    },
426}
427
428impl<T> FallibleTask<T> {
429    /// Creates a new fallible task that will resolve with the value.
430    pub fn ready(val: T) -> Self {
431        FallibleTask(FallibleTaskState::Ready(Some(val)))
432    }
433
434    /// Detaching a task runs it to completion in the background.
435    pub fn detach(self) {
436        match self.0 {
437            FallibleTaskState::Ready(_) => {}
438            FallibleTaskState::Spawned(task) => task.detach(),
439            FallibleTaskState::Downcast { inner, .. } => inner.detach(),
440        }
441    }
442}
443
444impl<T: 'static> Future for FallibleTask<T> {
445    type Output = Option<T>;
446
447    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
448        match unsafe { self.get_unchecked_mut() } {
449            FallibleTask(FallibleTaskState::Ready(val)) => Poll::Ready(val.take()),
450            FallibleTask(FallibleTaskState::Spawned(task)) => Pin::new(task).poll(cx),
451            FallibleTask(FallibleTaskState::Downcast { inner, .. }) => {
452                match Pin::new(inner.as_mut()).poll(cx) {
453                    Poll::Ready(Some(boxed_any)) => Poll::Ready(Some(
454                        *boxed_any
455                            .downcast::<T>()
456                            .expect("FallibleTask::poll: downcast type mismatch"),
457                    )),
458                    Poll::Ready(None) => Poll::Ready(None),
459                    Poll::Pending => Poll::Pending,
460                }
461            }
462        }
463    }
464}
465
466impl<T> std::fmt::Debug for FallibleTask<T> {
467    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468        match &self.0 {
469            FallibleTaskState::Ready(_) => f.debug_tuple("FallibleTask::Ready").finish(),
470            FallibleTaskState::Spawned(task) => {
471                f.debug_tuple("FallibleTask::Spawned").field(task).finish()
472            }
473            FallibleTaskState::Downcast { inner, .. } => f
474                .debug_tuple("FallibleTask::Downcast")
475                .field(inner)
476                .finish(),
477        }
478    }
479}
480
481impl<T: 'static> Future for Task<T> {
482    type Output = T;
483
484    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
485        match unsafe { self.get_unchecked_mut() } {
486            Task(TaskState::Ready(val)) => Poll::Ready(val.take().unwrap()),
487            Task(TaskState::Spawned(task)) => Pin::new(task).poll(cx),
488            Task(TaskState::Downcast { inner, .. }) => match Pin::new(inner.as_mut()).poll(cx) {
489                Poll::Ready(boxed_any) => Poll::Ready(
490                    *boxed_any
491                        .downcast::<T>()
492                        .expect("Task::poll: downcast type mismatch"),
493                ),
494                Poll::Pending => Poll::Pending,
495            },
496        }
497    }
498}
499
500/// Variant of `async_task::spawn_local` that includes the source location of the spawn in panics.
501#[track_caller]
502fn spawn_local_with_source_location<Fut, S>(
503    future: Fut,
504    schedule: S,
505    metadata: RunnableMeta,
506) -> (
507    async_task::Runnable<RunnableMeta>,
508    async_task::Task<Fut::Output, RunnableMeta>,
509)
510where
511    Fut: Future + 'static,
512    Fut::Output: 'static,
513    S: async_task::Schedule<RunnableMeta> + Send + Sync + 'static,
514{
515    #[inline]
516    fn thread_id() -> ThreadId {
517        std::thread_local! {
518            static ID: ThreadId = thread::current().id();
519        }
520        ID.try_with(|id| *id)
521            .unwrap_or_else(|_| thread::current().id())
522    }
523
524    struct Checked<F> {
525        id: ThreadId,
526        inner: ManuallyDrop<F>,
527        location: &'static Location<'static>,
528    }
529
530    impl<F> Drop for Checked<F> {
531        fn drop(&mut self) {
532            assert_eq!(
533                self.id,
534                thread_id(),
535                "local task dropped by a thread that didn't spawn it. Task spawned at {}",
536                self.location
537            );
538            // SAFETY: `inner` is wrapped in `ManuallyDrop`, so this is the only
539            // place it is dropped. The thread check above ensures local futures
540            // are dropped on the thread that created them.
541            unsafe { ManuallyDrop::drop(&mut self.inner) };
542        }
543    }
544
545    impl<F: Future> Future for Checked<F> {
546        type Output = F::Output;
547
548        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
549            // SAFETY: We don't move any fields out of `self`; this mutable
550            // reference is only used to check metadata and to project the pin to
551            // `inner` below.
552            let this = unsafe { self.get_unchecked_mut() };
553            assert!(
554                this.id == thread_id(),
555                "local task polled by a thread that didn't spawn it. Task spawned at {}",
556                this.location
557            );
558            // SAFETY: `inner` is structurally pinned by `Checked`; after
559            // `Checked` is pinned, `inner` is never moved. The thread check
560            // above ensures the local future is only polled by its spawning
561            // thread.
562            unsafe { Pin::new_unchecked(&mut *this.inner).poll(cx) }
563        }
564    }
565
566    let location = metadata.location;
567
568    let future = move |_| Checked {
569        id: thread_id(),
570        inner: ManuallyDrop::new(future),
571        location,
572    };
573
574    let builder = async_task::Builder::new().metadata(metadata);
575    // SAFETY: `Checked` enforces the invariants required by `spawn_unchecked`:
576    // the non-`Send` future is only polled and dropped on the thread that
577    // spawned it.
578    unsafe { builder.spawn_unchecked(future, schedule) }
579}