1use crate::{App, PlatformDispatcher, PlatformScheduler};
2use futures::channel::mpsc;
3use futures::prelude::*;
4use gpui_util::{TryFutureExt, TryFutureExtBacktrace};
5use scheduler::Instant;
6use scheduler::Scheduler;
7use std::{future::Future, marker::PhantomData, mem, pin::Pin, rc::Rc, sync::Arc, time::Duration};
8
9pub use scheduler::{
10 DedicatedExecutor, FallibleTask, LocalExecutor as SchedulerLocalExecutor, Priority, Task,
11};
12
13#[derive(Clone)]
16pub struct BackgroundExecutor {
17 inner: scheduler::BackgroundExecutor,
18 dispatcher: Arc<dyn PlatformDispatcher>,
19}
20
21#[derive(Clone)]
24pub struct ForegroundExecutor {
25 inner: scheduler::LocalExecutor,
26 dispatcher: Arc<dyn PlatformDispatcher>,
27 #[cfg(feature = "profiler")]
28 foreground_runnables: Option<crate::profiler::journal::ForegroundRunnableCounter>,
29 not_send: PhantomData<Rc<()>>,
30}
31
32pub trait TaskExt<T, E> {
36 fn detach_and_log_err(self, cx: &App);
38 fn detach_and_log_err_with_backtrace(self, cx: &App);
41}
42
43impl<T, E> TaskExt<T, E> for Task<Result<T, E>>
44where
45 T: 'static,
46 E: 'static + std::fmt::Display + std::fmt::Debug,
47{
48 #[track_caller]
49 fn detach_and_log_err(self, cx: &App) {
50 let location = core::panic::Location::caller();
51 cx.foreground_executor()
52 .spawn(self.log_tracked_err(*location))
53 .detach();
54 }
55
56 #[track_caller]
57 fn detach_and_log_err_with_backtrace(self, cx: &App) {
58 let location = *core::panic::Location::caller();
59 cx.foreground_executor()
60 .spawn(self.log_tracked_err_with_backtrace(location))
61 .detach();
62 }
63}
64
65impl BackgroundExecutor {
66 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
68 #[cfg(any(test, feature = "test-support"))]
69 let scheduler: Arc<dyn Scheduler> = if let Some(test_dispatcher) = dispatcher.as_test() {
70 test_dispatcher.scheduler().clone()
71 } else {
72 Arc::new(PlatformScheduler::new(dispatcher.clone()))
73 };
74
75 #[cfg(not(any(test, feature = "test-support")))]
76 let scheduler: Arc<dyn Scheduler> = Arc::new(PlatformScheduler::new(dispatcher.clone()));
77
78 Self {
79 inner: scheduler::BackgroundExecutor::new(scheduler),
80 dispatcher,
81 }
82 }
83
84 pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
88 self.inner.clone()
89 }
90
91 #[track_caller]
103 pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
104 where
105 F: FnOnce(SchedulerLocalExecutor) -> Fut + Send + 'static,
106 Fut: Future + 'static,
107 Fut::Output: Send + Sync + 'static,
108 {
109 self.inner.spawn_dedicated(f)
110 }
111
112 #[track_caller]
114 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
115 where
116 R: Send + 'static,
117 {
118 self.spawn_with_priority(Priority::default(), future.boxed())
119 }
120
121 #[track_caller]
126 pub fn spawn_with_priority<R>(
127 &self,
128 priority: Priority,
129 future: impl Future<Output = R> + Send + 'static,
130 ) -> Task<R>
131 where
132 R: Send + 'static,
133 {
134 if priority == Priority::RealtimeAudio {
135 self.inner.spawn_realtime(future)
136 } else {
137 self.inner.spawn_with_priority(priority, future)
138 }
139 }
140
141 pub async fn scoped<'scope, F>(&self, scheduler: F)
144 where
145 F: FnOnce(&mut Scope<'scope>),
146 {
147 let mut scope = Scope::new(self.clone(), Priority::default());
148 (scheduler)(&mut scope);
149 let spawned = mem::take(&mut scope.futures)
150 .into_iter()
151 .map(|f| self.spawn_with_priority(scope.priority, f))
152 .collect::<Vec<_>>();
153 for task in spawned {
154 task.await;
155 }
156 }
157
158 pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
161 where
162 F: FnOnce(&mut Scope<'scope>),
163 {
164 let mut scope = Scope::new(self.clone(), priority);
165 (scheduler)(&mut scope);
166 let spawned = mem::take(&mut scope.futures)
167 .into_iter()
168 .map(|f| self.spawn_with_priority(scope.priority, f))
169 .collect::<Vec<_>>();
170 for task in spawned {
171 task.await;
172 }
173 }
174
175 pub fn now(&self) -> Instant {
180 self.inner.scheduler().clock().now()
181 }
182
183 #[track_caller]
187 pub fn timer(&self, duration: Duration) -> Task<()> {
188 if duration.is_zero() {
189 return Task::ready(());
190 }
191 self.spawn(self.inner.scheduler().timer(duration))
192 }
193
194 #[cfg(any(test, feature = "test-support"))]
196 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
197 self.dispatcher.as_test().unwrap().simulate_random_delay()
198 }
199
200 #[cfg(any(test, feature = "test-support"))]
202 pub fn advance_clock(&self, duration: Duration) {
203 self.dispatcher.as_test().unwrap().advance_clock(duration)
204 }
205
206 #[cfg(any(test, feature = "test-support"))]
208 pub fn tick(&self) -> bool {
209 self.dispatcher.as_test().unwrap().scheduler().tick()
210 }
211
212 #[cfg(any(test, feature = "test-support"))]
219 pub fn run_until_parked(&self) {
220 let scheduler = self.dispatcher.as_test().unwrap().scheduler();
221 scheduler.run();
222 }
223
224 #[cfg(any(test, feature = "test-support"))]
226 pub fn allow_parking(&self) {
227 self.dispatcher
228 .as_test()
229 .unwrap()
230 .scheduler()
231 .allow_parking();
232
233 if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
234 log::warn!("[gpui::executor] allow_parking: enabled");
235 }
236 }
237
238 #[cfg(any(test, feature = "test-support"))]
240 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
241 self.dispatcher
242 .as_test()
243 .unwrap()
244 .scheduler()
245 .set_timeout_ticks(range);
246 }
247
248 #[cfg(any(test, feature = "test-support"))]
250 pub fn forbid_parking(&self) {
251 self.dispatcher
252 .as_test()
253 .unwrap()
254 .scheduler()
255 .forbid_parking();
256 }
257
258 #[cfg(any(test, feature = "test-support"))]
260 pub fn rng(&self) -> scheduler::SharedRng {
261 self.dispatcher.as_test().unwrap().scheduler().rng()
262 }
263
264 pub fn num_cpus(&self) -> usize {
266 #[cfg(any(test, feature = "test-support"))]
267 if let Some(test) = self.dispatcher.as_test() {
268 return test.num_cpus_override().unwrap_or(4);
269 }
270 num_cpus::get()
271 }
272
273 #[cfg(any(test, feature = "test-support"))]
276 pub fn set_num_cpus(&self, count: usize) {
277 self.dispatcher
278 .as_test()
279 .expect("set_num_cpus can only be called on a test executor")
280 .set_num_cpus(count);
281 }
282
283 pub fn is_main_thread(&self) -> bool {
285 self.dispatcher.is_main_thread()
286 }
287
288 #[doc(hidden)]
289 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
290 &self.dispatcher
291 }
292}
293
294impl ForegroundExecutor {
295 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
297 #[cfg(any(test, feature = "test-support"))]
298 let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
299 if let Some(test_dispatcher) = dispatcher.as_test() {
300 (
301 test_dispatcher.scheduler().clone(),
302 test_dispatcher.session_id(),
303 )
304 } else {
305 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
306 let inner = platform_scheduler.foreground_executor();
307 return Self {
308 inner,
309 dispatcher,
310 #[cfg(feature = "profiler")]
311 foreground_runnables: Some(platform_scheduler.foreground_runnable_counter()),
312 not_send: PhantomData,
313 };
314 };
315
316 #[cfg(not(any(test, feature = "test-support")))]
317 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
318 #[cfg(not(any(test, feature = "test-support")))]
319 let inner = platform_scheduler.foreground_executor();
320 #[cfg(all(not(any(test, feature = "test-support")), feature = "profiler"))]
321 let foreground_runnables = Some(platform_scheduler.foreground_runnable_counter());
322
323 #[cfg(any(test, feature = "test-support"))]
324 let inner = {
325 let scheduler_for_dispatch = Arc::downgrade(&scheduler);
326 scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
327 if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
328 scheduler.schedule_local(session_id, runnable);
329 }
330 })
331 };
332
333 #[cfg(all(any(test, feature = "test-support"), feature = "profiler"))]
334 let foreground_runnables = None;
337
338 Self {
339 inner,
340 dispatcher,
341 #[cfg(feature = "profiler")]
342 foreground_runnables,
343 not_send: PhantomData,
344 }
345 }
346
347 #[track_caller]
349 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
350 where
351 R: 'static,
352 {
353 self.inner.spawn(future.boxed_local())
354 }
355
356 #[track_caller]
358 pub fn spawn_with_priority<R>(
359 &self,
360 _priority: Priority,
361 future: impl Future<Output = R> + 'static,
362 ) -> Task<R>
363 where
364 R: 'static,
365 {
366 self.inner.spawn(future)
368 }
369
370 #[track_caller]
380 pub fn spawn_when_idle<R>(
381 &self,
382 timeout: Option<Duration>,
383 future: impl Future<Output = R> + 'static,
384 ) -> Task<R>
385 where
386 R: 'static,
387 {
388 let dispatcher = self.dispatcher.clone();
389 #[cfg(feature = "profiler")]
390 let foreground_runnables = self.foreground_runnables.clone();
391 self.inner
392 .spawn_with_dispatch(future.boxed_local(), move |runnable| {
393 #[cfg(feature = "profiler")]
394 if let Some(foreground_runnables) = &foreground_runnables {
395 foreground_runnables.queued();
396 }
397 dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
398 })
399 }
400
401 pub fn idle_time_remaining(&self) -> Option<Duration> {
407 self.dispatcher.idle_time_remaining()
408 }
409
410 #[cfg(any(test, feature = "test-support"))]
412 #[track_caller]
413 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
414 use std::cell::Cell;
415
416 let scheduler = self.inner.scheduler();
417
418 let output = Cell::new(None);
419 let future = async {
420 output.set(Some(future.await));
421 };
422 let mut future = std::pin::pin!(future);
423
424 scheduler.block(None, future.as_mut(), None);
428
429 output.take().expect("block_test future did not complete")
430 }
431
432 pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
435 self.inner.block_on(future)
436 }
437
438 pub fn block_with_timeout<R, Fut: Future<Output = R>>(
440 &self,
441 duration: Duration,
442 future: Fut,
443 ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
444 self.inner.block_with_timeout(duration, future)
445 }
446
447 #[doc(hidden)]
448 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
449 &self.dispatcher
450 }
451
452 #[doc(hidden)]
453 pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
454 self.inner.clone()
455 }
456}
457
458pub struct Scope<'a> {
460 executor: BackgroundExecutor,
461 priority: Priority,
462 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
463 tx: Option<mpsc::Sender<()>>,
464 rx: mpsc::Receiver<()>,
465 lifetime: PhantomData<&'a ()>,
466}
467
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
509impl Drop for Scope<'_> {
510 fn drop(&mut self) {
511 self.tx.take().unwrap();
512
513 let future = async {
516 self.rx.next().await;
517 };
518 let mut future = std::pin::pin!(future);
519 self.executor
520 .inner
521 .scheduler()
522 .block(None, future.as_mut(), None);
523 }
524}
525
526#[cfg(test)]
527mod test {
528 use super::*;
529 use crate::{App, TestDispatcher, TestPlatform};
530 use std::cell::RefCell;
531
532 fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
535 let dispatcher = TestDispatcher::new(0);
536 let arc_dispatcher = Arc::new(dispatcher.clone());
537 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
538 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
539
540 let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
541 let asset_source = Arc::new(());
542 let http_client = http_client::FakeHttpClient::with_404_response();
543
544 let app = App::new_app(platform, asset_source, http_client);
545 (dispatcher, background_executor, app)
546 }
547
548 #[test]
549 fn sanity_test_tasks_run() {
550 let (dispatcher, _background_executor, app) = create_test_app();
551 let foreground_executor = app.borrow().foreground_executor.clone();
552
553 let task_ran = Rc::new(RefCell::new(false));
554
555 foreground_executor
556 .spawn({
557 let task_ran = Rc::clone(&task_ran);
558 async move {
559 *task_ran.borrow_mut() = true;
560 }
561 })
562 .detach();
563
564 dispatcher.run_until_parked();
566
567 assert!(
569 *task_ran.borrow(),
570 "Task should run normally when app is alive"
571 );
572 }
573}