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#[derive(Clone)]
19pub struct BackgroundExecutor {
20 inner: scheduler::BackgroundExecutor,
21 dispatcher: Arc<dyn PlatformDispatcher>,
22}
23
24#[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
35pub trait TaskExt<T, E> {
39 fn detach_and_log_err(self, cx: &App);
41 fn detach_and_log_err_with_backtrace(self, cx: &App);
44}
45
46impl<T, E> TaskExt<T, E> for Task<Result<T, E>>
47where
48 T: 'static,
49 E: 'static + std::fmt::Display + std::fmt::Debug,
50{
51 #[track_caller]
52 fn detach_and_log_err(self, cx: &App) {
53 let location = core::panic::Location::caller();
54 cx.foreground_executor()
55 .spawn(self.log_tracked_err(*location))
56 .detach();
57 }
58
59 #[track_caller]
60 fn detach_and_log_err_with_backtrace(self, cx: &App) {
61 let location = *core::panic::Location::caller();
62 cx.foreground_executor()
63 .spawn(self.log_tracked_err_with_backtrace(location))
64 .detach();
65 }
66}
67
68impl BackgroundExecutor {
69 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
71 #[cfg(any(test, feature = "test-support"))]
72 let scheduler: Arc<dyn Scheduler> = if let Some(test_dispatcher) = dispatcher.as_test() {
73 test_dispatcher.scheduler().clone()
74 } else {
75 Arc::new(PlatformScheduler::new(dispatcher.clone()))
76 };
77
78 #[cfg(not(any(test, feature = "test-support")))]
79 let scheduler: Arc<dyn Scheduler> = Arc::new(PlatformScheduler::new(dispatcher.clone()));
80
81 Self {
82 inner: scheduler::BackgroundExecutor::new(scheduler),
83 dispatcher,
84 }
85 }
86
87 pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
91 self.inner.clone()
92 }
93
94 pub fn prevent_app_nap(&self, reason: &str) -> ActivityGuard {
98 self.dispatcher.prevent_app_nap(reason)
99 }
100
101 #[track_caller]
103 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
104 where
105 R: Send + 'static,
106 {
107 self.spawn_with_priority(Priority::default(), future.boxed())
108 }
109
110 #[track_caller]
115 pub fn spawn_with_priority<R>(
116 &self,
117 priority: Priority,
118 future: impl Future<Output = R> + Send + 'static,
119 ) -> Task<R>
120 where
121 R: Send + 'static,
122 {
123 if priority == Priority::RealtimeAudio {
124 self.inner.spawn_realtime(future)
125 } else {
126 self.inner.spawn_with_priority(priority, future)
127 }
128 }
129
130 #[cfg(not(target_family = "wasm"))]
135 pub async fn scoped<'scope, F>(&self, scheduler: F)
136 where
137 F: FnOnce(&mut Scope<'scope>),
138 {
139 let mut scope = Scope::new(self.clone(), Priority::default());
140 (scheduler)(&mut scope);
141 let spawned = mem::take(&mut scope.futures)
142 .into_iter()
143 .map(|f| self.spawn_with_priority(scope.priority, f))
144 .collect::<Vec<_>>();
145 for task in spawned {
146 task.await;
147 }
148 }
149
150 #[cfg(not(target_family = "wasm"))]
156 pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
157 where
158 F: FnOnce(&mut Scope<'scope>),
159 {
160 let mut scope = Scope::new(self.clone(), priority);
161 (scheduler)(&mut scope);
162 let spawned = mem::take(&mut scope.futures)
163 .into_iter()
164 .map(|f| self.spawn_with_priority(scope.priority, f))
165 .collect::<Vec<_>>();
166 for task in spawned {
167 task.await;
168 }
169 }
170
171 pub fn now(&self) -> Instant {
176 self.inner.scheduler().clock().now()
177 }
178
179 #[track_caller]
183 pub fn timer(&self, duration: Duration) -> Task<()> {
184 if duration.is_zero() {
185 return Task::ready(());
186 }
187 self.spawn(self.inner.scheduler().timer(duration))
188 }
189
190 #[cfg(any(test, feature = "test-support"))]
192 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
193 self.dispatcher.as_test().unwrap().simulate_random_delay()
194 }
195
196 #[cfg(any(test, feature = "test-support"))]
198 pub fn advance_clock(&self, duration: Duration) {
199 self.dispatcher.as_test().unwrap().advance_clock(duration)
200 }
201
202 #[cfg(any(test, feature = "test-support"))]
204 pub fn tick(&self) -> bool {
205 self.dispatcher.as_test().unwrap().scheduler().tick()
206 }
207
208 #[cfg(any(test, feature = "test-support"))]
215 pub fn run_until_parked(&self) {
216 let scheduler = self.dispatcher.as_test().unwrap().scheduler();
217 scheduler.run();
218 }
219
220 #[cfg(any(test, feature = "test-support"))]
222 pub fn allow_parking(&self) {
223 self.dispatcher
224 .as_test()
225 .unwrap()
226 .scheduler()
227 .allow_parking();
228
229 if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
230 log::warn!("[gpui::executor] allow_parking: enabled");
231 }
232 }
233
234 #[cfg(any(test, feature = "test-support"))]
236 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
237 self.dispatcher
238 .as_test()
239 .unwrap()
240 .scheduler()
241 .set_timeout_ticks(range);
242 }
243
244 #[cfg(any(test, feature = "test-support"))]
246 pub fn forbid_parking(&self) {
247 self.dispatcher
248 .as_test()
249 .unwrap()
250 .scheduler()
251 .forbid_parking();
252 }
253
254 #[cfg(any(test, feature = "test-support"))]
256 pub fn rng(&self) -> scheduler::SharedRng {
257 self.dispatcher.as_test().unwrap().scheduler().rng()
258 }
259
260 pub fn num_cpus(&self) -> usize {
262 #[cfg(any(test, feature = "test-support"))]
263 if let Some(test) = self.dispatcher.as_test() {
264 return test.num_cpus_override().unwrap_or(4);
265 }
266 num_cpus::get()
267 }
268
269 #[cfg(any(test, feature = "test-support"))]
272 pub fn set_num_cpus(&self, count: usize) {
273 self.dispatcher
274 .as_test()
275 .expect("set_num_cpus can only be called on a test executor")
276 .set_num_cpus(count);
277 }
278
279 pub fn is_main_thread(&self) -> bool {
281 self.dispatcher.is_main_thread()
282 }
283
284 pub fn is_test(&self) -> bool {
290 self.inner.is_test()
291 }
292
293 #[doc(hidden)]
294 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
295 &self.dispatcher
296 }
297}
298
299impl ForegroundExecutor {
300 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
302 #[cfg(any(test, feature = "test-support"))]
303 let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
304 if let Some(test_dispatcher) = dispatcher.as_test() {
305 (
306 test_dispatcher.scheduler().clone(),
307 test_dispatcher.session_id(),
308 )
309 } else {
310 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
311 let inner = platform_scheduler.foreground_executor();
312 return Self {
313 inner,
314 dispatcher,
315 #[cfg(feature = "profiler")]
316 foreground_runnables: Some(platform_scheduler.foreground_runnable_counter()),
317 not_send: PhantomData,
318 };
319 };
320
321 #[cfg(not(any(test, feature = "test-support")))]
322 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
323 #[cfg(not(any(test, feature = "test-support")))]
324 let inner = platform_scheduler.foreground_executor();
325 #[cfg(all(not(any(test, feature = "test-support")), feature = "profiler"))]
326 let foreground_runnables = Some(platform_scheduler.foreground_runnable_counter());
327
328 #[cfg(any(test, feature = "test-support"))]
329 let inner = {
330 let scheduler_for_dispatch = Arc::downgrade(&scheduler);
331 scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
332 if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
333 scheduler.schedule_local(session_id, runnable);
334 }
335 })
336 };
337
338 #[cfg(all(any(test, feature = "test-support"), feature = "profiler"))]
339 let foreground_runnables = None;
342
343 Self {
344 inner,
345 dispatcher,
346 #[cfg(feature = "profiler")]
347 foreground_runnables,
348 not_send: PhantomData,
349 }
350 }
351
352 #[track_caller]
354 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
355 where
356 R: 'static,
357 {
358 self.inner.spawn(future.boxed_local())
359 }
360
361 #[track_caller]
363 pub fn spawn_with_priority<R>(
364 &self,
365 _priority: Priority,
366 future: impl Future<Output = R> + 'static,
367 ) -> Task<R>
368 where
369 R: 'static,
370 {
371 self.inner.spawn(future)
373 }
374
375 #[track_caller]
385 pub fn spawn_when_idle<R>(
386 &self,
387 timeout: Option<Duration>,
388 future: impl Future<Output = R> + 'static,
389 ) -> Task<R>
390 where
391 R: 'static,
392 {
393 let dispatcher = self.dispatcher.clone();
394 #[cfg(feature = "profiler")]
395 let foreground_runnables = self.foreground_runnables.clone();
396 self.inner
397 .spawn_with_dispatch(future.boxed_local(), move |runnable| {
398 #[cfg(feature = "profiler")]
399 if let Some(foreground_runnables) = &foreground_runnables {
400 foreground_runnables.queued();
401 }
402 dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
403 })
404 }
405
406 pub fn idle_time_remaining(&self) -> Option<Duration> {
412 self.dispatcher.idle_time_remaining()
413 }
414
415 #[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
417 #[track_caller]
418 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
419 use std::cell::Cell;
420
421 let scheduler = self.inner.scheduler();
422
423 let output = Cell::new(None);
424 let future = async {
425 output.set(Some(future.await));
426 };
427 let mut future = std::pin::pin!(future);
428
429 scheduler.block(None, future.as_mut(), None);
433
434 output.take().expect("block_test future did not complete")
435 }
436
437 #[cfg(not(target_family = "wasm"))]
440 pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
441 self.inner.block_on(future)
442 }
443
444 #[cfg(not(target_family = "wasm"))]
446 pub fn block_with_timeout<R, Fut: Future<Output = R>>(
447 &self,
448 duration: Duration,
449 future: Fut,
450 ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
451 self.inner.block_with_timeout(duration, future)
452 }
453
454 #[doc(hidden)]
455 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
456 &self.dispatcher
457 }
458
459 #[doc(hidden)]
460 pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
461 self.inner.clone()
462 }
463}
464
465#[cfg(not(target_family = "wasm"))]
467pub struct Scope<'a> {
468 executor: BackgroundExecutor,
469 priority: Priority,
470 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
471 tx: Option<mpsc::Sender<()>>,
472 rx: mpsc::Receiver<()>,
473 lifetime: PhantomData<&'a ()>,
474}
475
476#[cfg(not(target_family = "wasm"))]
477impl<'a> Scope<'a> {
478 fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
479 let (tx, rx) = mpsc::channel(1);
480 Self {
481 executor,
482 priority,
483 tx: Some(tx),
484 rx,
485 futures: Default::default(),
486 lifetime: PhantomData,
487 }
488 }
489
490 pub fn num_cpus(&self) -> usize {
492 self.executor.num_cpus()
493 }
494
495 #[track_caller]
497 pub fn spawn<F>(&mut self, f: F)
498 where
499 F: Future<Output = ()> + Send + 'a,
500 {
501 let tx = self.tx.clone().unwrap();
502
503 let f = unsafe {
506 mem::transmute::<
507 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
508 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
509 >(Box::pin(async move {
510 f.await;
511 drop(tx);
512 }))
513 };
514 self.futures.push(f);
515 }
516}
517
518#[cfg(not(target_family = "wasm"))]
519impl Drop for Scope<'_> {
520 fn drop(&mut self) {
521 self.tx.take().unwrap();
522
523 let future = async {
526 self.rx.next().await;
527 };
528 let mut future = std::pin::pin!(future);
529 self.executor
530 .inner
531 .scheduler()
532 .block(None, future.as_mut(), None);
533 }
534}
535
536#[cfg(test)]
537mod test {
538 use super::*;
539 use crate::{App, TestDispatcher, TestPlatform};
540 use std::cell::RefCell;
541
542 fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
545 let dispatcher = TestDispatcher::new(0);
546 let arc_dispatcher = Arc::new(dispatcher.clone());
547 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
548 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
549
550 let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
551 let asset_source = Arc::new(());
552 let http_client = http_client::FakeHttpClient::with_404_response();
553
554 let app = App::new_app(platform, asset_source, http_client);
555 (dispatcher, background_executor, app)
556 }
557
558 #[test]
559 fn sanity_test_tasks_run() {
560 let (dispatcher, _background_executor, app) = create_test_app();
561 let foreground_executor = app.borrow().foreground_executor.clone();
562
563 let task_ran = Rc::new(RefCell::new(false));
564
565 foreground_executor
566 .spawn({
567 let task_ran = Rc::clone(&task_ran);
568 async move {
569 *task_ran.borrow_mut() = true;
570 }
571 })
572 .detach();
573
574 dispatcher.run_until_parked();
576
577 assert!(
579 *task_ran.borrow(),
580 "Task should run normally when app is alive"
581 );
582 }
583}