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 num_cpus::get();
546 }
547
548 pub fn is_main_thread(&self) -> bool {
550 self.dispatcher.is_main_thread()
551 }
552
553 #[cfg(any(test, feature = "test-support"))]
554 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
556 self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
557 }
558}
559
560impl ForegroundExecutor {
562 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
564 Self {
565 dispatcher,
566 not_send: PhantomData,
567 }
568 }
569
570 #[track_caller]
572 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
573 where
574 R: 'static,
575 {
576 let dispatcher = self.dispatcher.clone();
577
578 #[track_caller]
579 fn inner<R: 'static>(
580 dispatcher: Arc<dyn PlatformDispatcher>,
581 future: AnyLocalFuture<R>,
582 ) -> Task<R> {
583 let future = TimedFuture::new(future, Location::caller());
584 let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
585 dispatcher.dispatch_on_main_thread(runnable)
586 });
587 runnable.schedule();
588 Task(TaskState::Spawned(task))
589 }
590 inner::<R>(dispatcher, Box::pin(future))
591 }
592}
593
594#[track_caller]
599fn spawn_local_with_source_location<Fut, S>(
600 future: Fut,
601 schedule: S,
602) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
603where
604 Fut: Future + 'static,
605 Fut::Output: 'static,
606 S: async_task::Schedule<()> + Send + Sync + 'static,
607{
608 #[inline]
609 fn thread_id() -> ThreadId {
610 std::thread_local! {
611 static ID: ThreadId = thread::current().id();
612 }
613 ID.try_with(|id| *id)
614 .unwrap_or_else(|_| thread::current().id())
615 }
616
617 struct Checked<F> {
618 id: ThreadId,
619 inner: ManuallyDrop<F>,
620 location: &'static Location<'static>,
621 }
622
623 impl<F> Drop for Checked<F> {
624 fn drop(&mut self) {
625 assert!(
626 self.id == thread_id(),
627 "local task dropped by a thread that didn't spawn it. Task spawned at {}",
628 self.location
629 );
630 unsafe {
631 ManuallyDrop::drop(&mut self.inner);
632 }
633 }
634 }
635
636 impl<F: Future> Future for Checked<F> {
637 type Output = F::Output;
638
639 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
640 assert!(
641 self.id == thread_id(),
642 "local task polled by a thread that didn't spawn it. Task spawned at {}",
643 self.location
644 );
645 unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
646 }
647 }
648
649 let future = Checked {
651 id: thread_id(),
652 inner: ManuallyDrop::new(future),
653 location: Location::caller(),
654 };
655
656 unsafe { async_task::spawn_unchecked(future, schedule) }
657}
658
659pub struct Scope<'a> {
661 executor: BackgroundExecutor,
662 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
663 tx: Option<mpsc::Sender<()>>,
664 rx: mpsc::Receiver<()>,
665 lifetime: PhantomData<&'a ()>,
666}
667
668impl<'a> Scope<'a> {
669 fn new(executor: BackgroundExecutor) -> Self {
670 let (tx, rx) = mpsc::channel(1);
671 Self {
672 executor,
673 tx: Some(tx),
674 rx,
675 futures: Default::default(),
676 lifetime: PhantomData,
677 }
678 }
679
680 pub fn num_cpus(&self) -> usize {
682 self.executor.num_cpus()
683 }
684
685 pub fn spawn<F>(&mut self, f: F)
687 where
688 F: Future<Output = ()> + Send + 'a,
689 {
690 let tx = self.tx.clone().unwrap();
691
692 let f = unsafe {
695 mem::transmute::<
696 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
697 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
698 >(Box::pin(async move {
699 f.await;
700 drop(tx);
701 }))
702 };
703 self.futures.push(f);
704 }
705}
706
707impl Drop for Scope<'_> {
708 fn drop(&mut self) {
709 self.tx.take().unwrap();
710
711 self.executor.block(self.rx.next());
714 }
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720 use crate::{TestAppContext, profiler};
721 use std::sync::{
722 Arc, Mutex,
723 atomic::{AtomicBool, Ordering},
724 };
725
726 fn reset_profiler() {
727 profiler::set_enabled(false);
728 }
729
730 fn collect_timings() -> Vec<profiler::ThreadTimingsDelta> {
731 let mut collector = profiler::ProfilingCollector::new(Instant::now());
732 profiler::profiler_collect_timings(&mut collector)
733 }
734
735 #[test]
736 fn profiler_disabled_records_nothing() {
737 let _guard = profiler::test_guard();
738 reset_profiler();
739
740 let mut cx = TestAppContext::single();
741 cx.executor().spawn(async {}).detach();
742 cx.run_until_parked();
743
744 assert!(collect_timings().is_empty());
745 reset_profiler();
746 }
747
748 #[test]
749 fn profiler_enabled_records_background_task_timing() {
750 let _guard = profiler::test_guard();
751 reset_profiler();
752 profiler::set_enabled(true);
753
754 let mut cx = TestAppContext::single();
755 let mut collector = profiler::ProfilingCollector::new(Instant::now());
756 cx.executor().spawn(async {}).detach();
757 cx.run_until_parked();
758
759 let deltas = profiler::profiler_collect_timings(&mut collector);
760 let timing = deltas
761 .iter()
762 .flat_map(|delta| &delta.new_timings)
763 .next()
764 .expect("expected a recorded background task timing");
765 assert_eq!(timing.status, profiler::TaskTimingStatus::Completed);
766
767 reset_profiler();
768 }
769
770 #[test]
771 fn profiler_enabled_records_foreground_task_location() {
772 let _guard = profiler::test_guard();
773 reset_profiler();
774 profiler::set_enabled(true);
775
776 let mut cx = TestAppContext::single();
777 let mut collector = profiler::ProfilingCollector::new(Instant::now());
778 cx.foreground_executor().spawn(async {}).detach();
779 cx.run_until_parked();
780
781 let deltas = profiler::profiler_collect_timings(&mut collector);
782 let timing = deltas
783 .iter()
784 .flat_map(|delta| &delta.new_timings)
785 .find(|timing| timing.location.file.as_ref().contains("executor.rs"))
786 .expect("expected foreground task timing to include its source location");
787 assert!(timing.location.line > 0);
788 assert!(timing.location.column > 0);
789
790 reset_profiler();
791 }
792
793 #[test]
794 fn profiler_timing_start_is_first_poll_not_spawn() {
795 let _guard = profiler::test_guard();
796 reset_profiler();
797 profiler::set_enabled(true);
798
799 let mut cx = TestAppContext::single();
800 let mut collector = profiler::ProfilingCollector::new(Instant::now());
801 cx.executor().spawn(async {}).detach();
802 assert!(profiler::profiler_collect_timings(&mut collector).is_empty());
803
804 cx.run_until_parked();
805 assert!(!profiler::profiler_collect_timings(&mut collector).is_empty());
806
807 reset_profiler();
808 }
809
810 #[test]
811 fn baseline_foreground_task_runs_on_main_thread() {
812 let mut cx = TestAppContext::single();
813 let executor = cx.executor();
814 let ran_on_main_thread = Arc::new(AtomicBool::new(false));
815
816 cx.foreground_executor()
817 .spawn({
818 let ran_on_main_thread = ran_on_main_thread.clone();
819 async move {
820 ran_on_main_thread.store(executor.is_main_thread(), Ordering::SeqCst);
821 }
822 })
823 .detach();
824 cx.run_until_parked();
825
826 assert!(ran_on_main_thread.load(Ordering::SeqCst));
827 }
828
829 #[test]
830 fn baseline_background_task_completes_after_run_until_parked() {
831 let mut cx = TestAppContext::single();
832 let completed = Arc::new(AtomicBool::new(false));
833
834 cx.executor()
835 .spawn({
836 let completed = completed.clone();
837 async move {
838 completed.store(true, Ordering::SeqCst);
839 }
840 })
841 .detach();
842 cx.run_until_parked();
843
844 assert!(completed.load(Ordering::SeqCst));
845 }
846
847 #[test]
848 fn baseline_timer_fires_after_advance_clock() {
849 let cx = TestAppContext::single();
850 let executor = cx.executor();
851 let fired = Arc::new(AtomicBool::new(false));
852
853 executor
854 .spawn({
855 let executor = executor.clone();
856 let fired = fired.clone();
857 async move {
858 executor.timer(Duration::from_millis(10)).await;
859 fired.store(true, Ordering::SeqCst);
860 }
861 })
862 .detach();
863
864 executor.advance_clock(Duration::from_millis(10));
865 assert!(fired.load(Ordering::SeqCst));
866 }
867
868 #[test]
869 fn baseline_deprioritize_delays_labeled_task() {
870 let mut cx = TestAppContext::single();
871 let executor = cx.executor();
872 let label = TaskLabel::new();
873 let order = Arc::new(Mutex::new(Vec::new()));
874
875 executor.deprioritize(label);
876 executor
877 .spawn_labeled(label, {
878 let order = order.clone();
879 async move {
880 order.lock().unwrap().push("labeled");
881 }
882 })
883 .detach();
884 executor
885 .spawn({
886 let order = order.clone();
887 async move {
888 order.lock().unwrap().push("normal");
889 }
890 })
891 .detach();
892 cx.run_until_parked();
893
894 assert_eq!(&*order.lock().unwrap(), &["normal", "labeled"]);
895 }
896
897 #[test]
898 fn baseline_block_with_timeout_returns_bounded_pending_future() {
899 let cx = TestAppContext::single();
900 let executor = cx.executor();
901 executor.set_block_on_ticks(0..=0);
902
903 let result =
904 executor.block_with_timeout(Duration::from_millis(1), futures::future::pending::<()>());
905
906 assert!(result.is_err());
907 }
908
909 #[test]
910 fn baseline_headless_app_tasks_complete_without_window() {
911 let mut cx = TestAppContext::single();
912 let completed = Arc::new(AtomicBool::new(false));
913
914 cx.executor()
915 .spawn({
916 let completed = completed.clone();
917 async move {
918 completed.store(true, Ordering::SeqCst);
919 }
920 })
921 .detach();
922 cx.run_until_parked();
923
924 assert!(completed.load(Ordering::SeqCst));
925 assert!(cx.windows().is_empty());
926 }
927
928 #[test]
929 fn baseline_scoped_tasks_complete_before_scope_returns() {
930 let cx = TestAppContext::single();
931 let executor = cx.executor();
932 let completed = Arc::new(AtomicBool::new(false));
933
934 executor.block_test({
935 let executor = executor.clone();
936 let completed = completed.clone();
937 async move {
938 executor
939 .scoped(|scope| {
940 scope.spawn(async move {
941 completed.store(true, Ordering::SeqCst);
942 });
943 })
944 .await;
945 }
946 });
947
948 assert!(completed.load(Ordering::SeqCst));
949 }
950
951 #[test]
952 fn spawn_with_priority_defaults_to_medium_behavior() {
953 let mut cx = TestAppContext::single();
954 let completed = Arc::new(AtomicBool::new(false));
955
956 cx.executor()
957 .spawn_with_priority(TaskPriority::Medium, {
958 let completed = completed.clone();
959 async move {
960 completed.store(true, Ordering::SeqCst);
961 }
962 })
963 .detach();
964 cx.run_until_parked();
965
966 assert!(completed.load(Ordering::SeqCst));
967 }
968
969 #[test]
970 fn spawn_with_priority_high_is_observable_in_test_dispatcher() {
971 let mut cx = TestAppContext::single();
972 let executor = cx.executor();
973 let order = Arc::new(Mutex::new(Vec::new()));
974
975 executor
976 .spawn_with_priority(TaskPriority::Low, {
977 let order = order.clone();
978 async move {
979 order.lock().unwrap().push("low");
980 }
981 })
982 .detach();
983 executor
984 .spawn_with_priority(TaskPriority::High, {
985 let order = order.clone();
986 async move {
987 order.lock().unwrap().push("high");
988 }
989 })
990 .detach();
991 cx.run_until_parked();
992
993 assert_eq!(&*order.lock().unwrap(), &["high", "low"]);
994 }
995
996 #[test]
997 fn spawn_labeled_deprioritize_still_works_after_priority_addition() {
998 baseline_deprioritize_delays_labeled_task();
999 }
1000
1001 #[test]
1002 fn low_priority_task_eventually_runs() {
1003 let mut cx = TestAppContext::single();
1004 let completed = Arc::new(AtomicBool::new(false));
1005
1006 cx.executor()
1007 .spawn_with_priority(TaskPriority::Low, {
1008 let completed = completed.clone();
1009 async move {
1010 completed.store(true, Ordering::SeqCst);
1011 }
1012 })
1013 .detach();
1014 cx.run_until_parked();
1015
1016 assert!(completed.load(Ordering::SeqCst));
1017 }
1018}