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::{FallibleTask, LocalExecutor as SchedulerLocalExecutor, Priority, Task};
10
11#[derive(Clone)]
14pub struct BackgroundExecutor {
15 inner: scheduler::BackgroundExecutor,
16 dispatcher: Arc<dyn PlatformDispatcher>,
17}
18
19#[derive(Clone)]
22pub struct ForegroundExecutor {
23 inner: scheduler::LocalExecutor,
24 dispatcher: Arc<dyn PlatformDispatcher>,
25 not_send: PhantomData<Rc<()>>,
26}
27
28pub trait TaskExt<T, E> {
32 fn detach_and_log_err(self, cx: &App);
34 fn detach_and_log_err_with_backtrace(self, cx: &App);
37}
38
39impl<T, E> TaskExt<T, E> for Task<Result<T, E>>
40where
41 T: 'static,
42 E: 'static + std::fmt::Display + std::fmt::Debug,
43{
44 #[track_caller]
45 fn detach_and_log_err(self, cx: &App) {
46 let location = core::panic::Location::caller();
47 cx.foreground_executor()
48 .spawn(self.log_tracked_err(*location))
49 .detach();
50 }
51
52 #[track_caller]
53 fn detach_and_log_err_with_backtrace(self, cx: &App) {
54 let location = *core::panic::Location::caller();
55 cx.foreground_executor()
56 .spawn(self.log_tracked_err_with_backtrace(location))
57 .detach();
58 }
59}
60
61impl BackgroundExecutor {
62 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
64 #[cfg(any(test, feature = "test-support"))]
65 let scheduler: Arc<dyn Scheduler> = if let Some(test_dispatcher) = dispatcher.as_test() {
66 test_dispatcher.scheduler().clone()
67 } else {
68 Arc::new(PlatformScheduler::new(dispatcher.clone()))
69 };
70
71 #[cfg(not(any(test, feature = "test-support")))]
72 let scheduler: Arc<dyn Scheduler> = Arc::new(PlatformScheduler::new(dispatcher.clone()));
73
74 Self {
75 inner: scheduler::BackgroundExecutor::new(scheduler),
76 dispatcher,
77 }
78 }
79
80 pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
84 self.inner.clone()
85 }
86
87 #[track_caller]
89 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
90 where
91 R: Send + 'static,
92 {
93 self.spawn_with_priority(Priority::default(), future.boxed())
94 }
95
96 #[track_caller]
101 pub fn spawn_with_priority<R>(
102 &self,
103 priority: Priority,
104 future: impl Future<Output = R> + Send + 'static,
105 ) -> Task<R>
106 where
107 R: Send + 'static,
108 {
109 if priority == Priority::RealtimeAudio {
110 self.inner.spawn_realtime(future)
111 } else {
112 self.inner.spawn_with_priority(priority, future)
113 }
114 }
115
116 pub async fn scoped<'scope, F>(&self, scheduler: F)
119 where
120 F: FnOnce(&mut Scope<'scope>),
121 {
122 let mut scope = Scope::new(self.clone(), Priority::default());
123 (scheduler)(&mut scope);
124 let spawned = mem::take(&mut scope.futures)
125 .into_iter()
126 .map(|f| self.spawn_with_priority(scope.priority, f))
127 .collect::<Vec<_>>();
128 for task in spawned {
129 task.await;
130 }
131 }
132
133 pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
136 where
137 F: FnOnce(&mut Scope<'scope>),
138 {
139 let mut scope = Scope::new(self.clone(), priority);
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 pub fn now(&self) -> Instant {
155 self.inner.scheduler().clock().now()
156 }
157
158 #[track_caller]
162 pub fn timer(&self, duration: Duration) -> Task<()> {
163 if duration.is_zero() {
164 return Task::ready(());
165 }
166 self.spawn(self.inner.scheduler().timer(duration))
167 }
168
169 #[cfg(any(test, feature = "test-support"))]
171 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
172 self.dispatcher.as_test().unwrap().simulate_random_delay()
173 }
174
175 #[cfg(any(test, feature = "test-support"))]
177 pub fn advance_clock(&self, duration: Duration) {
178 self.dispatcher.as_test().unwrap().advance_clock(duration)
179 }
180
181 #[cfg(any(test, feature = "test-support"))]
183 pub fn tick(&self) -> bool {
184 self.dispatcher.as_test().unwrap().scheduler().tick()
185 }
186
187 #[cfg(any(test, feature = "test-support"))]
194 pub fn run_until_parked(&self) {
195 let scheduler = self.dispatcher.as_test().unwrap().scheduler();
196 scheduler.run();
197 }
198
199 #[cfg(any(test, feature = "test-support"))]
201 pub fn allow_parking(&self) {
202 self.dispatcher
203 .as_test()
204 .unwrap()
205 .scheduler()
206 .allow_parking();
207
208 if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
209 log::warn!("[gpui::executor] allow_parking: enabled");
210 }
211 }
212
213 #[cfg(any(test, feature = "test-support"))]
215 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
216 self.dispatcher
217 .as_test()
218 .unwrap()
219 .scheduler()
220 .set_timeout_ticks(range);
221 }
222
223 #[cfg(any(test, feature = "test-support"))]
225 pub fn forbid_parking(&self) {
226 self.dispatcher
227 .as_test()
228 .unwrap()
229 .scheduler()
230 .forbid_parking();
231 }
232
233 #[cfg(any(test, feature = "test-support"))]
235 pub fn rng(&self) -> scheduler::SharedRng {
236 self.dispatcher.as_test().unwrap().scheduler().rng()
237 }
238
239 pub fn num_cpus(&self) -> usize {
241 #[cfg(any(test, feature = "test-support"))]
242 if let Some(test) = self.dispatcher.as_test() {
243 return test.num_cpus_override().unwrap_or(4);
244 }
245 num_cpus::get()
246 }
247
248 #[cfg(any(test, feature = "test-support"))]
251 pub fn set_num_cpus(&self, count: usize) {
252 self.dispatcher
253 .as_test()
254 .expect("set_num_cpus can only be called on a test executor")
255 .set_num_cpus(count);
256 }
257
258 pub fn is_main_thread(&self) -> bool {
260 self.dispatcher.is_main_thread()
261 }
262
263 #[doc(hidden)]
264 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
265 &self.dispatcher
266 }
267}
268
269impl ForegroundExecutor {
270 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
272 #[cfg(any(test, feature = "test-support"))]
273 let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
274 if let Some(test_dispatcher) = dispatcher.as_test() {
275 (
276 test_dispatcher.scheduler().clone(),
277 test_dispatcher.session_id(),
278 )
279 } else {
280 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
281 let inner = platform_scheduler.foreground_executor();
282 return Self {
283 inner,
284 dispatcher,
285 not_send: PhantomData,
286 };
287 };
288
289 #[cfg(not(any(test, feature = "test-support")))]
290 let inner = {
291 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
292 platform_scheduler.foreground_executor()
293 };
294
295 #[cfg(any(test, feature = "test-support"))]
296 let inner = {
297 let scheduler_for_dispatch = Arc::downgrade(&scheduler);
298 scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
299 if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
300 scheduler.schedule_local(session_id, runnable);
301 }
302 })
303 };
304
305 Self {
306 inner,
307 dispatcher,
308 not_send: PhantomData,
309 }
310 }
311
312 #[track_caller]
314 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
315 where
316 R: 'static,
317 {
318 self.inner.spawn(future.boxed_local())
319 }
320
321 #[track_caller]
323 pub fn spawn_with_priority<R>(
324 &self,
325 _priority: Priority,
326 future: impl Future<Output = R> + 'static,
327 ) -> Task<R>
328 where
329 R: 'static,
330 {
331 self.inner.spawn(future)
333 }
334
335 #[track_caller]
345 pub fn spawn_when_idle<R>(
346 &self,
347 timeout: Option<Duration>,
348 future: impl Future<Output = R> + 'static,
349 ) -> Task<R>
350 where
351 R: 'static,
352 {
353 let dispatcher = self.dispatcher.clone();
354 self.inner
355 .spawn_with_dispatch(future.boxed_local(), move |runnable| {
356 dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
357 })
358 }
359
360 pub fn idle_time_remaining(&self) -> Option<Duration> {
366 self.dispatcher.idle_time_remaining()
367 }
368
369 #[cfg(any(test, feature = "test-support"))]
371 #[track_caller]
372 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
373 use std::cell::Cell;
374
375 let scheduler = self.inner.scheduler();
376
377 let output = Cell::new(None);
378 let future = async {
379 output.set(Some(future.await));
380 };
381 let mut future = std::pin::pin!(future);
382
383 scheduler.block(None, future.as_mut(), None);
387
388 output.take().expect("block_test future did not complete")
389 }
390
391 pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
394 self.inner.block_on(future)
395 }
396
397 pub fn block_with_timeout<R, Fut: Future<Output = R>>(
399 &self,
400 duration: Duration,
401 future: Fut,
402 ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
403 self.inner.block_with_timeout(duration, future)
404 }
405
406 #[doc(hidden)]
407 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
408 &self.dispatcher
409 }
410
411 #[doc(hidden)]
412 pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
413 self.inner.clone()
414 }
415}
416
417pub struct Scope<'a> {
419 executor: BackgroundExecutor,
420 priority: Priority,
421 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
422 tx: Option<mpsc::Sender<()>>,
423 rx: mpsc::Receiver<()>,
424 lifetime: PhantomData<&'a ()>,
425}
426
427impl<'a> Scope<'a> {
428 fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
429 let (tx, rx) = mpsc::channel(1);
430 Self {
431 executor,
432 priority,
433 tx: Some(tx),
434 rx,
435 futures: Default::default(),
436 lifetime: PhantomData,
437 }
438 }
439
440 pub fn num_cpus(&self) -> usize {
442 self.executor.num_cpus()
443 }
444
445 #[track_caller]
447 pub fn spawn<F>(&mut self, f: F)
448 where
449 F: Future<Output = ()> + Send + 'a,
450 {
451 let tx = self.tx.clone().unwrap();
452
453 let f = unsafe {
456 mem::transmute::<
457 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
458 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
459 >(Box::pin(async move {
460 f.await;
461 drop(tx);
462 }))
463 };
464 self.futures.push(f);
465 }
466}
467
468impl Drop for Scope<'_> {
469 fn drop(&mut self) {
470 self.tx.take().unwrap();
471
472 let future = async {
475 self.rx.next().await;
476 };
477 let mut future = std::pin::pin!(future);
478 self.executor
479 .inner
480 .scheduler()
481 .block(None, future.as_mut(), None);
482 }
483}
484
485#[cfg(test)]
486mod test {
487 use super::*;
488 use crate::{App, TestDispatcher, TestPlatform};
489 use std::cell::RefCell;
490
491 fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
494 let dispatcher = TestDispatcher::new(0);
495 let arc_dispatcher = Arc::new(dispatcher.clone());
496 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
497 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
498
499 let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
500 let asset_source = Arc::new(());
501 let http_client = http_client::FakeHttpClient::with_404_response();
502
503 let app = App::new_app(platform, asset_source, http_client);
504 (dispatcher, background_executor, app)
505 }
506
507 #[test]
508 fn sanity_test_tasks_run() {
509 let (dispatcher, _background_executor, app) = create_test_app();
510 let foreground_executor = app.borrow().foreground_executor.clone();
511
512 let task_ran = Rc::new(RefCell::new(false));
513
514 foreground_executor
515 .spawn({
516 let task_ran = Rc::clone(&task_ran);
517 async move {
518 *task_ran.borrow_mut() = true;
519 }
520 })
521 .detach();
522
523 dispatcher.run_until_parked();
525
526 assert!(
528 *task_ran.borrow(),
529 "Task should run normally when app is alive"
530 );
531 }
532}