1use std::future::Future;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11
12pub fn spawn<F>(task: F)
14where
15 F: Future<Output = ()> + Send + 'static,
16{
17 #[cfg(feature = "test-utils")]
18 let task = match seeded::spawn_here(Box::pin(task)) {
19 Ok(()) => return,
20 Err(task) => task,
21 };
22
23 tokio::spawn(task);
24}
25
26pub fn yield_now() -> impl Future<Output = ()> {
32 YieldNow { yielded: false }
33}
34
35pub fn random_delay() -> impl Future<Output = ()> {
42 RandomDelay { left: None }
43}
44
45#[cfg(feature = "test-utils")]
46const LONGEST_DELAY: usize = 10;
47
48struct RandomDelay {
49 left: Option<usize>,
50}
51
52impl Future for RandomDelay {
53 type Output = ();
54
55 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
56 let left = *self.left.get_or_insert_with(draw_delay);
57 if left == 0 {
58 return Poll::Ready(());
59 }
60
61 self.left = Some(left - 1);
62 cx.waker().wake_by_ref();
63 Poll::Pending
64 }
65}
66
67fn draw_delay() -> usize {
68 #[cfg(feature = "test-utils")]
69 if let Some(turns) = seeded::draw(LONGEST_DELAY + 1) {
70 return turns;
71 }
72
73 1
74}
75
76struct YieldNow {
77 yielded: bool,
78}
79
80impl Future for YieldNow {
81 type Output = ();
82
83 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
84 if self.yielded {
85 return Poll::Ready(());
86 }
87
88 self.yielded = true;
89 cx.waker().wake_by_ref();
90 Poll::Pending
91 }
92}
93
94#[cfg(feature = "test-utils")]
95pub use seeded::{Installed, Seeded, install};
96
97#[cfg(feature = "test-utils")]
98pub(crate) use seeded::queue_ui;
99
100#[cfg(feature = "test-utils")]
101mod seeded {
102 use std::cell::{Cell, RefCell};
103 use std::collections::{HashMap, VecDeque};
104 use std::future::Future;
105 use std::pin::Pin;
106 use std::rc::Rc;
107 use std::sync::{Arc, Mutex, MutexGuard};
108 use std::task::{Context, Poll, Wake, Waker};
109 use std::time::Duration;
110
111 use tokio::runtime::Runtime;
112 use tokio::time::{Instant, Sleep};
113
114 type Task = Pin<Box<dyn Future<Output = ()> + Send>>;
115 type Job = Box<dyn FnOnce() + Send>;
116
117 enum Work {
118 Poll(u64),
119 Ui(Job),
120 }
121
122 fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
123 mutex.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
124 }
125
126 #[derive(Default)]
135 struct Ready {
136 polls: Mutex<Vec<u64>>,
137 ui: Mutex<VecDeque<Job>>,
138 waiting: Mutex<Option<Waker>>,
141 }
142
143 impl Ready {
144 fn push(&self, work: Work) {
145 match work {
146 Work::Poll(id) => lock(&self.polls).push(id),
147 Work::Ui(job) => lock(&self.ui).push_back(job),
148 }
149
150 if let Some(waiting) = lock(&self.waiting).take() {
151 waiting.wake();
152 }
153 }
154
155 fn is_empty(&self) -> bool {
156 lock(&self.polls).is_empty() && lock(&self.ui).is_empty()
157 }
158 }
159
160 struct Until<'a> {
163 until: Pin<Box<Sleep>>,
164 ready: &'a Ready,
165 }
166
167 impl Future for Until<'_> {
168 type Output = bool;
169
170 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<bool> {
171 *lock(&self.ready.waiting) = Some(cx.waker().clone());
172
173 if !self.ready.is_empty() {
174 return Poll::Ready(false);
175 }
176
177 self.until.as_mut().poll(cx).map(|()| true)
178 }
179 }
180
181 struct Waking {
182 task: u64,
183 ready: Arc<Ready>,
184 }
185
186 impl Wake for Waking {
187 fn wake(self: Arc<Self>) {
188 self.wake_by_ref();
189 }
190
191 fn wake_by_ref(self: &Arc<Self>) {
192 self.ready.push(Work::Poll(self.task));
193 }
194 }
195
196 thread_local! {
197 static CURRENT: RefCell<Option<Rc<Seeded>>> = const { RefCell::new(None) };
198 }
199
200 pub struct Seeded {
211 seed: u64,
212 state: Cell<u64>,
213 ready: Arc<Ready>,
214 tasks: RefCell<HashMap<u64, Task>>,
215 next: Cell<u64>,
216 running: Cell<bool>,
217 clock: Runtime,
218 }
219
220 pub fn install(seed: u64) -> Installed {
222 let clock = tokio::runtime::Builder::new_current_thread()
223 .enable_all()
224 .start_paused(true)
225 .build()
226 .expect("a paused runtime to keep the test's time");
227
228 let executor = Rc::new(Seeded {
229 seed,
230 state: Cell::new(seed),
231 ready: Arc::default(),
232 tasks: RefCell::default(),
233 next: Cell::new(1),
234 running: Cell::new(false),
235 clock,
236 });
237
238 CURRENT.with(|current| *current.borrow_mut() = Some(executor.clone()));
239 Installed(executor)
240 }
241
242 pub struct Installed(Rc<Seeded>);
244
245 impl Installed {
246 pub fn seed(&self) -> u64 {
247 self.0.seed
248 }
249
250 pub fn run_until_parked(&self) -> usize {
253 self.0.run_until_parked()
254 }
255
256 pub fn step(&self) -> bool {
259 self.0.step()
260 }
261
262 pub fn advance(&self, by: Duration) -> usize {
266 self.0.advance(by)
267 }
268
269 pub fn wake_next(&self, horizon: Duration) -> bool {
273 self.0.wake_next(horizon)
274 }
275
276 pub fn stuck(&self) -> usize {
279 self.0.tasks.borrow().len()
280 }
281 }
282
283 impl Drop for Installed {
284 fn drop(&mut self) {
285 CURRENT.with(|current| {
286 let mut current = current.borrow_mut();
287 if current.as_ref().is_some_and(|installed| Rc::ptr_eq(installed, &self.0)) {
288 *current = None;
289 }
290 });
291 }
292 }
293
294 impl Seeded {
295 fn spawn(&self, task: Task) {
296 let id = self.next.get();
297 self.next.set(id + 1);
298
299 self.tasks.borrow_mut().insert(id, task);
300 self.ready.push(Work::Poll(id));
301 }
302
303 fn below(&self, bound: usize) -> usize {
306 let mut z = self.state.get().wrapping_add(0x9E37_79B9_7F4A_7C15);
307 self.state.set(z);
308
309 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
310 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
311 z ^= z >> 31;
312
313 (z % bound as u64) as usize
314 }
315
316 fn take(&self) -> Option<Work> {
319 let mut polls = lock(&self.ready.polls);
320 let mut ui = lock(&self.ready.ui);
321
322 let choices = polls.len() + usize::from(!ui.is_empty());
323 if choices == 0 {
324 return None;
325 }
326
327 let at = self.below(choices);
328 if at == polls.len() {
329 ui.pop_front().map(Work::Ui)
330 } else {
331 Some(Work::Poll(polls.swap_remove(at)))
332 }
333 }
334
335 fn step(&self) -> bool {
336 assert!(
337 !self.running.replace(true),
338 "the executor was asked to run work from inside work it is running"
339 );
340
341 let _clock = self.clock.enter();
342
343 let ran = match self.take() {
344 Some(Work::Ui(job)) => {
345 job();
346 true
347 }
348 Some(Work::Poll(id)) => {
349 self.poll(id);
350 true
351 }
352 None => false,
353 };
354
355 self.running.set(false);
356 ran
357 }
358
359 fn run_until_parked(&self) -> usize {
360 let mut ran = 0;
361 while self.step() {
362 ran += 1;
363 }
364 ran
365 }
366
367 fn now(&self) -> Instant {
368 self.clock.block_on(async { Instant::now() })
369 }
370
371 fn run_clock_until(&self, target: Instant) -> bool {
374 self.clock.block_on(async {
375 Until {
376 until: Box::pin(tokio::time::sleep_until(target)),
377 ready: &self.ready,
378 }
379 .await
380 })
381 }
382
383 fn advance(&self, by: Duration) -> usize {
384 let target = self.now() + by;
385 let mut ran = self.run_until_parked();
386
387 loop {
388 let reached = self.run_clock_until(target);
389 ran += self.run_until_parked();
390
391 if reached {
392 return ran;
393 }
394 }
395 }
396
397 fn wake_next(&self, horizon: Duration) -> bool {
398 let target = self.now() + horizon;
399 !self.run_clock_until(target)
400 }
401
402 fn poll(&self, id: u64) {
403 let Some(mut task) = self.tasks.borrow_mut().remove(&id) else {
404 return;
405 };
406
407 let waker = Waker::from(Arc::new(Waking {
408 task: id,
409 ready: self.ready.clone(),
410 }));
411
412 if task.as_mut().poll(&mut Context::from_waker(&waker)).is_pending() {
413 self.tasks.borrow_mut().insert(id, task);
414 }
415 }
416 }
417
418 fn current() -> Option<Rc<Seeded>> {
419 CURRENT.with(|current| current.borrow().clone())
420 }
421
422 pub(super) fn spawn_here(task: Task) -> Result<(), Task> {
424 match current() {
425 Some(executor) => {
426 executor.spawn(task);
427 Ok(())
428 }
429 None => Err(task),
430 }
431 }
432
433 pub(super) fn draw(bound: usize) -> Option<usize> {
436 current().map(|executor| executor.below(bound))
437 }
438
439 pub(crate) fn queue_ui<F>(job: F) -> Result<(), F>
442 where
443 F: FnOnce() + Send + 'static,
444 {
445 match current() {
446 Some(executor) => {
447 executor.ready.push(Work::Ui(Box::new(job)));
448 Ok(())
449 }
450 None => Err(job),
451 }
452 }
453
454 #[cfg(test)]
455 mod tests {
456 use super::*;
457 use std::sync::atomic::{AtomicUsize, Ordering};
458 use std::time::Duration;
459
460 fn order(seed: u64) -> Vec<&'static str> {
461 let executor = install(seed);
462 let seen = Arc::new(Mutex::new(Vec::new()));
463
464 for name in ["a", "b", "c"] {
465 let seen = seen.clone();
466 crate::executor::spawn(async move {
467 crate::executor::yield_now().await;
468 seen.lock().unwrap().push(name);
469 });
470 }
471
472 executor.run_until_parked();
473 let seen = seen.lock().unwrap().clone();
474 seen
475 }
476
477 #[test]
478 fn one_seed_is_one_order() {
479 for seed in 0..16 {
480 assert_eq!(order(seed), order(seed), "seed {seed}");
481 }
482 }
483
484 #[test]
485 fn the_seeds_between_them_reach_other_orders() {
486 let orders: std::collections::HashSet<Vec<&str>> = (0..64).map(order).collect();
487 assert!(orders.len() > 1, "every seed ran the tasks in one order: {orders:?}");
488 }
489
490 #[test]
491 fn work_for_the_ui_thread_runs_on_this_one() {
492 let executor = install(7);
493 let ran = Arc::new(AtomicUsize::new(0));
494
495 let counted = ran.clone();
496 crate::actor::invoke_on_ui(move || {
497 counted.fetch_add(1, Ordering::SeqCst);
498 });
499
500 executor.run_until_parked();
501 assert_eq!(ran.load(Ordering::SeqCst), 1);
502 }
503
504 #[test]
509 fn posts_to_the_ui_thread_keep_their_order_in_every_seed() {
510 let mut interleavings = std::collections::HashSet::new();
511
512 for seed in 0..64 {
513 let executor = install(seed);
514 let seen = Arc::new(Mutex::new(Vec::new()));
515
516 for post in ["first", "second", "third"] {
517 let seen = seen.clone();
518 crate::actor::invoke_on_ui(move || seen.lock().unwrap().push(post));
519 }
520
521 let beside = seen.clone();
522 crate::executor::spawn(async move { beside.lock().unwrap().push("task") });
523
524 executor.run_until_parked();
525 let seen = seen.lock().unwrap().clone();
526
527 let posts: Vec<&str> = seen.iter().copied().filter(|name| *name != "task").collect();
528 assert_eq!(posts, ["first", "second", "third"], "seed {seed}");
529 interleavings.insert(seen);
530 }
531
532 assert!(interleavings.len() > 1, "the task always ran in one place: {interleavings:?}");
533 }
534
535 #[test]
536 fn a_sleeping_task_wakes_when_the_clock_reaches_it_and_not_before() {
537 let started = std::time::Instant::now();
538 let executor = install(3);
539 let woke = Arc::new(AtomicUsize::new(0));
540
541 let marked = woke.clone();
542 crate::executor::spawn(async move {
543 tokio::time::sleep(Duration::from_millis(800)).await;
544 marked.fetch_add(1, Ordering::SeqCst);
545 });
546
547 executor.run_until_parked();
548 assert_eq!(woke.load(Ordering::SeqCst), 0, "time moved without being asked");
549
550 executor.advance(Duration::from_millis(799));
551 assert_eq!(woke.load(Ordering::SeqCst), 0, "woke a millisecond early");
552
553 executor.advance(Duration::from_millis(1));
554 assert_eq!(woke.load(Ordering::SeqCst), 1);
555 assert!(started.elapsed() < Duration::from_millis(200), "waited in real time");
556 }
557
558 #[test]
559 fn a_timer_that_never_ends_still_lets_advance_return() {
560 let executor = install(5);
561 let ticks = Arc::new(AtomicUsize::new(0));
562
563 let counted = ticks.clone();
564 crate::executor::spawn(async move {
565 let mut every = tokio::time::interval(Duration::from_millis(100));
566 loop {
567 every.tick().await;
568 counted.fetch_add(1, Ordering::SeqCst);
569 }
570 });
571
572 executor.advance(Duration::from_secs(1));
573 assert_eq!(ticks.load(Ordering::SeqCst), 11, "the first tick and one per 100 ms");
574 assert_eq!(executor.stuck(), 1);
575 }
576
577 #[test]
578 fn a_task_waiting_on_nothing_is_counted_as_stuck() {
579 let executor = install(1);
580 crate::executor::spawn(std::future::pending());
581
582 executor.run_until_parked();
583 assert_eq!(executor.stuck(), 1);
584 }
585 }
586}