Skip to main content

gpui_core/
executor.rs

1use crate::{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    /// Enqueues the given future to be run to completion on a background thread.
229    #[track_caller]
230    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
231    where
232        R: Send + 'static,
233    {
234        self.spawn_internal::<R>(Box::pin(future), None)
235    }
236
237    /// Enqueues the given future to be run to completion on a background thread.
238    /// The given label can be used to control the priority of the task in tests.
239    #[track_caller]
240    pub fn spawn_labeled<R>(
241        &self,
242        label: TaskLabel,
243        future: impl Future<Output = R> + Send + 'static,
244    ) -> Task<R>
245    where
246        R: Send + 'static,
247    {
248        self.spawn_internal::<R>(Box::pin(future), Some(label))
249    }
250
251    #[track_caller]
252    #[doc(hidden)]
253    pub fn spawn_with_priority<R>(
254        &self,
255        priority: TaskPriority,
256        future: impl Future<Output = R> + Send + 'static,
257    ) -> Task<R>
258    where
259        R: Send + 'static,
260    {
261        self.spawn_internal::<R>(
262            Box::pin(future),
263            Some(TaskLabel::new_with_priority(priority)),
264        )
265    }
266
267    #[track_caller]
268    fn spawn_internal<R: Send + 'static>(
269        &self,
270        future: AnyFuture<R>,
271        label: Option<TaskLabel>,
272    ) -> Task<R> {
273        let dispatcher = self.dispatcher.clone();
274        let future = TimedFuture::new(future, Location::caller());
275        let (runnable, task) =
276            async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
277        runnable.schedule();
278        Task(TaskState::Spawned(task))
279    }
280
281    /// Used by the test harness to run an async test in a synchronous fashion.
282    #[cfg(any(test, feature = "test-support"))]
283    #[track_caller]
284    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
285        if let Ok(value) = self.block_internal(false, future, None) {
286            value
287        } else {
288            unreachable!()
289        }
290    }
291
292    /// Block the current thread until the given future resolves.
293    /// Consider using `block_with_timeout` instead.
294    pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
295        if let Ok(value) = self.block_internal(true, future, None) {
296            value
297        } else {
298            unreachable!()
299        }
300    }
301
302    #[cfg(not(any(test, feature = "test-support")))]
303    pub(crate) fn block_internal<Fut: Future>(
304        &self,
305        _background_only: bool,
306        future: Fut,
307        timeout: Option<Duration>,
308    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
309        use std::time::Instant;
310
311        let mut future = Box::pin(future);
312        if timeout == Some(Duration::ZERO) {
313            return Err(future);
314        }
315        let deadline = timeout.map(|timeout| Instant::now() + timeout);
316
317        let unparker = self.dispatcher.unparker();
318        let waker = waker_fn(move || {
319            unparker.unpark();
320        });
321        let mut cx = std::task::Context::from_waker(&waker);
322
323        loop {
324            match future.as_mut().poll(&mut cx) {
325                Poll::Ready(result) => return Ok(result),
326                Poll::Pending => {
327                    let timeout =
328                        deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
329                    if !self.dispatcher.park(timeout)
330                        && deadline.is_some_and(|deadline| deadline < Instant::now())
331                    {
332                        return Err(future);
333                    }
334                }
335            }
336        }
337    }
338
339    #[cfg(any(test, feature = "test-support"))]
340    #[track_caller]
341    pub(crate) fn block_internal<Fut: Future>(
342        &self,
343        background_only: bool,
344        future: Fut,
345        timeout: Option<Duration>,
346    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
347        use std::sync::atomic::AtomicBool;
348
349        let mut future = Box::pin(future);
350        if timeout == Some(Duration::ZERO) {
351            return Err(future);
352        }
353        let Some(dispatcher) = self.dispatcher.as_test() else {
354            return Err(future);
355        };
356
357        let mut max_ticks = if timeout.is_some() {
358            dispatcher.gen_block_on_ticks()
359        } else {
360            usize::MAX
361        };
362        let unparker = self.dispatcher.unparker();
363        let awoken = Arc::new(AtomicBool::new(false));
364        let waker = waker_fn({
365            let awoken = awoken.clone();
366            move || {
367                awoken.store(true, SeqCst);
368                unparker.unpark();
369            }
370        });
371        let mut cx = std::task::Context::from_waker(&waker);
372
373        loop {
374            match future.as_mut().poll(&mut cx) {
375                Poll::Ready(result) => return Ok(result),
376                Poll::Pending => {
377                    if max_ticks == 0 {
378                        return Err(future);
379                    }
380                    max_ticks -= 1;
381
382                    if !dispatcher.tick(background_only) {
383                        if awoken.swap(false, SeqCst) {
384                            continue;
385                        }
386
387                        if !dispatcher.parking_allowed() {
388                            if dispatcher.advance_clock_to_next_delayed() {
389                                continue;
390                            }
391                            let mut backtrace_message = String::new();
392                            let mut waiting_message = String::new();
393                            if let Some(backtrace) = dispatcher.waiting_backtrace() {
394                                backtrace_message =
395                                    format!("\nbacktrace of waiting future:\n{:?}", backtrace);
396                            }
397                            if let Some(waiting_hint) = dispatcher.waiting_hint() {
398                                waiting_message = format!("\n  waiting on: {}\n", waiting_hint);
399                            }
400                            panic!(
401                                "parked with nothing left to run{waiting_message}{backtrace_message}",
402                            )
403                        }
404                        self.dispatcher.park(None);
405                    }
406                }
407            }
408        }
409    }
410
411    /// Block the current thread until the given future resolves
412    /// or `duration` has elapsed.
413    pub fn block_with_timeout<Fut: Future>(
414        &self,
415        duration: Duration,
416        future: Fut,
417    ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
418        self.block_internal(true, future, Some(duration))
419    }
420
421    /// Scoped lets you start a number of tasks and waits
422    /// for all of them to complete before returning.
423    pub async fn scoped<'scope, F>(&self, scheduler: F)
424    where
425        F: FnOnce(&mut Scope<'scope>),
426    {
427        let mut scope = Scope::new(self.clone());
428        (scheduler)(&mut scope);
429        let spawned = mem::take(&mut scope.futures)
430            .into_iter()
431            .map(|f| self.spawn(f))
432            .collect::<Vec<_>>();
433        for task in spawned {
434            task.await;
435        }
436    }
437
438    /// Get the current time.
439    ///
440    /// Calling this instead of `std::time::Instant::now` allows the use
441    /// of fake timers in tests.
442    pub fn now(&self) -> Instant {
443        self.dispatcher.now()
444    }
445
446    /// Returns a task that will complete after the given duration.
447    /// Depending on other concurrent tasks the elapsed duration may be longer
448    /// than requested.
449    #[track_caller]
450    pub fn timer(&self, duration: Duration) -> Task<()> {
451        if duration.is_zero() {
452            return Task::ready(());
453        }
454        let future = TimedFuture::new(Box::pin(async move {}), Location::caller());
455        let (runnable, task) = async_task::spawn(future, {
456            let dispatcher = self.dispatcher.clone();
457            move |runnable| dispatcher.dispatch_after(duration, runnable)
458        });
459        runnable.schedule();
460        Task(TaskState::Spawned(task))
461    }
462
463    /// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
464    #[cfg(any(test, feature = "test-support"))]
465    pub fn start_waiting(&self) {
466        self.dispatcher.as_test().unwrap().start_waiting();
467    }
468
469    /// in tests, removes the debugging data added by start_waiting
470    #[cfg(any(test, feature = "test-support"))]
471    pub fn finish_waiting(&self) {
472        self.dispatcher.as_test().unwrap().finish_waiting();
473    }
474
475    /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable)
476    #[cfg(any(test, feature = "test-support"))]
477    pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
478        self.dispatcher.as_test().unwrap().simulate_random_delay()
479    }
480
481    /// in tests, indicate that a given task from `spawn_labeled` should run after everything else
482    #[cfg(any(test, feature = "test-support"))]
483    pub fn deprioritize(&self, task_label: TaskLabel) {
484        self.dispatcher.as_test().unwrap().deprioritize(task_label)
485    }
486
487    /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready.
488    #[cfg(any(test, feature = "test-support"))]
489    pub fn advance_clock(&self, duration: Duration) {
490        self.dispatcher.as_test().unwrap().advance_clock(duration)
491    }
492
493    /// in tests, run one task.
494    #[cfg(any(test, feature = "test-support"))]
495    pub fn tick(&self) -> bool {
496        self.dispatcher.as_test().unwrap().tick(false)
497    }
498
499    /// in tests, run all tasks that are ready to run. If after doing so
500    /// the test still has outstanding tasks, this will panic. (See also [`Self::allow_parking`])
501    #[cfg(any(test, feature = "test-support"))]
502    pub fn run_until_parked(&self) {
503        self.dispatcher.as_test().unwrap().run_until_parked()
504    }
505
506    /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks.
507    /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that
508    /// do take real async time to run.
509    #[cfg(any(test, feature = "test-support"))]
510    pub fn allow_parking(&self) {
511        self.dispatcher.as_test().unwrap().allow_parking();
512    }
513
514    /// undoes the effect of [`Self::allow_parking`].
515    #[cfg(any(test, feature = "test-support"))]
516    pub fn forbid_parking(&self) {
517        self.dispatcher.as_test().unwrap().forbid_parking();
518    }
519
520    /// adds detail to the "parked with nothing let to run" message.
521    #[cfg(any(test, feature = "test-support"))]
522    pub fn set_waiting_hint(&self, msg: Option<String>) {
523        self.dispatcher.as_test().unwrap().set_waiting_hint(msg);
524    }
525
526    /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable
527    #[cfg(any(test, feature = "test-support"))]
528    pub fn rng(&self) -> StdRng {
529        self.dispatcher.as_test().unwrap().rng()
530    }
531
532    /// How many CPUs are available to the dispatcher.
533    pub fn num_cpus(&self) -> usize {
534        #[cfg(any(test, feature = "test-support"))]
535        return 4;
536
537        #[cfg(not(any(test, feature = "test-support")))]
538        return num_cpus::get();
539    }
540
541    /// Whether we're on the main thread.
542    pub fn is_main_thread(&self) -> bool {
543        self.dispatcher.is_main_thread()
544    }
545
546    #[cfg(any(test, feature = "test-support"))]
547    /// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
548    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
549        self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
550    }
551}
552
553/// ForegroundExecutor runs things on the main thread.
554impl ForegroundExecutor {
555    /// Creates a new ForegroundExecutor from the given PlatformDispatcher.
556    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
557        Self {
558            dispatcher,
559            not_send: PhantomData,
560        }
561    }
562
563    /// Enqueues the given Task to run on the main thread at some point in the future.
564    #[track_caller]
565    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
566    where
567        R: 'static,
568    {
569        let dispatcher = self.dispatcher.clone();
570
571        #[track_caller]
572        fn inner<R: 'static>(
573            dispatcher: Arc<dyn PlatformDispatcher>,
574            future: AnyLocalFuture<R>,
575        ) -> Task<R> {
576            let future = TimedFuture::new(future, Location::caller());
577            let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
578                dispatcher.dispatch_on_main_thread(runnable)
579            });
580            runnable.schedule();
581            Task(TaskState::Spawned(task))
582        }
583        inner::<R>(dispatcher, Box::pin(future))
584    }
585}
586
587/// Variant of `async_task::spawn_local` that includes the source location of the spawn in panics.
588///
589/// Copy-modified from:
590/// <https://github.com/smol-rs/async-task/blob/ca9dbe1db9c422fd765847fa91306e30a6bb58a9/src/runnable.rs#L405>
591#[track_caller]
592fn spawn_local_with_source_location<Fut, S>(
593    future: Fut,
594    schedule: S,
595) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
596where
597    Fut: Future + 'static,
598    Fut::Output: 'static,
599    S: async_task::Schedule<()> + Send + Sync + 'static,
600{
601    #[inline]
602    fn thread_id() -> ThreadId {
603        std::thread_local! {
604            static ID: ThreadId = thread::current().id();
605        }
606        ID.try_with(|id| *id)
607            .unwrap_or_else(|_| thread::current().id())
608    }
609
610    struct Checked<F> {
611        id: ThreadId,
612        inner: ManuallyDrop<F>,
613        location: &'static Location<'static>,
614    }
615
616    impl<F> Drop for Checked<F> {
617        fn drop(&mut self) {
618            assert!(
619                self.id == thread_id(),
620                "local task dropped by a thread that didn't spawn it. Task spawned at {}",
621                self.location
622            );
623            unsafe {
624                ManuallyDrop::drop(&mut self.inner);
625            }
626        }
627    }
628
629    impl<F: Future> Future for Checked<F> {
630        type Output = F::Output;
631
632        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
633            assert!(
634                self.id == thread_id(),
635                "local task polled by a thread that didn't spawn it. Task spawned at {}",
636                self.location
637            );
638            unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
639        }
640    }
641
642    // Wrap the future into one that checks which thread it's on.
643    let future = Checked {
644        id: thread_id(),
645        inner: ManuallyDrop::new(future),
646        location: Location::caller(),
647    };
648
649    unsafe { async_task::spawn_unchecked(future, schedule) }
650}
651
652/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
653pub struct Scope<'a> {
654    executor: BackgroundExecutor,
655    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
656    tx: Option<mpsc::Sender<()>>,
657    rx: mpsc::Receiver<()>,
658    lifetime: PhantomData<&'a ()>,
659}
660
661impl<'a> Scope<'a> {
662    fn new(executor: BackgroundExecutor) -> Self {
663        let (tx, rx) = mpsc::channel(1);
664        Self {
665            executor,
666            tx: Some(tx),
667            rx,
668            futures: Default::default(),
669            lifetime: PhantomData,
670        }
671    }
672
673    /// How many CPUs are available to the dispatcher.
674    pub fn num_cpus(&self) -> usize {
675        self.executor.num_cpus()
676    }
677
678    /// Spawn a future into this scope.
679    pub fn spawn<F>(&mut self, f: F)
680    where
681        F: Future<Output = ()> + Send + 'a,
682    {
683        let tx = self.tx.clone().unwrap();
684
685        // SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
686        // dropping this `Scope` blocks until all of the futures have resolved.
687        let f = unsafe {
688            mem::transmute::<
689                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
690                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
691            >(Box::pin(async move {
692                f.await;
693                drop(tx);
694            }))
695        };
696        self.futures.push(f);
697    }
698}
699
700impl Drop for Scope<'_> {
701    fn drop(&mut self) {
702        self.tx.take().unwrap();
703
704        // Wait until the channel is closed, which means that all of the spawned
705        // futures have resolved.
706        self.executor.block(self.rx.next());
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713    use crate::{TestAppContext, profiler};
714    use std::sync::{
715        Arc, Mutex,
716        atomic::{AtomicBool, Ordering},
717    };
718
719    fn reset_profiler() {
720        profiler::set_enabled(false);
721    }
722
723    fn collect_timings() -> Vec<profiler::ThreadTimingsDelta> {
724        let mut collector = profiler::ProfilingCollector::new(Instant::now());
725        profiler::profiler_collect_timings(&mut collector)
726    }
727
728    #[test]
729    fn profiler_disabled_records_nothing() {
730        let _guard = profiler::test_guard();
731        reset_profiler();
732
733        let mut cx = TestAppContext::single();
734        cx.executor().spawn(async {}).detach();
735        cx.run_until_parked();
736
737        assert!(collect_timings().is_empty());
738        reset_profiler();
739    }
740
741    #[test]
742    fn profiler_enabled_records_background_task_timing() {
743        let _guard = profiler::test_guard();
744        reset_profiler();
745        profiler::set_enabled(true);
746
747        let mut cx = TestAppContext::single();
748        let mut collector = profiler::ProfilingCollector::new(Instant::now());
749        cx.executor().spawn(async {}).detach();
750        cx.run_until_parked();
751
752        let deltas = profiler::profiler_collect_timings(&mut collector);
753        let timing = deltas
754            .iter()
755            .flat_map(|delta| &delta.new_timings)
756            .next()
757            .expect("expected a recorded background task timing");
758        assert_eq!(timing.status, profiler::TaskTimingStatus::Completed);
759
760        reset_profiler();
761    }
762
763    #[test]
764    fn profiler_enabled_records_foreground_task_location() {
765        let _guard = profiler::test_guard();
766        reset_profiler();
767        profiler::set_enabled(true);
768
769        let mut cx = TestAppContext::single();
770        let mut collector = profiler::ProfilingCollector::new(Instant::now());
771        cx.foreground_executor().spawn(async {}).detach();
772        cx.run_until_parked();
773
774        let deltas = profiler::profiler_collect_timings(&mut collector);
775        let timing = deltas
776            .iter()
777            .flat_map(|delta| &delta.new_timings)
778            .find(|timing| timing.location.file.as_ref().contains("executor.rs"))
779            .expect("expected foreground task timing to include its source location");
780        assert!(timing.location.line > 0);
781        assert!(timing.location.column > 0);
782
783        reset_profiler();
784    }
785
786    #[test]
787    fn profiler_timing_start_is_first_poll_not_spawn() {
788        let _guard = profiler::test_guard();
789        reset_profiler();
790        profiler::set_enabled(true);
791
792        let mut cx = TestAppContext::single();
793        let mut collector = profiler::ProfilingCollector::new(Instant::now());
794        cx.executor().spawn(async {}).detach();
795        assert!(profiler::profiler_collect_timings(&mut collector).is_empty());
796
797        cx.run_until_parked();
798        assert!(!profiler::profiler_collect_timings(&mut collector).is_empty());
799
800        reset_profiler();
801    }
802
803    #[test]
804    fn baseline_foreground_task_runs_on_main_thread() {
805        let mut cx = TestAppContext::single();
806        let executor = cx.executor();
807        let ran_on_main_thread = Arc::new(AtomicBool::new(false));
808
809        cx.foreground_executor()
810            .spawn({
811                let ran_on_main_thread = ran_on_main_thread.clone();
812                async move {
813                    ran_on_main_thread.store(executor.is_main_thread(), Ordering::SeqCst);
814                }
815            })
816            .detach();
817        cx.run_until_parked();
818
819        assert!(ran_on_main_thread.load(Ordering::SeqCst));
820    }
821
822    #[test]
823    fn baseline_background_task_completes_after_run_until_parked() {
824        let mut cx = TestAppContext::single();
825        let completed = Arc::new(AtomicBool::new(false));
826
827        cx.executor()
828            .spawn({
829                let completed = completed.clone();
830                async move {
831                    completed.store(true, Ordering::SeqCst);
832                }
833            })
834            .detach();
835        cx.run_until_parked();
836
837        assert!(completed.load(Ordering::SeqCst));
838    }
839
840    #[test]
841    fn baseline_timer_fires_after_advance_clock() {
842        let cx = TestAppContext::single();
843        let executor = cx.executor();
844        let fired = Arc::new(AtomicBool::new(false));
845
846        executor
847            .spawn({
848                let executor = executor.clone();
849                let fired = fired.clone();
850                async move {
851                    executor.timer(Duration::from_millis(10)).await;
852                    fired.store(true, Ordering::SeqCst);
853                }
854            })
855            .detach();
856
857        executor.advance_clock(Duration::from_millis(10));
858        assert!(fired.load(Ordering::SeqCst));
859    }
860
861    #[test]
862    fn baseline_deprioritize_delays_labeled_task() {
863        let mut cx = TestAppContext::single();
864        let executor = cx.executor();
865        let label = TaskLabel::new();
866        let order = Arc::new(Mutex::new(Vec::new()));
867
868        executor.deprioritize(label);
869        executor
870            .spawn_labeled(label, {
871                let order = order.clone();
872                async move {
873                    order.lock().unwrap().push("labeled");
874                }
875            })
876            .detach();
877        executor
878            .spawn({
879                let order = order.clone();
880                async move {
881                    order.lock().unwrap().push("normal");
882                }
883            })
884            .detach();
885        cx.run_until_parked();
886
887        assert_eq!(&*order.lock().unwrap(), &["normal", "labeled"]);
888    }
889
890    #[test]
891    fn baseline_block_with_timeout_returns_bounded_pending_future() {
892        let cx = TestAppContext::single();
893        let executor = cx.executor();
894        executor.set_block_on_ticks(0..=0);
895
896        let result =
897            executor.block_with_timeout(Duration::from_millis(1), futures::future::pending::<()>());
898
899        assert!(result.is_err());
900    }
901
902    #[test]
903    fn baseline_headless_app_tasks_complete_without_window() {
904        let mut cx = TestAppContext::single();
905        let completed = Arc::new(AtomicBool::new(false));
906
907        cx.executor()
908            .spawn({
909                let completed = completed.clone();
910                async move {
911                    completed.store(true, Ordering::SeqCst);
912                }
913            })
914            .detach();
915        cx.run_until_parked();
916
917        assert!(completed.load(Ordering::SeqCst));
918        assert!(cx.windows().is_empty());
919    }
920
921    #[test]
922    fn baseline_scoped_tasks_complete_before_scope_returns() {
923        let cx = TestAppContext::single();
924        let executor = cx.executor();
925        let completed = Arc::new(AtomicBool::new(false));
926
927        executor.block_test({
928            let executor = executor.clone();
929            let completed = completed.clone();
930            async move {
931                executor
932                    .scoped(|scope| {
933                        scope.spawn(async move {
934                            completed.store(true, Ordering::SeqCst);
935                        });
936                    })
937                    .await;
938            }
939        });
940
941        assert!(completed.load(Ordering::SeqCst));
942    }
943
944    #[test]
945    fn spawn_with_priority_defaults_to_medium_behavior() {
946        let mut cx = TestAppContext::single();
947        let completed = Arc::new(AtomicBool::new(false));
948
949        cx.executor()
950            .spawn_with_priority(TaskPriority::Medium, {
951                let completed = completed.clone();
952                async move {
953                    completed.store(true, Ordering::SeqCst);
954                }
955            })
956            .detach();
957        cx.run_until_parked();
958
959        assert!(completed.load(Ordering::SeqCst));
960    }
961
962    #[test]
963    fn spawn_with_priority_high_is_observable_in_test_dispatcher() {
964        let mut cx = TestAppContext::single();
965        let executor = cx.executor();
966        let order = Arc::new(Mutex::new(Vec::new()));
967
968        executor
969            .spawn_with_priority(TaskPriority::Low, {
970                let order = order.clone();
971                async move {
972                    order.lock().unwrap().push("low");
973                }
974            })
975            .detach();
976        executor
977            .spawn_with_priority(TaskPriority::High, {
978                let order = order.clone();
979                async move {
980                    order.lock().unwrap().push("high");
981                }
982            })
983            .detach();
984        cx.run_until_parked();
985
986        assert_eq!(&*order.lock().unwrap(), &["high", "low"]);
987    }
988
989    #[test]
990    fn spawn_labeled_deprioritize_still_works_after_priority_addition() {
991        baseline_deprioritize_delays_labeled_task();
992    }
993
994    #[test]
995    fn low_priority_task_eventually_runs() {
996        let mut cx = TestAppContext::single();
997        let completed = Arc::new(AtomicBool::new(false));
998
999        cx.executor()
1000            .spawn_with_priority(TaskPriority::Low, {
1001                let completed = completed.clone();
1002                async move {
1003                    completed.store(true, Ordering::SeqCst);
1004                }
1005            })
1006            .detach();
1007        cx.run_until_parked();
1008
1009        assert!(completed.load(Ordering::SeqCst));
1010    }
1011}