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, Priority, Task,
14};
15
16/// A pointer to the executor that is currently running,
17/// for spawning background tasks.
18#[derive(Clone)]
19pub struct BackgroundExecutor {
20    inner: scheduler::BackgroundExecutor,
21    dispatcher: Arc<dyn PlatformDispatcher>,
22}
23
24/// A pointer to the executor that is currently running,
25/// for spawning tasks on the main thread.
26#[derive(Clone)]
27pub struct ForegroundExecutor {
28    inner: scheduler::LocalExecutor,
29    dispatcher: Arc<dyn PlatformDispatcher>,
30    #[cfg(feature = "profiler")]
31    foreground_runnables: Option<crate::profiler::journal::ForegroundRunnableCounter>,
32    not_send: PhantomData<Rc<()>>,
33}
34
35/// A thread-safe handle for spawning `Send` futures on the main thread.
36#[derive(Clone)]
37pub struct SendForegroundExecutor {
38    inner: scheduler::SendLocalExecutor,
39}
40
41impl SendForegroundExecutor {
42    /// Enqueues a future to run on the main thread, even when submitted from another thread.
43    #[track_caller]
44    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
45    where
46        R: Send + 'static,
47    {
48        self.inner.spawn(future)
49    }
50}
51
52/// Extension trait for `Task<Result<T, E>>` that adds `detach_and_log_err` with an `&App` context.
53///
54/// This trait is automatically implemented for all `Task<Result<T, E>>` types.
55pub trait TaskExt<T, E> {
56    /// Run the task to completion in the background and log any errors that occur.
57    fn detach_and_log_err(self, cx: &App);
58    /// Like [`Self::detach_and_log_err`], but uses `{:?}` formatting on failure so `anyhow::Error`
59    /// values emit their full backtrace. Prefer `detach_and_log_err` unless a backtrace is wanted.
60    fn detach_and_log_err_with_backtrace(self, cx: &App);
61}
62
63impl<T, E> TaskExt<T, E> for Task<Result<T, E>>
64where
65    T: 'static,
66    E: 'static + std::fmt::Display + std::fmt::Debug,
67{
68    #[track_caller]
69    fn detach_and_log_err(self, cx: &App) {
70        let location = core::panic::Location::caller();
71        cx.foreground_executor()
72            .spawn(self.log_tracked_err(*location))
73            .detach();
74    }
75
76    #[track_caller]
77    fn detach_and_log_err_with_backtrace(self, cx: &App) {
78        let location = *core::panic::Location::caller();
79        cx.foreground_executor()
80            .spawn(self.log_tracked_err_with_backtrace(location))
81            .detach();
82    }
83}
84
85impl BackgroundExecutor {
86    /// Creates a new BackgroundExecutor from the given PlatformDispatcher.
87    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
88        #[cfg(any(test, feature = "test-support"))]
89        let scheduler: Arc<dyn Scheduler> = if let Some(test_dispatcher) = dispatcher.as_test() {
90            test_dispatcher.scheduler().clone()
91        } else {
92            Arc::new(PlatformScheduler::new(dispatcher.clone()))
93        };
94
95        #[cfg(not(any(test, feature = "test-support")))]
96        let scheduler: Arc<dyn Scheduler> = Arc::new(PlatformScheduler::new(dispatcher.clone()));
97
98        Self {
99            inner: scheduler::BackgroundExecutor::new(scheduler),
100            dispatcher,
101        }
102    }
103
104    /// Returns the underlying scheduler::BackgroundExecutor.
105    ///
106    /// This is used by Ex to pass the executor to thread/worktree code.
107    pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
108        self.inner.clone()
109    }
110
111    /// Prevents App Nap-style throttling while the returned guard is held.
112    ///
113    /// This does not prevent the system from entering idle sleep.
114    pub fn prevent_app_nap(&self, reason: &str) -> ActivityGuard {
115        self.dispatcher.prevent_app_nap(reason)
116    }
117
118    /// Enqueues the given future to be run to completion on a background thread.
119    #[track_caller]
120    pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
121    where
122        R: Send + 'static,
123    {
124        self.spawn_with_priority(Priority::default(), future.boxed())
125    }
126
127    /// Enqueues the given future to be run to completion on a background thread with the given priority.
128    ///
129    /// When `Priority::RealtimeAudio` is used, the task runs on a dedicated thread with
130    /// realtime scheduling priority, suitable for audio processing.
131    #[track_caller]
132    pub fn spawn_with_priority<R>(
133        &self,
134        priority: Priority,
135        future: impl Future<Output = R> + Send + 'static,
136    ) -> Task<R>
137    where
138        R: Send + 'static,
139    {
140        if priority == Priority::RealtimeAudio {
141            self.inner.spawn_realtime(future)
142        } else {
143            self.inner.spawn_with_priority(priority, future)
144        }
145    }
146
147    /// Runs background tasks that may borrow from their environment and waits for all of them to complete.
148    ///
149    /// Dropping the returned future cancels its tasks and synchronously waits for their futures to
150    /// be destroyed before returning.
151    #[cfg(not(target_family = "wasm"))]
152    pub async fn scoped<'scope, F>(&self, scheduler: F)
153    where
154        F: FnOnce(&mut Scope<'scope>),
155    {
156        let mut scope = Scope::new(self.clone(), Priority::default());
157        (scheduler)(&mut scope);
158        let spawned = mem::take(&mut scope.futures)
159            .into_iter()
160            .map(|f| self.spawn_with_priority(scope.priority, f))
161            .collect::<Vec<_>>();
162        for task in spawned {
163            task.await;
164        }
165    }
166
167    /// Runs prioritized background tasks that may borrow from their environment and waits for all
168    /// of them to complete.
169    ///
170    /// Dropping the returned future cancels its tasks and synchronously waits for their futures to
171    /// be destroyed before returning.
172    #[cfg(not(target_family = "wasm"))]
173    pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
174    where
175        F: FnOnce(&mut Scope<'scope>),
176    {
177        let mut scope = Scope::new(self.clone(), priority);
178        (scheduler)(&mut scope);
179        let spawned = mem::take(&mut scope.futures)
180            .into_iter()
181            .map(|f| self.spawn_with_priority(scope.priority, f))
182            .collect::<Vec<_>>();
183        for task in spawned {
184            task.await;
185        }
186    }
187
188    /// Get the current time.
189    ///
190    /// Calling this instead of `std::time::Instant::now` allows the use
191    /// of fake timers in tests.
192    pub fn now(&self) -> Instant {
193        self.inner.scheduler().clock().now()
194    }
195
196    /// Returns a task that will complete after the given duration.
197    /// Depending on other concurrent tasks the elapsed duration may be longer
198    /// than requested.
199    #[track_caller]
200    pub fn timer(&self, duration: Duration) -> Task<()> {
201        if duration.is_zero() {
202            return Task::ready(());
203        }
204        self.spawn(self.inner.scheduler().timer(duration))
205    }
206
207    /// In tests, run an arbitrary number of tasks (determined by the SEED environment variable)
208    #[cfg(any(test, feature = "test-support"))]
209    pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
210        self.dispatcher.as_test().unwrap().simulate_random_delay()
211    }
212
213    /// In tests, move time forward. This does not run any tasks, but does make `timer`s ready.
214    #[cfg(any(test, feature = "test-support"))]
215    pub fn advance_clock(&self, duration: Duration) {
216        self.dispatcher.as_test().unwrap().advance_clock(duration)
217    }
218
219    /// In tests, run one task.
220    #[cfg(any(test, feature = "test-support"))]
221    pub fn tick(&self) -> bool {
222        self.dispatcher.as_test().unwrap().scheduler().tick()
223    }
224
225    /// In tests, run tasks until the scheduler would park.
226    ///
227    /// Under the scheduler-backed test dispatcher, `tick()` will not advance the clock, so a pending
228    /// timer can keep `has_pending_tasks()` true even after all currently-runnable tasks have been
229    /// drained. To preserve the historical semantics that tests relied on (drain all work that can
230    /// make progress), we advance the clock to the next timer when no runnable tasks remain.
231    #[cfg(any(test, feature = "test-support"))]
232    pub fn run_until_parked(&self) {
233        let scheduler = self.dispatcher.as_test().unwrap().scheduler();
234        scheduler.run();
235    }
236
237    /// In tests, prevents `run_until_parked` from panicking if there are outstanding tasks.
238    #[cfg(any(test, feature = "test-support"))]
239    pub fn allow_parking(&self) {
240        self.dispatcher
241            .as_test()
242            .unwrap()
243            .scheduler()
244            .allow_parking();
245
246        if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
247            log::warn!("[gpui::executor] allow_parking: enabled");
248        }
249    }
250
251    /// Sets the range of ticks to run before timing out in block_on.
252    #[cfg(any(test, feature = "test-support"))]
253    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
254        self.dispatcher
255            .as_test()
256            .unwrap()
257            .scheduler()
258            .set_timeout_ticks(range);
259    }
260
261    /// Undoes the effect of [`Self::allow_parking`].
262    #[cfg(any(test, feature = "test-support"))]
263    pub fn forbid_parking(&self) {
264        self.dispatcher
265            .as_test()
266            .unwrap()
267            .scheduler()
268            .forbid_parking();
269    }
270
271    /// In tests, returns the rng used by the dispatcher.
272    #[cfg(any(test, feature = "test-support"))]
273    pub fn rng(&self) -> scheduler::SharedRng {
274        self.dispatcher.as_test().unwrap().scheduler().rng()
275    }
276
277    /// How many CPUs are available to the dispatcher.
278    pub fn num_cpus(&self) -> usize {
279        #[cfg(any(test, feature = "test-support"))]
280        if let Some(test) = self.dispatcher.as_test() {
281            return test.num_cpus_override().unwrap_or(4);
282        }
283        num_cpus::get()
284    }
285
286    /// Override the number of CPUs reported by this executor in tests.
287    /// Panics if not called on a test executor.
288    #[cfg(any(test, feature = "test-support"))]
289    pub fn set_num_cpus(&self, count: usize) {
290        self.dispatcher
291            .as_test()
292            .expect("set_num_cpus can only be called on a test executor")
293            .set_num_cpus(count);
294    }
295
296    /// Whether we're on the main thread.
297    pub fn is_main_thread(&self) -> bool {
298        self.dispatcher.is_main_thread()
299    }
300
301    /// Whether this executor runs on the deterministic test scheduler.
302    ///
303    /// Available in every build. Use it, rather than `cfg(feature =
304    /// "test-support")`, for behavior that must differ under tests, so that
305    /// compiling `test-support` into an application doesn't change it.
306    pub fn is_test(&self) -> bool {
307        self.inner.is_test()
308    }
309
310    #[doc(hidden)]
311    pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
312        &self.dispatcher
313    }
314}
315
316impl ForegroundExecutor {
317    /// Creates a thread-safe handle that submits `Send` futures to this foreground executor.
318    pub fn to_send(&self) -> SendForegroundExecutor {
319        SendForegroundExecutor {
320            inner: self.inner.to_send(),
321        }
322    }
323
324    /// Creates a new ForegroundExecutor from the given PlatformDispatcher.
325    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
326        #[cfg(any(test, feature = "test-support"))]
327        let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
328            if let Some(test_dispatcher) = dispatcher.as_test() {
329                (
330                    test_dispatcher.scheduler().clone(),
331                    test_dispatcher.session_id(),
332                )
333            } else {
334                let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
335                let inner = platform_scheduler.foreground_executor();
336                return Self {
337                    inner,
338                    dispatcher,
339                    #[cfg(feature = "profiler")]
340                    foreground_runnables: Some(platform_scheduler.foreground_runnable_counter()),
341                    not_send: PhantomData,
342                };
343            };
344
345        #[cfg(not(any(test, feature = "test-support")))]
346        let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
347        #[cfg(not(any(test, feature = "test-support")))]
348        let inner = platform_scheduler.foreground_executor();
349        #[cfg(all(not(any(test, feature = "test-support")), feature = "profiler"))]
350        let foreground_runnables = Some(platform_scheduler.foreground_runnable_counter());
351
352        #[cfg(any(test, feature = "test-support"))]
353        let inner = {
354            let scheduler_for_dispatch = Arc::downgrade(&scheduler);
355            scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
356                if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
357                    scheduler.schedule_local(session_id, runnable);
358                }
359            })
360        };
361
362        #[cfg(all(any(test, feature = "test-support"), feature = "profiler"))]
363        // The deterministic test scheduler does not invoke GPUI's task profiler
364        // hooks, so an increment here would have no matching decrement.
365        let foreground_runnables = None;
366
367        Self {
368            inner,
369            dispatcher,
370            #[cfg(feature = "profiler")]
371            foreground_runnables,
372            not_send: PhantomData,
373        }
374    }
375
376    /// Enqueues the given Task to run on the main thread.
377    #[track_caller]
378    pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
379    where
380        R: 'static,
381    {
382        self.inner.spawn(future.boxed_local())
383    }
384
385    /// Enqueues the given Task to run on the main thread with the given priority.
386    #[track_caller]
387    pub fn spawn_with_priority<R>(
388        &self,
389        _priority: Priority,
390        future: impl Future<Output = R> + 'static,
391    ) -> Task<R>
392    where
393        R: 'static,
394    {
395        // Priority is ignored for foreground tasks - they run in order on the main thread
396        self.inner.spawn(future)
397    }
398
399    /// On platforms with dedicated support, enqueues the given future to run
400    /// on the main thread during platform idle time. Without a `timeout`,
401    /// polls may be deferred indefinitely while the platform stays busy;
402    /// with one, a poll still waiting after that long runs as ordinary main-thread work.
403    /// Each poll occupies part of one idle slice, so long synchronous stretches
404    /// should bound themselves against [`Self::idle_time_remaining`] and yield.
405    ///
406    /// On platforms without dedicated support, schedules the given future to run
407    /// with a low priority, ignoring `timeout`.
408    #[track_caller]
409    pub fn spawn_when_idle<R>(
410        &self,
411        timeout: Option<Duration>,
412        future: impl Future<Output = R> + 'static,
413    ) -> Task<R>
414    where
415        R: 'static,
416    {
417        let dispatcher = self.dispatcher.clone();
418        #[cfg(feature = "profiler")]
419        let foreground_runnables = self.foreground_runnables.clone();
420        self.inner
421            .spawn_with_dispatch(future.boxed_local(), move |runnable| {
422                #[cfg(feature = "profiler")]
423                if let Some(foreground_runnables) = &foreground_runnables {
424                    foreground_runnables.queued();
425                }
426                dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
427            })
428    }
429
430    /// The time remaining in the current idle slice, when called from a task
431    /// spawned via [`Self::spawn_when_idle`] on a platform that meters idle
432    /// time. `None` when idle time is unmetered (or the caller is not inside
433    /// an idle slice); work that must bound itself should then fall back to a
434    /// budget of its own.
435    pub fn idle_time_remaining(&self) -> Option<Duration> {
436        self.dispatcher.idle_time_remaining()
437    }
438
439    /// Used by the test harness to run an async test in a synchronous fashion.
440    #[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
441    #[track_caller]
442    pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
443        use std::cell::Cell;
444
445        let scheduler = self.inner.scheduler();
446
447        let output = Cell::new(None);
448        let future = async {
449            output.set(Some(future.await));
450        };
451        let mut future = std::pin::pin!(future);
452
453        // In async GPUI tests, we must allow foreground tasks scheduled by the test itself
454        // (which are associated with the test session) to make progress while we block.
455        // Otherwise, awaiting futures that depend on same-session foreground work can deadlock.
456        scheduler.block(None, future.as_mut(), None);
457
458        output.take().expect("block_test future did not complete")
459    }
460
461    /// Block the current thread until the given future resolves.
462    /// Consider using `block_with_timeout` instead.
463    #[cfg(not(target_family = "wasm"))]
464    pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
465        self.inner.block_on(future)
466    }
467
468    /// Block the current thread until the given future resolves or the timeout elapses.
469    #[cfg(not(target_family = "wasm"))]
470    pub fn block_with_timeout<R, Fut: Future<Output = R>>(
471        &self,
472        duration: Duration,
473        future: Fut,
474    ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
475        self.inner.block_with_timeout(duration, future)
476    }
477
478    #[doc(hidden)]
479    pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
480        &self.dispatcher
481    }
482
483    #[doc(hidden)]
484    pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
485        self.inner.clone()
486    }
487}
488
489/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
490#[cfg(not(target_family = "wasm"))]
491pub struct Scope<'a> {
492    executor: BackgroundExecutor,
493    priority: Priority,
494    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
495    tx: Option<mpsc::Sender<()>>,
496    rx: mpsc::Receiver<()>,
497    lifetime: PhantomData<&'a ()>,
498}
499
500#[cfg(not(target_family = "wasm"))]
501impl<'a> Scope<'a> {
502    fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
503        let (tx, rx) = mpsc::channel(1);
504        Self {
505            executor,
506            priority,
507            tx: Some(tx),
508            rx,
509            futures: Default::default(),
510            lifetime: PhantomData,
511        }
512    }
513
514    /// How many CPUs are available to the dispatcher.
515    pub fn num_cpus(&self) -> usize {
516        self.executor.num_cpus()
517    }
518
519    /// Spawn a future into this scope.
520    #[track_caller]
521    pub fn spawn<F>(&mut self, f: F)
522    where
523        F: Future<Output = ()> + Send + 'a,
524    {
525        let tx = self.tx.clone().unwrap();
526
527        // SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
528        // dropping this `Scope` blocks until all of the futures have resolved.
529        let f = unsafe {
530            mem::transmute::<
531                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
532                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
533            >(Box::pin(async move {
534                f.await;
535                drop(tx);
536            }))
537        };
538        self.futures.push(f);
539    }
540}
541
542#[cfg(not(target_family = "wasm"))]
543impl Drop for Scope<'_> {
544    fn drop(&mut self) {
545        self.tx.take().unwrap();
546
547        // Wait until the channel is closed, which means that all of the spawned
548        // futures have resolved.
549        let future = async {
550            self.rx.next().await;
551        };
552        let mut future = std::pin::pin!(future);
553        self.executor
554            .inner
555            .scheduler()
556            .block(None, future.as_mut(), None);
557    }
558}
559
560#[cfg(test)]
561mod test {
562    use super::*;
563    use crate::{App, TestDispatcher, TestPlatform};
564    use std::cell::RefCell;
565
566    /// Helper to create test infrastructure.
567    /// Returns (dispatcher, background_executor, app).
568    fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
569        let dispatcher = TestDispatcher::new(0);
570        let arc_dispatcher = Arc::new(dispatcher.clone());
571        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
572        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
573
574        let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
575        let asset_source = Arc::new(());
576        let http_client = http_client::FakeHttpClient::with_404_response();
577
578        let app = App::new_app(platform, asset_source, http_client);
579        (dispatcher, background_executor, app)
580    }
581
582    #[test]
583    fn send_foreground_executor_submits_from_worker() {
584        let dispatcher = TestDispatcher::new(0);
585        let foreground_executor = ForegroundExecutor::new(Arc::new(dispatcher.clone()));
586        let main_thread = std::thread::current().id();
587        let foreground_executor = foreground_executor.to_send();
588        let task = std::thread::spawn(move || {
589            foreground_executor.spawn(async move {
590                assert_eq!(std::thread::current().id(), main_thread);
591                42
592            })
593        })
594        .join()
595        .expect("worker must submit the foreground task");
596
597        dispatcher.run_until_parked();
598        assert_eq!(task.now_or_never(), Some(42));
599    }
600
601    #[test]
602    fn sanity_test_tasks_run() {
603        let (dispatcher, _background_executor, app) = create_test_app();
604        let foreground_executor = app.borrow().foreground_executor.clone();
605
606        let task_ran = Rc::new(RefCell::new(false));
607
608        foreground_executor
609            .spawn({
610                let task_ran = Rc::clone(&task_ran);
611                async move {
612                    *task_ran.borrow_mut() = true;
613                }
614            })
615            .detach();
616
617        // Run dispatcher while app is still alive
618        dispatcher.run_until_parked();
619
620        // Task should have run
621        assert!(
622            *task_ran.borrow(),
623            "Task should run normally when app is alive"
624        );
625    }
626}