Skip to main content

gpui/
executor.rs

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