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 #[doc(hidden)]
285 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
286 &self.dispatcher
287 }
288}
289
290impl ForegroundExecutor {
291 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
293 #[cfg(any(test, feature = "test-support"))]
294 let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
295 if let Some(test_dispatcher) = dispatcher.as_test() {
296 (
297 test_dispatcher.scheduler().clone(),
298 test_dispatcher.session_id(),
299 )
300 } else {
301 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
302 let inner = platform_scheduler.foreground_executor();
303 return Self {
304 inner,
305 dispatcher,
306 #[cfg(feature = "profiler")]
307 foreground_runnables: Some(platform_scheduler.foreground_runnable_counter()),
308 not_send: PhantomData,
309 };
310 };
311
312 #[cfg(not(any(test, feature = "test-support")))]
313 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
314 #[cfg(not(any(test, feature = "test-support")))]
315 let inner = platform_scheduler.foreground_executor();
316 #[cfg(all(not(any(test, feature = "test-support")), feature = "profiler"))]
317 let foreground_runnables = Some(platform_scheduler.foreground_runnable_counter());
318
319 #[cfg(any(test, feature = "test-support"))]
320 let inner = {
321 let scheduler_for_dispatch = Arc::downgrade(&scheduler);
322 scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
323 if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
324 scheduler.schedule_local(session_id, runnable);
325 }
326 })
327 };
328
329 #[cfg(all(any(test, feature = "test-support"), feature = "profiler"))]
330 let foreground_runnables = None;
333
334 Self {
335 inner,
336 dispatcher,
337 #[cfg(feature = "profiler")]
338 foreground_runnables,
339 not_send: PhantomData,
340 }
341 }
342
343 #[track_caller]
345 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
346 where
347 R: 'static,
348 {
349 self.inner.spawn(future.boxed_local())
350 }
351
352 #[track_caller]
354 pub fn spawn_with_priority<R>(
355 &self,
356 _priority: Priority,
357 future: impl Future<Output = R> + 'static,
358 ) -> Task<R>
359 where
360 R: 'static,
361 {
362 self.inner.spawn(future)
364 }
365
366 #[track_caller]
376 pub fn spawn_when_idle<R>(
377 &self,
378 timeout: Option<Duration>,
379 future: impl Future<Output = R> + 'static,
380 ) -> Task<R>
381 where
382 R: 'static,
383 {
384 let dispatcher = self.dispatcher.clone();
385 #[cfg(feature = "profiler")]
386 let foreground_runnables = self.foreground_runnables.clone();
387 self.inner
388 .spawn_with_dispatch(future.boxed_local(), move |runnable| {
389 #[cfg(feature = "profiler")]
390 if let Some(foreground_runnables) = &foreground_runnables {
391 foreground_runnables.queued();
392 }
393 dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
394 })
395 }
396
397 pub fn idle_time_remaining(&self) -> Option<Duration> {
403 self.dispatcher.idle_time_remaining()
404 }
405
406 #[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
408 #[track_caller]
409 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
410 use std::cell::Cell;
411
412 let scheduler = self.inner.scheduler();
413
414 let output = Cell::new(None);
415 let future = async {
416 output.set(Some(future.await));
417 };
418 let mut future = std::pin::pin!(future);
419
420 scheduler.block(None, future.as_mut(), None);
424
425 output.take().expect("block_test future did not complete")
426 }
427
428 #[cfg(not(target_family = "wasm"))]
431 pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
432 self.inner.block_on(future)
433 }
434
435 #[cfg(not(target_family = "wasm"))]
437 pub fn block_with_timeout<R, Fut: Future<Output = R>>(
438 &self,
439 duration: Duration,
440 future: Fut,
441 ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
442 self.inner.block_with_timeout(duration, future)
443 }
444
445 #[doc(hidden)]
446 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
447 &self.dispatcher
448 }
449
450 #[doc(hidden)]
451 pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
452 self.inner.clone()
453 }
454}
455
456#[cfg(not(target_family = "wasm"))]
458pub struct Scope<'a> {
459 executor: BackgroundExecutor,
460 priority: Priority,
461 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
462 tx: Option<mpsc::Sender<()>>,
463 rx: mpsc::Receiver<()>,
464 lifetime: PhantomData<&'a ()>,
465}
466
467#[cfg(not(target_family = "wasm"))]
468impl<'a> Scope<'a> {
469 fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
470 let (tx, rx) = mpsc::channel(1);
471 Self {
472 executor,
473 priority,
474 tx: Some(tx),
475 rx,
476 futures: Default::default(),
477 lifetime: PhantomData,
478 }
479 }
480
481 pub fn num_cpus(&self) -> usize {
483 self.executor.num_cpus()
484 }
485
486 #[track_caller]
488 pub fn spawn<F>(&mut self, f: F)
489 where
490 F: Future<Output = ()> + Send + 'a,
491 {
492 let tx = self.tx.clone().unwrap();
493
494 let f = unsafe {
497 mem::transmute::<
498 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
499 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
500 >(Box::pin(async move {
501 f.await;
502 drop(tx);
503 }))
504 };
505 self.futures.push(f);
506 }
507}
508
509#[cfg(not(target_family = "wasm"))]
510impl Drop for Scope<'_> {
511 fn drop(&mut self) {
512 self.tx.take().unwrap();
513
514 let future = async {
517 self.rx.next().await;
518 };
519 let mut future = std::pin::pin!(future);
520 self.executor
521 .inner
522 .scheduler()
523 .block(None, future.as_mut(), None);
524 }
525}
526
527#[cfg(test)]
528mod test {
529 use super::*;
530 use crate::{App, TestDispatcher, TestPlatform};
531 use std::cell::RefCell;
532
533 fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
536 let dispatcher = TestDispatcher::new(0);
537 let arc_dispatcher = Arc::new(dispatcher.clone());
538 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
539 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
540
541 let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
542 let asset_source = Arc::new(());
543 let http_client = http_client::FakeHttpClient::with_404_response();
544
545 let app = App::new_app(platform, asset_source, http_client);
546 (dispatcher, background_executor, app)
547 }
548
549 #[test]
550 fn sanity_test_tasks_run() {
551 let (dispatcher, _background_executor, app) = create_test_app();
552 let foreground_executor = app.borrow().foreground_executor.clone();
553
554 let task_ran = Rc::new(RefCell::new(false));
555
556 foreground_executor
557 .spawn({
558 let task_ran = Rc::clone(&task_ran);
559 async move {
560 *task_ran.borrow_mut() = true;
561 }
562 })
563 .detach();
564
565 dispatcher.run_until_parked();
567
568 assert!(
570 *task_ran.borrow(),
571 "Task should run normally when app is alive"
572 );
573 }
574}