Skip to main content

must/runtime/
mod.rs

1//! Processes as stackless coroutines that implement [`Program`] by replay. A [`System`]
2//! registers process bodies as re-runnable factories; computing `next(traces)` re-creates
3//! each body and polls it once, letting the trace replay the events already in the graph
4//! and stopping at the first new one.
5//!
6//! This module knows nothing about execution graphs, consistency or the explorer - it
7//! depends only on `event` and `program`.
8//!
9//! ## Contract and honest limitations
10//!
11//! A process body must be a pure function of the values it receives, and cooperatively
12//! bounded:
13//! * A pure CPU loop with no API calls (e.g. `loop {}`) cannot be interrupted: the event
14//!   budget only bounds bodies that reach `send`/`recv`/`assert_that`. A synchronous loop
15//!   that never awaits never yields control, so bounding it is the caller's job (unroll it,
16//!   or add a step limit in the body).
17//! * Signal assertion failures with [`Ctx::assert_that`] (which emits `Label::Error`),
18//!   never `panic!` - a `panic!` inside a body crashes the checker.
19//! * A body may only `.await` the futures returned by [`Ctx::recv`], [`Ctx::recv_timeout`]
20//!   and [`Ctx::nondet`]. Awaiting any other future is reported as a `Label::Error`.
21
22mod replay;
23
24use std::cell::RefCell;
25use std::future::Future;
26use std::pin::Pin;
27use std::rc::Rc;
28
29use crate::event::{Model, Tid, Val};
30use crate::program::{Program, ThreadNext};
31
32use replay::{run_once, ThreadCell};
33
34/// The boxed, pinned future produced by a process body - polled with a no-op waker.
35pub type LocalFut = Pin<Box<dyn Future<Output = ()>>>;
36
37/// A receive predicate closure, boxed so it can be stored and later moved into a
38/// `Label::Recv`. `Send + Sync` so the resulting `Pred`/`Label`/`ExecutionGraph` can
39/// cross threads (see [`crate::event::Pred`]).
40pub(crate) type BoxedPred = Box<dyn Fn(&str) -> bool + Send + Sync>;
41
42/// Default per-thread event budget: a body emitting more events than this yields
43/// `Label::Error` rather than diverging.
44pub const DEFAULT_MAX_EVENTS: usize = 10_000;
45
46/// A system of processes. Each process is stored as a factory `Fn(Ctx) -> LocalFut` so
47/// it can be re-executed from scratch on every replay; the checker never keeps a
48/// suspended coroutine around.
49pub struct System {
50    factories: Vec<Box<dyn Fn(Ctx) -> LocalFut>>,
51    max_events: usize,
52}
53
54impl Default for System {
55    fn default() -> Self {
56        System::new()
57    }
58}
59
60impl System {
61    pub fn new() -> Self {
62        System {
63            factories: Vec::new(),
64            max_events: DEFAULT_MAX_EVENTS,
65        }
66    }
67
68    /// Set the per-thread event budget (see [`DEFAULT_MAX_EVENTS`]).
69    pub fn with_max_events(mut self, max_events: usize) -> Self {
70        self.max_events = max_events;
71        self
72    }
73
74    /// Register a process body; returns its thread id (`0`-based, in registration
75    /// order). `body` is a `Fn` (not `FnOnce`) because it is re-run on every replay.
76    pub fn add<F, Fut>(&mut self, body: F) -> Tid
77    where
78        F: Fn(Ctx) -> Fut + 'static,
79        Fut: Future<Output = ()> + 'static,
80    {
81        let tid = self.factories.len();
82        self.factories
83            .push(Box::new(move |ctx| Box::pin(body(ctx)) as LocalFut));
84        tid
85    }
86
87    /// Replay thread `tid` against `trace` and read off its next event.
88    fn run_thread(&self, tid: Tid, trace: Vec<Option<Val>>) -> ThreadNext {
89        let cell = Rc::new(RefCell::new(ThreadCell::new(trace, self.max_events)));
90        let ctx = Ctx {
91            tid,
92            cell: cell.clone(),
93        };
94        let fut = (self.factories[tid])(ctx);
95        run_once(fut, &cell)
96    }
97}
98
99impl Program for System {
100    fn num_threads(&self) -> usize {
101        self.factories.len()
102    }
103
104    fn next(&self, traces: &[Vec<Option<Val>>]) -> Vec<ThreadNext> {
105        debug_assert_eq!(
106            traces.len(),
107            self.num_threads(),
108            "expected one trace per thread"
109        );
110        (0..self.factories.len())
111            .map(|tid| {
112                let trace = traces.get(tid).cloned().unwrap_or_default();
113                self.run_thread(tid, trace)
114            })
115            .collect()
116    }
117
118    /// Per-thread replay: re-run only thread `tid`'s body, not all of them.
119    fn next_thread(&self, tid: Tid, trace: &[Option<Val>]) -> ThreadNext {
120        self.run_thread(tid, trace.to_vec())
121    }
122}
123
124/// What a process body sees. Its methods drive the per-thread replay of the trace; the
125/// body itself must be a pure function of the values it receives.
126pub struct Ctx {
127    tid: Tid,
128    cell: Rc<RefCell<ThreadCell>>,
129}
130
131impl Ctx {
132    /// This process's thread id.
133    pub fn tid(&self) -> Tid {
134        self.tid
135    }
136
137    /// Send `msg` to thread `to` under communication model `model` (fire-and-forget).
138    /// Synchronous: emits a `Label::Send`. Sending to oneself (`to == self.tid()`) is
139    /// allowed.
140    pub fn send(&self, to: Tid, msg: impl Into<Val>, model: Model) {
141        self.cell.borrow_mut().record_send(to, msg.into(), model);
142    }
143
144    /// Blocking selective receive: awaits a message satisfying `pred` and returns it.
145    /// During replay it resolves to the value the graph already gave this receive; if
146    /// this is the thread's next event it parks, emitting `Label::Recv`.
147    pub fn recv(&self, pred: impl Fn(&str) -> bool + Send + Sync + 'static) -> RecvFuture {
148        RecvFuture {
149            cell: self.cell.clone(),
150            pred_fn: Some(Box::new(pred)),
151        }
152    }
153
154    /// Non-blocking selective receive: awaits a message satisfying `pred` and returns
155    /// `Some(v)`, or `None` when the timeout fires (reading no message). When it parks it
156    /// emits `Label::recv_nb`.
157    ///
158    /// In the DPOR, "no message" is an extra rf source on top of every consistent send,
159    /// and - unlike a send - may be read by several non-blocking receives at once. This is
160    /// deliberately not a nondet-encoded timeout: N threads each doing one `recv_timeout`
161    /// explore exactly one execution, not `2^N`.
162    pub fn recv_timeout(
163        &self,
164        pred: impl Fn(&str) -> bool + Send + Sync + 'static,
165    ) -> RecvTimeoutFuture {
166        RecvTimeoutFuture {
167            cell: self.cell.clone(),
168            pred_fn: Some(Box::new(pred)),
169        }
170    }
171
172    /// Data non-determinism: returns some value of the finite option set `set`. The search
173    /// enumerates every value of `set`; during replay it resolves to the value already
174    /// committed for this choice, or — when this is the thread's next event — parks and emits
175    /// `Label::nondet(set)`. `set` must be non-empty.
176    pub fn nondet(&self, set: impl IntoIterator<Item = impl Into<Val>>) -> NondetFuture {
177        NondetFuture {
178            cell: self.cell.clone(),
179            set: Some(set.into_iter().map(Into::into).collect()),
180        }
181    }
182
183    /// Assertion: if `cond` is false, emit `Label::Error` as this thread's next event. A
184    /// holding assertion is pure control flow.
185    pub fn assert_that(&self, cond: bool, msg: &str) {
186        self.cell.borrow_mut().record_assert(cond, msg);
187    }
188}
189
190/// Future returned by [`Ctx::recv`]; resolves to the received message.
191pub struct RecvFuture {
192    cell: Rc<RefCell<ThreadCell>>,
193    /// Predicate closure, moved into the emitted `Label::Recv` when the receive parks.
194    pred_fn: Option<BoxedPred>,
195}
196
197impl Future for RecvFuture {
198    type Output = String;
199
200    fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<String> {
201        use std::task::Poll;
202        // RecvFuture is Unpin (Rc + Option<Box>), so projecting the fields is safe.
203        let this = self.get_mut();
204        let mut cell = this.cell.borrow_mut();
205        // The graph stores payloads as interned `Sym`s; the process body works in `String`,
206        // so resolve at the await boundary.
207        match cell.poll_recv(&mut this.pred_fn, true) {
208            Poll::Ready(Some(v)) => Poll::Ready(crate::intern::resolve(v).to_owned()),
209            // A blocking receive never resolves to `None`: on a committed no-message read
210            // `poll_recv` blocks the thread and returns `Pending` instead of `Ready(None)`.
211            Poll::Ready(None) => unreachable!("blocking receive resolved to nothing"),
212            Poll::Pending => Poll::Pending,
213        }
214    }
215}
216
217/// Future returned by [`Ctx::recv_timeout`]; resolves to `Some(v)` for a received message
218/// or `None` when the timeout fires (no message). Unlike [`RecvFuture`], a committed
219/// no-message read resolves the future rather than blocking the thread.
220pub struct RecvTimeoutFuture {
221    cell: Rc<RefCell<ThreadCell>>,
222    /// Predicate closure, moved into the emitted `Label::recv_nb` when the receive parks.
223    pred_fn: Option<BoxedPred>,
224}
225
226impl Future for RecvTimeoutFuture {
227    type Output = Option<String>;
228
229    fn poll(
230        self: Pin<&mut Self>,
231        _cx: &mut std::task::Context<'_>,
232    ) -> std::task::Poll<Option<String>> {
233        // RecvTimeoutFuture is Unpin (Rc + Option<Box>), so projecting the fields is safe.
234        let this = self.get_mut();
235        let mut cell = this.cell.borrow_mut();
236        // Resolve the interned payload to a `String` for the body (see `RecvFuture::poll`).
237        match cell.poll_recv(&mut this.pred_fn, false) {
238            std::task::Poll::Ready(opt) => {
239                std::task::Poll::Ready(opt.map(|v| crate::intern::resolve(v).to_owned()))
240            }
241            std::task::Poll::Pending => std::task::Poll::Pending,
242        }
243    }
244}
245
246/// Future returned by [`Ctx::nondet`]; resolves to the chosen nondet value. Like
247/// [`RecvFuture`] it is a real `Future` so replay can park at the choice point: a nondet
248/// value already committed in the trace resolves immediately, otherwise the choice parks
249/// and emits `Label::nondet(set)`. A nondet event never resolves to nothing.
250pub struct NondetFuture {
251    cell: Rc<RefCell<ThreadCell>>,
252    /// Option set, moved into the emitted `Label::nondet` when the choice parks.
253    set: Option<Vec<Val>>,
254}
255
256impl Future for NondetFuture {
257    type Output = String;
258
259    fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<String> {
260        // NondetFuture is Unpin (Rc + Option<Vec>), so projecting the fields is safe.
261        let this = self.get_mut();
262        let mut cell = this.cell.borrow_mut();
263        // Resolve the interned choice to a `String` for the body (see `RecvFuture::poll`).
264        match cell.poll_nondet(&mut this.set) {
265            std::task::Poll::Ready(v) => {
266                std::task::Poll::Ready(crate::intern::resolve(v).to_owned())
267            }
268            std::task::Poll::Pending => std::task::Poll::Pending,
269        }
270    }
271}