use std::fmt;
use super::optimal::{Frame, Wut};
use crate::model::{ObjectID, ProcessID, State, Transition};
pub trait Observer {
fn step(&mut self, step: Step<'_>, cx: StepCx<'_, '_>) {
let _ = (step, cx);
}
}
impl Observer for () {}
pub struct StepCx<'a, 'w> {
tree: &'a Wut,
frames: &'a [Frame],
prefix: &'a [Transition],
state: &'a State<'w>,
}
impl<'a, 'w> StepCx<'a, 'w> {
pub(in crate::search) fn new(
tree: &'a Wut,
frames: &'a [Frame],
prefix: &'a [Transition],
state: &'a State<'w>,
) -> Self {
Self {
tree,
frames,
prefix,
state,
}
}
pub fn state(&self) -> &State<'w> {
self.state
}
pub fn prefix(&self) -> &[Transition] {
self.prefix
}
pub fn label(&self, t: Transition) -> String {
self.state.world().label(t)
}
pub fn process(&self, pid: ProcessID) -> &str {
&self.state.world().processes()[pid]
}
pub fn object(&self, oid: ObjectID) -> &str {
&self.state.world().objects()[oid]
}
pub fn depth(&self) -> usize {
self.frames.len()
}
pub fn sleep(&self, depth: usize) -> &[ProcessID] {
self.frames[depth].sleep()
}
pub fn pending(&self, depth: usize) -> &[Transition] {
self.frames[depth].pending()
}
pub fn wakeup(&self) -> WakeupNode<'a> {
WakeupNode { node: self.tree }
}
}
impl fmt::Debug for StepCx<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StepCx")
.field("depth", &self.depth())
.field("prefix", &self.prefix)
.finish_non_exhaustive()
}
}
pub struct WakeupNode<'a> {
node: &'a Wut,
}
impl<'a> WakeupNode<'a> {
pub fn children(&self) -> Vec<(Transition, WakeupNode<'a>)> {
self.node
.children()
.iter()
.map(|(edge, sub)| (*edge, WakeupNode { node: sub }))
.collect()
}
}
impl fmt::Debug for WakeupNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.node.children().iter().map(|(edge, _)| edge.pid()))
.finish()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum RaceOutcome {
Disabling,
CoveredBySleeper {
insert_depth: usize,
covering_pid: ProcessID,
},
ExistingLeaf {
insert_depth: usize,
},
Grafted {
insert_depth: usize,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Step<'a> {
Visit,
RootSeed {
seeded: Transition,
},
RootEmpty,
Maximal {
trace: &'a [Transition],
failure: bool,
},
Descend {
depth: usize,
committed: Transition,
parent_sleep: &'a [ProcessID],
child_sleep: &'a [ProcessID],
},
SeedChild {
depth: usize,
seeded: Option<Transition>,
},
Race {
i: usize,
j: usize,
e: Transition,
ep: Transition,
notdep: &'a [Transition],
v: &'a [Transition],
outcome: RaceOutcome,
},
Pop {
finished_pid: ProcessID,
from_depth: usize,
into_depth: usize,
},
Replay {
prefix: &'a [Transition],
},
Done,
}
#[cfg(test)]
mod tests {
use std::fmt::Write as _;
use super::{Observer, RaceOutcome, Step, StepCx};
use crate::Atomic;
use crate::model::World;
use crate::search::explore;
fn spawn_store<'a>(world: &mut World<'a>, name: &str, cell: Atomic<u32>, value: u32) {
world.spawn(name.to_string(), async move {
cell.store(value).await;
Ok(())
});
}
fn spawn_load<'a>(world: &mut World<'a>, name: &str, cell: Atomic<u32>) {
world.spawn(name.to_string(), async move {
cell.load().await;
Ok(())
});
}
fn one_writer_two_readers(world: &mut World) {
let x = world.atomic("x", 0u32);
spawn_store(world, "writer", x.clone(), 1);
spawn_load(world, "reader-1", x.clone());
spawn_load(world, "reader-2", x);
}
#[derive(Default)]
struct Recorder {
lines: Vec<String>,
}
fn names(cx: &StepCx<'_, '_>, pids: &[usize]) -> String {
let mut s = String::new();
s.push('[');
for (k, &p) in pids.iter().enumerate() {
if k > 0 {
s.push(',');
}
s.push_str(cx.process(p));
}
s.push(']');
s
}
fn proc_names(cx: &StepCx<'_, '_>, ts: &[crate::model::Transition]) -> String {
names(cx, &ts.iter().map(|t| t.pid()).collect::<Vec<_>>())
}
impl Observer for Recorder {
fn step(&mut self, step: Step<'_>, cx: StepCx<'_, '_>) {
let mut line = String::new();
match step {
Step::Visit => {
write!(
line,
"Visit d{} terminal={}",
cx.state().trace().len(),
cx.state().is_terminal()
)
.unwrap();
}
Step::RootSeed { seeded } => {
write!(line, "RootSeed {}", cx.process(seeded.pid())).unwrap();
}
Step::RootEmpty => line.push_str("RootEmpty"),
Step::Descend {
depth,
committed,
parent_sleep,
child_sleep,
} => {
let dropped: Vec<usize> = parent_sleep
.iter()
.copied()
.filter(|p| !child_sleep.contains(p))
.collect();
write!(
line,
"Descend d{depth} {} parent_sleep={} child_sleep={} dropped={}",
cx.process(committed.pid()),
names(&cx, parent_sleep),
names(&cx, child_sleep),
names(&cx, &dropped),
)
.unwrap();
}
Step::SeedChild { depth, seeded } => match seeded {
Some(q_t) => {
write!(line, "SeedChild d{depth} {}", cx.process(q_t.pid())).unwrap()
}
None => write!(line, "SeedChild d{depth} -").unwrap(),
},
Step::Maximal { trace, failure } => {
write!(line, "Maximal {} failure={failure}", proc_names(&cx, trace)).unwrap();
}
Step::Race {
i,
j,
e,
ep,
notdep: _,
v,
outcome,
} => {
write!(
line,
"Race i{i} j{j} ({},{}) v={} -> ",
cx.process(e.pid()),
cx.process(ep.pid()),
proc_names(&cx, v),
)
.unwrap();
match outcome {
RaceOutcome::Disabling => line.push_str("disabling"),
RaceOutcome::CoveredBySleeper {
insert_depth,
covering_pid,
} => write!(
line,
"covered@{insert_depth} by {}",
cx.process(covering_pid)
)
.unwrap(),
RaceOutcome::ExistingLeaf { insert_depth } => {
write!(line, "existingleaf@{insert_depth}").unwrap()
}
RaceOutcome::Grafted { insert_depth } => {
write!(line, "grafted@{insert_depth}").unwrap()
}
}
}
Step::Pop {
finished_pid,
from_depth,
into_depth,
} => {
write!(
line,
"Pop {} {from_depth}->{into_depth}",
cx.process(finished_pid)
)
.unwrap();
}
Step::Replay { prefix } => {
write!(line, "Replay {}", proc_names(&cx, prefix)).unwrap();
}
Step::Done => line.push_str("Done"),
}
self.lines.push(line);
}
}
#[test]
fn one_writer_two_readers_step_stream() {
let mut rec = Recorder::default();
let res = explore(&one_writer_two_readers, &mut rec);
assert!(res.is_ok(), "fixture explores cleanly");
let expected = [
"Visit d0 terminal=false",
"RootSeed writer",
"Visit d1 terminal=false",
"Descend d0 writer parent_sleep=[] child_sleep=[] dropped=[]",
"SeedChild d1 reader-1",
"Visit d2 terminal=false",
"Descend d1 reader-1 parent_sleep=[] child_sleep=[] dropped=[]",
"SeedChild d2 reader-2",
"Visit d3 terminal=true",
"Descend d2 reader-2 parent_sleep=[] child_sleep=[] dropped=[]",
"Maximal [writer,reader-1,reader-2] failure=false",
"Race i1 j2 (writer,reader-1) v=[reader-1] -> grafted@0",
"Race i1 j3 (writer,reader-2) v=[reader-2] -> existingleaf@0",
"Pop reader-2 3->2",
"Pop reader-1 2->1",
"Pop writer 1->0",
"Replay []",
"Visit d1 terminal=false",
"Descend d0 reader-1 parent_sleep=[writer] child_sleep=[] dropped=[writer]",
"SeedChild d1 writer",
"Visit d2 terminal=false",
"Descend d1 writer parent_sleep=[] child_sleep=[] dropped=[]",
"SeedChild d2 reader-2",
"Visit d3 terminal=true",
"Descend d2 reader-2 parent_sleep=[] child_sleep=[] dropped=[]",
"Maximal [reader-1,writer,reader-2] failure=false",
"Race i1 j2 (reader-1,writer) v=[writer] -> covered@0 by writer",
"Race i2 j3 (writer,reader-2) v=[reader-2] -> grafted@1",
"Pop reader-2 3->2",
"Pop writer 2->1",
"Replay [reader-1]",
"Visit d2 terminal=false",
"Descend d1 reader-2 parent_sleep=[writer] child_sleep=[] dropped=[writer]",
"SeedChild d2 writer",
"Visit d3 terminal=true",
"Descend d2 writer parent_sleep=[] child_sleep=[] dropped=[]",
"Maximal [reader-1,reader-2,writer] failure=false",
"Race i1 j3 (reader-1,writer) v=[reader-2,writer] -> grafted@0",
"Race i2 j3 (reader-2,writer) v=[writer] -> covered@1 by writer",
"Pop writer 3->2",
"Pop reader-2 2->1",
"Pop reader-1 1->0",
"Replay []",
"Visit d1 terminal=false",
"Descend d0 reader-2 parent_sleep=[writer,reader-1] child_sleep=[reader-1] dropped=[writer]",
"SeedChild d1 writer",
"Visit d2 terminal=false",
"Descend d1 writer parent_sleep=[reader-1] child_sleep=[] dropped=[reader-1]",
"SeedChild d2 reader-1",
"Visit d3 terminal=true",
"Descend d2 reader-1 parent_sleep=[] child_sleep=[] dropped=[]",
"Maximal [reader-2,writer,reader-1] failure=false",
"Race i1 j2 (reader-2,writer) v=[writer] -> covered@0 by writer",
"Race i2 j3 (writer,reader-1) v=[reader-1] -> covered@1 by reader-1",
"Pop reader-1 3->2",
"Pop writer 2->1",
"Pop reader-2 1->0",
"Done",
];
assert_eq!(
rec.lines, expected,
"step stream must match the expert-verified ground truth"
);
}
}