1use crate::{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 #[track_caller]
230 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
231 where
232 R: Send + 'static,
233 {
234 self.spawn_internal::<R>(Box::pin(future), None)
235 }
236
237 #[track_caller]
240 pub fn spawn_labeled<R>(
241 &self,
242 label: TaskLabel,
243 future: impl Future<Output = R> + Send + 'static,
244 ) -> Task<R>
245 where
246 R: Send + 'static,
247 {
248 self.spawn_internal::<R>(Box::pin(future), Some(label))
249 }
250
251 #[track_caller]
252 #[doc(hidden)]
253 pub fn spawn_with_priority<R>(
254 &self,
255 priority: TaskPriority,
256 future: impl Future<Output = R> + Send + 'static,
257 ) -> Task<R>
258 where
259 R: Send + 'static,
260 {
261 self.spawn_internal::<R>(
262 Box::pin(future),
263 Some(TaskLabel::new_with_priority(priority)),
264 )
265 }
266
267 #[track_caller]
268 fn spawn_internal<R: Send + 'static>(
269 &self,
270 future: AnyFuture<R>,
271 label: Option<TaskLabel>,
272 ) -> Task<R> {
273 let dispatcher = self.dispatcher.clone();
274 let future = TimedFuture::new(future, Location::caller());
275 let (runnable, task) =
276 async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
277 runnable.schedule();
278 Task(TaskState::Spawned(task))
279 }
280
281 #[cfg(any(test, feature = "test-support"))]
283 #[track_caller]
284 pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
285 if let Ok(value) = self.block_internal(false, future, None) {
286 value
287 } else {
288 unreachable!()
289 }
290 }
291
292 pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
295 if let Ok(value) = self.block_internal(true, future, None) {
296 value
297 } else {
298 unreachable!()
299 }
300 }
301
302 #[cfg(not(any(test, feature = "test-support")))]
303 pub(crate) fn block_internal<Fut: Future>(
304 &self,
305 _background_only: bool,
306 future: Fut,
307 timeout: Option<Duration>,
308 ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
309 use std::time::Instant;
310
311 let mut future = Box::pin(future);
312 if timeout == Some(Duration::ZERO) {
313 return Err(future);
314 }
315 let deadline = timeout.map(|timeout| Instant::now() + timeout);
316
317 let unparker = self.dispatcher.unparker();
318 let waker = waker_fn(move || {
319 unparker.unpark();
320 });
321 let mut cx = std::task::Context::from_waker(&waker);
322
323 loop {
324 match future.as_mut().poll(&mut cx) {
325 Poll::Ready(result) => return Ok(result),
326 Poll::Pending => {
327 let timeout =
328 deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
329 if !self.dispatcher.park(timeout)
330 && deadline.is_some_and(|deadline| deadline < Instant::now())
331 {
332 return Err(future);
333 }
334 }
335 }
336 }
337 }
338
339 #[cfg(any(test, feature = "test-support"))]
340 #[track_caller]
341 pub(crate) fn block_internal<Fut: Future>(
342 &self,
343 background_only: bool,
344 future: Fut,
345 timeout: Option<Duration>,
346 ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
347 use std::sync::atomic::AtomicBool;
348
349 let mut future = Box::pin(future);
350 if timeout == Some(Duration::ZERO) {
351 return Err(future);
352 }
353 let Some(dispatcher) = self.dispatcher.as_test() else {
354 return Err(future);
355 };
356
357 let mut max_ticks = if timeout.is_some() {
358 dispatcher.gen_block_on_ticks()
359 } else {
360 usize::MAX
361 };
362 let unparker = self.dispatcher.unparker();
363 let awoken = Arc::new(AtomicBool::new(false));
364 let waker = waker_fn({
365 let awoken = awoken.clone();
366 move || {
367 awoken.store(true, SeqCst);
368 unparker.unpark();
369 }
370 });
371 let mut cx = std::task::Context::from_waker(&waker);
372
373 loop {
374 match future.as_mut().poll(&mut cx) {
375 Poll::Ready(result) => return Ok(result),
376 Poll::Pending => {
377 if max_ticks == 0 {
378 return Err(future);
379 }
380 max_ticks -= 1;
381
382 if !dispatcher.tick(background_only) {
383 if awoken.swap(false, SeqCst) {
384 continue;
385 }
386
387 if !dispatcher.parking_allowed() {
388 if dispatcher.advance_clock_to_next_delayed() {
389 continue;
390 }
391 let mut backtrace_message = String::new();
392 let mut waiting_message = String::new();
393 if let Some(backtrace) = dispatcher.waiting_backtrace() {
394 backtrace_message =
395 format!("\nbacktrace of waiting future:\n{:?}", backtrace);
396 }
397 if let Some(waiting_hint) = dispatcher.waiting_hint() {
398 waiting_message = format!("\n waiting on: {}\n", waiting_hint);
399 }
400 panic!(
401 "parked with nothing left to run{waiting_message}{backtrace_message}",
402 )
403 }
404 self.dispatcher.park(None);
405 }
406 }
407 }
408 }
409 }
410
411 pub fn block_with_timeout<Fut: Future>(
414 &self,
415 duration: Duration,
416 future: Fut,
417 ) -> Result<Fut::Output, impl Future<Output = Fut::Output> + use<Fut>> {
418 self.block_internal(true, future, Some(duration))
419 }
420
421 pub async fn scoped<'scope, F>(&self, scheduler: F)
424 where
425 F: FnOnce(&mut Scope<'scope>),
426 {
427 let mut scope = Scope::new(self.clone());
428 (scheduler)(&mut scope);
429 let spawned = mem::take(&mut scope.futures)
430 .into_iter()
431 .map(|f| self.spawn(f))
432 .collect::<Vec<_>>();
433 for task in spawned {
434 task.await;
435 }
436 }
437
438 pub fn now(&self) -> Instant {
443 self.dispatcher.now()
444 }
445
446 #[track_caller]
450 pub fn timer(&self, duration: Duration) -> Task<()> {
451 if duration.is_zero() {
452 return Task::ready(());
453 }
454 let future = TimedFuture::new(Box::pin(async move {}), Location::caller());
455 let (runnable, task) = async_task::spawn(future, {
456 let dispatcher = self.dispatcher.clone();
457 move |runnable| dispatcher.dispatch_after(duration, runnable)
458 });
459 runnable.schedule();
460 Task(TaskState::Spawned(task))
461 }
462
463 #[cfg(any(test, feature = "test-support"))]
465 pub fn start_waiting(&self) {
466 self.dispatcher.as_test().unwrap().start_waiting();
467 }
468
469 #[cfg(any(test, feature = "test-support"))]
471 pub fn finish_waiting(&self) {
472 self.dispatcher.as_test().unwrap().finish_waiting();
473 }
474
475 #[cfg(any(test, feature = "test-support"))]
477 pub fn simulate_random_delay(&self) -> impl Future<Output = ()> + use<> {
478 self.dispatcher.as_test().unwrap().simulate_random_delay()
479 }
480
481 #[cfg(any(test, feature = "test-support"))]
483 pub fn deprioritize(&self, task_label: TaskLabel) {
484 self.dispatcher.as_test().unwrap().deprioritize(task_label)
485 }
486
487 #[cfg(any(test, feature = "test-support"))]
489 pub fn advance_clock(&self, duration: Duration) {
490 self.dispatcher.as_test().unwrap().advance_clock(duration)
491 }
492
493 #[cfg(any(test, feature = "test-support"))]
495 pub fn tick(&self) -> bool {
496 self.dispatcher.as_test().unwrap().tick(false)
497 }
498
499 #[cfg(any(test, feature = "test-support"))]
502 pub fn run_until_parked(&self) {
503 self.dispatcher.as_test().unwrap().run_until_parked()
504 }
505
506 #[cfg(any(test, feature = "test-support"))]
510 pub fn allow_parking(&self) {
511 self.dispatcher.as_test().unwrap().allow_parking();
512 }
513
514 #[cfg(any(test, feature = "test-support"))]
516 pub fn forbid_parking(&self) {
517 self.dispatcher.as_test().unwrap().forbid_parking();
518 }
519
520 #[cfg(any(test, feature = "test-support"))]
522 pub fn set_waiting_hint(&self, msg: Option<String>) {
523 self.dispatcher.as_test().unwrap().set_waiting_hint(msg);
524 }
525
526 #[cfg(any(test, feature = "test-support"))]
528 pub fn rng(&self) -> StdRng {
529 self.dispatcher.as_test().unwrap().rng()
530 }
531
532 pub fn num_cpus(&self) -> usize {
534 #[cfg(any(test, feature = "test-support"))]
535 return 4;
536
537 #[cfg(not(any(test, feature = "test-support")))]
538 return num_cpus::get();
539 }
540
541 pub fn is_main_thread(&self) -> bool {
543 self.dispatcher.is_main_thread()
544 }
545
546 #[cfg(any(test, feature = "test-support"))]
547 pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
549 self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
550 }
551}
552
553impl ForegroundExecutor {
555 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
557 Self {
558 dispatcher,
559 not_send: PhantomData,
560 }
561 }
562
563 #[track_caller]
565 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
566 where
567 R: 'static,
568 {
569 let dispatcher = self.dispatcher.clone();
570
571 #[track_caller]
572 fn inner<R: 'static>(
573 dispatcher: Arc<dyn PlatformDispatcher>,
574 future: AnyLocalFuture<R>,
575 ) -> Task<R> {
576 let future = TimedFuture::new(future, Location::caller());
577 let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
578 dispatcher.dispatch_on_main_thread(runnable)
579 });
580 runnable.schedule();
581 Task(TaskState::Spawned(task))
582 }
583 inner::<R>(dispatcher, Box::pin(future))
584 }
585}
586
587#[track_caller]
592fn spawn_local_with_source_location<Fut, S>(
593 future: Fut,
594 schedule: S,
595) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
596where
597 Fut: Future + 'static,
598 Fut::Output: 'static,
599 S: async_task::Schedule<()> + Send + Sync + 'static,
600{
601 #[inline]
602 fn thread_id() -> ThreadId {
603 std::thread_local! {
604 static ID: ThreadId = thread::current().id();
605 }
606 ID.try_with(|id| *id)
607 .unwrap_or_else(|_| thread::current().id())
608 }
609
610 struct Checked<F> {
611 id: ThreadId,
612 inner: ManuallyDrop<F>,
613 location: &'static Location<'static>,
614 }
615
616 impl<F> Drop for Checked<F> {
617 fn drop(&mut self) {
618 assert!(
619 self.id == thread_id(),
620 "local task dropped by a thread that didn't spawn it. Task spawned at {}",
621 self.location
622 );
623 unsafe {
624 ManuallyDrop::drop(&mut self.inner);
625 }
626 }
627 }
628
629 impl<F: Future> Future for Checked<F> {
630 type Output = F::Output;
631
632 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
633 assert!(
634 self.id == thread_id(),
635 "local task polled by a thread that didn't spawn it. Task spawned at {}",
636 self.location
637 );
638 unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
639 }
640 }
641
642 let future = Checked {
644 id: thread_id(),
645 inner: ManuallyDrop::new(future),
646 location: Location::caller(),
647 };
648
649 unsafe { async_task::spawn_unchecked(future, schedule) }
650}
651
652pub struct Scope<'a> {
654 executor: BackgroundExecutor,
655 futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
656 tx: Option<mpsc::Sender<()>>,
657 rx: mpsc::Receiver<()>,
658 lifetime: PhantomData<&'a ()>,
659}
660
661impl<'a> Scope<'a> {
662 fn new(executor: BackgroundExecutor) -> Self {
663 let (tx, rx) = mpsc::channel(1);
664 Self {
665 executor,
666 tx: Some(tx),
667 rx,
668 futures: Default::default(),
669 lifetime: PhantomData,
670 }
671 }
672
673 pub fn num_cpus(&self) -> usize {
675 self.executor.num_cpus()
676 }
677
678 pub fn spawn<F>(&mut self, f: F)
680 where
681 F: Future<Output = ()> + Send + 'a,
682 {
683 let tx = self.tx.clone().unwrap();
684
685 let f = unsafe {
688 mem::transmute::<
689 Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
690 Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
691 >(Box::pin(async move {
692 f.await;
693 drop(tx);
694 }))
695 };
696 self.futures.push(f);
697 }
698}
699
700impl Drop for Scope<'_> {
701 fn drop(&mut self) {
702 self.tx.take().unwrap();
703
704 self.executor.block(self.rx.next());
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use crate::{TestAppContext, profiler};
714 use std::sync::{
715 Arc, Mutex,
716 atomic::{AtomicBool, Ordering},
717 };
718
719 fn reset_profiler() {
720 profiler::set_enabled(false);
721 }
722
723 fn collect_timings() -> Vec<profiler::ThreadTimingsDelta> {
724 let mut collector = profiler::ProfilingCollector::new(Instant::now());
725 profiler::profiler_collect_timings(&mut collector)
726 }
727
728 #[test]
729 fn profiler_disabled_records_nothing() {
730 let _guard = profiler::test_guard();
731 reset_profiler();
732
733 let mut cx = TestAppContext::single();
734 cx.executor().spawn(async {}).detach();
735 cx.run_until_parked();
736
737 assert!(collect_timings().is_empty());
738 reset_profiler();
739 }
740
741 #[test]
742 fn profiler_enabled_records_background_task_timing() {
743 let _guard = profiler::test_guard();
744 reset_profiler();
745 profiler::set_enabled(true);
746
747 let mut cx = TestAppContext::single();
748 let mut collector = profiler::ProfilingCollector::new(Instant::now());
749 cx.executor().spawn(async {}).detach();
750 cx.run_until_parked();
751
752 let deltas = profiler::profiler_collect_timings(&mut collector);
753 let timing = deltas
754 .iter()
755 .flat_map(|delta| &delta.new_timings)
756 .next()
757 .expect("expected a recorded background task timing");
758 assert_eq!(timing.status, profiler::TaskTimingStatus::Completed);
759
760 reset_profiler();
761 }
762
763 #[test]
764 fn profiler_enabled_records_foreground_task_location() {
765 let _guard = profiler::test_guard();
766 reset_profiler();
767 profiler::set_enabled(true);
768
769 let mut cx = TestAppContext::single();
770 let mut collector = profiler::ProfilingCollector::new(Instant::now());
771 cx.foreground_executor().spawn(async {}).detach();
772 cx.run_until_parked();
773
774 let deltas = profiler::profiler_collect_timings(&mut collector);
775 let timing = deltas
776 .iter()
777 .flat_map(|delta| &delta.new_timings)
778 .find(|timing| timing.location.file.as_ref().contains("executor.rs"))
779 .expect("expected foreground task timing to include its source location");
780 assert!(timing.location.line > 0);
781 assert!(timing.location.column > 0);
782
783 reset_profiler();
784 }
785
786 #[test]
787 fn profiler_timing_start_is_first_poll_not_spawn() {
788 let _guard = profiler::test_guard();
789 reset_profiler();
790 profiler::set_enabled(true);
791
792 let mut cx = TestAppContext::single();
793 let mut collector = profiler::ProfilingCollector::new(Instant::now());
794 cx.executor().spawn(async {}).detach();
795 assert!(profiler::profiler_collect_timings(&mut collector).is_empty());
796
797 cx.run_until_parked();
798 assert!(!profiler::profiler_collect_timings(&mut collector).is_empty());
799
800 reset_profiler();
801 }
802
803 #[test]
804 fn baseline_foreground_task_runs_on_main_thread() {
805 let mut cx = TestAppContext::single();
806 let executor = cx.executor();
807 let ran_on_main_thread = Arc::new(AtomicBool::new(false));
808
809 cx.foreground_executor()
810 .spawn({
811 let ran_on_main_thread = ran_on_main_thread.clone();
812 async move {
813 ran_on_main_thread.store(executor.is_main_thread(), Ordering::SeqCst);
814 }
815 })
816 .detach();
817 cx.run_until_parked();
818
819 assert!(ran_on_main_thread.load(Ordering::SeqCst));
820 }
821
822 #[test]
823 fn baseline_background_task_completes_after_run_until_parked() {
824 let mut cx = TestAppContext::single();
825 let completed = Arc::new(AtomicBool::new(false));
826
827 cx.executor()
828 .spawn({
829 let completed = completed.clone();
830 async move {
831 completed.store(true, Ordering::SeqCst);
832 }
833 })
834 .detach();
835 cx.run_until_parked();
836
837 assert!(completed.load(Ordering::SeqCst));
838 }
839
840 #[test]
841 fn baseline_timer_fires_after_advance_clock() {
842 let cx = TestAppContext::single();
843 let executor = cx.executor();
844 let fired = Arc::new(AtomicBool::new(false));
845
846 executor
847 .spawn({
848 let executor = executor.clone();
849 let fired = fired.clone();
850 async move {
851 executor.timer(Duration::from_millis(10)).await;
852 fired.store(true, Ordering::SeqCst);
853 }
854 })
855 .detach();
856
857 executor.advance_clock(Duration::from_millis(10));
858 assert!(fired.load(Ordering::SeqCst));
859 }
860
861 #[test]
862 fn baseline_deprioritize_delays_labeled_task() {
863 let mut cx = TestAppContext::single();
864 let executor = cx.executor();
865 let label = TaskLabel::new();
866 let order = Arc::new(Mutex::new(Vec::new()));
867
868 executor.deprioritize(label);
869 executor
870 .spawn_labeled(label, {
871 let order = order.clone();
872 async move {
873 order.lock().unwrap().push("labeled");
874 }
875 })
876 .detach();
877 executor
878 .spawn({
879 let order = order.clone();
880 async move {
881 order.lock().unwrap().push("normal");
882 }
883 })
884 .detach();
885 cx.run_until_parked();
886
887 assert_eq!(&*order.lock().unwrap(), &["normal", "labeled"]);
888 }
889
890 #[test]
891 fn baseline_block_with_timeout_returns_bounded_pending_future() {
892 let cx = TestAppContext::single();
893 let executor = cx.executor();
894 executor.set_block_on_ticks(0..=0);
895
896 let result =
897 executor.block_with_timeout(Duration::from_millis(1), futures::future::pending::<()>());
898
899 assert!(result.is_err());
900 }
901
902 #[test]
903 fn baseline_headless_app_tasks_complete_without_window() {
904 let mut cx = TestAppContext::single();
905 let completed = Arc::new(AtomicBool::new(false));
906
907 cx.executor()
908 .spawn({
909 let completed = completed.clone();
910 async move {
911 completed.store(true, Ordering::SeqCst);
912 }
913 })
914 .detach();
915 cx.run_until_parked();
916
917 assert!(completed.load(Ordering::SeqCst));
918 assert!(cx.windows().is_empty());
919 }
920
921 #[test]
922 fn baseline_scoped_tasks_complete_before_scope_returns() {
923 let cx = TestAppContext::single();
924 let executor = cx.executor();
925 let completed = Arc::new(AtomicBool::new(false));
926
927 executor.block_test({
928 let executor = executor.clone();
929 let completed = completed.clone();
930 async move {
931 executor
932 .scoped(|scope| {
933 scope.spawn(async move {
934 completed.store(true, Ordering::SeqCst);
935 });
936 })
937 .await;
938 }
939 });
940
941 assert!(completed.load(Ordering::SeqCst));
942 }
943
944 #[test]
945 fn spawn_with_priority_defaults_to_medium_behavior() {
946 let mut cx = TestAppContext::single();
947 let completed = Arc::new(AtomicBool::new(false));
948
949 cx.executor()
950 .spawn_with_priority(TaskPriority::Medium, {
951 let completed = completed.clone();
952 async move {
953 completed.store(true, Ordering::SeqCst);
954 }
955 })
956 .detach();
957 cx.run_until_parked();
958
959 assert!(completed.load(Ordering::SeqCst));
960 }
961
962 #[test]
963 fn spawn_with_priority_high_is_observable_in_test_dispatcher() {
964 let mut cx = TestAppContext::single();
965 let executor = cx.executor();
966 let order = Arc::new(Mutex::new(Vec::new()));
967
968 executor
969 .spawn_with_priority(TaskPriority::Low, {
970 let order = order.clone();
971 async move {
972 order.lock().unwrap().push("low");
973 }
974 })
975 .detach();
976 executor
977 .spawn_with_priority(TaskPriority::High, {
978 let order = order.clone();
979 async move {
980 order.lock().unwrap().push("high");
981 }
982 })
983 .detach();
984 cx.run_until_parked();
985
986 assert_eq!(&*order.lock().unwrap(), &["high", "low"]);
987 }
988
989 #[test]
990 fn spawn_labeled_deprioritize_still_works_after_priority_addition() {
991 baseline_deprioritize_delays_labeled_task();
992 }
993
994 #[test]
995 fn low_priority_task_eventually_runs() {
996 let mut cx = TestAppContext::single();
997 let completed = Arc::new(AtomicBool::new(false));
998
999 cx.executor()
1000 .spawn_with_priority(TaskPriority::Low, {
1001 let completed = completed.clone();
1002 async move {
1003 completed.store(true, Ordering::SeqCst);
1004 }
1005 })
1006 .detach();
1007 cx.run_until_parked();
1008
1009 assert!(completed.load(Ordering::SeqCst));
1010 }
1011}