Skip to main content

gpui_platform/
executor_runtime.rs

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