1use crate::{ActivityGuard, App, PlatformDispatcher, profiler};
2use async_task::Runnable;
3use futures::channel::mpsc;
4use smol::prelude::*;
5use std::mem::ManuallyDrop;
6use std::panic::Location;
7use std::thread::{self, ThreadId};
8use std::{
9 fmt::Debug,
10 marker::PhantomData,
11 mem,
12 num::NonZeroUsize,
13 pin::Pin,
14 rc::Rc,
15 sync::{
16 Arc,
17 atomic::{AtomicUsize, Ordering::SeqCst},
18 },
19 task::{Context, Poll},
20 time::{Duration, Instant},
21};
22use util::TryFutureExt;
23use waker_fn::waker_fn;
24
25#[cfg(any(test, feature = "test-support"))]
26use rand::rngs::StdRng;
27
28#[derive(Clone)]
31pub struct BackgroundExecutor {
32 #[doc(hidden)]
33 pub dispatcher: Arc<dyn PlatformDispatcher>,
34}
35
36#[derive(Clone)]
44pub struct ForegroundExecutor {
45 #[doc(hidden)]
46 pub dispatcher: Arc<dyn PlatformDispatcher>,
47 not_send: PhantomData<Rc<()>>,
48}
49
50#[must_use]
57#[derive(Debug)]
58pub struct Task<T>(TaskState<T>);
59
60#[derive(Debug)]
61enum TaskState<T> {
62 Ready(Option<T>),
64
65 Spawned(async_task::Task<T>),
67}
68
69impl<T> Task<T> {
70 pub fn ready(val: T) -> Self {
72 Task(TaskState::Ready(Some(val)))
73 }
74
75 pub fn detach(self) {
77 match self {
78 Task(TaskState::Ready(_)) => {}
79 Task(TaskState::Spawned(task)) => task.detach(),
80 }
81 }
82}
83
84impl<E, T> Task<Result<T, E>>
85where
86 T: 'static,
87 E: 'static + Debug,
88{
89 #[track_caller]
92 pub fn detach_and_log_err(self, cx: &App) {
93 let location = core::panic::Location::caller();
94 cx.foreground_executor()
95 .spawn(self.log_tracked_err(*location))
96 .detach();
97 }
98}
99
100impl<T> Future for Task<T> {
101 type Output = T;
102
103 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
104 match unsafe { self.get_unchecked_mut() } {
105 Task(TaskState::Ready(val)) => Poll::Ready(val.take().unwrap()),
106 Task(TaskState::Spawned(task)) => task.poll(cx),
107 }
108 }
109}
110
111#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
113pub enum TaskPriority {
114 High,
116 #[default]
118 Medium,
119 Low,
121}
122
123#[derive(Clone, Copy, Debug, Eq)]
125pub struct TaskLabel {
126 id: NonZeroUsize,
127 priority: TaskPriority,
128}
129
130impl PartialEq for TaskLabel {
131 fn eq(&self, other: &Self) -> bool {
132 self.id == other.id
133 }
134}
135
136impl std::hash::Hash for TaskLabel {
137 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
138 self.id.hash(state);
139 }
140}
141
142impl Default for TaskLabel {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl TaskLabel {
149 pub fn new() -> Self {
151 Self::new_with_priority(TaskPriority::Medium)
152 }
153
154 fn new_with_priority(priority: TaskPriority) -> Self {
155 static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
156 Self {
157 id: NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap(),
158 priority,
159 }
160 }
161
162 #[doc(hidden)]
163 pub fn priority(self) -> TaskPriority {
164 self.priority
165 }
166}
167
168type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
169
170type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
171
172struct TimedFuture<F> {
173 inner: F,
174 location: &'static Location<'static>,
175 started: bool,
176 timing: Option<profiler::TaskTimingHandle>,
177}
178
179impl<F> TimedFuture<F> {
180 fn new(inner: F, location: &'static Location<'static>) -> Self {
181 Self {
182 inner,
183 location,
184 started: false,
185 timing: None,
186 }
187 }
188}
189
190impl<F: Future> Future for TimedFuture<F> {
191 type Output = F::Output;
192
193 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
194 let this = unsafe { self.get_unchecked_mut() };
195 if !this.started {
196 this.started = true;
197 this.timing = profiler::record_start(this.location);
198 }
199
200 let result = unsafe { Pin::new_unchecked(&mut this.inner) }.poll(cx);
201 if result.is_ready()
202 && let Some(timing) = this.timing.take()
203 {
204 profiler::record_end(timing, profiler::TaskTimingStatus::Completed);
205 }
206 result
207 }
208}
209
210impl<F> Drop for TimedFuture<F> {
211 fn drop(&mut self) {
212 if let Some(timing) = self.timing.take() {
213 profiler::record_end(timing, profiler::TaskTimingStatus::Cancelled);
214 }
215 }
216}
217
218impl BackgroundExecutor {
223 #[doc(hidden)]
224 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
225 Self { dispatcher }
226 }
227
228 pub fn prevent_app_nap(&self, reason: &str) -> ActivityGuard {
232 self.dispatcher.prevent_app_nap(reason)
233 }
234
235 #[track_caller]
237 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
238 where
239 R: Send + 'static,
240 {
241 self.spawn_internal::<R>(Box::pin(future), None)
242 }
243
244 #[track_caller]
247 pub fn spawn_labeled<R>(
248 &self,
249 label: TaskLabel,
250 future: impl Future<Output = R> + Send + 'static,
251 ) -> Task<R>
252 where
253 R: Send + 'static,
254 {
255 self.spawn_internal::<R>(Box::pin(future), Some(label))
256 }
257
258 #[track_caller]
259 #[doc(hidden)]
260 pub fn spawn_with_priority<R>(
261 &self,
262 priority: TaskPriority,
263 future: impl Future<Output = R> + Send + 'static,
264 ) -> Task<R>
265 where
266 R: Send + 'static,
267 {
268 self.spawn_internal::<R>(
269 Box::pin(future),
270 Some(TaskLabel::new_with_priority(priority)),
271 )
272 }
273
274 #[track_caller]
275 fn spawn_internal<R: Send + 'static>(
276 &self,
277 future: AnyFuture<R>,
278 label: Option<TaskLabel>,
279 ) -> Task<R> {
280 let dispatcher = self.dispatcher.clone();
281 let future = TimedFuture::new(future, Location::caller());
282 let (runnable, task) =
283 async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
284 runnable.schedule();
285 Task(TaskState::Spawned(task))
286 }
287
288 #[cfg(any(test, feature = "test-support"))]
290 #[track_caller]
291 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
292 if let Ok(value) = self.block_internal(false, future, None) {
293 value
294 } else {
295 unreachable!()
296 }
297 }
298
299 pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
302 if let Ok(value) = self.block_internal(true, future, None) {
303 value
304 } else {
305 unreachable!()
306 }
307 }
308
309 #[cfg(not(any(test, feature = "test-support")))]
310 pub(crate) fn block_internal<Fut: Future>(
311 &self,
312 _background_only: bool,
313 future: Fut,
314 timeout: Option<Duration>,
315 ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
316 use std::time::Instant;
317
318 let mut future = Box::pin(future);
319 if timeout == Some(Duration::ZERO) {
320 return Err(future);
321 }
322 let deadline = timeout.map(|timeout| Instant::now() + timeout);
323
324 let unparker = self.dispatcher.unparker();
325 let waker = waker_fn(move || {
326 unparker.unpark();
327 });
328 let mut cx = std::task::Context::from_waker(&waker);
329
330 loop {
331 match future.as_mut().poll(&mut cx) {
332 Poll::Ready(result) => return Ok(result),
333 Poll::Pending => {
334 let timeout =
335 deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
336 if !self.dispatcher.park(timeout)
337 && deadline.is_some_and(|deadline| deadline < Instant::now())
338 {
339 return Err(future);
340 }
341 }
342 }
343 }
344 }
345
346 #[cfg(any(test, feature = "test-support"))]
347 #[track_caller]
348 pub(crate) fn block_internal<Fut: Future>(
349 &self,
350 background_only: bool,
351 future: Fut,
352 timeout: Option<Duration>,
353 ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
354 use std::sync::atomic::AtomicBool;
355
356 let mut future = Box::pin(future);
357 if timeout == Some(Duration::ZERO) {
358 return Err(future);
359 }
360 let Some(dispatcher) = self.dispatcher.as_test() else {
361 return Err(future);
362 };
363
364 let mut max_ticks = if timeout.is_some() {
365 dispatcher.gen_block_on_ticks()
366 } else {
367 usize::MAX
368 };
369 let unparker = self.dispatcher.unparker();
370 let awoken = Arc::new(AtomicBool::new(false));
371 let waker = waker_fn({
372 let awoken = awoken.clone();
373 move || {
374 awoken.store(true, SeqCst);
375 unparker.unpark();
376 }
377 });
378 let mut cx = std::task::Context::from_waker(&waker);
379
380 loop {
381 match future.as_mut().poll(&mut cx) {
382 Poll::Ready(result) => return Ok(result),
383 Poll::Pending => {
384 if max_ticks == 0 {
385 return Err(future);
386 }
387 max_ticks -= 1;
388
389 if !dispatcher.tick(background_only) {
390 if awoken.swap(false, SeqCst) {
391 continue;
392 }
393
394 if !dispatcher.parking_allowed() {
395 if dispatcher.advance_clock_to_next_delayed() {
396 continue;
397 }
398 let mut backtrace_message = String::new();
399 let mut waiting_message = String::new();
400 if let Some(backtrace) = dispatcher.waiting_backtrace() {
401 backtrace_message =
402 format!("\nbacktrace of waiting future:\n{:?}", backtrace);
403 }
404 if let Some(waiting_hint) = dispatcher.waiting_hint() {
405 waiting_message = format!("\n waiting on: {}\n", waiting_hint);
406 }
407 panic!(
408 "parked with nothing left to run{waiting_message}{backtrace_message}",
409 )
410 }
411 self.dispatcher.park(None);
412 }
413 }
414 }
415 }
416 }
417
418 pub fn block_with_timeout<Fut: Future>(
421 &self,
422 duration: Duration,
423 future: Fut,
424 ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
425 self.block_internal(true, future, Some(duration))
426 }
427
428 pub async fn scoped<'scope, F>(&self, scheduler: F)
431 where
432 F: FnOnce(&mut Scope<'scope>),
433 {
434 let mut scope = Scope::new(self.clone());
435 (scheduler)(&mut scope);
436 let spawned = mem::take(&mut scope.futures)
437 .into_iter()
438 .map(|f| self.spawn(f))
439 .collect::<Vec<_>>();
440 for task in spawned {
441 task.await;
442 }
443 }
444
445 pub fn now(&self) -> Instant {
450 self.dispatcher.now()
451 }
452
453 #[track_caller]
457 pub fn timer(&self, duration: Duration) -> Task<()> {
458 if duration.is_zero() {
459 return Task::ready(());
460 }
461 let future = TimedFuture::new(Box::pin(async move {}), Location::caller());
462 let (runnable, task) = async_task::spawn(future, {
463 let dispatcher = self.dispatcher.clone();
464 move |runnable| dispatcher.dispatch_after(duration, runnable)
465 });
466 runnable.schedule();
467 Task(TaskState::Spawned(task))
468 }
469
470 #[cfg(any(test, feature = "test-support"))]
472 pub fn start_waiting(&self) {
473 self.dispatcher.as_test().unwrap().start_waiting();
474 }
475
476 #[cfg(any(test, feature = "test-support"))]
478 pub fn finish_waiting(&self) {
479 self.dispatcher.as_test().unwrap().finish_waiting();
480 }
481
482 #[cfg(any(test, feature = "test-support"))]
484 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
485 self.dispatcher.as_test().unwrap().simulate_random_delay()
486 }
487
488 #[cfg(any(test, feature = "test-support"))]
490 pub fn deprioritize(&self, task_label: TaskLabel) {
491 self.dispatcher.as_test().unwrap().deprioritize(task_label)
492 }
493
494 #[cfg(any(test, feature = "test-support"))]
496 pub fn advance_clock(&self, duration: Duration) {
497 self.dispatcher.as_test().unwrap().advance_clock(duration)
498 }
499
500 #[cfg(any(test, feature = "test-support"))]
502 pub fn tick(&self) -> bool {
503 self.dispatcher.as_test().unwrap().tick(false)
504 }
505
506 #[cfg(any(test, feature = "test-support"))]
509 pub fn run_until_parked(&self) {
510 self.dispatcher.as_test().unwrap().run_until_parked()
511 }
512
513 #[cfg(any(test, feature = "test-support"))]
517 pub fn allow_parking(&self) {
518 self.dispatcher.as_test().unwrap().allow_parking();
519 }
520
521 #[cfg(any(test, feature = "test-support"))]
523 pub fn forbid_parking(&self) {
524 self.dispatcher.as_test().unwrap().forbid_parking();
525 }
526
527 #[cfg(any(test, feature = "test-support"))]
529 pub fn set_waiting_hint(&self, msg: Option<String>) {
530 self.dispatcher.as_test().unwrap().set_waiting_hint(msg);
531 }
532
533 #[cfg(any(test, feature = "test-support"))]
535 pub fn rng(&self) -> StdRng {
536 self.dispatcher.as_test().unwrap().rng()
537 }
538
539 pub fn num_cpus(&self) -> usize {
541 #[cfg(any(test, feature = "test-support"))]
542 return 4;
543
544 #[cfg(not(any(test, feature = "test-support")))]
545 return std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
546 }
547
548 pub fn is_main_thread(&self) -> bool {
550 self.dispatcher.is_main_thread()
551 }
552
553 pub fn is_test(&self) -> bool {
559 #[cfg(any(test, feature = "test-support"))]
560 {
561 self.dispatcher.as_test().is_some()
562 }
563 #[cfg(not(any(test, feature = "test-support")))]
564 {
565 false
566 }
567 }
568
569 #[cfg(any(test, feature = "test-support"))]
570 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
572 self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
573 }
574}
575
576impl ForegroundExecutor {
578 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
580 Self {
581 dispatcher,
582 not_send: PhantomData,
583 }
584 }
585
586 #[track_caller]
588 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
589 where
590 R: 'static,
591 {
592 let dispatcher = self.dispatcher.clone();
593
594 #[track_caller]
595 fn inner<R: 'static>(
596 dispatcher: Arc<dyn PlatformDispatcher>,
597 future: AnyLocalFuture<R>,
598 ) -> Task<R> {
599 let future = TimedFuture::new(future, Location::caller());
600 let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
601 dispatcher.dispatch_on_main_thread(runnable)
602 });
603 runnable.schedule();
604 Task(TaskState::Spawned(task))
605 }
606 inner::<R>(dispatcher, Box::pin(future))
607 }
608}
609
610#[track_caller]
615fn spawn_local_with_source_location<Fut, S>(
616 future: Fut,
617 schedule: S,
618) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
619where
620 Fut: Future + 'static,
621 Fut::Output: 'static,
622 S: async_task::Schedule<()> + Send + Sync + 'static,
623{
624 #[inline]
625 fn thread_id() -> ThreadId {
626 std::thread_local! {
627 static ID: ThreadId = thread::current().id();
628 }
629 ID.try_with(|id| *id)
630 .unwrap_or_else(|_| thread::current().id())
631 }
632
633 struct Checked<F> {
634 id: ThreadId,
635 inner: ManuallyDrop<F>,
636 location: &'static Location<'static>,
637 }
638
639 impl<F> Drop for Checked<F> {
640 fn drop(&mut self) {
641 assert!(
642 self.id == thread_id(),
643 "local task dropped by a thread that didn't spawn it. Task spawned at {}",
644 self.location
645 );
646 unsafe {
647 ManuallyDrop::drop(&mut self.inner);
648 }
649 }
650 }
651
652 impl<F: Future> Future for Checked<F> {
653 type Output = F::Output;
654
655 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
656 assert!(
657 self.id == thread_id(),
658 "local task polled by a thread that didn't spawn it. Task spawned at {}",
659 self.location
660 );
661 unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
662 }
663 }
664
665 let future = Checked {
667 id: thread_id(),
668 inner: ManuallyDrop::new(future),
669 location: Location::caller(),
670 };
671
672 unsafe { async_task::spawn_unchecked(future, schedule) }
673}
674
675pub struct Scope<'a> {
677 executor: BackgroundExecutor,
678 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
679 tx: Option<mpsc::Sender<()>>,
680 rx: mpsc::Receiver<()>,
681 lifetime: PhantomData<&'a ()>,
682}
683
684impl<'a> Scope<'a> {
685 fn new(executor: BackgroundExecutor) -> Self {
686 let (tx, rx) = mpsc::channel(1);
687 Self {
688 executor,
689 tx: Some(tx),
690 rx,
691 futures: Default::default(),
692 lifetime: PhantomData,
693 }
694 }
695
696 pub fn num_cpus(&self) -> usize {
698 self.executor.num_cpus()
699 }
700
701 pub fn spawn<F>(&mut self, f: F)
703 where
704 F: Future<Output = ()> + Send + 'a,
705 {
706 let tx = self.tx.clone().unwrap();
707
708 let f = unsafe {
711 mem::transmute::<
712 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
713 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
714 >(Box::pin(async move {
715 f.await;
716 drop(tx);
717 }))
718 };
719 self.futures.push(f);
720 }
721}
722
723impl Drop for Scope<'_> {
724 fn drop(&mut self) {
725 self.tx.take().unwrap();
726
727 self.executor.block(self.rx.next());
730 }
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736 use crate::{TestAppContext, profiler};
737
738 fn is_probe_timing(timing: &profiler::SerializedTaskTiming) -> bool {
739 timing
740 .location
741 .file
742 .as_ref()
743 .contains("executor_timing_probe")
744 }
745 use std::sync::{
746 Arc, Mutex,
747 atomic::{AtomicBool, Ordering},
748 };
749
750 fn reset_profiler() {
751 profiler::set_enabled(false);
752 }
753
754 fn collect_timings() -> Vec<profiler::ThreadTimingsDelta> {
755 let mut collector = profiler::ProfilingCollector::new(Instant::now());
756 profiler::profiler_collect_timings(&mut collector)
757 }
758
759 #[test]
760 fn profiler_disabled_records_nothing() {
761 let _guard = profiler::test_guard();
762 reset_profiler();
763
764 let mut cx = TestAppContext::single();
765 cx.executor().spawn(async {}).detach();
766 cx.run_until_parked();
767
768 assert!(collect_timings().is_empty());
769 reset_profiler();
770 }
771
772 #[test]
773 fn profiler_enabled_records_background_task_timing() {
774 let _guard = profiler::test_guard();
775 reset_profiler();
776 profiler::set_enabled(true);
777
778 let mut cx = TestAppContext::single();
779 let mut collector = profiler::ProfilingCollector::new(Instant::now());
780 cx.executor().spawn(async {}).detach();
781 cx.run_until_parked();
782
783 let deltas = profiler::profiler_collect_timings(&mut collector);
784 let timing = deltas
785 .iter()
786 .flat_map(|delta| &delta.new_timings)
787 .next()
788 .expect("expected a recorded background task timing");
789 assert_eq!(timing.status, profiler::TaskTimingStatus::Completed);
790
791 reset_profiler();
792 }
793
794 #[test]
795 fn profiler_enabled_records_foreground_task_location() {
796 let _guard = profiler::test_guard();
797 reset_profiler();
798 profiler::set_enabled(true);
799
800 let mut cx = TestAppContext::single();
801 let mut collector = profiler::ProfilingCollector::new(Instant::now());
802 cx.foreground_executor().spawn(async {}).detach();
803 cx.run_until_parked();
804
805 let deltas = profiler::profiler_collect_timings(&mut collector);
806 let timing = deltas
807 .iter()
808 .flat_map(|delta| &delta.new_timings)
809 .find(|timing| timing.location.file.as_ref().contains("executor.rs"))
810 .expect("expected foreground task timing to include its source location");
811 assert!(timing.location.line > 0);
812 assert!(timing.location.column > 0);
813
814 reset_profiler();
815 }
816
817 #[test]
818 fn profiler_timing_start_is_first_poll_not_spawn() {
819 let _guard = profiler::test_guard();
820 reset_profiler();
821 profiler::set_enabled(true);
822
823 let mut cx = TestAppContext::single();
824 let mut collector = profiler::ProfilingCollector::new(Instant::now());
825 crate::executor_timing_probe::spawn_noop(&cx.executor());
826 let early = profiler::profiler_collect_timings(&mut collector);
830 assert!(
831 early
832 .iter()
833 .flat_map(|delta| &delta.new_timings)
834 .all(|timing| !is_probe_timing(timing))
835 );
836
837 cx.run_until_parked();
838 let recorded = profiler::profiler_collect_timings(&mut collector);
839 assert!(
840 recorded
841 .iter()
842 .flat_map(|delta| &delta.new_timings)
843 .any(is_probe_timing)
844 );
845
846 reset_profiler();
847 }
848
849 #[test]
850 fn baseline_foreground_task_runs_on_main_thread() {
851 let mut cx = TestAppContext::single();
852 let executor = cx.executor();
853 let ran_on_main_thread = Arc::new(AtomicBool::new(false));
854
855 cx.foreground_executor()
856 .spawn({
857 let ran_on_main_thread = ran_on_main_thread.clone();
858 async move {
859 ran_on_main_thread.store(executor.is_main_thread(), Ordering::SeqCst);
860 }
861 })
862 .detach();
863 cx.run_until_parked();
864
865 assert!(ran_on_main_thread.load(Ordering::SeqCst));
866 }
867
868 #[test]
869 fn baseline_background_task_completes_after_run_until_parked() {
870 let mut cx = TestAppContext::single();
871 let completed = Arc::new(AtomicBool::new(false));
872
873 cx.executor()
874 .spawn({
875 let completed = completed.clone();
876 async move {
877 completed.store(true, Ordering::SeqCst);
878 }
879 })
880 .detach();
881 cx.run_until_parked();
882
883 assert!(completed.load(Ordering::SeqCst));
884 }
885
886 #[test]
887 fn baseline_timer_fires_after_advance_clock() {
888 let cx = TestAppContext::single();
889 let executor = cx.executor();
890 let fired = Arc::new(AtomicBool::new(false));
891
892 executor
893 .spawn({
894 let executor = executor.clone();
895 let fired = fired.clone();
896 async move {
897 executor.timer(Duration::from_millis(10)).await;
898 fired.store(true, Ordering::SeqCst);
899 }
900 })
901 .detach();
902
903 executor.advance_clock(Duration::from_millis(10));
904 assert!(fired.load(Ordering::SeqCst));
905 }
906
907 #[test]
908 fn baseline_deprioritize_delays_labeled_task() {
909 let mut cx = TestAppContext::single();
910 let executor = cx.executor();
911 let label = TaskLabel::new();
912 let order = Arc::new(Mutex::new(Vec::new()));
913
914 executor.deprioritize(label);
915 executor
916 .spawn_labeled(label, {
917 let order = order.clone();
918 async move {
919 order.lock().unwrap().push("labeled");
920 }
921 })
922 .detach();
923 executor
924 .spawn({
925 let order = order.clone();
926 async move {
927 order.lock().unwrap().push("normal");
928 }
929 })
930 .detach();
931 cx.run_until_parked();
932
933 assert_eq!(&*order.lock().unwrap(), &["normal", "labeled"]);
934 }
935
936 #[test]
937 fn baseline_block_with_timeout_returns_bounded_pending_future() {
938 let cx = TestAppContext::single();
939 let executor = cx.executor();
940 executor.set_block_on_ticks(0..=0);
941
942 let result =
943 executor.block_with_timeout(Duration::from_millis(1), futures::future::pending::<()>());
944
945 assert!(result.is_err());
946 }
947
948 #[test]
949 fn baseline_headless_app_tasks_complete_without_window() {
950 let mut cx = TestAppContext::single();
951 let completed = Arc::new(AtomicBool::new(false));
952
953 cx.executor()
954 .spawn({
955 let completed = completed.clone();
956 async move {
957 completed.store(true, Ordering::SeqCst);
958 }
959 })
960 .detach();
961 cx.run_until_parked();
962
963 assert!(completed.load(Ordering::SeqCst));
964 assert!(cx.windows().is_empty());
965 }
966
967 #[test]
968 fn baseline_scoped_tasks_complete_before_scope_returns() {
969 let cx = TestAppContext::single();
970 let executor = cx.executor();
971 let completed = Arc::new(AtomicBool::new(false));
972
973 executor.block_test({
974 let executor = executor.clone();
975 let completed = completed.clone();
976 async move {
977 executor
978 .scoped(|scope| {
979 scope.spawn(async move {
980 completed.store(true, Ordering::SeqCst);
981 });
982 })
983 .await;
984 }
985 });
986
987 assert!(completed.load(Ordering::SeqCst));
988 }
989
990 #[test]
991 fn spawn_with_priority_defaults_to_medium_behavior() {
992 let mut cx = TestAppContext::single();
993 let completed = Arc::new(AtomicBool::new(false));
994
995 cx.executor()
996 .spawn_with_priority(TaskPriority::Medium, {
997 let completed = completed.clone();
998 async move {
999 completed.store(true, Ordering::SeqCst);
1000 }
1001 })
1002 .detach();
1003 cx.run_until_parked();
1004
1005 assert!(completed.load(Ordering::SeqCst));
1006 }
1007
1008 #[test]
1009 fn spawn_with_priority_high_is_observable_in_test_dispatcher() {
1010 let mut cx = TestAppContext::single();
1011 let executor = cx.executor();
1012 let order = Arc::new(Mutex::new(Vec::new()));
1013
1014 executor
1015 .spawn_with_priority(TaskPriority::Low, {
1016 let order = order.clone();
1017 async move {
1018 order.lock().unwrap().push("low");
1019 }
1020 })
1021 .detach();
1022 executor
1023 .spawn_with_priority(TaskPriority::High, {
1024 let order = order.clone();
1025 async move {
1026 order.lock().unwrap().push("high");
1027 }
1028 })
1029 .detach();
1030 cx.run_until_parked();
1031
1032 assert_eq!(&*order.lock().unwrap(), &["high", "low"]);
1033 }
1034
1035 #[test]
1036 fn spawn_labeled_deprioritize_still_works_after_priority_addition() {
1037 baseline_deprioritize_delays_labeled_task();
1038 }
1039
1040 #[test]
1041 fn low_priority_task_eventually_runs() {
1042 let mut cx = TestAppContext::single();
1043 let completed = Arc::new(AtomicBool::new(false));
1044
1045 cx.executor()
1046 .spawn_with_priority(TaskPriority::Low, {
1047 let completed = completed.clone();
1048 async move {
1049 completed.store(true, Ordering::SeqCst);
1050 }
1051 })
1052 .detach();
1053 cx.run_until_parked();
1054
1055 assert!(completed.load(Ordering::SeqCst));
1056 }
1057}