Skip to main content

gpui/
executor.rs

1use crate::{ActivityGuard, App, PlatformDispatcher, PlatformScheduler};
2#[cfg(not(target_family = "wasm"))]
3use futures::channel::mpsc;
4use futures::prelude::*;
5use gpui_util::{TryFutureExt, TryFutureExtBacktrace};
6use scheduler::Instant;
7use scheduler::Scheduler;
8use std::{future::Future, marker::PhantomData, rc::Rc, sync::Arc, time::Duration};
9#[cfg(not(target_family = "wasm"))]
10use std::{mem, pin::Pin};
11
12pub use scheduler::{
13    DedicatedExecutor, FallibleTask, LocalExecutor as SchedulerLocalExecutor, LocalTaskScope,
14    Priority, Task,
15};
16
17/// Runs `Send` futures on GPUI's background executor.
18///
19/// Get one from [`App::background_executor`] or [`crate::AsyncApp::background_executor`].
20/// Its futures must be `Send` and `'static`, so they cannot borrow entities or contexts. Pass in
21/// plain data and return plain data. Native backends use worker threads; a single-threaded
22/// web build runs these futures on the browser's main thread. See
23/// [executors](crate::_documentation::concepts::executors).
24#[derive(Clone)]
25pub struct BackgroundExecutor {
26    inner: scheduler::BackgroundExecutor,
27    dispatcher: Arc<dyn PlatformDispatcher>,
28}
29
30/// Runs futures on the main thread, interleaved with event handling and drawing.
31///
32/// Get one from [`App::foreground_executor`] or [`crate::AsyncApp::foreground_executor`].
33/// Futures need only be `'static`, so they can hold entity handles. Each poll
34/// runs until the future returns `Pending` or `Ready`; an await can complete
35/// immediately. Keep CPU-heavy work out of a foreground poll and move it to a
36/// [`BackgroundExecutor`]. See
37/// [executors](crate::_documentation::concepts::executors).
38#[derive(Clone)]
39pub struct ForegroundExecutor {
40    inner: scheduler::LocalExecutor,
41    dispatcher: Arc<dyn PlatformDispatcher>,
42    #[cfg(feature = "profiler")]
43    foreground_runnables: Option<crate::profiler::journal::ForegroundRunnableCounter>,
44    not_send: PhantomData<Rc<()>>,
45}
46
47/// Owner-thread cancellation barrier for a platform-scoped foreground executor.
48#[doc(hidden)]
49#[derive(Clone)]
50pub struct ForegroundTaskRetirement {
51    dispatcher: Arc<dyn PlatformDispatcher>,
52    scope: Arc<LocalTaskScope>,
53    not_send: PhantomData<Rc<()>>,
54}
55
56impl ForegroundTaskRetirement {
57    /// Latches retirement and wakes every tracked local task.
58    pub fn begin(&self) {
59        // Let the platform route subsequent wakes to its owner-thread cancellation queue
60        // before waking tasks that may already have a runnable in flight.
61        self.dispatcher.begin_local_task_retirement(&self.scope);
62        self.scope.begin_retirement();
63    }
64
65    /// Drains one batch of accepted/cancellation runnables on the owner thread.
66    pub fn drain_once(&self) {
67        self.dispatcher.drain_local_task_retirements(&self.scope);
68    }
69
70    /// Finishes the scope once its queues and tracked futures are quiescent.
71    ///
72    /// Returns false while another owner-thread drain is required.
73    pub fn try_finish(&self) -> bool {
74        self.dispatcher.finish_local_task_retirement(&self.scope)
75    }
76}
77
78/// Extension methods for tasks that produce a [`Result`].
79///
80/// The methods log a failure instead of dropping it silently, so use them for work
81/// that you start and don't wait for.
82///
83/// # Examples
84///
85/// ```
86/// use gpui::{TaskExt, TestApp};
87///
88/// let mut app = TestApp::new();
89/// app.update(|cx| {
90///     let save = cx.background_executor().spawn(async { Err::<(), _>("disk full") });
91///     save.detach_and_log_err(cx);
92/// });
93/// ```
94pub trait TaskExt<T, E> {
95    /// Runs the task to completion in the background. If it fails, the error is written
96    /// to the log.
97    fn detach_and_log_err(self, cx: &App);
98    /// Like [`TaskExt::detach_and_log_err`], but logs the error with `Debug` formatting,
99    /// so an `anyhow::Error` includes its backtrace. Use it when you need the backtrace,
100    /// and the other method otherwise.
101    fn detach_and_log_err_with_backtrace(self, cx: &App);
102}
103
104impl<T, E> TaskExt<T, E> for Task<Result<T, E>>
105where
106    T: 'static,
107    E: 'static + std::fmt::Display + std::fmt::Debug,
108{
109    #[track_caller]
110    fn detach_and_log_err(self, cx: &App) {
111        let location = core::panic::Location::caller();
112        cx.foreground_executor()
113            .spawn(self.log_tracked_err(*location))
114            .detach();
115    }
116
117    #[track_caller]
118    fn detach_and_log_err_with_backtrace(self, cx: &App) {
119        let location = *core::panic::Location::caller();
120        cx.foreground_executor()
121            .spawn(self.log_tracked_err_with_backtrace(location))
122            .detach();
123    }
124}
125
126impl BackgroundExecutor {
127    /// Creates an executor that sends its work through `dispatcher`.
128    ///
129    /// Platform code creates the executor. Apps get one from [`App::background_executor`].
130    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
131        #[cfg(any(test, feature = "test-support"))]
132        let scheduler: Arc<dyn Scheduler> = if let Some(test_dispatcher) = dispatcher.as_test() {
133            test_dispatcher.scheduler().clone()
134        } else {
135            Arc::new(PlatformScheduler::new(dispatcher.clone()))
136        };
137
138        #[cfg(not(any(test, feature = "test-support")))]
139        let scheduler: Arc<dyn Scheduler> = Arc::new(PlatformScheduler::new(dispatcher.clone()));
140
141        Self {
142            inner: scheduler::BackgroundExecutor::new(scheduler),
143            dispatcher,
144        }
145    }
146
147    /// Returns the underlying scheduler executor, for code that takes one directly.
148    pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
149        self.inner.clone()
150    }
151
152    /// Runs the future that `f` returns on a thread of its own, and returns a task for
153    /// its result.
154    ///
155    /// Use this instead of [`BackgroundExecutor::spawn`] for work whose polls need more
156    /// stack than the shared background threads provide. A dedicated thread gets the
157    /// standard library's default stack of 2 MiB. Dropping the task cancels the work, as it
158    /// does for any task.
159    #[track_caller]
160    pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
161    where
162        F: FnOnce(SchedulerLocalExecutor) -> Fut + Send + 'static,
163        Fut: Future + 'static,
164        Fut::Output: Send + Sync + 'static,
165    {
166        self.inner.spawn_dedicated(f)
167    }
168
169    /// Prevents App Nap-style throttling while the returned guard is held.
170    ///
171    /// This does not prevent the system from entering idle sleep.
172    pub fn prevent_app_nap(&self, reason: &str) -> ActivityGuard {
173        self.dispatcher.prevent_app_nap(reason)
174    }
175
176    /// Schedules `future` on this executor and returns a task for its result.
177    ///
178    /// On native targets, use this for computation or blocking I/O that should leave
179    /// the main thread free. On a single-threaded web build it uses the main thread,
180    /// so each poll must remain short.
181    /// The future must be `Send` and `'static`. To get the result on the main thread,
182    /// await the task from a foreground future. Dropping the task cancels the future.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use gpui::TestApp;
188    ///
189    /// let app = TestApp::new();
190    /// let task = app.background_executor().spawn(async { 6 * 7 });
191    /// assert_eq!(app.foreground_executor().block_test(task), 42);
192    /// ```
193    #[track_caller]
194    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
195    where
196        R: Send + 'static,
197    {
198        self.spawn_with_priority(Priority::default(), future.boxed())
199    }
200
201    /// Like [`BackgroundExecutor::spawn`], but with a [`Priority`].
202    ///
203    /// Higher priorities are more likely to run first, but the order isn't guaranteed.
204    /// [`Priority::RealtimeAudio`] runs the future on a dedicated thread with realtime
205    /// scheduling. Use it only for audio work.
206    #[track_caller]
207    pub fn spawn_with_priority<R>(
208        &self,
209        priority: Priority,
210        future: impl Future<Output = R> + Send + 'static,
211    ) -> Task<R>
212    where
213        R: Send + 'static,
214    {
215        if priority == Priority::RealtimeAudio {
216            self.inner.spawn_realtime(future)
217        } else {
218            self.inner.spawn_with_priority(priority, future)
219        }
220    }
221
222    /// Runs futures that may borrow from the caller in parallel, and waits for all of
223    /// them to finish.
224    ///
225    /// `scheduler` receives a [`Scope`] and spawns the futures on it. Unlike
226    /// [`BackgroundExecutor::spawn`], those futures needn't be `'static`, because the
227    /// returned future doesn't complete until they have. Dropping the returned future
228    /// cancels the futures, and blocks until they are destroyed. If `scheduler` panics, the
229    /// futures that haven't started are dropped, and the panic unwinds without hanging.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use std::sync::atomic::{AtomicUsize, Ordering};
235    /// use gpui::TestApp;
236    ///
237    /// let app = TestApp::new();
238    /// let total = AtomicUsize::new(0);
239    /// app.foreground_executor().block_test(app.background_executor().scoped(|scope| {
240    ///     for n in 1..=3 {
241    ///         let total = &total;
242    ///         scope.spawn(async move {
243    ///             total.fetch_add(n, Ordering::SeqCst);
244    ///         });
245    ///     }
246    /// }));
247    /// assert_eq!(total.into_inner(), 6);
248    /// ```
249    #[cfg(not(target_family = "wasm"))]
250    pub async fn scoped<'scope, F>(&self, scheduler: F)
251    where
252        F: FnOnce(&mut Scope<'scope>),
253    {
254        let mut scope = Scope::new(self.clone(), Priority::default());
255        (scheduler)(&mut scope);
256        let spawned = mem::take(&mut scope.futures)
257            .into_iter()
258            .map(|f| self.spawn_with_priority(scope.priority, f))
259            .collect::<Vec<_>>();
260        for task in spawned {
261            task.await;
262        }
263    }
264
265    /// Like [`BackgroundExecutor::scoped`], but runs every future in the scope at
266    /// `priority`.
267    #[cfg(not(target_family = "wasm"))]
268    pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
269    where
270        F: FnOnce(&mut Scope<'scope>),
271    {
272        let mut scope = Scope::new(self.clone(), priority);
273        (scheduler)(&mut scope);
274        let spawned = mem::take(&mut scope.futures)
275            .into_iter()
276            .map(|f| self.spawn_with_priority(scope.priority, f))
277            .collect::<Vec<_>>();
278        for task in spawned {
279            task.await;
280        }
281    }
282
283    /// Returns the current time.
284    ///
285    /// Use this in place of `std::time::Instant::now` so that tests can control time. In
286    /// tests it reads the simulated clock, which moves only when the test advances it.
287    pub fn now(&self) -> Instant {
288        self.inner.scheduler().clock().now()
289    }
290
291    /// Returns a task that completes after `duration`.
292    ///
293    /// Await it to delay work or to repeat it on an interval. In tests, time moves only
294    /// when the test calls [`BackgroundExecutor::advance_clock`]. Other work can delay
295    /// the wake-up, so the real wait can be longer than `duration`. A zero duration
296    /// returns a task that is already complete.
297    ///
298    /// # Examples
299    ///
300    /// ```
301    /// use std::time::Duration;
302    /// use gpui::TestApp;
303    ///
304    /// let app = TestApp::new();
305    /// let timer = app.background_executor().timer(Duration::from_millis(500));
306    /// app.advance_clock(Duration::from_millis(500));
307    /// assert!(timer.is_ready());
308    /// ```
309    #[track_caller]
310    pub fn timer(&self, duration: Duration) -> Task<()> {
311        if duration.is_zero() {
312            return Task::ready(());
313        }
314        self.spawn(self.inner.scheduler().timer(duration))
315    }
316
317    /// Yields a random number of times, allowing other ready tasks to run between steps.
318    ///
319    /// Await it between steps of a test to shake out ordering bugs. Only available in
320    /// tests.
321    ///
322    /// # Panics
323    ///
324    /// Panics if this executor isn't a test executor.
325    #[cfg(any(test, feature = "test-support"))]
326    pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
327        self.dispatcher.as_test().unwrap().simulate_random_delay()
328    }
329
330    /// Moves the simulated clock forward by `duration`.
331    ///
332    /// Ready tasks run as the clock passes each timer, in time order, so a task that
333    /// waits on a timer resumes at the right moment. When the call returns, the clock
334    /// reads its earlier value plus `duration`. Only available in tests.
335    ///
336    /// # Panics
337    ///
338    /// Panics if this executor isn't a test executor.
339    #[cfg(any(test, feature = "test-support"))]
340    pub fn advance_clock(&self, duration: Duration) {
341        self.dispatcher.as_test().unwrap().advance_clock(duration)
342    }
343
344    /// Runs one unit of ready work in tests.
345    ///
346    /// Returns `true` if it ran a task or fired a timer, and `false` if there was nothing
347    /// to do. Only available in tests.
348    ///
349    /// # Panics
350    ///
351    /// Panics if this executor isn't a test executor.
352    #[cfg(any(test, feature = "test-support"))]
353    pub fn tick(&self) -> bool {
354        self.dispatcher.as_test().unwrap().scheduler().tick()
355    }
356
357    /// Runs ready tasks in tests until none can make progress.
358    ///
359    /// This doesn't move the simulated clock. A timer that hasn't expired stays pending,
360    /// until [`BackgroundExecutor::advance_clock`] moves time past it. Only available in
361    /// tests.
362    #[cfg(any(test, feature = "test-support"))]
363    pub fn run_until_parked(&self) {
364        let scheduler = self.dispatcher.as_test().unwrap().scheduler();
365        scheduler.run();
366    }
367
368    /// Lets blocking waits in tests sleep in real time when no test work can make progress.
369    ///
370    /// By default, a blocking call that cannot progress panics. With parking allowed, it waits
371    /// in real time instead, and the test fails if the wait lasts 15 seconds. Use this for tests
372    /// that wait on real I/O. Undo it with [`BackgroundExecutor::forbid_parking`].
373    #[cfg(any(test, feature = "test-support"))]
374    pub fn allow_parking(&self) {
375        self.dispatcher
376            .as_test()
377            .unwrap()
378            .scheduler()
379            .allow_parking();
380
381        if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
382            log::warn!("[gpui::executor] allow_parking: enabled");
383        }
384    }
385
386    /// Sets the range that the tick budget of a [`ForegroundExecutor::block_with_timeout`]
387    /// call is drawn from, in tests.
388    ///
389    /// The default is 1 to 1000 ticks. Only available in tests.
390    #[cfg(any(test, feature = "test-support"))]
391    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
392        self.dispatcher
393            .as_test()
394            .unwrap()
395            .scheduler()
396            .set_timeout_ticks(range);
397    }
398
399    /// Undoes [`BackgroundExecutor::allow_parking`], so a blocking call that can't
400    /// progress panics again. Only available in tests.
401    #[cfg(any(test, feature = "test-support"))]
402    pub fn forbid_parking(&self) {
403        self.dispatcher
404            .as_test()
405            .unwrap()
406            .scheduler()
407            .forbid_parking();
408    }
409
410    /// Returns the seeded random number generator that the test scheduler uses.
411    ///
412    /// Draw from it when a test makes its own random choices, so that the choices
413    /// repeat with the same `SEED`. Only available in tests.
414    #[cfg(any(test, feature = "test-support"))]
415    pub fn rng(&self) -> scheduler::SharedRng {
416        self.dispatcher.as_test().unwrap().scheduler().rng()
417    }
418
419    /// Returns the number of CPUs the dispatcher reports.
420    ///
421    /// In tests this is 4 unless it was changed with [`BackgroundExecutor::set_num_cpus`].
422    pub fn num_cpus(&self) -> usize {
423        #[cfg(any(test, feature = "test-support"))]
424        if let Some(test) = self.dispatcher.as_test() {
425            return test.num_cpus_override().unwrap_or(4);
426        }
427        num_cpus::get()
428    }
429
430    /// Sets the value that [`BackgroundExecutor::num_cpus`] returns, in tests.
431    ///
432    /// # Panics
433    ///
434    /// Panics if this executor isn't a test executor.
435    #[cfg(any(test, feature = "test-support"))]
436    pub fn set_num_cpus(&self, count: usize) {
437        self.dispatcher
438            .as_test()
439            .expect("set_num_cpus can only be called on a test executor")
440            .set_num_cpus(count);
441    }
442
443    /// Returns whether the current thread is the main thread.
444    pub fn is_main_thread(&self) -> bool {
445        self.dispatcher.is_main_thread()
446    }
447
448    /// Whether this executor runs on the deterministic test scheduler.
449    ///
450    /// Available in every build. Use it, rather than `cfg(feature =
451    /// "test-support")`, for behavior that must differ under tests, so that
452    /// compiling `test-support` into an application doesn't change it.
453    pub fn is_test(&self) -> bool {
454        self.inner.is_test()
455    }
456
457    /// Returns the platform dispatcher this executor sends work through. GPUI uses this
458    /// internally.
459    #[doc(hidden)]
460    pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
461        &self.dispatcher
462    }
463}
464
465impl ForegroundExecutor {
466    /// Creates an executor that runs futures on the main thread through `dispatcher`.
467    ///
468    /// Platform code creates the executor. Apps get one from
469    /// [`App::foreground_executor`] or [`crate::AsyncApp::foreground_executor`].
470    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
471        #[cfg(any(test, feature = "test-support"))]
472        let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
473            if let Some(test_dispatcher) = dispatcher.as_test() {
474                (
475                    test_dispatcher.scheduler().clone(),
476                    test_dispatcher.session_id(),
477                )
478            } else {
479                let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
480                let inner = platform_scheduler.foreground_executor();
481                return Self {
482                    inner,
483                    dispatcher,
484                    #[cfg(feature = "profiler")]
485                    foreground_runnables: Some(platform_scheduler.foreground_runnable_counter()),
486                    not_send: PhantomData,
487                };
488            };
489
490        #[cfg(not(any(test, feature = "test-support")))]
491        let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
492        #[cfg(not(any(test, feature = "test-support")))]
493        let inner = platform_scheduler.foreground_executor();
494        #[cfg(all(not(any(test, feature = "test-support")), feature = "profiler"))]
495        let foreground_runnables = Some(platform_scheduler.foreground_runnable_counter());
496
497        #[cfg(any(test, feature = "test-support"))]
498        let inner = {
499            let scheduler_for_dispatch = Arc::downgrade(&scheduler);
500            scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
501                if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
502                    scheduler.schedule_local(session_id, runnable);
503                }
504            })
505        };
506
507        #[cfg(all(any(test, feature = "test-support"), feature = "profiler"))]
508        // The deterministic test scheduler does not invoke GPUI's task profiler
509        // hooks, so an increment here would have no matching decrement.
510        let foreground_runnables = None;
511
512        Self {
513            inner,
514            dispatcher,
515            #[cfg(feature = "profiler")]
516            foreground_runnables,
517            not_send: PhantomData,
518        }
519    }
520
521    /// Returns the platform retirement barrier when the dispatcher opted into
522    /// owner-thread task retirement.
523    #[doc(hidden)]
524    pub fn retirement_handle(&self) -> Option<ForegroundTaskRetirement> {
525        Some(ForegroundTaskRetirement {
526            dispatcher: self.dispatcher.clone(),
527            scope: self.inner.retirement_scope()?,
528            not_send: PhantomData,
529        })
530    }
531
532    /// Runs `future` on the main thread, and returns a task for its result.
533    ///
534    /// The future needn't be `Send`, so it can hold entity handles and other state that
535    /// lives on the main thread. Each poll runs until the future returns `Pending` or
536    /// `Ready`; an await can complete immediately. Keep long computations out of a poll
537    /// and use [`BackgroundExecutor::spawn`] for them. Dropping the task cancels the
538    /// future, but cannot interrupt synchronous code already running on this thread.
539    ///
540    /// # Examples
541    ///
542    /// ```
543    /// use gpui::TestApp;
544    ///
545    /// let app = TestApp::new();
546    /// let answer = app.foreground_executor().spawn(async { 6 * 7 });
547    /// assert_eq!(app.foreground_executor().block_test(answer), 42);
548    /// ```
549    #[track_caller]
550    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
551    where
552        R: 'static,
553    {
554        self.inner.spawn(future.boxed_local())
555    }
556
557    /// Like [`ForegroundExecutor::spawn`]. The priority is ignored: foreground futures run
558    /// in the order they were spawned.
559    #[track_caller]
560    pub fn spawn_with_priority<R>(
561        &self,
562        _priority: Priority,
563        future: impl Future<Output = R> + 'static,
564    ) -> Task<R>
565    where
566        R: 'static,
567    {
568        // Priority is ignored for foreground tasks - they run in order on the main thread
569        self.inner.spawn(future)
570    }
571
572    /// Runs `future` on the main thread when the platform is idle.
573    ///
574    /// Use this for low-priority UI work that can wait. `timeout` sets how long the
575    /// future may wait. `None` lets it wait as long as the platform stays busy. With a
576    /// timeout, a poll that is still waiting after that long runs as ordinary main-thread
577    /// work. On platforms without idle scheduling the future runs at low priority, and
578    /// `timeout` is ignored.
579    ///
580    /// Each poll uses part of one idle slice. Long synchronous stretches should check
581    /// [`ForegroundExecutor::idle_time_remaining`] and yield when it runs low.
582    #[track_caller]
583    pub fn spawn_when_idle<R>(
584        &self,
585        timeout: Option<Duration>,
586        future: impl Future<Output = R> + 'static,
587    ) -> Task<R>
588    where
589        R: 'static,
590    {
591        let dispatcher = self.dispatcher.clone();
592        let retirement_scope = self.inner.retirement_scope();
593        #[cfg(feature = "profiler")]
594        let foreground_runnables = self.foreground_runnables.clone();
595        self.inner
596            .spawn_with_dispatch(future.boxed_local(), move |runnable| {
597                #[cfg(feature = "profiler")]
598                if let Some(foreground_runnables) = &foreground_runnables {
599                    foreground_runnables.queued();
600                }
601                if let Some(scope) = retirement_scope.clone() {
602                    dispatcher.dispatch_local_task_when_idle(scope, runnable, timeout);
603                } else {
604                    dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
605                }
606            })
607    }
608
609    /// Returns how much of the current idle slice is left.
610    ///
611    /// Call it from a future that was spawned with
612    /// [`ForegroundExecutor::spawn_when_idle`]. Returns `None` on platforms that don't
613    /// meter idle time, and when the call isn't inside an idle slice. In that case,
614    /// fall back to a budget of your own.
615    pub fn idle_time_remaining(&self) -> Option<Duration> {
616        self.dispatcher.idle_time_remaining()
617    }
618
619    /// Runs `future` on the main thread until it completes, and returns its output. Only
620    /// available in tests.
621    ///
622    /// If the future is waiting and no other test work can run, the simulated clock
623    /// moves to the next timer.
624    ///
625    /// # Panics
626    ///
627    /// Panics if `future` is waiting, no timer is pending, and parking isn't allowed.
628    #[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
629    #[track_caller]
630    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
631        use std::cell::Cell;
632
633        let scheduler = self.inner.scheduler();
634
635        let output = Cell::new(None);
636        let future = async {
637            output.set(Some(future.await));
638        };
639        let mut future = std::pin::pin!(future);
640
641        // In async GPUI tests, we must allow foreground tasks scheduled by the test itself
642        // (which are associated with the test session) to make progress while we block.
643        // Otherwise, awaiting futures that depend on same-session foreground work can deadlock.
644        scheduler.block(None, future.as_mut(), None);
645
646        output.take().expect("block_test future did not complete")
647    }
648
649    /// Blocks the current thread until `future` completes, and returns its output.
650    ///
651    /// Prefer [`ForegroundExecutor::block_with_timeout`], which gives up instead of
652    /// waiting indefinitely.
653    #[cfg(not(target_family = "wasm"))]
654    pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
655        self.inner.block_on(future)
656    }
657
658    /// Blocks the current thread until `future` completes or `duration` passes.
659    ///
660    /// Returns `Ok` with the output if the future finished. Otherwise returns `Err` with
661    /// the unfinished future, which you can poll later.
662    ///
663    /// # Examples
664    ///
665    /// ```
666    /// use std::time::Duration;
667    /// use gpui::TestApp;
668    ///
669    /// let app = TestApp::new();
670    /// let result = app
671    ///     .foreground_executor()
672    ///     .block_with_timeout(Duration::from_secs(1), async { 5 });
673    /// assert_eq!(result.ok(), Some(5));
674    /// ```
675    #[cfg(not(target_family = "wasm"))]
676    pub fn block_with_timeout<R, Fut: Future<Output = R>>(
677        &self,
678        duration: Duration,
679        future: Fut,
680    ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
681        self.inner.block_with_timeout(duration, future)
682    }
683
684    /// Returns the platform dispatcher this executor sends work through. GPUI uses this
685    /// internally.
686    #[doc(hidden)]
687    pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
688        &self.dispatcher
689    }
690
691    /// Returns the scheduler's local executor, for code that takes one directly.
692    #[doc(hidden)]
693    pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
694        self.inner.clone()
695    }
696}
697
698/// A set of futures that run together and are awaited together.
699///
700/// [`BackgroundExecutor::scoped`] and [`BackgroundExecutor::scoped_priority`] pass
701/// you a `Scope`. Spawn futures on it inside the closure. Not available on wasm.
702#[cfg(not(target_family = "wasm"))]
703pub struct Scope<'a> {
704    executor: BackgroundExecutor,
705    priority: Priority,
706    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
707    tx: Option<mpsc::Sender<()>>,
708    rx: mpsc::Receiver<()>,
709    lifetime: PhantomData<&'a ()>,
710}
711
712#[cfg(not(target_family = "wasm"))]
713impl<'a> Scope<'a> {
714    fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
715        let (tx, rx) = mpsc::channel(1);
716        Self {
717            executor,
718            priority,
719            tx: Some(tx),
720            rx,
721            futures: Default::default(),
722            lifetime: PhantomData,
723        }
724    }
725
726    /// Returns the number of CPUs the dispatcher reports. See
727    /// [`BackgroundExecutor::num_cpus`].
728    pub fn num_cpus(&self) -> usize {
729        self.executor.num_cpus()
730    }
731
732    /// Adds `f` to the scope.
733    ///
734    /// The futures start when the scope's future is first polled, and run in parallel
735    /// with the others. The scope waits for every future, so `f` may borrow data that
736    /// lives as long as the scope.
737    #[track_caller]
738    pub fn spawn<F>(&mut self, f: F)
739    where
740        F: Future<Output = ()> + Send + 'a,
741    {
742        let tx = self.tx.clone().unwrap();
743
744        // SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
745        // dropping this `Scope` blocks until all of the futures have resolved.
746        let f = unsafe {
747            mem::transmute::<
748                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
749                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
750            >(Box::pin(async move {
751                f.await;
752                drop(tx);
753            }))
754        };
755        self.futures.push(f);
756    }
757}
758
759#[cfg(not(target_family = "wasm"))]
760impl Drop for Scope<'_> {
761    fn drop(&mut self) {
762        // Futures that were never submitted hold clones of `tx`, which would keep the
763        // channel open forever when the scope is dropped while unwinding.
764        self.futures.clear();
765        self.tx.take().unwrap();
766
767        // Wait until the channel is closed, which means that all of the spawned
768        // futures have resolved.
769        let future = async {
770            self.rx.next().await;
771        };
772        let mut future = std::pin::pin!(future);
773        self.executor
774            .inner
775            .scheduler()
776            .block(None, future.as_mut(), None);
777    }
778}
779
780#[cfg(test)]
781mod test {
782    use super::*;
783    use crate::{App, AssetRegistry, TestDispatcher, TestPlatform};
784    use std::cell::RefCell;
785
786    /// Helper to create test infrastructure.
787    /// Returns (dispatcher, background_executor, app).
788    fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
789        let dispatcher = TestDispatcher::new(0);
790        let arc_dispatcher = Arc::new(dispatcher.clone());
791        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
792        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
793
794        let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
795        let http_client = crate::http_client::FakeHttpClient::with_404_response();
796
797        let app = App::new_app(platform, AssetRegistry::default().into(), http_client);
798        (dispatcher, background_executor, app)
799    }
800
801    #[test]
802    #[cfg(not(target_family = "wasm"))]
803    fn scoped_registration_panic_does_not_block_on_unstarted_futures() {
804        use std::sync::mpsc;
805
806        // A broken cleanup path blocks forever in `Scope::drop`, so run it on a
807        // separate thread and fail with a timeout instead of hanging the test.
808        let (completed_tx, completed_rx) = mpsc::channel();
809        let worker = std::thread::spawn(move || {
810            let dispatcher = Arc::new(TestDispatcher::new(0));
811            let executor = BackgroundExecutor::new(dispatcher);
812            let unwound = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
813                futures::executor::block_on(executor.scoped(|scope| {
814                    scope.spawn(async { unreachable!("an unsubmitted future must not run") });
815                    panic!("registration failed");
816                }));
817            }));
818            completed_tx.send(unwound.is_err()).unwrap();
819        });
820
821        assert_eq!(
822            completed_rx.recv_timeout(Duration::from_secs(5)),
823            Ok(true),
824            "the scope must unwind after dropping its unstarted futures"
825        );
826        worker.join().unwrap();
827    }
828
829    #[test]
830    fn sanity_test_tasks_run() {
831        let (dispatcher, _background_executor, app) = create_test_app();
832        let foreground_executor = app.borrow().foreground_executor.clone();
833
834        let task_ran = Rc::new(RefCell::new(false));
835
836        foreground_executor
837            .spawn({
838                let task_ran = Rc::clone(&task_ran);
839                async move {
840                    *task_ran.borrow_mut() = true;
841                }
842            })
843            .detach();
844
845        // Run dispatcher while app is still alive
846        dispatcher.run_until_parked();
847
848        // Task should have run
849        assert!(
850            *task_ran.borrow(),
851            "Task should run normally when app is alive"
852        );
853    }
854}