1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct SchedSeed(pub u64);
14
15#[derive(Debug, Clone)]
21pub struct SeededRng {
22 state: u64,
23}
24
25impl SeededRng {
26 pub fn new(seed: SchedSeed) -> Self {
28 SeededRng { state: seed.0 }
29 }
30
31 pub fn next_u64(&mut self) -> u64 {
33 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
34 let mut z = self.state;
35 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
36 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
37 z ^ (z >> 31)
38 }
39
40 pub fn below(&mut self, n: usize) -> usize {
42 if n <= 1 {
43 return 0;
44 }
45 (self.next_u64() % n as u64) as usize
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub enum ChoiceKind {
52 TaskPick,
54 SelectWinner,
56 ChanWaiterWake,
58 TimerTieBreak,
60 WorkerPlacement,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct ChoicePoint {
69 pub kind: ChoiceKind,
71 pub options: usize,
73 pub chosen: usize,
75}
76
77#[derive(Debug, Clone, PartialEq)]
80pub struct SchedTrace {
81 pub seed: SchedSeed,
83 pub choices: Vec<ChoicePoint>,
85}
86
87#[derive(Debug, Clone)]
92pub enum Chooser {
93 Record {
95 rng: SeededRng,
96 seed: SchedSeed,
97 choices: Vec<ChoicePoint>,
98 },
99 Replay { trace: SchedTrace, pos: usize },
101}
102
103impl Chooser {
104 pub fn record(seed: SchedSeed) -> Self {
106 Chooser::Record {
107 rng: SeededRng::new(seed),
108 seed,
109 choices: Vec::new(),
110 }
111 }
112
113 pub fn replay(trace: SchedTrace) -> Self {
115 Chooser::Replay { trace, pos: 0 }
116 }
117
118 pub fn decide(&mut self, kind: ChoiceKind, options: usize) -> usize {
125 match self {
126 Chooser::Record { rng, choices, .. } => {
127 let chosen = rng.below(options);
128 choices.push(ChoicePoint { kind, options, chosen });
129 chosen
130 }
131 Chooser::Replay { trace, pos } => {
132 let cp = trace.choices.get(*pos).copied().unwrap_or_else(|| {
133 panic!(
134 "replay divergence: ran out of recorded choices at index {} \
135 (live decision was {:?} over {} options)",
136 pos, kind, options
137 )
138 });
139 assert_eq!(
140 cp.kind, kind,
141 "replay divergence at {}: recorded {:?}, live {:?}",
142 *pos, cp.kind, kind
143 );
144 assert_eq!(
145 cp.options, options,
146 "replay divergence at {}: recorded {} options, live {}",
147 *pos, cp.options, options
148 );
149 *pos += 1;
150 cp.chosen
151 }
152 }
153 }
154
155 pub fn into_trace(self) -> SchedTrace {
157 match self {
158 Chooser::Record { seed, choices, .. } => SchedTrace { seed, choices },
159 Chooser::Replay { trace, .. } => trace,
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn seed_rng_is_deterministic() {
170 let mut a = SeededRng::new(SchedSeed(42));
171 let mut b = SeededRng::new(SchedSeed(42));
172 let seq_a: Vec<u64> = (0..16).map(|_| a.next_u64()).collect();
173 let seq_b: Vec<u64> = (0..16).map(|_| b.next_u64()).collect();
174 assert_eq!(seq_a, seq_b, "same seed must yield the same stream");
175
176 let mut c = SeededRng::new(SchedSeed(43));
177 let seq_c: Vec<u64> = (0..16).map(|_| c.next_u64()).collect();
178 assert_ne!(seq_a, seq_c, "different seeds must (almost surely) differ");
179 }
180
181 #[test]
182 fn seed_below_is_in_range() {
183 let mut r = SeededRng::new(SchedSeed(7));
184 for _ in 0..1000 {
185 assert!(r.below(5) < 5);
186 }
187 assert_eq!(r.below(0), 0, "below(0) is 0");
188 assert_eq!(r.below(1), 0, "below(1) is 0");
189 }
190
191 #[test]
192 fn replay_roundtrip_is_bit_identical() {
193 let shapes = [
194 (ChoiceKind::TaskPick, 3usize),
195 (ChoiceKind::SelectWinner, 2),
196 (ChoiceKind::TaskPick, 4),
197 (ChoiceKind::TimerTieBreak, 2),
198 (ChoiceKind::WorkerPlacement, 8),
199 ];
200 let mut rec = Chooser::record(SchedSeed(1234));
201 let recorded: Vec<usize> = shapes.iter().map(|(k, n)| rec.decide(*k, *n)).collect();
202 let trace = rec.into_trace();
203
204 let mut rep = Chooser::replay(trace.clone());
205 let replayed: Vec<usize> = shapes.iter().map(|(k, n)| rep.decide(*k, *n)).collect();
206 assert_eq!(recorded, replayed, "replay reproduces the recorded choices");
207
208 let mut rec2 = Chooser::record(SchedSeed(1234));
210 let recorded2: Vec<usize> = shapes.iter().map(|(k, n)| rec2.decide(*k, *n)).collect();
211 assert_eq!(recorded, recorded2, "same seed is reproducible");
212 assert_eq!(trace.seed, SchedSeed(1234));
213 assert_eq!(trace.choices.len(), shapes.len());
214 }
215
216 #[test]
217 #[should_panic(expected = "replay divergence")]
218 fn replay_divergence_panics_on_option_mismatch() {
219 let mut rec = Chooser::record(SchedSeed(9));
220 rec.decide(ChoiceKind::TaskPick, 3);
221 let trace = rec.into_trace();
222
223 let mut rep = Chooser::replay(trace);
224 rep.decide(ChoiceKind::TaskPick, 5);
226 }
227
228 #[test]
229 #[should_panic(expected = "replay divergence")]
230 fn replay_divergence_panics_on_kind_mismatch() {
231 let mut rec = Chooser::record(SchedSeed(9));
232 rec.decide(ChoiceKind::TaskPick, 3);
233 let trace = rec.into_trace();
234
235 let mut rep = Chooser::replay(trace);
236 rep.decide(ChoiceKind::SelectWinner, 3);
237 }
238}