Skip to main content

gpui_core/
executor.rs

1use crate::{ActivityGuard, App, PlatformDispatcher, profiler};
2use async_task::Runnable;
3use futures::channel::mpsc;
4use smol::prelude::*;
5use std::mem::ManuallyDrop;
6use std::panic::Location;
7use std::thread::{self, ThreadId};
8use std::{
9    fmt::Debug,
10    marker::PhantomData,
11    mem,
12    num::NonZeroUsize,
13    pin::Pin,
14    rc::Rc,
15    sync::{
16        Arc,
17        atomic::{AtomicUsize, Ordering::SeqCst},
18    },
19    task::{Context, Poll},
20    time::{Duration, Instant},
21};
22use util::TryFutureExt;
23use waker_fn::waker_fn;
24
25#[cfg(any(test, feature = "test-support"))]
26use rand::rngs::StdRng;
27
28/// A pointer to the executor that is currently running,
29/// for spawning background tasks.
30#[derive(Clone)]
31pub struct BackgroundExecutor {
32    #[doc(hidden)]
33    pub dispatcher: Arc<dyn PlatformDispatcher>,
34}
35
36/// A pointer to the executor that is currently running,
37/// for spawning tasks on the main thread.
38///
39/// This is intentionally `!Send` via the `not_send` marker field. This is because
40/// `ForegroundExecutor::spawn` does not require `Send` but checks at runtime that the future is
41/// only polled from the same thread it was spawned from. These checks would fail when spawning
42/// foreground tasks from from background threads.
43#[derive(Clone)]
44pub struct ForegroundExecutor {
45    #[doc(hidden)]
46    pub dispatcher: Arc<dyn PlatformDispatcher>,
47    not_send: PhantomData<Rc<()>>,
48}
49
50/// Task is a primitive that allows work to happen in the background.
51///
52/// It implements [`Future`] so you can `.await` on it.
53///
54/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
55/// the task to continue running, but with no way to return a value.
56#[must_use]
57#[derive(Debug)]
58pub struct Task<T>(TaskState<T>);
59
60#[derive(Debug)]
61enum TaskState<T> {
62    /// A task that is ready to return a value
63    Ready(Option<T>),
64
65    /// A task that is currently running.
66    Spawned(async_task::Task<T>),
67}
68
69impl<T> Task<T> {
70    /// Creates a new task that will resolve with the value
71    pub fn ready(val: T) -> Self {
72        Task(TaskState::Ready(Some(val)))
73    }
74
75    /// Detaching a task runs it to completion in the background
76    pub fn detach(self) {
77        match self {
78            Task(TaskState::Ready(_)) => {}
79            Task(TaskState::Spawned(task)) => task.detach(),
80        }
81    }
82}
83
84impl<E, T> Task<Result<T, E>>
85where
86    T: 'static,
87    E: 'static + Debug,
88{
89    /// Run the task to completion in the background and log any
90    /// errors that occur.
91    #[track_caller]
92    pub fn detach_and_log_err(self, cx: &App) {
93        let location = core::panic::Location::caller();
94        cx.foreground_executor()
95            .spawn(self.log_tracked_err(*location))
96            .detach();
97    }
98}
99
100impl<T> Future for Task<T> {
101    type Output = T;
102
103    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
104        match unsafe { self.get_unchecked_mut() } {
105            Task(TaskState::Ready(val)) => Poll::Ready(val.take().unwrap()),
106            Task(TaskState::Spawned(task)) => task.poll(cx),
107        }
108    }
109}
110
111/// The scheduling priority for a task.
112#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
113pub enum TaskPriority {
114    /// Work that should be preferred over ordinary background work.
115    High,
116    /// Ordinary background work. This is the default and matches existing behavior.
117    #[default]
118    Medium,
119    /// Work that can run after ordinary background work.
120    Low,
121}
122
123/// A task label is an opaque identifier that you can use to refer to a task in tests.
124#[derive(Clone, Copy, Debug, Eq)]
125pub struct TaskLabel {
126    id: NonZeroUsize,
127    priority: TaskPriority,
128}
129
130impl PartialEq for TaskLabel {
131    fn eq(&self, other: &Self) -> bool {
132        self.id == other.id
133    }
134}
135
136impl std::hash::Hash for TaskLabel {
137    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
138        self.id.hash(state);
139    }
140}
141
142impl Default for TaskLabel {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl TaskLabel {
149    /// Construct a new task label.
150    pub fn new() -> Self {
151        Self::new_with_priority(TaskPriority::Medium)
152    }
153
154    fn new_with_priority(priority: TaskPriority) -> Self {
155        static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
156        Self {
157            id: NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap(),
158            priority,
159        }
160    }
161
162    #[doc(hidden)]
163    pub fn priority(self) -> TaskPriority {
164        self.priority
165    }
166}
167
168type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
169
170type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
171
172struct TimedFuture<F> {
173    inner: F,
174    location: &'static Location<'static>,
175    started: bool,
176    timing: Option<profiler::TaskTimingHandle>,
177}
178
179impl<F> TimedFuture<F> {
180    fn new(inner: F, location: &'static Location<'static>) -> Self {
181        Self {
182            inner,
183            location,
184            started: false,
185            timing: None,
186        }
187    }
188}
189
190impl<F: Future> Future for TimedFuture<F> {
191    type Output = F::Output;
192
193    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
194        let this = unsafe { self.get_unchecked_mut() };
195        if !this.started {
196            this.started = true;
197            this.timing = profiler::record_start(this.location);
198        }
199
200        let result = unsafe { Pin::new_unchecked(&mut this.inner) }.poll(cx);
201        if result.is_ready()
202            && let Some(timing) = this.timing.take()
203        {
204            profiler::record_end(timing, profiler::TaskTimingStatus::Completed);
205        }
206        result
207    }
208}
209
210impl<F> Drop for TimedFuture<F> {
211    fn drop(&mut self) {
212        if let Some(timing) = self.timing.take() {
213            profiler::record_end(timing, profiler::TaskTimingStatus::Cancelled);
214        }
215    }
216}
217
218/// BackgroundExecutor lets you run things on background threads.
219/// In production this is a thread pool with no ordering guarantees.
220/// In tests this is simulated by running tasks one by one in a deterministic
221/// (but arbitrary) order controlled by the `SEED` environment variable.
222impl BackgroundExecutor {
223    #[doc(hidden)]
224    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
225        Self { dispatcher }
226    }
227
228    /// Prevents App Nap-style throttling while the returned guard is held.
229    ///
230    /// This does not prevent the system from entering idle sleep.
231    pub fn prevent_app_nap(&self, reason: &str) -> ActivityGuard {
232        self.dispatcher.prevent_app_nap(reason)
233    }
234
235    /// Enqueues the given future to be run to completion on a background thread.
236    #[track_caller]
237    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
238    where
239        R: Send + 'static,
240    {
241        self.spawn_internal::<R>(Box::pin(future), None)
242    }
243
244    /// Enqueues the given future to be run to completion on a background thread.
245    /// The given label can be used to control the priority of the task in tests.
246    #[track_caller]
247    pub fn spawn_labeled<R>(
248        &self,
249        label: TaskLabel,
250        future: impl Future<Output = R> + Send + 'static,
251    ) -> Task<R>
252    where
253        R: Send + 'static,
254    {
255        self.spawn_internal::<R>(Box::pin(future), Some(label))
256    }
257
258    #[track_caller]
259    #[doc(hidden)]
260    pub fn spawn_with_priority<R>(
261        &self,
262        priority: TaskPriority,
263        future: impl Future<Output = R> + Send + 'static,
264    ) -> Task<R>
265    where
266        R: Send + 'static,
267    {
268        self.spawn_internal::<R>(
269            Box::pin(future),
270            Some(TaskLabel::new_with_priority(priority)),
271        )
272    }
273
274    #[track_caller]
275    fn spawn_internal<R: Send + 'static>(
276        &self,
277        future: AnyFuture<R>,
278        label: Option<TaskLabel>,
279    ) -> Task<R> {
280        let dispatcher = self.dispatcher.clone();
281        let future = TimedFuture::new(future, Location::caller());
282        let (runnable, task) =
283            async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
284        runnable.schedule();
285        Task(TaskState::Spawned(task))
286    }
287
288    /// Used by the test harness to run an async test in a synchronous fashion.
289    #[cfg(any(test, feature = "test-support"))]
290    #[track_caller]
291    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
292        if let Ok(value) = self.block_internal(false, future, None) {
293            value
294        } else {
295            unreachable!()
296        }
297    }
298
299    /// Block the current thread until the given future resolves.
300    /// Consider using `block_with_timeout` instead.
301    pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
302        if let Ok(value) = self.block_internal(true, future, None) {
303            value
304        } else {
305            unreachable!()
306        }
307    }
308
309    #[cfg(not(any(test, feature = "test-support")))]
310    pub(crate) fn block_internal<Fut: Future>(
311        &self,
312        _background_only: bool,
313        future: Fut,
314        timeout: Option<Duration>,
315    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
316        use std::time::Instant;
317
318        let mut future = Box::pin(future);
319        if timeout == Some(Duration::ZERO) {
320            return Err(future);
321        }
322        let deadline = timeout.map(|timeout| Instant::now() + timeout);
323
324        let unparker = self.dispatcher.unparker();
325        let waker = waker_fn(move || {
326            unparker.unpark();
327        });
328        let mut cx = std::task::Context::from_waker(&waker);
329
330        loop {
331            match future.as_mut().poll(&mut cx) {
332                Poll::Ready(result) => return Ok(result),
333                Poll::Pending => {
334                    let timeout =
335                        deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
336                    if !self.dispatcher.park(timeout)
337                        && deadline.is_some_and(|deadline| deadline < Instant::now())
338                    {
339                        return Err(future);
340                    }
341                }
342            }
343        }
344    }
345
346    #[cfg(any(test, feature = "test-support"))]
347    #[track_caller]
348    pub(crate) fn block_internal<Fut: Future>(
349        &self,
350        background_only: bool,
351        future: Fut,
352        timeout: Option<Duration>,
353    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
354        use std::sync::atomic::AtomicBool;
355
356        let mut future = Box::pin(future);
357        if timeout == Some(Duration::ZERO) {
358            return Err(future);
359        }
360        let Some(dispatcher) = self.dispatcher.as_test() else {
361            return Err(future);
362        };
363
364        let mut max_ticks = if timeout.is_some() {
365            dispatcher.gen_block_on_ticks()
366        } else {
367            usize::MAX
368        };
369        let unparker = self.dispatcher.unparker();
370        let awoken = Arc::new(AtomicBool::new(false));
371        let waker = waker_fn({
372            let awoken = awoken.clone();
373            move || {
374                awoken.store(true, SeqCst);
375                unparker.unpark();
376            }
377        });
378        let mut cx = std::task::Context::from_waker(&waker);
379
380        loop {
381            match future.as_mut().poll(&mut cx) {
382                Poll::Ready(result) => return Ok(result),
383                Poll::Pending => {
384                    if max_ticks == 0 {
385                        return Err(future);
386                    }
387                    max_ticks -= 1;
388
389                    if !dispatcher.tick(background_only) {
390                        if awoken.swap(false, SeqCst) {
391                            continue;
392                        }
393
394                        if !dispatcher.parking_allowed() {
395                            if dispatcher.advance_clock_to_next_delayed() {
396                                continue;
397                            }
398                            let mut backtrace_message = String::new();
399                            let mut waiting_message = String::new();
400                            if let Some(backtrace) = dispatcher.waiting_backtrace() {
401                                backtrace_message =
402                                    format!("\nbacktrace of waiting future:\n{:?}", backtrace);
403                            }
404                            if let Some(waiting_hint) = dispatcher.waiting_hint() {
405                                waiting_message = format!("\n  waiting on: {}\n", waiting_hint);
406                            }
407                            panic!(
408                                "parked with nothing left to run{waiting_message}{backtrace_message}",
409                            )
410                        }
411                        self.dispatcher.park(None);
412                    }
413                }
414            }
415        }
416    }
417
418    /// Block the current thread until the given future resolves
419    /// or `duration` has elapsed.
420    pub fn block_with_timeout<Fut: Future>(
421        &self,
422        duration: Duration,
423        future: Fut,
424    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
425        self.block_internal(true, future, Some(duration))
426    }
427
428    /// Scoped lets you start a number of tasks and waits
429    /// for all of them to complete before returning.
430    pub async fn scoped<'scope, F>(&self, scheduler: F)
431    where
432        F: FnOnce(&mut Scope<'scope>),
433    {
434        let mut scope = Scope::new(self.clone());
435        (scheduler)(&mut scope);
436        let spawned = mem::take(&mut scope.futures)
437            .into_iter()
438            .map(|f| self.spawn(f))
439            .collect::<Vec<_>>();
440        for task in spawned {
441            task.await;
442        }
443    }
444
445    /// Get the current time.
446    ///
447    /// Calling this instead of `std::time::Instant::now` allows the use
448    /// of fake timers in tests.
449    pub fn now(&self) -> Instant {
450        self.dispatcher.now()
451    }
452
453    /// Returns a task that will complete after the given duration.
454    /// Depending on other concurrent tasks the elapsed duration may be longer
455    /// than requested.
456    #[track_caller]
457    pub fn timer(&self, duration: Duration) -> Task<()> {
458        if duration.is_zero() {
459            return Task::ready(());
460        }
461        let future = TimedFuture::new(Box::pin(async move {}), Location::caller());
462        let (runnable, task) = async_task::spawn(future, {
463            let dispatcher = self.dispatcher.clone();
464            move |runnable| dispatcher.dispatch_after(duration, runnable)
465        });
466        runnable.schedule();
467        Task(TaskState::Spawned(task))
468    }
469
470    /// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
471    #[cfg(any(test, feature = "test-support"))]
472    pub fn start_waiting(&self) {
473        self.dispatcher.as_test().unwrap().start_waiting();
474    }
475
476    /// in tests, removes the debugging data added by start_waiting
477    #[cfg(any(test, feature = "test-support"))]
478    pub fn finish_waiting(&self) {
479        self.dispatcher.as_test().unwrap().finish_waiting();
480    }
481
482    /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable)
483    #[cfg(any(test, feature = "test-support"))]
484    pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
485        self.dispatcher.as_test().unwrap().simulate_random_delay()
486    }
487
488    /// in tests, indicate that a given task from `spawn_labeled` should run after everything else
489    #[cfg(any(test, feature = "test-support"))]
490    pub fn deprioritize(&self, task_label: TaskLabel) {
491        self.dispatcher.as_test().unwrap().deprioritize(task_label)
492    }
493
494    /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready.
495    #[cfg(any(test, feature = "test-support"))]
496    pub fn advance_clock(&self, duration: Duration) {
497        self.dispatcher.as_test().unwrap().advance_clock(duration)
498    }
499
500    /// in tests, run one task.
501    #[cfg(any(test, feature = "test-support"))]
502    pub fn tick(&self) -> bool {
503        self.dispatcher.as_test().unwrap().tick(false)
504    }
505
506    /// in tests, run all tasks that are ready to run. If after doing so
507    /// the test still has outstanding tasks, this will panic. (See also [`Self::allow_parking`])
508    #[cfg(any(test, feature = "test-support"))]
509    pub fn run_until_parked(&self) {
510        self.dispatcher.as_test().unwrap().run_until_parked()
511    }
512
513    /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks.
514    /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that
515    /// do take real async time to run.
516    #[cfg(any(test, feature = "test-support"))]
517    pub fn allow_parking(&self) {
518        self.dispatcher.as_test().unwrap().allow_parking();
519    }
520
521    /// undoes the effect of [`Self::allow_parking`].
522    #[cfg(any(test, feature = "test-support"))]
523    pub fn forbid_parking(&self) {
524        self.dispatcher.as_test().unwrap().forbid_parking();
525    }
526
527    /// adds detail to the "parked with nothing let to run" message.
528    #[cfg(any(test, feature = "test-support"))]
529    pub fn set_waiting_hint(&self, msg: Option<String>) {
530        self.dispatcher.as_test().unwrap().set_waiting_hint(msg);
531    }
532
533    /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable
534    #[cfg(any(test, feature = "test-support"))]
535    pub fn rng(&self) -> StdRng {
536        self.dispatcher.as_test().unwrap().rng()
537    }
538
539    /// How many CPUs are available to the dispatcher.
540    pub fn num_cpus(&self) -> usize {
541        #[cfg(any(test, feature = "test-support"))]
542        return 4;
543
544        #[cfg(not(any(test, feature = "test-support")))]
545        return num_cpus::get();
546    }
547
548    /// Whether we're on the main thread.
549    pub fn is_main_thread(&self) -> bool {
550        self.dispatcher.is_main_thread()
551    }
552
553    #[cfg(any(test, feature = "test-support"))]
554    /// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
555    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
556        self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
557    }
558}
559
560/// ForegroundExecutor runs things on the main thread.
561impl ForegroundExecutor {
562    /// Creates a new ForegroundExecutor from the given PlatformDispatcher.
563    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
564        Self {
565            dispatcher,
566            not_send: PhantomData,
567        }
568    }
569
570    /// Enqueues the given Task to run on the main thread at some point in the future.
571    #[track_caller]
572    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
573    where
574        R: 'static,
575    {
576        let dispatcher = self.dispatcher.clone();
577
578        #[track_caller]
579        fn inner<R: 'static>(
580            dispatcher: Arc<dyn PlatformDispatcher>,
581            future: AnyLocalFuture<R>,
582        ) -> Task<R> {
583            let future = TimedFuture::new(future, Location::caller());
584            let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
585                dispatcher.dispatch_on_main_thread(runnable)
586            });
587            runnable.schedule();
588            Task(TaskState::Spawned(task))
589        }
590        inner::<R>(dispatcher, Box::pin(future))
591    }
592}
593
594/// Variant of `async_task::spawn_local` that includes the source location of the spawn in panics.
595///
596/// Copy-modified from:
597/// <https://github.com/smol-rs/async-task/blob/ca9dbe1db9c422fd765847fa91306e30a6bb58a9/src/runnable.rs#L405>
598#[track_caller]
599fn spawn_local_with_source_location<Fut, S>(
600    future: Fut,
601    schedule: S,
602) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
603where
604    Fut: Future + 'static,
605    Fut::Output: 'static,
606    S: async_task::Schedule<()> + Send + Sync + 'static,
607{
608    #[inline]
609    fn thread_id() -> ThreadId {
610        std::thread_local! {
611            static ID: ThreadId = thread::current().id();
612        }
613        ID.try_with(|id| *id)
614            .unwrap_or_else(|_| thread::current().id())
615    }
616
617    struct Checked<F> {
618        id: ThreadId,
619        inner: ManuallyDrop<F>,
620        location: &'static Location<'static>,
621    }
622
623    impl<F> Drop for Checked<F> {
624        fn drop(&mut self) {
625            assert!(
626                self.id == thread_id(),
627                "local task dropped by a thread that didn't spawn it. Task spawned at {}",
628                self.location
629            );
630            unsafe {
631                ManuallyDrop::drop(&mut self.inner);
632            }
633        }
634    }
635
636    impl<F: Future> Future for Checked<F> {
637        type Output = F::Output;
638
639        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
640            assert!(
641                self.id == thread_id(),
642                "local task polled by a thread that didn't spawn it. Task spawned at {}",
643                self.location
644            );
645            unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
646        }
647    }
648
649    // Wrap the future into one that checks which thread it's on.
650    let future = Checked {
651        id: thread_id(),
652        inner: ManuallyDrop::new(future),
653        location: Location::caller(),
654    };
655
656    unsafe { async_task::spawn_unchecked(future, schedule) }
657}
658
659/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
660pub struct Scope<'a> {
661    executor: BackgroundExecutor,
662    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
663    tx: Option<mpsc::Sender<()>>,
664    rx: mpsc::Receiver<()>,
665    lifetime: PhantomData<&'a ()>,
666}
667
668impl<'a> Scope<'a> {
669    fn new(executor: BackgroundExecutor) -> Self {
670        let (tx, rx) = mpsc::channel(1);
671        Self {
672            executor,
673            tx: Some(tx),
674            rx,
675            futures: Default::default(),
676            lifetime: PhantomData,
677        }
678    }
679
680    /// How many CPUs are available to the dispatcher.
681    pub fn num_cpus(&self) -> usize {
682        self.executor.num_cpus()
683    }
684
685    /// Spawn a future into this scope.
686    pub fn spawn<F>(&mut self, f: F)
687    where
688        F: Future<Output = ()> + Send + 'a,
689    {
690        let tx = self.tx.clone().unwrap();
691
692        // SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
693        // dropping this `Scope` blocks until all of the futures have resolved.
694        let f = unsafe {
695            mem::transmute::<
696                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
697                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
698            >(Box::pin(async move {
699                f.await;
700                drop(tx);
701            }))
702        };
703        self.futures.push(f);
704    }
705}
706
707impl Drop for Scope<'_> {
708    fn drop(&mut self) {
709        self.tx.take().unwrap();
710
711        // Wait until the channel is closed, which means that all of the spawned
712        // futures have resolved.
713        self.executor.block(self.rx.next());
714    }
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720    use crate::{TestAppContext, profiler};
721    use std::sync::{
722        Arc, Mutex,
723        atomic::{AtomicBool, Ordering},
724    };
725
726    fn reset_profiler() {
727        profiler::set_enabled(false);
728    }
729
730    fn collect_timings() -> Vec<profiler::ThreadTimingsDelta> {
731        let mut collector = profiler::ProfilingCollector::new(Instant::now());
732        profiler::profiler_collect_timings(&mut collector)
733    }
734
735    #[test]
736    fn profiler_disabled_records_nothing() {
737        let _guard = profiler::test_guard();
738        reset_profiler();
739
740        let mut cx = TestAppContext::single();
741        cx.executor().spawn(async {}).detach();
742        cx.run_until_parked();
743
744        assert!(collect_timings().is_empty());
745        reset_profiler();
746    }
747
748    #[test]
749    fn profiler_enabled_records_background_task_timing() {
750        let _guard = profiler::test_guard();
751        reset_profiler();
752        profiler::set_enabled(true);
753
754        let mut cx = TestAppContext::single();
755        let mut collector = profiler::ProfilingCollector::new(Instant::now());
756        cx.executor().spawn(async {}).detach();
757        cx.run_until_parked();
758
759        let deltas = profiler::profiler_collect_timings(&mut collector);
760        let timing = deltas
761            .iter()
762            .flat_map(|delta| &delta.new_timings)
763            .next()
764            .expect("expected a recorded background task timing");
765        assert_eq!(timing.status, profiler::TaskTimingStatus::Completed);
766
767        reset_profiler();
768    }
769
770    #[test]
771    fn profiler_enabled_records_foreground_task_location() {
772        let _guard = profiler::test_guard();
773        reset_profiler();
774        profiler::set_enabled(true);
775
776        let mut cx = TestAppContext::single();
777        let mut collector = profiler::ProfilingCollector::new(Instant::now());
778        cx.foreground_executor().spawn(async {}).detach();
779        cx.run_until_parked();
780
781        let deltas = profiler::profiler_collect_timings(&mut collector);
782        let timing = deltas
783            .iter()
784            .flat_map(|delta| &delta.new_timings)
785            .find(|timing| timing.location.file.as_ref().contains("executor.rs"))
786            .expect("expected foreground task timing to include its source location");
787        assert!(timing.location.line > 0);
788        assert!(timing.location.column > 0);
789
790        reset_profiler();
791    }
792
793    #[test]
794    fn profiler_timing_start_is_first_poll_not_spawn() {
795        let _guard = profiler::test_guard();
796        reset_profiler();
797        profiler::set_enabled(true);
798
799        let mut cx = TestAppContext::single();
800        let mut collector = profiler::ProfilingCollector::new(Instant::now());
801        cx.executor().spawn(async {}).detach();
802        assert!(profiler::profiler_collect_timings(&mut collector).is_empty());
803
804        cx.run_until_parked();
805        assert!(!profiler::profiler_collect_timings(&mut collector).is_empty());
806
807        reset_profiler();
808    }
809
810    #[test]
811    fn baseline_foreground_task_runs_on_main_thread() {
812        let mut cx = TestAppContext::single();
813        let executor = cx.executor();
814        let ran_on_main_thread = Arc::new(AtomicBool::new(false));
815
816        cx.foreground_executor()
817            .spawn({
818                let ran_on_main_thread = ran_on_main_thread.clone();
819                async move {
820                    ran_on_main_thread.store(executor.is_main_thread(), Ordering::SeqCst);
821                }
822            })
823            .detach();
824        cx.run_until_parked();
825
826        assert!(ran_on_main_thread.load(Ordering::SeqCst));
827    }
828
829    #[test]
830    fn baseline_background_task_completes_after_run_until_parked() {
831        let mut cx = TestAppContext::single();
832        let completed = Arc::new(AtomicBool::new(false));
833
834        cx.executor()
835            .spawn({
836                let completed = completed.clone();
837                async move {
838                    completed.store(true, Ordering::SeqCst);
839                }
840            })
841            .detach();
842        cx.run_until_parked();
843
844        assert!(completed.load(Ordering::SeqCst));
845    }
846
847    #[test]
848    fn baseline_timer_fires_after_advance_clock() {
849        let cx = TestAppContext::single();
850        let executor = cx.executor();
851        let fired = Arc::new(AtomicBool::new(false));
852
853        executor
854            .spawn({
855                let executor = executor.clone();
856                let fired = fired.clone();
857                async move {
858                    executor.timer(Duration::from_millis(10)).await;
859                    fired.store(true, Ordering::SeqCst);
860                }
861            })
862            .detach();
863
864        executor.advance_clock(Duration::from_millis(10));
865        assert!(fired.load(Ordering::SeqCst));
866    }
867
868    #[test]
869    fn baseline_deprioritize_delays_labeled_task() {
870        let mut cx = TestAppContext::single();
871        let executor = cx.executor();
872        let label = TaskLabel::new();
873        let order = Arc::new(Mutex::new(Vec::new()));
874
875        executor.deprioritize(label);
876        executor
877            .spawn_labeled(label, {
878                let order = order.clone();
879                async move {
880                    order.lock().unwrap().push("labeled");
881                }
882            })
883            .detach();
884        executor
885            .spawn({
886                let order = order.clone();
887                async move {
888                    order.lock().unwrap().push("normal");
889                }
890            })
891            .detach();
892        cx.run_until_parked();
893
894        assert_eq!(&*order.lock().unwrap(), &["normal", "labeled"]);
895    }
896
897    #[test]
898    fn baseline_block_with_timeout_returns_bounded_pending_future() {
899        let cx = TestAppContext::single();
900        let executor = cx.executor();
901        executor.set_block_on_ticks(0..=0);
902
903        let result =
904            executor.block_with_timeout(Duration::from_millis(1), futures::future::pending::<()>());
905
906        assert!(result.is_err());
907    }
908
909    #[test]
910    fn baseline_headless_app_tasks_complete_without_window() {
911        let mut cx = TestAppContext::single();
912        let completed = Arc::new(AtomicBool::new(false));
913
914        cx.executor()
915            .spawn({
916                let completed = completed.clone();
917                async move {
918                    completed.store(true, Ordering::SeqCst);
919                }
920            })
921            .detach();
922        cx.run_until_parked();
923
924        assert!(completed.load(Ordering::SeqCst));
925        assert!(cx.windows().is_empty());
926    }
927
928    #[test]
929    fn baseline_scoped_tasks_complete_before_scope_returns() {
930        let cx = TestAppContext::single();
931        let executor = cx.executor();
932        let completed = Arc::new(AtomicBool::new(false));
933
934        executor.block_test({
935            let executor = executor.clone();
936            let completed = completed.clone();
937            async move {
938                executor
939                    .scoped(|scope| {
940                        scope.spawn(async move {
941                            completed.store(true, Ordering::SeqCst);
942                        });
943                    })
944                    .await;
945            }
946        });
947
948        assert!(completed.load(Ordering::SeqCst));
949    }
950
951    #[test]
952    fn spawn_with_priority_defaults_to_medium_behavior() {
953        let mut cx = TestAppContext::single();
954        let completed = Arc::new(AtomicBool::new(false));
955
956        cx.executor()
957            .spawn_with_priority(TaskPriority::Medium, {
958                let completed = completed.clone();
959                async move {
960                    completed.store(true, Ordering::SeqCst);
961                }
962            })
963            .detach();
964        cx.run_until_parked();
965
966        assert!(completed.load(Ordering::SeqCst));
967    }
968
969    #[test]
970    fn spawn_with_priority_high_is_observable_in_test_dispatcher() {
971        let mut cx = TestAppContext::single();
972        let executor = cx.executor();
973        let order = Arc::new(Mutex::new(Vec::new()));
974
975        executor
976            .spawn_with_priority(TaskPriority::Low, {
977                let order = order.clone();
978                async move {
979                    order.lock().unwrap().push("low");
980                }
981            })
982            .detach();
983        executor
984            .spawn_with_priority(TaskPriority::High, {
985                let order = order.clone();
986                async move {
987                    order.lock().unwrap().push("high");
988                }
989            })
990            .detach();
991        cx.run_until_parked();
992
993        assert_eq!(&*order.lock().unwrap(), &["high", "low"]);
994    }
995
996    #[test]
997    fn spawn_labeled_deprioritize_still_works_after_priority_addition() {
998        baseline_deprioritize_delays_labeled_task();
999    }
1000
1001    #[test]
1002    fn low_priority_task_eventually_runs() {
1003        let mut cx = TestAppContext::single();
1004        let completed = Arc::new(AtomicBool::new(false));
1005
1006        cx.executor()
1007            .spawn_with_priority(TaskPriority::Low, {
1008                let completed = completed.clone();
1009                async move {
1010                    completed.store(true, Ordering::SeqCst);
1011                }
1012            })
1013            .detach();
1014        cx.run_until_parked();
1015
1016        assert!(completed.load(Ordering::SeqCst));
1017    }
1018}