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
35#[derive(Clone)]
37pub struct SendForegroundExecutor {
38 inner: scheduler::SendLocalExecutor,
39}
40
41impl SendForegroundExecutor {
42 #[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
52pub trait TaskExt<T, E> {
56 fn detach_and_log_err(self, cx: &App);
58 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 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 pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
108 self.inner.clone()
109 }
110
111 pub fn prevent_app_nap(&self, reason: &str) -> ActivityGuard {
115 self.dispatcher.prevent_app_nap(reason)
116 }
117
118 #[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 #[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 #[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 #[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 pub fn now(&self) -> Instant {
193 self.inner.scheduler().clock().now()
194 }
195
196 #[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 #[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 #[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 #[cfg(any(test, feature = "test-support"))]
221 pub fn tick(&self) -> bool {
222 self.dispatcher.as_test().unwrap().scheduler().tick()
223 }
224
225 #[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 #[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 #[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 #[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 #[cfg(any(test, feature = "test-support"))]
273 pub fn rng(&self) -> scheduler::SharedRng {
274 self.dispatcher.as_test().unwrap().scheduler().rng()
275 }
276
277 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 #[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 pub fn is_main_thread(&self) -> bool {
298 self.dispatcher.is_main_thread()
299 }
300
301 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 pub fn to_send(&self) -> SendForegroundExecutor {
319 SendForegroundExecutor {
320 inner: self.inner.to_send(),
321 }
322 }
323
324 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 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 #[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 #[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 self.inner.spawn(future)
397 }
398
399 #[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 pub fn idle_time_remaining(&self) -> Option<Duration> {
436 self.dispatcher.idle_time_remaining()
437 }
438
439 #[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 scheduler.block(None, future.as_mut(), None);
457
458 output.take().expect("block_test future did not complete")
459 }
460
461 #[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 #[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#[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 pub fn num_cpus(&self) -> usize {
516 self.executor.num_cpus()
517 }
518
519 #[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 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 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 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 dispatcher.run_until_parked();
619
620 assert!(
622 *task_ran.borrow(),
623 "Task should run normally when app is alive"
624 );
625 }
626}