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 std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::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    /// Whether this executor runs on the deterministic test dispatcher.
554    ///
555    /// Available in every build. Use it, rather than `cfg(feature =
556    /// "test-support")`, for behavior that must differ under tests, so that
557    /// compiling `test-support` into an application doesn't change it.
558    pub fn is_test(&self) -> bool {
559        #[cfg(any(test, feature = "test-support"))]
560        {
561            self.dispatcher.as_test().is_some()
562        }
563        #[cfg(not(any(test, feature = "test-support")))]
564        {
565            false
566        }
567    }
568
569    #[cfg(any(test, feature = "test-support"))]
570    /// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
571    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
572        self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
573    }
574}
575
576/// ForegroundExecutor runs things on the main thread.
577impl ForegroundExecutor {
578    /// Creates a new ForegroundExecutor from the given PlatformDispatcher.
579    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
580        Self {
581            dispatcher,
582            not_send: PhantomData,
583        }
584    }
585
586    /// Enqueues the given Task to run on the main thread at some point in the future.
587    #[track_caller]
588    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
589    where
590        R: 'static,
591    {
592        let dispatcher = self.dispatcher.clone();
593
594        #[track_caller]
595        fn inner<R: 'static>(
596            dispatcher: Arc<dyn PlatformDispatcher>,
597            future: AnyLocalFuture<R>,
598        ) -> Task<R> {
599            let future = TimedFuture::new(future, Location::caller());
600            let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
601                dispatcher.dispatch_on_main_thread(runnable)
602            });
603            runnable.schedule();
604            Task(TaskState::Spawned(task))
605        }
606        inner::<R>(dispatcher, Box::pin(future))
607    }
608}
609
610/// Variant of `async_task::spawn_local` that includes the source location of the spawn in panics.
611///
612/// Copy-modified from:
613/// <https://github.com/smol-rs/async-task/blob/ca9dbe1db9c422fd765847fa91306e30a6bb58a9/src/runnable.rs#L405>
614#[track_caller]
615fn spawn_local_with_source_location<Fut, S>(
616    future: Fut,
617    schedule: S,
618) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
619where
620    Fut: Future + 'static,
621    Fut::Output: 'static,
622    S: async_task::Schedule<()> + Send + Sync + 'static,
623{
624    #[inline]
625    fn thread_id() -> ThreadId {
626        std::thread_local! {
627            static ID: ThreadId = thread::current().id();
628        }
629        ID.try_with(|id| *id)
630            .unwrap_or_else(|_| thread::current().id())
631    }
632
633    struct Checked<F> {
634        id: ThreadId,
635        inner: ManuallyDrop<F>,
636        location: &'static Location<'static>,
637    }
638
639    impl<F> Drop for Checked<F> {
640        fn drop(&mut self) {
641            assert!(
642                self.id == thread_id(),
643                "local task dropped by a thread that didn't spawn it. Task spawned at {}",
644                self.location
645            );
646            unsafe {
647                ManuallyDrop::drop(&mut self.inner);
648            }
649        }
650    }
651
652    impl<F: Future> Future for Checked<F> {
653        type Output = F::Output;
654
655        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
656            assert!(
657                self.id == thread_id(),
658                "local task polled by a thread that didn't spawn it. Task spawned at {}",
659                self.location
660            );
661            unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
662        }
663    }
664
665    // Wrap the future into one that checks which thread it's on.
666    let future = Checked {
667        id: thread_id(),
668        inner: ManuallyDrop::new(future),
669        location: Location::caller(),
670    };
671
672    unsafe { async_task::spawn_unchecked(future, schedule) }
673}
674
675/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
676pub struct Scope<'a> {
677    executor: BackgroundExecutor,
678    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
679    tx: Option<mpsc::Sender<()>>,
680    rx: mpsc::Receiver<()>,
681    lifetime: PhantomData<&'a ()>,
682}
683
684impl<'a> Scope<'a> {
685    fn new(executor: BackgroundExecutor) -> Self {
686        let (tx, rx) = mpsc::channel(1);
687        Self {
688            executor,
689            tx: Some(tx),
690            rx,
691            futures: Default::default(),
692            lifetime: PhantomData,
693        }
694    }
695
696    /// How many CPUs are available to the dispatcher.
697    pub fn num_cpus(&self) -> usize {
698        self.executor.num_cpus()
699    }
700
701    /// Spawn a future into this scope.
702    pub fn spawn<F>(&mut self, f: F)
703    where
704        F: Future<Output = ()> + Send + 'a,
705    {
706        let tx = self.tx.clone().unwrap();
707
708        // SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
709        // dropping this `Scope` blocks until all of the futures have resolved.
710        let f = unsafe {
711            mem::transmute::<
712                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
713                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
714            >(Box::pin(async move {
715                f.await;
716                drop(tx);
717            }))
718        };
719        self.futures.push(f);
720    }
721}
722
723impl Drop for Scope<'_> {
724    fn drop(&mut self) {
725        self.tx.take().unwrap();
726
727        // Wait until the channel is closed, which means that all of the spawned
728        // futures have resolved.
729        self.executor.block(self.rx.next());
730    }
731}
732
733#[cfg(test)]
734mod tests {
735    use super::*;
736    use crate::{TestAppContext, profiler};
737
738    fn is_probe_timing(timing: &profiler::SerializedTaskTiming) -> bool {
739        timing
740            .location
741            .file
742            .as_ref()
743            .contains("executor_timing_probe")
744    }
745    use std::sync::{
746        Arc, Mutex,
747        atomic::{AtomicBool, Ordering},
748    };
749
750    fn reset_profiler() {
751        profiler::set_enabled(false);
752    }
753
754    fn collect_timings() -> Vec<profiler::ThreadTimingsDelta> {
755        let mut collector = profiler::ProfilingCollector::new(Instant::now());
756        profiler::profiler_collect_timings(&mut collector)
757    }
758
759    #[test]
760    fn profiler_disabled_records_nothing() {
761        let _guard = profiler::test_guard();
762        reset_profiler();
763
764        let mut cx = TestAppContext::single();
765        cx.executor().spawn(async {}).detach();
766        cx.run_until_parked();
767
768        assert!(collect_timings().is_empty());
769        reset_profiler();
770    }
771
772    #[test]
773    fn profiler_enabled_records_background_task_timing() {
774        let _guard = profiler::test_guard();
775        reset_profiler();
776        profiler::set_enabled(true);
777
778        let mut cx = TestAppContext::single();
779        let mut collector = profiler::ProfilingCollector::new(Instant::now());
780        cx.executor().spawn(async {}).detach();
781        cx.run_until_parked();
782
783        let deltas = profiler::profiler_collect_timings(&mut collector);
784        let timing = deltas
785            .iter()
786            .flat_map(|delta| &delta.new_timings)
787            .next()
788            .expect("expected a recorded background task timing");
789        assert_eq!(timing.status, profiler::TaskTimingStatus::Completed);
790
791        reset_profiler();
792    }
793
794    #[test]
795    fn profiler_enabled_records_foreground_task_location() {
796        let _guard = profiler::test_guard();
797        reset_profiler();
798        profiler::set_enabled(true);
799
800        let mut cx = TestAppContext::single();
801        let mut collector = profiler::ProfilingCollector::new(Instant::now());
802        cx.foreground_executor().spawn(async {}).detach();
803        cx.run_until_parked();
804
805        let deltas = profiler::profiler_collect_timings(&mut collector);
806        let timing = deltas
807            .iter()
808            .flat_map(|delta| &delta.new_timings)
809            .find(|timing| timing.location.file.as_ref().contains("executor.rs"))
810            .expect("expected foreground task timing to include its source location");
811        assert!(timing.location.line > 0);
812        assert!(timing.location.column > 0);
813
814        reset_profiler();
815    }
816
817    #[test]
818    fn profiler_timing_start_is_first_poll_not_spawn() {
819        let _guard = profiler::test_guard();
820        reset_profiler();
821        profiler::set_enabled(true);
822
823        let mut cx = TestAppContext::single();
824        let mut collector = profiler::ProfilingCollector::new(Instant::now());
825        crate::executor_timing_probe::spawn_noop(&cx.executor());
826        // Other tests running in parallel also record timings while the
827        // profiler is enabled, so only assert about this test's own task,
828        // identified by the probe file it was spawned from.
829        let early = profiler::profiler_collect_timings(&mut collector);
830        assert!(
831            early
832                .iter()
833                .flat_map(|delta| &delta.new_timings)
834                .all(|timing| !is_probe_timing(timing))
835        );
836
837        cx.run_until_parked();
838        let recorded = profiler::profiler_collect_timings(&mut collector);
839        assert!(
840            recorded
841                .iter()
842                .flat_map(|delta| &delta.new_timings)
843                .any(is_probe_timing)
844        );
845
846        reset_profiler();
847    }
848
849    #[test]
850    fn baseline_foreground_task_runs_on_main_thread() {
851        let mut cx = TestAppContext::single();
852        let executor = cx.executor();
853        let ran_on_main_thread = Arc::new(AtomicBool::new(false));
854
855        cx.foreground_executor()
856            .spawn({
857                let ran_on_main_thread = ran_on_main_thread.clone();
858                async move {
859                    ran_on_main_thread.store(executor.is_main_thread(), Ordering::SeqCst);
860                }
861            })
862            .detach();
863        cx.run_until_parked();
864
865        assert!(ran_on_main_thread.load(Ordering::SeqCst));
866    }
867
868    #[test]
869    fn baseline_background_task_completes_after_run_until_parked() {
870        let mut cx = TestAppContext::single();
871        let completed = Arc::new(AtomicBool::new(false));
872
873        cx.executor()
874            .spawn({
875                let completed = completed.clone();
876                async move {
877                    completed.store(true, Ordering::SeqCst);
878                }
879            })
880            .detach();
881        cx.run_until_parked();
882
883        assert!(completed.load(Ordering::SeqCst));
884    }
885
886    #[test]
887    fn baseline_timer_fires_after_advance_clock() {
888        let cx = TestAppContext::single();
889        let executor = cx.executor();
890        let fired = Arc::new(AtomicBool::new(false));
891
892        executor
893            .spawn({
894                let executor = executor.clone();
895                let fired = fired.clone();
896                async move {
897                    executor.timer(Duration::from_millis(10)).await;
898                    fired.store(true, Ordering::SeqCst);
899                }
900            })
901            .detach();
902
903        executor.advance_clock(Duration::from_millis(10));
904        assert!(fired.load(Ordering::SeqCst));
905    }
906
907    #[test]
908    fn baseline_deprioritize_delays_labeled_task() {
909        let mut cx = TestAppContext::single();
910        let executor = cx.executor();
911        let label = TaskLabel::new();
912        let order = Arc::new(Mutex::new(Vec::new()));
913
914        executor.deprioritize(label);
915        executor
916            .spawn_labeled(label, {
917                let order = order.clone();
918                async move {
919                    order.lock().unwrap().push("labeled");
920                }
921            })
922            .detach();
923        executor
924            .spawn({
925                let order = order.clone();
926                async move {
927                    order.lock().unwrap().push("normal");
928                }
929            })
930            .detach();
931        cx.run_until_parked();
932
933        assert_eq!(&*order.lock().unwrap(), &["normal", "labeled"]);
934    }
935
936    #[test]
937    fn baseline_block_with_timeout_returns_bounded_pending_future() {
938        let cx = TestAppContext::single();
939        let executor = cx.executor();
940        executor.set_block_on_ticks(0..=0);
941
942        let result =
943            executor.block_with_timeout(Duration::from_millis(1), futures::future::pending::<()>());
944
945        assert!(result.is_err());
946    }
947
948    #[test]
949    fn baseline_headless_app_tasks_complete_without_window() {
950        let mut cx = TestAppContext::single();
951        let completed = Arc::new(AtomicBool::new(false));
952
953        cx.executor()
954            .spawn({
955                let completed = completed.clone();
956                async move {
957                    completed.store(true, Ordering::SeqCst);
958                }
959            })
960            .detach();
961        cx.run_until_parked();
962
963        assert!(completed.load(Ordering::SeqCst));
964        assert!(cx.windows().is_empty());
965    }
966
967    #[test]
968    fn baseline_scoped_tasks_complete_before_scope_returns() {
969        let cx = TestAppContext::single();
970        let executor = cx.executor();
971        let completed = Arc::new(AtomicBool::new(false));
972
973        executor.block_test({
974            let executor = executor.clone();
975            let completed = completed.clone();
976            async move {
977                executor
978                    .scoped(|scope| {
979                        scope.spawn(async move {
980                            completed.store(true, Ordering::SeqCst);
981                        });
982                    })
983                    .await;
984            }
985        });
986
987        assert!(completed.load(Ordering::SeqCst));
988    }
989
990    #[test]
991    fn spawn_with_priority_defaults_to_medium_behavior() {
992        let mut cx = TestAppContext::single();
993        let completed = Arc::new(AtomicBool::new(false));
994
995        cx.executor()
996            .spawn_with_priority(TaskPriority::Medium, {
997                let completed = completed.clone();
998                async move {
999                    completed.store(true, Ordering::SeqCst);
1000                }
1001            })
1002            .detach();
1003        cx.run_until_parked();
1004
1005        assert!(completed.load(Ordering::SeqCst));
1006    }
1007
1008    #[test]
1009    fn spawn_with_priority_high_is_observable_in_test_dispatcher() {
1010        let mut cx = TestAppContext::single();
1011        let executor = cx.executor();
1012        let order = Arc::new(Mutex::new(Vec::new()));
1013
1014        executor
1015            .spawn_with_priority(TaskPriority::Low, {
1016                let order = order.clone();
1017                async move {
1018                    order.lock().unwrap().push("low");
1019                }
1020            })
1021            .detach();
1022        executor
1023            .spawn_with_priority(TaskPriority::High, {
1024                let order = order.clone();
1025                async move {
1026                    order.lock().unwrap().push("high");
1027                }
1028            })
1029            .detach();
1030        cx.run_until_parked();
1031
1032        assert_eq!(&*order.lock().unwrap(), &["high", "low"]);
1033    }
1034
1035    #[test]
1036    fn spawn_labeled_deprioritize_still_works_after_priority_addition() {
1037        baseline_deprioritize_delays_labeled_task();
1038    }
1039
1040    #[test]
1041    fn low_priority_task_eventually_runs() {
1042        let mut cx = TestAppContext::single();
1043        let completed = Arc::new(AtomicBool::new(false));
1044
1045        cx.executor()
1046            .spawn_with_priority(TaskPriority::Low, {
1047                let completed = completed.clone();
1048                async move {
1049                    completed.store(true, Ordering::SeqCst);
1050                }
1051            })
1052            .detach();
1053        cx.run_until_parked();
1054
1055        assert!(completed.load(Ordering::SeqCst));
1056    }
1057}