1use crate::{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 #[track_caller]
106 pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
107 where
108 F: FnOnce(SchedulerLocalExecutor) -> Fut + Send + 'static,
109 Fut: Future + 'static,
110 Fut::Output: Send + Sync + 'static,
111 {
112 self.inner.spawn_dedicated(f)
113 }
114
115 #[track_caller]
117 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
118 where
119 R: Send + 'static,
120 {
121 self.spawn_with_priority(Priority::default(), future.boxed())
122 }
123
124 #[track_caller]
129 pub fn spawn_with_priority<R>(
130 &self,
131 priority: Priority,
132 future: impl Future<Output = R> + Send + 'static,
133 ) -> Task<R>
134 where
135 R: Send + 'static,
136 {
137 if priority == Priority::RealtimeAudio {
138 self.inner.spawn_realtime(future)
139 } else {
140 self.inner.spawn_with_priority(priority, future)
141 }
142 }
143
144 #[cfg(not(target_family = "wasm"))]
149 pub async fn scoped<'scope, F>(&self, scheduler: F)
150 where
151 F: FnOnce(&mut Scope<'scope>),
152 {
153 let mut scope = Scope::new(self.clone(), Priority::default());
154 (scheduler)(&mut scope);
155 let spawned = mem::take(&mut scope.futures)
156 .into_iter()
157 .map(|f| self.spawn_with_priority(scope.priority, f))
158 .collect::<Vec<_>>();
159 for task in spawned {
160 task.await;
161 }
162 }
163
164 #[cfg(not(target_family = "wasm"))]
170 pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
171 where
172 F: FnOnce(&mut Scope<'scope>),
173 {
174 let mut scope = Scope::new(self.clone(), priority);
175 (scheduler)(&mut scope);
176 let spawned = mem::take(&mut scope.futures)
177 .into_iter()
178 .map(|f| self.spawn_with_priority(scope.priority, f))
179 .collect::<Vec<_>>();
180 for task in spawned {
181 task.await;
182 }
183 }
184
185 pub fn now(&self) -> Instant {
190 self.inner.scheduler().clock().now()
191 }
192
193 #[track_caller]
197 pub fn timer(&self, duration: Duration) -> Task<()> {
198 if duration.is_zero() {
199 return Task::ready(());
200 }
201 self.spawn(self.inner.scheduler().timer(duration))
202 }
203
204 #[cfg(any(test, feature = "test-support"))]
206 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
207 self.dispatcher.as_test().unwrap().simulate_random_delay()
208 }
209
210 #[cfg(any(test, feature = "test-support"))]
212 pub fn advance_clock(&self, duration: Duration) {
213 self.dispatcher.as_test().unwrap().advance_clock(duration)
214 }
215
216 #[cfg(any(test, feature = "test-support"))]
218 pub fn tick(&self) -> bool {
219 self.dispatcher.as_test().unwrap().scheduler().tick()
220 }
221
222 #[cfg(any(test, feature = "test-support"))]
229 pub fn run_until_parked(&self) {
230 let scheduler = self.dispatcher.as_test().unwrap().scheduler();
231 scheduler.run();
232 }
233
234 #[cfg(any(test, feature = "test-support"))]
236 pub fn allow_parking(&self) {
237 self.dispatcher
238 .as_test()
239 .unwrap()
240 .scheduler()
241 .allow_parking();
242
243 if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
244 log::warn!("[gpui::executor] allow_parking: enabled");
245 }
246 }
247
248 #[cfg(any(test, feature = "test-support"))]
250 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
251 self.dispatcher
252 .as_test()
253 .unwrap()
254 .scheduler()
255 .set_timeout_ticks(range);
256 }
257
258 #[cfg(any(test, feature = "test-support"))]
260 pub fn forbid_parking(&self) {
261 self.dispatcher
262 .as_test()
263 .unwrap()
264 .scheduler()
265 .forbid_parking();
266 }
267
268 #[cfg(any(test, feature = "test-support"))]
270 pub fn rng(&self) -> scheduler::SharedRng {
271 self.dispatcher.as_test().unwrap().scheduler().rng()
272 }
273
274 pub fn num_cpus(&self) -> usize {
276 #[cfg(any(test, feature = "test-support"))]
277 if let Some(test) = self.dispatcher.as_test() {
278 return test.num_cpus_override().unwrap_or(4);
279 }
280 num_cpus::get()
281 }
282
283 #[cfg(any(test, feature = "test-support"))]
286 pub fn set_num_cpus(&self, count: usize) {
287 self.dispatcher
288 .as_test()
289 .expect("set_num_cpus can only be called on a test executor")
290 .set_num_cpus(count);
291 }
292
293 pub fn is_main_thread(&self) -> bool {
295 self.dispatcher.is_main_thread()
296 }
297
298 #[doc(hidden)]
299 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
300 &self.dispatcher
301 }
302}
303
304impl ForegroundExecutor {
305 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
307 #[cfg(any(test, feature = "test-support"))]
308 let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
309 if let Some(test_dispatcher) = dispatcher.as_test() {
310 (
311 test_dispatcher.scheduler().clone(),
312 test_dispatcher.session_id(),
313 )
314 } else {
315 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
316 let inner = platform_scheduler.foreground_executor();
317 return Self {
318 inner,
319 dispatcher,
320 #[cfg(feature = "profiler")]
321 foreground_runnables: Some(platform_scheduler.foreground_runnable_counter()),
322 not_send: PhantomData,
323 };
324 };
325
326 #[cfg(not(any(test, feature = "test-support")))]
327 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
328 #[cfg(not(any(test, feature = "test-support")))]
329 let inner = platform_scheduler.foreground_executor();
330 #[cfg(all(not(any(test, feature = "test-support")), feature = "profiler"))]
331 let foreground_runnables = Some(platform_scheduler.foreground_runnable_counter());
332
333 #[cfg(any(test, feature = "test-support"))]
334 let inner = {
335 let scheduler_for_dispatch = Arc::downgrade(&scheduler);
336 scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
337 if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
338 scheduler.schedule_local(session_id, runnable);
339 }
340 })
341 };
342
343 #[cfg(all(any(test, feature = "test-support"), feature = "profiler"))]
344 let foreground_runnables = None;
347
348 Self {
349 inner,
350 dispatcher,
351 #[cfg(feature = "profiler")]
352 foreground_runnables,
353 not_send: PhantomData,
354 }
355 }
356
357 #[track_caller]
359 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
360 where
361 R: 'static,
362 {
363 self.inner.spawn(future.boxed_local())
364 }
365
366 #[track_caller]
368 pub fn spawn_with_priority<R>(
369 &self,
370 _priority: Priority,
371 future: impl Future<Output = R> + 'static,
372 ) -> Task<R>
373 where
374 R: 'static,
375 {
376 self.inner.spawn(future)
378 }
379
380 #[track_caller]
390 pub fn spawn_when_idle<R>(
391 &self,
392 timeout: Option<Duration>,
393 future: impl Future<Output = R> + 'static,
394 ) -> Task<R>
395 where
396 R: 'static,
397 {
398 let dispatcher = self.dispatcher.clone();
399 #[cfg(feature = "profiler")]
400 let foreground_runnables = self.foreground_runnables.clone();
401 self.inner
402 .spawn_with_dispatch(future.boxed_local(), move |runnable| {
403 #[cfg(feature = "profiler")]
404 if let Some(foreground_runnables) = &foreground_runnables {
405 foreground_runnables.queued();
406 }
407 dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
408 })
409 }
410
411 pub fn idle_time_remaining(&self) -> Option<Duration> {
417 self.dispatcher.idle_time_remaining()
418 }
419
420 #[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
422 #[track_caller]
423 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
424 use std::cell::Cell;
425
426 let scheduler = self.inner.scheduler();
427
428 let output = Cell::new(None);
429 let future = async {
430 output.set(Some(future.await));
431 };
432 let mut future = std::pin::pin!(future);
433
434 scheduler.block(None, future.as_mut(), None);
438
439 output.take().expect("block_test future did not complete")
440 }
441
442 #[cfg(not(target_family = "wasm"))]
445 pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
446 self.inner.block_on(future)
447 }
448
449 #[cfg(not(target_family = "wasm"))]
451 pub fn block_with_timeout<R, Fut: Future<Output = R>>(
452 &self,
453 duration: Duration,
454 future: Fut,
455 ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
456 self.inner.block_with_timeout(duration, future)
457 }
458
459 #[doc(hidden)]
460 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
461 &self.dispatcher
462 }
463
464 #[doc(hidden)]
465 pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
466 self.inner.clone()
467 }
468}
469
470#[cfg(not(target_family = "wasm"))]
472pub struct Scope<'a> {
473 executor: BackgroundExecutor,
474 priority: Priority,
475 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
476 tx: Option<mpsc::Sender<()>>,
477 rx: mpsc::Receiver<()>,
478 lifetime: PhantomData<&'a ()>,
479}
480
481#[cfg(not(target_family = "wasm"))]
482impl<'a> Scope<'a> {
483 fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
484 let (tx, rx) = mpsc::channel(1);
485 Self {
486 executor,
487 priority,
488 tx: Some(tx),
489 rx,
490 futures: Default::default(),
491 lifetime: PhantomData,
492 }
493 }
494
495 pub fn num_cpus(&self) -> usize {
497 self.executor.num_cpus()
498 }
499
500 #[track_caller]
502 pub fn spawn<F>(&mut self, f: F)
503 where
504 F: Future<Output = ()> + Send + 'a,
505 {
506 let tx = self.tx.clone().unwrap();
507
508 let f = unsafe {
511 mem::transmute::<
512 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
513 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
514 >(Box::pin(async move {
515 f.await;
516 drop(tx);
517 }))
518 };
519 self.futures.push(f);
520 }
521}
522
523#[cfg(not(target_family = "wasm"))]
524impl Drop for Scope<'_> {
525 fn drop(&mut self) {
526 self.tx.take().unwrap();
527
528 let future = async {
531 self.rx.next().await;
532 };
533 let mut future = std::pin::pin!(future);
534 self.executor
535 .inner
536 .scheduler()
537 .block(None, future.as_mut(), None);
538 }
539}
540
541#[cfg(test)]
542mod test {
543 use super::*;
544 use crate::{App, TestDispatcher, TestPlatform};
545 use std::cell::RefCell;
546
547 fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
550 let dispatcher = TestDispatcher::new(0);
551 let arc_dispatcher = Arc::new(dispatcher.clone());
552 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
553 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
554
555 let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
556 let asset_source = Arc::new(());
557 let http_client = http_client::FakeHttpClient::with_404_response();
558
559 let app = App::new_app(platform, asset_source, http_client);
560 (dispatcher, background_executor, app)
561 }
562
563 #[test]
564 fn sanity_test_tasks_run() {
565 let (dispatcher, _background_executor, app) = create_test_app();
566 let foreground_executor = app.borrow().foreground_executor.clone();
567
568 let task_ran = Rc::new(RefCell::new(false));
569
570 foreground_executor
571 .spawn({
572 let task_ran = Rc::clone(&task_ran);
573 async move {
574 *task_ran.borrow_mut() = true;
575 }
576 })
577 .detach();
578
579 dispatcher.run_until_parked();
581
582 assert!(
584 *task_ran.borrow(),
585 "Task should run normally when app is alive"
586 );
587 }
588}