Skip to main content

miri/concurrency/
scheduler.rs

1//! This file contains Miri's scheduler, which is in charge of picking the next thread to run when
2//! the current thread blocks/yields. It also manages timeouts and runs an async event loop
3//! to deal with host I/O. In GenMC mode, it handles delegating schedule control to GenMC.
4
5use std::sync::atomic::Ordering;
6use std::task::Poll;
7use std::time::Duration;
8
9use rand::seq::IteratorRandom;
10use rustc_const_eval::CTRL_C_RECEIVED;
11use rustc_index::Idx;
12use rustc_span::DUMMY_SP;
13
14use crate::shims::readiness::DelayedReadinessUpdates;
15use crate::*;
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18enum SchedulingAction {
19    /// Execute step on the active thread.
20    ExecuteStep,
21    /// Wait for a bit, but at most as long as the duration specified.
22    /// We wake up early if an I/O event happened.
23    /// If the duration is [`None`], we sleep indefinitely. This is
24    /// only allowed when isolation is disabled and there are threads waiting for I/O!
25    SleepAndWaitForIo(Option<Duration>),
26}
27
28impl<'tcx> EvalContextPrivExt<'tcx> for MiriInterpCx<'tcx> {}
29trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
30    #[inline]
31    fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> {
32        let this = self.eval_context_mut();
33        let active_thread = this.active_thread_mut();
34        active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applied
35        let mut callback = active_thread
36            .on_stack_empty
37            .take()
38            .expect("`on_stack_empty` not set up, or already running");
39        let res = callback(this)?;
40        this.active_thread_mut().on_stack_empty = Some(callback);
41        interp_ok(res)
42    }
43
44    /// Decide which action to take next and on which thread.
45    ///
46    /// The currently implemented scheduling policy is the one that is commonly
47    /// used in stateless model checkers such as Loom: run the active thread as
48    /// long as we can and switch only when we have to (the active thread was
49    /// blocked, terminated, or has explicitly asked to be preempted).
50    ///
51    /// If GenMC mode is active, the scheduling is instead handled by GenMC.
52    fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
53        let this = self.eval_context_mut();
54
55        // In GenMC mode, we let GenMC do the scheduling.
56        if this.machine.data_race.as_genmc_ref().is_some() {
57            loop {
58                let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap();
59                let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else {
60                    return interp_ok(SchedulingAction::ExecuteStep);
61                };
62                // If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again.
63                if this.machine.threads.thread_ref(next_thread_id).is_blocked_on(BlockReason::Genmc)
64                {
65                    info!(
66                        "GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now."
67                    );
68                    this.unblock_thread(next_thread_id, BlockReason::Genmc)?;
69                }
70                // The thread we just unblocked may have been blocked again during the unblocking callback.
71                // In that case, we need to ask for a different thread to run next.
72                let thread_manager = &mut this.machine.threads;
73                if thread_manager.thread_ref(next_thread_id).is_enabled() {
74                    // Set the new active thread.
75                    thread_manager.set_active_thread(next_thread_id);
76                    return interp_ok(SchedulingAction::ExecuteStep);
77                }
78            }
79        }
80
81        // We are not in GenMC mode, so we control the scheduling.
82        let thread_manager = &this.machine.threads;
83        // Check if we can just keep running the current thread.
84        if thread_manager.active_thread_ref().is_enabled() && !thread_manager.yield_active_thread {
85            // The currently active thread is still enabled, just continue with it.
86            return interp_ok(SchedulingAction::ExecuteStep);
87        }
88
89        // The active thread yielded or got terminated. Let's see if there are any I/O events
90        // or timeouts to take care of.
91
92        // There may be delayed readiness updates we have to process. We only care about this
93        // if it may unblock other threads, or if someone calls `epoll_wait`, and for the latter
94        // case there is another call in `ReadinessWatcher::get_ready_interests`.
95        DelayedReadinessUpdates::process(this)?;
96
97        if this.machine.communicate() {
98            // When isolation is disabled we need to check for events for threads
99            // which are blocked on host I/O. Unlike the `poll_and_unblock` before
100            // any foreign item, the call here is needed to ensure that threads which
101            // are blocked on host I/O are woken up even if no shimmed functions are
102            // executed afterwards.
103            // We do this before running any other threads such that the threads
104            // which received events are available for scheduling afterwards.
105
106            // Perform a non-blocking poll for newly available I/O events from the OS.
107            this.poll_and_unblock(Some(Duration::ZERO))?;
108        }
109
110        // We also check timeouts before running any other thread, to ensure that timeouts
111        // "in the past" fire before any other thread can take an action. This ensures that for
112        // `pthread_cond_timedwait`, "an error is returned if [...] the absolute time specified by
113        // abstime has already been passed at the time of the call".
114        // <https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html>
115        let potential_sleep_time = this.unblock_expired_deadlines()?;
116
117        let thread_manager = &mut this.machine.threads;
118        let rng = this.machine.rng.get_mut();
119
120        // No callbacks immediately scheduled, pick a regular thread to execute.
121        // The active thread blocked or yielded. So we go search for another enabled thread.
122        // We build the list of threads by starting with the threads after the current one, followed by
123        // the threads before the current one and then the current thread itself (i.e., this iterator acts
124        // like `threads.rotate_left(self.active_thread.index() + 1)`. This ensures that if we pick the first
125        // eligible thread, we do regular round-robin scheduling, and all threads get a chance to take a step.
126        let mut threads_iter = thread_manager
127            .all_threads()
128            .skip(thread_manager.active_thread().index() + 1)
129            .chain(thread_manager.all_threads().take(thread_manager.active_thread().index() + 1))
130            .filter(|(_id, thread)| thread.is_enabled());
131        // Pick a new thread, and switch to it.
132        let new_thread = if thread_manager.fixed_scheduling() {
133            let next = threads_iter.next();
134            drop(threads_iter);
135            next
136        } else {
137            threads_iter.choose(rng)
138        };
139
140        if let Some((id, _thread)) = new_thread {
141            if thread_manager.active_thread() != id {
142                thread_manager.set_active_thread(id);
143            }
144        }
145        // This completes the `yield`, if any was requested.
146        thread_manager.yield_active_thread = false;
147
148        if thread_manager.active_thread_ref().is_enabled() {
149            return interp_ok(SchedulingAction::ExecuteStep);
150        }
151
152        // We have not found a thread to execute.
153        if this.machine.threads.all_threads().all(|(_id, thread)| thread.is_terminated()) {
154            unreachable!("all threads terminated without the main thread terminating?!");
155        } else if let Some(sleep_time) = potential_sleep_time {
156            // All threads are currently blocked, but we have unexecuted
157            // timeout_callbacks, which may unblock some of the threads. Hence,
158            // sleep until the first callback.
159            interp_ok(SchedulingAction::SleepAndWaitForIo(Some(sleep_time)))
160        } else if this.any_thread_blocked_on_host() {
161            // At least one thread doesn't have a timeout set, and is blocked on host I/O or is waiting on an
162            // epoll instance which contains a host source interest. Hence, we sleep indefinitely in the
163            // hope that eventually an I/O event happens.
164            interp_ok(SchedulingAction::SleepAndWaitForIo(None))
165        } else {
166            throw_machine_stop!(TerminationInfo::GlobalDeadlock);
167        }
168    }
169}
170
171impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
172pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
173    /// Public because this is used by Priroda.
174    fn step_current_thread(&mut self) -> InterpResult<'tcx> {
175        let this = self.eval_context_mut();
176
177        if !this.step()? {
178            // See if this thread can do something else.
179            match this.run_on_stack_empty()? {
180                Poll::Pending => {} // keep going
181                Poll::Ready(()) => {
182                    this.terminate_active_thread(TlsAllocAction::Deallocate)?;
183                }
184            }
185        }
186
187        interp_ok(())
188    }
189
190    /// Run the core interpreter loop. Returns only when an interrupt occurs (an error or program
191    /// termination).
192    fn run_threads(&mut self) -> InterpResult<'tcx, !> {
193        let this = self.eval_context_mut();
194        loop {
195            if CTRL_C_RECEIVED.load(Ordering::Relaxed) {
196                this.machine.handle_abnormal_termination();
197                throw_machine_stop!(TerminationInfo::Interrupted);
198            }
199            match this.schedule()? {
200                SchedulingAction::ExecuteStep => {
201                    this.step_current_thread()?;
202                }
203                SchedulingAction::SleepAndWaitForIo(duration) => {
204                    if this.machine.communicate() {
205                        // When we're running with isolation disabled, instead of
206                        // strictly sleeping the duration we allow waking up
207                        // early for I/O events from the OS.
208
209                        this.poll_and_unblock(duration)?;
210                    } else {
211                        let duration = duration.expect(
212                            "Infinite sleep should not be triggered when isolation is enabled",
213                        );
214                        this.machine.monotonic_clock.sleep(duration);
215                    }
216                }
217            }
218        }
219    }
220}