Skip to main content

scheduler/
test_scheduler.rs

1use crate::{
2    BackgroundExecutor, Clock, Instant, LocalExecutor, Priority, RunnableMeta, Scheduler,
3    SessionId, Task, TestClock, Timer,
4};
5use async_task::Runnable;
6use backtrace::{Backtrace, BacktraceFrame};
7use futures::channel::oneshot;
8use parking_lot::{Mutex, MutexGuard};
9use rand::{
10    distr::{StandardUniform, uniform::SampleRange, uniform::SampleUniform},
11    prelude::*,
12};
13use std::any::Any;
14use std::{
15    any::type_name_of_val,
16    collections::{BTreeMap, HashSet, VecDeque},
17    env,
18    fmt::Write,
19    future::Future,
20    mem,
21    ops::RangeInclusive,
22    pin::Pin,
23    sync::{
24        Arc, Weak,
25        atomic::{AtomicBool, Ordering::SeqCst},
26    },
27    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
28    thread::{self, Thread},
29    time::Duration,
30};
31
32const PENDING_TRACES_VAR_NAME: &str = "PENDING_TRACES";
33
34pub struct TestScheduler {
35    clock: Arc<TestClock>,
36    rng: Arc<Mutex<StdRng>>,
37    state: Arc<Mutex<SchedulerState>>,
38    thread: Thread,
39}
40
41impl TestScheduler {
42    /// Run a test once with default configuration (seed 0)
43    pub fn once<R>(f: impl AsyncFnOnce(Arc<TestScheduler>) -> R) -> R {
44        Self::with_seed(0, f)
45    }
46
47    /// Run a test multiple times with sequential seeds (0, 1, 2, ...)
48    pub fn many<R>(
49        default_iterations: usize,
50        mut f: impl AsyncFnMut(Arc<TestScheduler>) -> R,
51    ) -> Vec<R> {
52        let num_iterations = std::env::var("ITERATIONS")
53            .map(|iterations| iterations.parse().unwrap())
54            .unwrap_or(default_iterations);
55
56        let seed = std::env::var("SEED")
57            .map(|seed| seed.parse().unwrap())
58            .unwrap_or(0);
59
60        let interactive = !std::env::var("SCHEDULER_NONINTERACTIVE").is_ok();
61
62        (seed..seed + num_iterations as u64)
63            .map(|seed| {
64                let mut unwind_safe_f = std::panic::AssertUnwindSafe(&mut f);
65                if interactive {
66                    eprintln!("Running seed: {seed}");
67                }
68                match std::panic::catch_unwind(move || Self::with_seed(seed, &mut *unwind_safe_f)) {
69                    Ok(result) => result,
70                    Err(error) => {
71                        eprintln!("\x1b[31mFailing Seed: {seed}\x1b[0m");
72                        std::panic::resume_unwind(error);
73                    }
74                }
75            })
76            .collect()
77    }
78
79    fn with_seed<R>(seed: u64, f: impl AsyncFnOnce(Arc<TestScheduler>) -> R) -> R {
80        let scheduler = Arc::new(TestScheduler::new(TestSchedulerConfig::with_seed(seed)));
81        let output = std::cell::Cell::new(None);
82        let future = f(scheduler.clone());
83        let future = async {
84            output.set(Some(future.await));
85        };
86        let mut future = std::pin::pin!(future);
87        scheduler.block_until(None, future.as_mut(), None);
88        let result = output.take().expect("test future did not complete");
89        scheduler.run(); // Ensure spawned tasks finish up before returning in tests
90        result
91    }
92
93    fn block_until(
94        &self,
95        session_id: Option<SessionId>,
96        mut future: Pin<&mut dyn Future<Output = ()>>,
97        timeout: Option<Duration>,
98    ) -> bool {
99        if let Some(session_id) = session_id {
100            self.state.lock().blocked_sessions.push(session_id);
101        }
102
103        let deadline = timeout.map(|timeout| Instant::now() + timeout);
104        let awoken = Arc::new(AtomicBool::new(false));
105        let waker = Box::new(TracingWaker {
106            id: None,
107            awoken: awoken.clone(),
108            thread: self.thread.clone(),
109            state: self.state.clone(),
110        });
111        let waker = unsafe { Waker::new(Box::into_raw(waker) as *const (), &WAKER_VTABLE) };
112        let max_ticks = if timeout.is_some() {
113            self.rng
114                .lock()
115                .random_range(self.state.lock().timeout_ticks.clone())
116        } else {
117            usize::MAX
118        };
119        let mut cx = Context::from_waker(&waker);
120
121        let mut completed = false;
122        for _ in 0..max_ticks {
123            match future.as_mut().poll(&mut cx) {
124                Poll::Ready(()) => {
125                    completed = true;
126                    break;
127                }
128                Poll::Pending => {}
129            }
130
131            let mut stepped = None;
132            while self.rng.lock().random() {
133                let stepped = stepped.get_or_insert(false);
134                if self.step() {
135                    *stepped = true;
136                } else {
137                    break;
138                }
139            }
140
141            let stepped = stepped.unwrap_or(true);
142            let awoken = awoken.swap(false, SeqCst);
143            if !stepped && !awoken {
144                let parking_allowed = self.state.lock().allow_parking;
145                // In deterministic mode (parking forbidden), instantly jump to the next timer.
146                // In non-deterministic mode (parking allowed), let real time pass instead.
147                let advanced_to_timer = !parking_allowed && self.advance_clock_to_next_timer();
148                if !advanced_to_timer && !self.park(deadline) {
149                    break;
150                }
151            }
152        }
153
154        if session_id.is_some() {
155            self.state.lock().blocked_sessions.pop();
156        }
157
158        completed
159    }
160
161    pub fn new(config: TestSchedulerConfig) -> Self {
162        crate::TEST_SCHEDULER_CREATED.store(true, std::sync::atomic::Ordering::Relaxed);
163        Self {
164            rng: Arc::new(Mutex::new(StdRng::seed_from_u64(config.seed))),
165            state: Arc::new(Mutex::new(SchedulerState {
166                runnables: VecDeque::new(),
167                timers: Vec::new(),
168                blocked_sessions: Vec::new(),
169                randomize_order: config.randomize_order,
170                allow_parking: config.allow_parking,
171                timeout_ticks: config.timeout_ticks,
172                next_session_id: SessionId(0),
173                capture_pending_traces: config.capture_pending_traces,
174                pending_traces: BTreeMap::new(),
175                next_trace_id: TraceId(0),
176                is_main_thread: true,
177                non_determinism_error: None,
178                finished: false,
179                parking_allowed_once: false,
180                unparked: false,
181            })),
182            clock: Arc::new(TestClock::new()),
183            thread: thread::current(),
184        }
185    }
186
187    pub fn end_test(&self) {
188        let mut state = self.state.lock();
189        if let Some((message, backtrace)) = &state.non_determinism_error {
190            if cfg!(miri) {
191                // miri cannot debug print backtraces with `miri-disable-isolation` enabled
192                panic!("{}", message)
193            } else {
194                panic!("{}\n{:?}", message, backtrace)
195            }
196        }
197        state.finished = true;
198    }
199
200    pub fn clock(&self) -> Arc<TestClock> {
201        self.clock.clone()
202    }
203
204    pub fn rng(&self) -> SharedRng {
205        SharedRng(self.rng.clone())
206    }
207
208    pub fn set_timeout_ticks(&self, timeout_ticks: RangeInclusive<usize>) {
209        self.state.lock().timeout_ticks = timeout_ticks;
210    }
211
212    pub fn allow_parking(&self) {
213        let mut state = self.state.lock();
214        state.allow_parking = true;
215        state.parking_allowed_once = true;
216    }
217
218    pub fn forbid_parking(&self) {
219        self.state.lock().allow_parking = false;
220    }
221
222    pub fn parking_allowed(&self) -> bool {
223        self.state.lock().allow_parking
224    }
225
226    pub fn is_main_thread(&self) -> bool {
227        self.state.lock().is_main_thread
228    }
229
230    pub fn allocate_session_id(&self) -> SessionId {
231        let mut state = self.state.lock();
232        state.next_session_id.0 += 1;
233        state.next_session_id
234    }
235
236    /// Create a local executor for this scheduler.
237    pub fn foreground(self: &Arc<Self>) -> LocalExecutor {
238        let session_id = self.allocate_session_id();
239        LocalExecutor::new(
240            session_id,
241            self.clone(),
242            schedule_local_dispatch(Arc::downgrade(self), session_id),
243        )
244    }
245
246    /// Create a background executor for this scheduler
247    pub fn background(self: &Arc<Self>) -> BackgroundExecutor {
248        BackgroundExecutor::new(self.clone())
249    }
250
251    pub fn yield_random(&self) -> Yield {
252        let rng = &mut *self.rng.lock();
253        if rng.random_bool(0.1) {
254            Yield(rng.random_range(10..20))
255        } else {
256            Yield(rng.random_range(0..2))
257        }
258    }
259
260    pub fn run(&self) {
261        while self.step() {
262            // Continue until no work remains
263        }
264    }
265
266    pub fn run_with_clock_advancement(&self) {
267        while self.step() || self.advance_clock_to_next_timer() {
268            // Continue until no work remains
269        }
270    }
271
272    /// Execute one tick of the scheduler, processing expired timers and running
273    /// at most one task. Returns true if any work was done.
274    ///
275    /// This is the public interface for GPUI's TestDispatcher to drive task execution.
276    pub fn tick(&self) -> bool {
277        self.step_filtered(false)
278    }
279
280    /// Execute one tick, but only run background tasks (no foreground/session tasks).
281    /// Returns true if any work was done.
282    pub fn tick_background_only(&self) -> bool {
283        self.step_filtered(true)
284    }
285
286    /// Check if there are any pending tasks or timers that could run.
287    pub fn has_pending_tasks(&self) -> bool {
288        let state = self.state.lock();
289        !state.runnables.is_empty() || !state.timers.is_empty()
290    }
291
292    /// Returns counts of (foreground_tasks, background_tasks) currently queued.
293    /// Foreground tasks are those with a session_id, background tasks have none.
294    pub fn pending_task_counts(&self) -> (usize, usize) {
295        let state = self.state.lock();
296        let foreground = state
297            .runnables
298            .iter()
299            .filter(|r| r.session_id.is_some())
300            .count();
301        let background = state
302            .runnables
303            .iter()
304            .filter(|r| r.session_id.is_none())
305            .count();
306        (foreground, background)
307    }
308
309    fn step(&self) -> bool {
310        self.step_filtered(false)
311    }
312
313    fn step_filtered(&self, background_only: bool) -> bool {
314        let (elapsed_count, runnables_before) = {
315            let mut state = self.state.lock();
316            let end_ix = state
317                .timers
318                .partition_point(|timer| timer.expiration <= self.clock.now());
319            let elapsed: Vec<_> = state.timers.drain(..end_ix).collect();
320            let count = elapsed.len();
321            let runnables = state.runnables.len();
322            drop(state);
323            // Dropping elapsed timers here wakes the waiting futures
324            drop(elapsed);
325            (count, runnables)
326        };
327
328        if elapsed_count > 0 {
329            let runnables_after = self.state.lock().runnables.len();
330            if std::env::var("DEBUG_SCHEDULER").is_ok() {
331                eprintln!(
332                    "[scheduler] Expired {} timers at {:?}, runnables: {} -> {}",
333                    elapsed_count,
334                    self.clock.now(),
335                    runnables_before,
336                    runnables_after
337                );
338            }
339            return true;
340        }
341
342        let runnable = {
343            let state = &mut *self.state.lock();
344
345            // Find candidate tasks:
346            // - For foreground tasks (with session_id), only the first task from each session
347            //   is a candidate (to preserve intra-session ordering)
348            // - For background tasks (no session_id), all are candidates
349            // - Tasks from blocked sessions are excluded
350            // - If background_only is true, skip foreground tasks entirely
351            let mut seen_sessions = HashSet::new();
352            let candidate_indices: Vec<usize> = state
353                .runnables
354                .iter()
355                .enumerate()
356                .filter(|(_, runnable)| {
357                    if let Some(session_id) = runnable.session_id {
358                        // Skip foreground tasks if background_only mode
359                        if background_only {
360                            return false;
361                        }
362                        // Exclude tasks from blocked sessions
363                        if state.blocked_sessions.contains(&session_id) {
364                            return false;
365                        }
366                        // Only include first task from each session (insert returns true if new)
367                        seen_sessions.insert(session_id)
368                    } else {
369                        // Background tasks are always candidates
370                        true
371                    }
372                })
373                .map(|(ix, _)| ix)
374                .collect();
375
376            if candidate_indices.is_empty() {
377                None
378            } else if state.randomize_order {
379                // Use priority-weighted random selection
380                let weights: Vec<u32> = candidate_indices
381                    .iter()
382                    .map(|&ix| state.runnables[ix].priority.weight())
383                    .collect();
384                let total_weight: u32 = weights.iter().sum();
385
386                if total_weight == 0 {
387                    // Fallback to uniform random if all weights are zero
388                    let choice = self.rng.lock().random_range(0..candidate_indices.len());
389                    state.runnables.remove(candidate_indices[choice])
390                } else {
391                    let mut target = self.rng.lock().random_range(0..total_weight);
392                    let mut selected_idx = 0;
393                    for (i, &weight) in weights.iter().enumerate() {
394                        if target < weight {
395                            selected_idx = i;
396                            break;
397                        }
398                        target -= weight;
399                    }
400                    state.runnables.remove(candidate_indices[selected_idx])
401                }
402            } else {
403                // Non-randomized: just take the first candidate task
404                state.runnables.remove(candidate_indices[0])
405            }
406        };
407
408        if let Some(runnable) = runnable {
409            let is_foreground = runnable.session_id.is_some();
410            let was_main_thread = self.state.lock().is_main_thread;
411            self.state.lock().is_main_thread = is_foreground;
412            runnable.run();
413            self.state.lock().is_main_thread = was_main_thread;
414            return true;
415        }
416
417        false
418    }
419
420    /// Drops all runnable tasks from the scheduler.
421    ///
422    /// This is used by the leak detector to ensure that all tasks have been dropped as tasks may keep entities alive otherwise.
423    /// Why do we even have tasks left when tests finish you may ask. The reason for that is simple, the scheduler itself is the executor and it retains the scheduled runnables.
424    /// A lot of tasks, including every foreground task contain an executor handle that keeps the test scheduler alive, causing a reference cycle, thus the need for this function right now.
425    pub fn drain_tasks(&self) {
426        // dropping runnables may reschedule tasks
427        // due to drop impls with executors in them
428        // so drop until we reach a fixpoint
429        loop {
430            let mut state = self.state.lock();
431            if state.runnables.is_empty() && state.timers.is_empty() {
432                break;
433            }
434            let runnables = std::mem::take(&mut state.runnables);
435            let timers = std::mem::take(&mut state.timers);
436            drop(state);
437            drop(timers);
438            drop(runnables);
439        }
440    }
441
442    pub fn advance_clock_to_next_timer(&self) -> bool {
443        if let Some(timer) = self.state.lock().timers.first() {
444            self.clock.advance(timer.expiration - self.clock.now());
445            true
446        } else {
447            false
448        }
449    }
450
451    pub fn advance_clock(&self, duration: Duration) {
452        let debug = std::env::var("DEBUG_SCHEDULER").is_ok();
453        let start = self.clock.now();
454        let next_now = start + duration;
455        if debug {
456            let timer_count = self.state.lock().timers.len();
457            eprintln!(
458                "[scheduler] advance_clock({:?}) from {:?}, {} pending timers",
459                duration, start, timer_count
460            );
461        }
462        loop {
463            self.run();
464            if let Some(timer) = self.state.lock().timers.first()
465                && timer.expiration <= next_now
466            {
467                let advance_to = timer.expiration;
468                if debug {
469                    eprintln!(
470                        "[scheduler] Advancing clock {:?} -> {:?} for timer",
471                        self.clock.now(),
472                        advance_to
473                    );
474                }
475                self.clock.advance(advance_to - self.clock.now());
476            } else {
477                break;
478            }
479        }
480        self.clock.advance(next_now - self.clock.now());
481        if debug {
482            eprintln!(
483                "[scheduler] advance_clock done, now at {:?}",
484                self.clock.now()
485            );
486        }
487    }
488
489    fn park(&self, deadline: Option<Instant>) -> bool {
490        if self.state.lock().allow_parking {
491            let start = Instant::now();
492            // Enforce a hard timeout to prevent tests from hanging indefinitely
493            let hard_deadline = start + Duration::from_secs(15);
494
495            // Use the earlier of the provided deadline or the hard timeout deadline
496            let effective_deadline = deadline
497                .map(|d| d.min(hard_deadline))
498                .unwrap_or(hard_deadline);
499
500            // Park in small intervals to allow checking both deadlines
501            const PARK_INTERVAL: Duration = Duration::from_millis(100);
502            loop {
503                let now = Instant::now();
504                if now >= effective_deadline {
505                    // Check if we hit the hard timeout
506                    if now >= hard_deadline {
507                        panic!(
508                            "Test timed out after 15 seconds while parking. \
509                            This may indicate a deadlock or missing waker.",
510                        );
511                    }
512                    // Hit the provided deadline
513                    return false;
514                }
515
516                let remaining = effective_deadline.saturating_duration_since(now);
517                let park_duration = remaining.min(PARK_INTERVAL);
518                let before_park = Instant::now();
519                thread::park_timeout(park_duration);
520                let elapsed = before_park.elapsed();
521
522                // Advance the test clock by the real elapsed time while parking
523                self.clock.advance(elapsed);
524
525                // Check if any timers have expired after advancing the clock.
526                // If so, return so the caller can process them.
527                if self
528                    .state
529                    .lock()
530                    .timers
531                    .first()
532                    .map_or(false, |t| t.expiration <= self.clock.now())
533                {
534                    return true;
535                }
536
537                // Check if we were woken up by a different thread.
538                // We use a flag because timing-based detection is unreliable:
539                // OS scheduling delays can cause elapsed >= park_duration even when
540                // we were woken early by unpark().
541                if std::mem::take(&mut self.state.lock().unparked) {
542                    return true;
543                }
544            }
545        } else if deadline.is_some() {
546            false
547        } else if cfg!(miri) {
548            // miri cannot debug print backtraces with `miri-disable-isolation` enabled
549            panic!("Parking forbidden.");
550        } else if self.state.lock().capture_pending_traces {
551            let mut pending_traces = String::new();
552            for (_, trace) in mem::take(&mut self.state.lock().pending_traces) {
553                writeln!(pending_traces, "{:?}", exclude_wakers_from_trace(trace)).unwrap();
554            }
555            panic!("Parking forbidden. Pending traces:\n{}", pending_traces);
556        } else {
557            panic!(
558                "Parking forbidden. Re-run with {PENDING_TRACES_VAR_NAME}=1 to show pending traces"
559            );
560        }
561    }
562}
563
564fn assert_correct_thread(expected: &Thread, state: &Arc<Mutex<SchedulerState>>) {
565    let current_thread = thread::current();
566    let mut state = state.lock();
567    if state.parking_allowed_once {
568        return;
569    }
570    if current_thread.id() == expected.id() {
571        return;
572    }
573
574    let message = format!(
575        "Detected activity on thread {:?} {:?}, but test scheduler is running on {:?} {:?}. Your test is not deterministic.",
576        current_thread.name(),
577        current_thread.id(),
578        expected.name(),
579        expected.id(),
580    );
581    let backtrace = Backtrace::new();
582    if state.finished {
583        panic!("{}", message);
584    } else {
585        state.non_determinism_error = Some((message, backtrace))
586    }
587}
588
589impl Scheduler for TestScheduler {
590    /// Block until the given future completes, with an optional timeout. If the
591    /// future is unable to make progress at any moment before the timeout and
592    /// no other tasks or timers remain, we panic unless parking is allowed. If
593    /// parking is allowed, we block up to the timeout or indefinitely if none
594    /// is provided. This is to allow testing a mix of deterministic and
595    /// non-deterministic async behavior, such as when interacting with I/O in
596    /// an otherwise deterministic test.
597    #[cfg(not(target_family = "wasm"))]
598    fn block(
599        &self,
600        session_id: Option<SessionId>,
601        future: Pin<&mut dyn Future<Output = ()>>,
602        timeout: Option<Duration>,
603    ) -> bool {
604        self.block_until(session_id, future, timeout)
605    }
606
607    fn schedule_local(&self, session_id: SessionId, runnable: Runnable<RunnableMeta>) {
608        assert_correct_thread(&self.thread, &self.state);
609        let mut state = self.state.lock();
610        let ix = if state.randomize_order {
611            let start_ix = state
612                .runnables
613                .iter()
614                .rposition(|task| task.session_id == Some(session_id))
615                .map_or(0, |ix| ix + 1);
616            self.rng
617                .lock()
618                .random_range(start_ix..=state.runnables.len())
619        } else {
620            state.runnables.len()
621        };
622        state.runnables.insert(
623            ix,
624            ScheduledRunnable {
625                session_id: Some(session_id),
626                priority: Priority::default(),
627                runnable,
628            },
629        );
630        state.unparked = true;
631        drop(state);
632        self.thread.unpark();
633    }
634
635    fn schedule_background_with_priority(
636        &self,
637        runnable: Runnable<RunnableMeta>,
638        priority: Priority,
639    ) {
640        assert_correct_thread(&self.thread, &self.state);
641        let mut state = self.state.lock();
642        let ix = if state.randomize_order {
643            self.rng.lock().random_range(0..=state.runnables.len())
644        } else {
645            state.runnables.len()
646        };
647        state.runnables.insert(
648            ix,
649            ScheduledRunnable {
650                session_id: None,
651                priority,
652                runnable,
653            },
654        );
655        state.unparked = true;
656        drop(state);
657        self.thread.unpark();
658    }
659
660    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
661        std::thread::spawn(move || {
662            f();
663        });
664    }
665
666    #[track_caller]
667    fn timer(&self, duration: Duration) -> Timer {
668        let (tx, rx) = oneshot::channel();
669        let state = &mut *self.state.lock();
670        state.timers.push(ScheduledTimer {
671            expiration: self.clock.now() + duration,
672            _notify: tx,
673        });
674        state.timers.sort_by_key(|timer| timer.expiration);
675        Timer(rx)
676    }
677
678    fn clock(&self) -> Arc<dyn Clock> {
679        self.clock.clone()
680    }
681
682    /// In the test world, dedicated work is just a fresh local session driven
683    /// by the test scheduler's run loop alongside everything else. No real
684    /// thread is spawned, so determinism under `TestScheduler::many` is
685    /// preserved.
686    fn spawn_dedicated(
687        self: Arc<Self>,
688        f: Box<
689            dyn FnOnce(
690                    LocalExecutor,
691                )
692                    -> Pin<Box<dyn Future<Output = Box<dyn Any + Send + Sync>> + 'static>>
693                + Send
694                + 'static,
695        >,
696    ) -> Task<Box<dyn Any + Send + Sync>> {
697        let session_id = self.allocate_session_id();
698        let weak_scheduler = Arc::downgrade(&self);
699        let spawner = LocalExecutor::new(
700            session_id,
701            self,
702            schedule_local_dispatch(weak_scheduler.clone(), session_id),
703        );
704        // The dedicated future sits in this scheduler's own queue until its
705        // first poll. A `LocalExecutor` holds the scheduler strongly, so
706        // capturing one here would create a reference cycle
707        // (scheduler -> queued runnable -> future -> executor -> scheduler)
708        // that leaks both if the scheduler is dropped before ever being run.
709        // Construct the executor lazily at first poll, when the runnable is
710        // already out of the queue.
711        spawner.spawn(async move {
712            let scheduler = weak_scheduler
713                .upgrade()
714                .expect("dedicated tasks are only polled by their scheduler, which is still alive when polled");
715            let executor = LocalExecutor::new(
716                session_id,
717                scheduler,
718                schedule_local_dispatch(weak_scheduler, session_id),
719            );
720            f(executor).await
721        })
722    }
723
724    fn as_test(&self) -> Option<&TestScheduler> {
725        Some(self)
726    }
727}
728
729/// Dispatch closure for `LocalExecutor`s backed by a `TestScheduler`. Holds
730/// the scheduler weakly so queued runnables don't keep it alive.
731fn schedule_local_dispatch(
732    scheduler: Weak<TestScheduler>,
733    session_id: SessionId,
734) -> impl Fn(Runnable<RunnableMeta>) + Send + Sync + 'static {
735    move |runnable| {
736        if let Some(scheduler) = scheduler.upgrade() {
737            scheduler.schedule_local(session_id, runnable);
738        }
739    }
740}
741
742#[derive(Clone, Debug)]
743pub struct TestSchedulerConfig {
744    pub seed: u64,
745    pub randomize_order: bool,
746    pub allow_parking: bool,
747    pub capture_pending_traces: bool,
748    pub timeout_ticks: RangeInclusive<usize>,
749}
750
751impl TestSchedulerConfig {
752    pub fn with_seed(seed: u64) -> Self {
753        Self {
754            seed,
755            ..Default::default()
756        }
757    }
758}
759
760impl Default for TestSchedulerConfig {
761    fn default() -> Self {
762        Self {
763            seed: 0,
764            randomize_order: true,
765            allow_parking: false,
766            capture_pending_traces: env::var(PENDING_TRACES_VAR_NAME)
767                .map_or(false, |var| var == "1" || var == "true"),
768            timeout_ticks: 1..=1000,
769        }
770    }
771}
772
773struct ScheduledRunnable {
774    session_id: Option<SessionId>,
775    priority: Priority,
776    runnable: Runnable<RunnableMeta>,
777}
778
779impl ScheduledRunnable {
780    fn run(self) {
781        self.runnable.run();
782    }
783}
784
785struct ScheduledTimer {
786    expiration: Instant,
787    _notify: oneshot::Sender<()>,
788}
789
790struct SchedulerState {
791    runnables: VecDeque<ScheduledRunnable>,
792    timers: Vec<ScheduledTimer>,
793    blocked_sessions: Vec<SessionId>,
794    randomize_order: bool,
795    allow_parking: bool,
796    timeout_ticks: RangeInclusive<usize>,
797    next_session_id: SessionId,
798    capture_pending_traces: bool,
799    next_trace_id: TraceId,
800    pending_traces: BTreeMap<TraceId, Backtrace>,
801    is_main_thread: bool,
802    non_determinism_error: Option<(String, Backtrace)>,
803    parking_allowed_once: bool,
804    finished: bool,
805    unparked: bool,
806}
807
808const WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
809    TracingWaker::clone_raw,
810    TracingWaker::wake_raw,
811    TracingWaker::wake_by_ref_raw,
812    TracingWaker::drop_raw,
813);
814
815#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
816struct TraceId(usize);
817
818struct TracingWaker {
819    id: Option<TraceId>,
820    awoken: Arc<AtomicBool>,
821    thread: Thread,
822    state: Arc<Mutex<SchedulerState>>,
823}
824
825impl Clone for TracingWaker {
826    fn clone(&self) -> Self {
827        let mut state = self.state.lock();
828        let id = if state.capture_pending_traces {
829            let id = state.next_trace_id;
830            state.next_trace_id.0 += 1;
831            state.pending_traces.insert(id, Backtrace::new_unresolved());
832            Some(id)
833        } else {
834            None
835        };
836        Self {
837            id,
838            awoken: self.awoken.clone(),
839            thread: self.thread.clone(),
840            state: self.state.clone(),
841        }
842    }
843}
844
845impl Drop for TracingWaker {
846    fn drop(&mut self) {
847        assert_correct_thread(&self.thread, &self.state);
848
849        if let Some(id) = self.id {
850            self.state.lock().pending_traces.remove(&id);
851        }
852    }
853}
854
855impl TracingWaker {
856    fn wake(self) {
857        self.wake_by_ref();
858    }
859
860    fn wake_by_ref(&self) {
861        assert_correct_thread(&self.thread, &self.state);
862
863        let mut state = self.state.lock();
864        if let Some(id) = self.id {
865            state.pending_traces.remove(&id);
866        }
867        state.unparked = true;
868        drop(state);
869        self.awoken.store(true, SeqCst);
870        self.thread.unpark();
871    }
872
873    fn clone_raw(waker: *const ()) -> RawWaker {
874        let waker = waker as *const TracingWaker;
875        let waker = unsafe { &*waker };
876        RawWaker::new(
877            Box::into_raw(Box::new(waker.clone())) as *const (),
878            &WAKER_VTABLE,
879        )
880    }
881
882    fn wake_raw(waker: *const ()) {
883        let waker = unsafe { Box::from_raw(waker as *mut TracingWaker) };
884        waker.wake();
885    }
886
887    fn wake_by_ref_raw(waker: *const ()) {
888        let waker = waker as *const TracingWaker;
889        let waker = unsafe { &*waker };
890        waker.wake_by_ref();
891    }
892
893    fn drop_raw(waker: *const ()) {
894        let waker = unsafe { Box::from_raw(waker as *mut TracingWaker) };
895        drop(waker);
896    }
897}
898
899pub struct Yield(usize);
900
901/// A wrapper around `Arc<Mutex<StdRng>>` that provides convenient methods
902/// for random number generation without requiring explicit locking.
903#[derive(Clone)]
904pub struct SharedRng(Arc<Mutex<StdRng>>);
905
906impl SharedRng {
907    /// Lock the inner RNG for direct access. Use this when you need multiple
908    /// random operations without re-locking between each one.
909    pub fn lock(&self) -> MutexGuard<'_, StdRng> {
910        self.0.lock()
911    }
912
913    /// Generate a random value in the given range.
914    pub fn random_range<T, R>(&self, range: R) -> T
915    where
916        T: SampleUniform,
917        R: SampleRange<T>,
918    {
919        self.0.lock().random_range(range)
920    }
921
922    /// Generate a random boolean with the given probability of being true.
923    pub fn random_bool(&self, p: f64) -> bool {
924        self.0.lock().random_bool(p)
925    }
926
927    /// Generate a random value of the given type.
928    pub fn random<T>(&self) -> T
929    where
930        StandardUniform: Distribution<T>,
931    {
932        self.0.lock().random()
933    }
934
935    /// Generate a random ratio - true with probability `numerator/denominator`.
936    pub fn random_ratio(&self, numerator: u32, denominator: u32) -> bool {
937        self.0.lock().random_ratio(numerator, denominator)
938    }
939}
940
941impl Future for Yield {
942    type Output = ();
943
944    fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
945        if self.0 == 0 {
946            Poll::Ready(())
947        } else {
948            self.0 -= 1;
949            cx.waker().wake_by_ref();
950            Poll::Pending
951        }
952    }
953}
954
955fn exclude_wakers_from_trace(mut trace: Backtrace) -> Backtrace {
956    trace.resolve();
957    let mut frames: Vec<BacktraceFrame> = trace.into();
958    let waker_clone_frame_ix = frames.iter().position(|frame| {
959        frame.symbols().iter().any(|symbol| {
960            symbol
961                .name()
962                .is_some_and(|name| format!("{name:#?}") == type_name_of_val(&Waker::clone))
963        })
964    });
965
966    if let Some(waker_clone_frame_ix) = waker_clone_frame_ix {
967        frames.drain(..waker_clone_frame_ix + 1);
968    }
969
970    Backtrace::from(frames)
971}