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}