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 not_send: PhantomData<Rc<()>>,
28}
29
30pub trait TaskExt<T, E> {
34 fn detach_and_log_err(self, cx: &App);
36 fn detach_and_log_err_with_backtrace(self, cx: &App);
39}
40
41impl<T, E> TaskExt<T, E> for Task<Result<T, E>>
42where
43 T: 'static,
44 E: 'static + std::fmt::Display + std::fmt::Debug,
45{
46 #[track_caller]
47 fn detach_and_log_err(self, cx: &App) {
48 let location = core::panic::Location::caller();
49 cx.foreground_executor()
50 .spawn(self.log_tracked_err(*location))
51 .detach();
52 }
53
54 #[track_caller]
55 fn detach_and_log_err_with_backtrace(self, cx: &App) {
56 let location = *core::panic::Location::caller();
57 cx.foreground_executor()
58 .spawn(self.log_tracked_err_with_backtrace(location))
59 .detach();
60 }
61}
62
63impl BackgroundExecutor {
64 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
66 #[cfg(any(test, feature = "test-support"))]
67 let scheduler: Arc<dyn Scheduler> = if let Some(test_dispatcher) = dispatcher.as_test() {
68 test_dispatcher.scheduler().clone()
69 } else {
70 Arc::new(PlatformScheduler::new(dispatcher.clone()))
71 };
72
73 #[cfg(not(any(test, feature = "test-support")))]
74 let scheduler: Arc<dyn Scheduler> = Arc::new(PlatformScheduler::new(dispatcher.clone()));
75
76 Self {
77 inner: scheduler::BackgroundExecutor::new(scheduler),
78 dispatcher,
79 }
80 }
81
82 pub fn scheduler_executor(&self) -> scheduler::BackgroundExecutor {
86 self.inner.clone()
87 }
88
89 #[track_caller]
101 pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
102 where
103 F: FnOnce(SchedulerLocalExecutor) -> Fut + Send + 'static,
104 Fut: Future + 'static,
105 Fut::Output: Send + Sync + 'static,
106 {
107 self.inner.spawn_dedicated(f)
108 }
109
110 #[track_caller]
112 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
113 where
114 R: Send + 'static,
115 {
116 self.spawn_with_priority(Priority::default(), future.boxed())
117 }
118
119 #[track_caller]
124 pub fn spawn_with_priority<R>(
125 &self,
126 priority: Priority,
127 future: impl Future<Output = R> + Send + 'static,
128 ) -> Task<R>
129 where
130 R: Send + 'static,
131 {
132 if priority == Priority::RealtimeAudio {
133 self.inner.spawn_realtime(future)
134 } else {
135 self.inner.spawn_with_priority(priority, future)
136 }
137 }
138
139 pub async fn scoped<'scope, F>(&self, scheduler: F)
142 where
143 F: FnOnce(&mut Scope<'scope>),
144 {
145 let mut scope = Scope::new(self.clone(), Priority::default());
146 (scheduler)(&mut scope);
147 let spawned = mem::take(&mut scope.futures)
148 .into_iter()
149 .map(|f| self.spawn_with_priority(scope.priority, f))
150 .collect::<Vec<_>>();
151 for task in spawned {
152 task.await;
153 }
154 }
155
156 pub async fn scoped_priority<'scope, F>(&self, priority: Priority, scheduler: F)
159 where
160 F: FnOnce(&mut Scope<'scope>),
161 {
162 let mut scope = Scope::new(self.clone(), priority);
163 (scheduler)(&mut scope);
164 let spawned = mem::take(&mut scope.futures)
165 .into_iter()
166 .map(|f| self.spawn_with_priority(scope.priority, f))
167 .collect::<Vec<_>>();
168 for task in spawned {
169 task.await;
170 }
171 }
172
173 pub fn now(&self) -> Instant {
178 self.inner.scheduler().clock().now()
179 }
180
181 #[track_caller]
185 pub fn timer(&self, duration: Duration) -> Task<()> {
186 if duration.is_zero() {
187 return Task::ready(());
188 }
189 self.spawn(self.inner.scheduler().timer(duration))
190 }
191
192 #[cfg(any(test, feature = "test-support"))]
194 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
195 self.dispatcher.as_test().unwrap().simulate_random_delay()
196 }
197
198 #[cfg(any(test, feature = "test-support"))]
200 pub fn advance_clock(&self, duration: Duration) {
201 self.dispatcher.as_test().unwrap().advance_clock(duration)
202 }
203
204 #[cfg(any(test, feature = "test-support"))]
206 pub fn tick(&self) -> bool {
207 self.dispatcher.as_test().unwrap().scheduler().tick()
208 }
209
210 #[cfg(any(test, feature = "test-support"))]
217 pub fn run_until_parked(&self) {
218 let scheduler = self.dispatcher.as_test().unwrap().scheduler();
219 scheduler.run();
220 }
221
222 #[cfg(any(test, feature = "test-support"))]
224 pub fn allow_parking(&self) {
225 self.dispatcher
226 .as_test()
227 .unwrap()
228 .scheduler()
229 .allow_parking();
230
231 if std::env::var("GPUI_RUN_UNTIL_PARKED_LOG").ok().as_deref() == Some("1") {
232 log::warn!("[gpui::executor] allow_parking: enabled");
233 }
234 }
235
236 #[cfg(any(test, feature = "test-support"))]
238 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
239 self.dispatcher
240 .as_test()
241 .unwrap()
242 .scheduler()
243 .set_timeout_ticks(range);
244 }
245
246 #[cfg(any(test, feature = "test-support"))]
248 pub fn forbid_parking(&self) {
249 self.dispatcher
250 .as_test()
251 .unwrap()
252 .scheduler()
253 .forbid_parking();
254 }
255
256 #[cfg(any(test, feature = "test-support"))]
258 pub fn rng(&self) -> scheduler::SharedRng {
259 self.dispatcher.as_test().unwrap().scheduler().rng()
260 }
261
262 pub fn num_cpus(&self) -> usize {
264 #[cfg(any(test, feature = "test-support"))]
265 if let Some(test) = self.dispatcher.as_test() {
266 return test.num_cpus_override().unwrap_or(4);
267 }
268 num_cpus::get()
269 }
270
271 #[cfg(any(test, feature = "test-support"))]
274 pub fn set_num_cpus(&self, count: usize) {
275 self.dispatcher
276 .as_test()
277 .expect("set_num_cpus can only be called on a test executor")
278 .set_num_cpus(count);
279 }
280
281 pub fn is_main_thread(&self) -> bool {
283 self.dispatcher.is_main_thread()
284 }
285
286 #[doc(hidden)]
287 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
288 &self.dispatcher
289 }
290}
291
292impl ForegroundExecutor {
293 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
295 #[cfg(any(test, feature = "test-support"))]
296 let (scheduler, session_id): (Arc<dyn Scheduler>, _) =
297 if let Some(test_dispatcher) = dispatcher.as_test() {
298 (
299 test_dispatcher.scheduler().clone(),
300 test_dispatcher.session_id(),
301 )
302 } else {
303 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
304 let inner = platform_scheduler.foreground_executor();
305 return Self {
306 inner,
307 dispatcher,
308 not_send: PhantomData,
309 };
310 };
311
312 #[cfg(not(any(test, feature = "test-support")))]
313 let inner = {
314 let platform_scheduler = Arc::new(PlatformScheduler::new(dispatcher.clone()));
315 platform_scheduler.foreground_executor()
316 };
317
318 #[cfg(any(test, feature = "test-support"))]
319 let inner = {
320 let scheduler_for_dispatch = Arc::downgrade(&scheduler);
321 scheduler::LocalExecutor::new(session_id, scheduler, move |runnable| {
322 if let Some(scheduler) = scheduler_for_dispatch.upgrade() {
323 scheduler.schedule_local(session_id, runnable);
324 }
325 })
326 };
327
328 Self {
329 inner,
330 dispatcher,
331 not_send: PhantomData,
332 }
333 }
334
335 #[track_caller]
337 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
338 where
339 R: 'static,
340 {
341 self.inner.spawn(future.boxed_local())
342 }
343
344 #[track_caller]
346 pub fn spawn_with_priority<R>(
347 &self,
348 _priority: Priority,
349 future: impl Future<Output = R> + 'static,
350 ) -> Task<R>
351 where
352 R: 'static,
353 {
354 self.inner.spawn(future)
356 }
357
358 #[track_caller]
368 pub fn spawn_when_idle<R>(
369 &self,
370 timeout: Option<Duration>,
371 future: impl Future<Output = R> + 'static,
372 ) -> Task<R>
373 where
374 R: 'static,
375 {
376 let dispatcher = self.dispatcher.clone();
377 self.inner
378 .spawn_with_dispatch(future.boxed_local(), move |runnable| {
379 dispatcher.dispatch_on_main_thread_when_idle(runnable, timeout);
380 })
381 }
382
383 pub fn idle_time_remaining(&self) -> Option<Duration> {
389 self.dispatcher.idle_time_remaining()
390 }
391
392 #[cfg(any(test, feature = "test-support"))]
394 #[track_caller]
395 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
396 use std::cell::Cell;
397
398 let scheduler = self.inner.scheduler();
399
400 let output = Cell::new(None);
401 let future = async {
402 output.set(Some(future.await));
403 };
404 let mut future = std::pin::pin!(future);
405
406 scheduler.block(None, future.as_mut(), None);
410
411 output.take().expect("block_test future did not complete")
412 }
413
414 pub fn block_on<R>(&self, future: impl Future<Output = R>) -> R {
417 self.inner.block_on(future)
418 }
419
420 pub fn block_with_timeout<R, Fut: Future<Output = R>>(
422 &self,
423 duration: Duration,
424 future: Fut,
425 ) -> Result<R, impl Future<Output = R> + use<R, Fut>> {
426 self.inner.block_with_timeout(duration, future)
427 }
428
429 #[doc(hidden)]
430 pub fn dispatcher(&self) -> &Arc<dyn PlatformDispatcher> {
431 &self.dispatcher
432 }
433
434 #[doc(hidden)]
435 pub fn scheduler_executor(&self) -> SchedulerLocalExecutor {
436 self.inner.clone()
437 }
438}
439
440pub struct Scope<'a> {
442 executor: BackgroundExecutor,
443 priority: Priority,
444 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
445 tx: Option<mpsc::Sender<()>>,
446 rx: mpsc::Receiver<()>,
447 lifetime: PhantomData<&'a ()>,
448}
449
450impl<'a> Scope<'a> {
451 fn new(executor: BackgroundExecutor, priority: Priority) -> Self {
452 let (tx, rx) = mpsc::channel(1);
453 Self {
454 executor,
455 priority,
456 tx: Some(tx),
457 rx,
458 futures: Default::default(),
459 lifetime: PhantomData,
460 }
461 }
462
463 pub fn num_cpus(&self) -> usize {
465 self.executor.num_cpus()
466 }
467
468 #[track_caller]
470 pub fn spawn<F>(&mut self, f: F)
471 where
472 F: Future<Output = ()> + Send + 'a,
473 {
474 let tx = self.tx.clone().unwrap();
475
476 let f = unsafe {
479 mem::transmute::<
480 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
481 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
482 >(Box::pin(async move {
483 f.await;
484 drop(tx);
485 }))
486 };
487 self.futures.push(f);
488 }
489}
490
491impl Drop for Scope<'_> {
492 fn drop(&mut self) {
493 self.tx.take().unwrap();
494
495 let future = async {
498 self.rx.next().await;
499 };
500 let mut future = std::pin::pin!(future);
501 self.executor
502 .inner
503 .scheduler()
504 .block(None, future.as_mut(), None);
505 }
506}
507
508#[cfg(test)]
509mod test {
510 use super::*;
511 use crate::{App, TestDispatcher, TestPlatform};
512 use std::cell::RefCell;
513
514 fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
517 let dispatcher = TestDispatcher::new(0);
518 let arc_dispatcher = Arc::new(dispatcher.clone());
519 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
520 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
521
522 let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
523 let asset_source = Arc::new(());
524 let http_client = http_client::FakeHttpClient::with_404_response();
525
526 let app = App::new_app(platform, asset_source, http_client);
527 (dispatcher, background_executor, app)
528 }
529
530 #[test]
531 fn sanity_test_tasks_run() {
532 let (dispatcher, _background_executor, app) = create_test_app();
533 let foreground_executor = app.borrow().foreground_executor.clone();
534
535 let task_ran = Rc::new(RefCell::new(false));
536
537 foreground_executor
538 .spawn({
539 let task_ran = Rc::clone(&task_ran);
540 async move {
541 *task_ran.borrow_mut() = true;
542 }
543 })
544 .detach();
545
546 dispatcher.run_until_parked();
548
549 assert!(
551 *task_ran.borrow(),
552 "Task should run normally when app is alive"
553 );
554 }
555}