Skip to main content

fusee_sim/
world.rs

1//! The simulated world: shards on one machine, a client, an external
2//! service, a seeded scheduler and fault injection.
3
4use crate::disk::SimFile;
5use crate::env::{Io, Machine, MachineRef, SimEnv, SimLog, SimLogRef};
6use crate::rng::Rng;
7use crate::scenario::{Check, Outcome, Setup, UserAction};
8use fusee::codec::Codec;
9use fusee::ctx::{ChildOptions, Ctx, RetryPolicy, StepOptions};
10use fusee::error::Error;
11use fusee::limits::{LimitBackend, LimitSpec, LocalLimits};
12use fusee::queue::{BATCH_TYPE, ENDED, EVENT, Event, QueueState, Receipt, Rule};
13use fusee::runtime::{
14    IdPolicy, Message, OnFinished, OnRunning, ParentClose, ResultState, RunOutcome, RuntimeConfig,
15    Services, ShardRuntime, SharedRegistry, StartRequest, UpdateRequest, WorkflowFn,
16};
17use fusee::schedule::{Overlap, SCHEDULE_TYPE, ScheduleSpec, ScheduleState, schedule_id};
18use fusee_core::record::{Payload, Record, Status};
19use fusee_core::shard::{Output, shard_of};
20use serde::{Deserialize, Serialize};
21use std::cell::{Cell, RefCell};
22use std::cmp::Reverse;
23use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
24use std::future::Future;
25use std::pin::Pin;
26use std::rc::Rc;
27use std::sync::atomic::{AtomicBool, Ordering};
28use std::sync::{Arc, Mutex, RwLock};
29use std::task::{Context, Poll, Waker};
30use std::time::Duration;
31
32/// Knobs for one simulation run.
33#[derive(Clone, Debug)]
34pub struct SimConfig {
35    pub seed: u64,
36    pub shards: u32,
37    pub workflows: u32,
38    /// Simulated milliseconds of client activity with faults on.
39    pub duration_ms: u64,
40    /// Mean simulated milliseconds between crashes (0 = never).
41    pub crash_every_ms: u64,
42    pub tear: bool,
43    pub flush_fail_per_mille: u32,
44    pub max_io_ms: u64,
45    pub max_msg_ms: u64,
46    pub park_after_ms: u64,
47    pub checkpoint_interval_ms: u64,
48    pub segment_bytes: u64,
49    /// Log format 3: the shards share one log ([`fusee::wal`]); otherwise
50    /// each has its own segments.
51    pub shared_log: bool,
52    /// The shared log offloads records whose encoding is longer than this
53    /// to its blob area (0: none). Low, so that ordinary records go there.
54    pub offload_bytes: u64,
55    /// Simulated milliseconds every acknowledged workflow gets to finish
56    /// once faults stop.
57    pub settle_ms: u64,
58    /// A user scenario ([`crate::scenario`]) in place of the built-in
59    /// workload.
60    pub user: Option<crate::scenario::UserSetup>,
61    #[doc(hidden)]
62    pub sabotage: fusee::runtime::Sabotage,
63    /// Sabotage: the shared log writes a batch as soon as the blobs it
64    /// references are written, before they are durable.
65    #[doc(hidden)]
66    pub early_blobs: bool,
67}
68
69impl SimConfig {
70    pub fn for_seed(seed: u64) -> Self {
71        let mut r = Rng::new(seed);
72        Self {
73            seed,
74            shards: 1 + r.below(3) as u32,
75            workflows: 10 + r.below(40) as u32,
76            duration_ms: 2_000,
77            crash_every_ms: [0, 150, 400, 900][r.below(4) as usize],
78            tear: true,
79            flush_fail_per_mille: [0, 0, 2, 10][r.below(4) as usize],
80            max_io_ms: 1 + r.below(4),
81            max_msg_ms: r.below(3),
82            park_after_ms: [0, 5, 50, u64::MAX][r.below(4) as usize],
83            checkpoint_interval_ms: [20, 100, 500][r.below(3) as usize],
84            segment_bytes: [16 << 10, 64 << 10, 1 << 20][r.below(3) as usize],
85            // By parity, so the other knobs of every seed stay as they were.
86            shared_log: seed % 2 == 1,
87            // From bits of the seed the other knobs do not use.
88            offload_bytes: [0, 16, 40, 120][(seed / 18 % 4) as usize],
89            settle_ms: 60_000,
90            user: None,
91            sabotage: Default::default(),
92            early_blobs: false,
93        }
94    }
95}
96
97#[derive(Clone, Debug, Default)]
98pub struct Report {
99    pub seed: u64,
100    pub crashes: u32,
101    pub flush_failures: u32,
102    pub workflows: u32,
103    pub finished: u32,
104    pub service_calls: u64,
105    pub sim_ms: u64,
106    pub records: u64,
107    /// Feature coverage: update replies, query answers, completions
108    /// accepted, scheduled runs, resets acknowledged, heartbeat progress
109    /// reports.
110    pub updates: u32,
111    pub queries: u32,
112    pub completions: u32,
113    pub scheduled: u32,
114    pub resets: u32,
115    pub heartbeats: u32,
116    /// Read-only calls that ran again with a different input after a crash
117    /// took back what they had read from.
118    pub reads_rerun: u32,
119    /// Debounced or batched events acknowledged, and the window runs they
120    /// joined.
121    pub queued: u32,
122    pub windows: u32,
123    /// Stream chunks readers received, and readers that read a stream to
124    /// its end.
125    pub chunks_read: u64,
126    pub streams_read: u32,
127    /// The seed ran on the shared log (log format 3), and the batches the
128    /// last process life ended with.
129    pub shared_log: bool,
130    pub log_batches: u64,
131    /// Records the shared log offloaded to its blob area, over all lives.
132    pub offloaded: u64,
133}
134
135#[derive(Clone, Debug)]
136pub struct Failure {
137    pub seed: u64,
138    pub message: String,
139    pub trace: Vec<String>,
140    /// A user scenario's seed ([`crate::scenario::Scenario::run`]).
141    pub scenario: bool,
142}
143
144impl std::fmt::Display for Failure {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        writeln!(f, "seed {} failed: {}", self.seed, self.message)?;
147        if self.scenario {
148            writeln!(f, "reproduce: scenario.run({})", self.seed)?;
149        } else {
150            writeln!(f, "reproduce: fusee-sim --seed {}", self.seed)?;
151        }
152        for l in self.trace.iter().rev().take(60).rev() {
153            writeln!(f, "  {l}")?;
154        }
155        Ok(())
156    }
157}
158
159// ---------------------------------------------------------------------------
160// Simulated time for step functions.
161
162thread_local! {
163    static NOW: Cell<u64> = const { Cell::new(0) };
164    static TIMERS: RefCell<BinaryHeap<Reverse<(u64, u64)>>> = const { RefCell::new(BinaryHeap::new()) };
165    static WAKERS: RefCell<BTreeMap<u64, Waker>> = const { RefCell::new(BTreeMap::new()) };
166    static TIMER_SEQ: Cell<u64> = const { Cell::new(0) };
167}
168
169/// Sleeps in simulated time (for step functions that model slow services).
170pub struct SimSleep {
171    at: u64,
172    armed: Option<u64>,
173}
174
175pub fn sim_sleep(ms: u64) -> SimSleep {
176    SimSleep {
177        at: NOW.with(Cell::get) + ms,
178        armed: None,
179    }
180}
181
182impl Future for SimSleep {
183    type Output = ();
184    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
185        if NOW.with(Cell::get) >= self.at {
186            return Poll::Ready(());
187        }
188        if self.armed.is_none() {
189            let id = TIMER_SEQ.with(|s| {
190                let v = s.get();
191                s.set(v + 1);
192                v
193            });
194            TIMERS.with(|t| t.borrow_mut().push(Reverse((self.at, id))));
195            WAKERS.with(|w| w.borrow_mut().insert(id, cx.waker().clone()));
196            self.armed = Some(id);
197        } else if let Some(id) = self.armed {
198            WAKERS.with(|w| w.borrow_mut().insert(id, cx.waker().clone()));
199        }
200        Poll::Pending
201    }
202}
203
204fn sim_timers_next() -> Option<u64> {
205    TIMERS.with(|t| t.borrow().peek().map(|Reverse((at, _))| *at))
206}
207
208fn sim_timers_fire(now: u64) -> bool {
209    NOW.with(|n| n.set(now));
210    let mut any = false;
211    loop {
212        let due = TIMERS.with(|t| {
213            let mut t = t.borrow_mut();
214            match t.peek() {
215                Some(Reverse((at, id))) if *at <= now => {
216                    let id = *id;
217                    t.pop();
218                    Some(id)
219                }
220                _ => None,
221            }
222        });
223        let Some(id) = due else { break };
224        if let Some(w) = WAKERS.with(|w| w.borrow_mut().remove(&id)) {
225            w.wake();
226            any = true;
227        }
228    }
229    any
230}
231
232fn sim_timers_clear() {
233    TIMERS.with(|t| t.borrow_mut().clear());
234    WAKERS.with(|w| w.borrow_mut().clear());
235}
236
237// ---------------------------------------------------------------------------
238// The external service.
239
240/// A mock external service. It logs every call with its idempotency key and
241/// input. Responses differ per call, so a step that runs again yields a
242/// different value; if any later step had already run on the first value,
243/// the log shows one key called with two different inputs.
244#[derive(Default)]
245pub struct Service {
246    pub calls: BTreeMap<String, Vec<u64>>,
247    pub violations: Vec<String>,
248    pub total: u64,
249    pub seed: u64,
250    /// Progress each step key reported through heartbeats.
251    pub progress: BTreeMap<String, BTreeSet<u32>>,
252    pub stalled: BTreeSet<String>,
253    /// Completion tokens handed out, in order: (workflow ID, token).
254    pub tokens: Vec<(String, String)>,
255    pub issued: BTreeMap<String, u32>,
256    /// Runs seen per scheduled workflow ID.
257    pub ticks: BTreeMap<String, BTreeSet<u64>>,
258    /// Inputs of read-only calls per key. Reads may run on state a crash
259    /// takes back, so a key read with different inputs is allowed; it is
260    /// counted to show that read-only steps really run ahead of the disk.
261    pub reads: BTreeMap<String, Vec<u64>>,
262    pub reads_rerun: u32,
263    /// Tokens a model step asked for per key, and the keys whose call
264    /// failed once.
265    pub talk_calls: BTreeMap<String, u64>,
266    pub talk_failed: BTreeSet<String>,
267}
268
269impl Service {
270    fn violation(&mut self, v: String) {
271        if self.violations.len() < 16 {
272            self.violations.push(v);
273        }
274    }
275
276    /// A step resumed from heartbeat progress `start`: it must be progress
277    /// the step reported (a crash may only take back newer progress).
278    pub fn resume(&mut self, key: &str, start: u32) {
279        if start > 0
280            && !self
281                .progress
282                .get(key)
283                .is_some_and(|p| p.contains(&(start - 1)))
284        {
285            self.violation(format!(
286                "step {key} resumed from progress {start}, which it never reported"
287            ));
288        }
289    }
290
291    pub fn report_progress(&mut self, key: &str, p: u32) {
292        self.progress.entry(key.to_owned()).or_default().insert(p);
293    }
294
295    /// True once per key: the step stalls without heartbeats.
296    pub fn stall_once(&mut self, key: &str) -> bool {
297        self.stalled.insert(key.to_owned())
298    }
299
300    /// Hands out a completion token. The first attempt per workflow fails.
301    pub fn issue(&mut self, wf: &str, token: &str) -> Result<(), String> {
302        let n = self.issued.entry(wf.to_owned()).or_insert(0);
303        *n += 1;
304        if *n == 1 {
305            return Err("approval system unavailable".into());
306        }
307        self.tokens.push((wf.to_owned(), token.to_owned()));
308        Ok(())
309    }
310
311    pub fn tick(&mut self, wf: &str, run: u64) {
312        let runs = self.ticks.entry(wf.to_owned()).or_default();
313        runs.insert(run);
314        if runs.len() > 1 {
315            let v = format!(
316                "scheduled workflow {wf} was started as runs {runs:?}: one scheduled time fired twice"
317            );
318            self.violation(v);
319        }
320    }
321
322    /// A read-only call: logged apart from calls with effects and never a
323    /// violation. Answers differ per call like [`Service::call`].
324    pub fn read(&mut self, key: &str, input: u64, fail_first: u32) -> Result<u64, String> {
325        self.total += 1;
326        let reads = self.reads.entry(key.to_owned()).or_default();
327        if reads.first().is_some_and(|first| *first != input) && !reads.contains(&input) {
328            self.reads_rerun += 1;
329        }
330        reads.push(input);
331        let n = reads.len() as u64;
332        if n <= u64::from(fail_first) {
333            return Err(format!("transient read failure {n}"));
334        }
335        let mut h = self.seed ^ 0x8422_2325_cbf2_9ce4;
336        for b in key.as_bytes() {
337            h = (h ^ u64::from(*b)).wrapping_mul(0x100_0000_01b3);
338        }
339        h ^= input.rotate_left(29) ^ n.wrapping_mul(0x9e37_79b9_7f4a_7c15);
340        Ok(h % 1_000_000)
341    }
342
343    /// A model's next token: a new value on every call, as sampling gives
344    /// (so a token generated again differs from the first). Fails once per
345    /// even key at the third token.
346    pub fn token(&mut self, key: &str, i: usize) -> Result<u64, String> {
347        self.total += 1;
348        let n = self.talk_calls.entry(key.to_owned()).or_insert(0);
349        *n += 1;
350        let mut h = self.seed ^ 0x7a1c_e5d1_4b3f_0e29;
351        for b in key.as_bytes() {
352            h = (h ^ u64::from(*b)).wrapping_mul(0x100_0000_01b3);
353        }
354        if i == 2 && h.is_multiple_of(2) && self.talk_failed.insert(key.to_owned()) {
355            return Err(format!("the model failed at token {i}"));
356        }
357        h ^= (i as u64).rotate_left(23) ^ n.wrapping_mul(0x9e37_79b9_7f4a_7c15);
358        Ok(h % 1_000_000)
359    }
360
361    pub fn call(&mut self, key: &str, input: u64, fail_first: u32) -> Result<u64, String> {
362        self.total += 1;
363        let calls = self.calls.entry(key.to_owned()).or_default();
364        if let Some(first) = calls.first()
365            && *first != input
366            && self.violations.len() < 16
367        {
368            self.violations.push(format!(
369                "service key {key} was called with input {first} and later with {input}: \
370                     a step ran on state a crash took back"
371            ));
372        }
373        calls.push(input);
374        let n = calls.len() as u64;
375        if n <= u64::from(fail_first) {
376            return Err(format!("transient failure {n}"));
377        }
378        let mut h = self.seed ^ 0xcbf2_9ce4_8422_2325;
379        for b in key.as_bytes() {
380            h = (h ^ u64::from(*b)).wrapping_mul(0x100_0000_01b3);
381        }
382        h ^= input.rotate_left(17) ^ n.wrapping_mul(0x9e37_79b9_7f4a_7c15);
383        Ok(h % 1_000_000)
384    }
385}
386
387pub type ServiceRef = Arc<Mutex<Service>>;
388
389// ---------------------------------------------------------------------------
390// Workflows.
391
392#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
393pub enum Job {
394    Order {
395        n: u64,
396    },
397    Fanout {
398        n: u32,
399    },
400    Pinger {
401        target: String,
402    },
403    /// Updates and queries.
404    Counter,
405    /// Continue-as-new.
406    Looper {
407        left: u32,
408        acc: u64,
409    },
410    /// Patching: new code is deployed at the first crash.
411    Patchy {
412        n: u64,
413    },
414    /// Heartbeats and heartbeat timeouts.
415    Export,
416    /// Async completion.
417    Approval,
418    /// Parent close policy.
419    Parent {
420        kids: u32,
421    },
422    /// Reset.
423    Resettable {
424        n: u64,
425    },
426    /// Read-only steps feeding a step with effects.
427    Reader {
428        n: u64,
429    },
430    /// Streams: a model call as a step streams its tokens (continuing after
431    /// the recorded prefix when it runs again), and the workflow streams
432    /// its progress.
433    Talker {
434        n: u32,
435    },
436}
437
438/// What a [`Job::Talker`] returns: a hash of its tokens in order, which the
439/// checker compares with what readers of its stream saw.
440pub fn tokens_hash(tokens: &[u64]) -> u64 {
441    tokens.iter().fold(0xcbf2_9ce4_8422_2325, |h, t| {
442        (h ^ t).wrapping_mul(0x100_0000_01b3)
443    })
444}
445
446/// The progress a [`Job::Talker`] streams.
447pub const TALKER_PROGRESS: [&str; 3] = ["start", "generated", "end"];
448
449/// A deterministic stand-in for encryption (the simulator must be
450/// reproducible from its seed).
451#[derive(Debug)]
452pub struct SimCodec;
453
454impl Codec for SimCodec {
455    fn encode(&self, p: &[u8]) -> Result<Vec<u8>, String> {
456        let mut v = b"SIMC".to_vec();
457        v.extend(p.iter().rev().map(|b| b ^ 0x5a));
458        Ok(v)
459    }
460
461    fn decode(&self, d: &[u8]) -> Result<Vec<u8>, String> {
462        match d.strip_prefix(b"SIMC") {
463            Some(r) => Ok(r.iter().rev().map(|b| b ^ 0x5a).collect()),
464            None => Ok(d.to_vec()),
465        }
466    }
467}
468
469pub(crate) fn erase<I, O, F, Fut>(f: F) -> WorkflowFn
470where
471    I: Serialize + serde::de::DeserializeOwned + 'static,
472    O: Serialize + serde::de::DeserializeOwned + 'static,
473    F: Fn(Ctx, I) -> Fut + Send + Sync + 'static,
474    Fut: Future<Output = Result<O, Error>> + 'static,
475{
476    let f = Arc::new(f);
477    Arc::new(move |ctx: Ctx| {
478        let f = f.clone();
479        let input = ctx.input_bytes(|bytes| serde_json::from_slice::<I>(bytes));
480        Box::pin(async move {
481            let map = |e: Error| -> (Status, Payload) {
482                let (s, m) = match e {
483                    Error::Cancelled(r) => (Status::Cancelled, r),
484                    Error::TimedOut => (Status::TimedOut, "timed out".into()),
485                    other => (Status::Failed, other.to_string()),
486                };
487                (s, Arc::from(m.into_bytes()))
488            };
489            let input: I = input.map_err(|e| map(Error::Codec(e.to_string())))?;
490            let out = f(ctx, input).await.map_err(map)?;
491            serde_json::to_vec(&out)
492                .map(Arc::from)
493                .map_err(|e| map(Error::Codec(e.to_string())))
494        }) as Pin<Box<dyn Future<Output = RunOutcome>>>
495    })
496}
497
498/// The client's request for a debounced start or a batched event.
499fn enqueue_action(
500    workflow: &str,
501    key: &str,
502    event_id: &str,
503    input: serde_json::Value,
504    rule: Rule,
505    continue_at: u32,
506) -> Action {
507    let event = Event {
508        id: event_id.into(),
509        rule,
510        input,
511    };
512    let (wtype, coordinator, state, event) = fusee::queue::request(workflow, key, &event);
513    let mut state: QueueState = serde_json::from_value(state).unwrap_or_default();
514    state.continue_at = continue_at;
515    Action::Enqueue {
516        coordinator,
517        wtype,
518        workflow: workflow.into(),
519        state: serde_json::to_vec(&state).unwrap_or_default(),
520        event_id: event_id.into(),
521        event: serde_json::to_vec(&event).unwrap_or_default(),
522    }
523}
524
525async fn call(
526    ctx: &Ctx,
527    svc: &ServiceRef,
528    name: &str,
529    input: u64,
530    fail_first: u32,
531) -> Result<u64, Error> {
532    let mut opts = StepOptions::default().retry(RetryPolicy::exponential(
533        fail_first + 2,
534        std::time::Duration::from_millis(1),
535        std::time::Duration::from_millis(4),
536    ));
537    if name == "charge" || name == "ship" {
538        opts = opts.limit("svc");
539        // Odd seeds queue for the permit by priority and tenant (neither
540        // is journaled, so replay passes the same ones again).
541        if svc.lock().is_ok_and(|s| s.seed % 2 == 1) {
542            opts = opts.priority((input % 3) as i32 - 1);
543            if input % 2 == 1 {
544                opts = opts
545                    .fairness(&format!("tenant-{}", input % 5))
546                    .fairness_weight(1 + (input % 3) as u32);
547            }
548        }
549    }
550    ctx.step(name, || {
551        let key = ctx.key();
552        let svc = svc.clone();
553        async move {
554            let delay = {
555                let s = svc.lock().map_err(|_| "poisoned".to_string())?;
556                (s.total + input) % 3
557            };
558            sim_sleep(delay).await;
559
560            svc.lock()
561                .map_err(|_| "poisoned".to_string())?
562                .call(&key, input, fail_first)
563        }
564    })
565    .options(opts)
566    .await
567}
568
569/// A read-only step: runs without waiting for the records before it.
570async fn read(
571    ctx: &Ctx,
572    svc: &ServiceRef,
573    name: &str,
574    input: u64,
575    fail_first: u32,
576) -> Result<u64, Error> {
577    let opts = StepOptions::default()
578        .read_only()
579        .retry(RetryPolicy::exponential(
580            fail_first + 2,
581            std::time::Duration::from_millis(1),
582            std::time::Duration::from_millis(4),
583        ));
584    ctx.step(name, || {
585        let key = ctx.key();
586        let svc = svc.clone();
587        async move {
588            let delay = {
589                let s = svc.lock().map_err(|_| "poisoned".to_string())?;
590                (s.total + input) % 2
591            };
592            sim_sleep(delay).await;
593            svc.lock()
594                .map_err(|_| "poisoned".to_string())?
595                .read(&key, input, fail_first)
596        }
597    })
598    .options(opts)
599    .await
600}
601
602pub fn register(registry: &SharedRegistry, svc: &ServiceRef, deployed: Arc<AtomicBool>) {
603    let mut r = registry.write().unwrap_or_else(|e| e.into_inner());
604    let s = svc.clone();
605    let deployed2 = deployed.clone();
606    r.register(
607        "job",
608        1,
609        erase(move |ctx: Ctx, job: Job| {
610            let svc = s.clone();
611            let deployed2 = deployed2.clone();
612            async move {
613                match job {
614                    Job::Order { n } => {
615                        let quote = call(&ctx, &svc, "quote", n, 0).await?;
616                        let charge = call(&ctx, &svc, "charge", quote, 2).await?;
617                        ctx.sleep("cool-off", std::time::Duration::from_millis(3))
618                            .await?;
619                        let approved: Option<u64> = match ctx
620                            .wait_for("approve")
621                            .timeout(std::time::Duration::from_millis(60))
622                            .await
623                        {
624                            Ok(v) => v,
625                            Err(Error::Cancelled(_)) => {
626                                let r = call(&ctx, &svc, "refund", charge, 0).await?;
627                                return Err(Error::Cancelled(format!("refunded {r}")));
628                            }
629                            Err(e) => return Err(e),
630                        };
631                        match approved {
632                            Some(v) => {
633                                let id = format!("{}/ship", ctx.workflow_id());
634                                ctx.child::<u64, u64>("ship", &id, &(charge ^ v)).await
635                            }
636                            None => call(&ctx, &svc, "refund", charge, 0).await,
637                        }
638                    }
639                    Job::Fanout { n } => {
640                        let branches: Vec<_> = (0..n)
641                            .map(|i| {
642                                let ctx = ctx.clone();
643                                let svc = svc.clone();
644                                async move {
645                                    call(&ctx, &svc, &format!("leg{i}"), u64::from(i), i % 2).await
646                                }
647                            })
648                            .collect();
649                        let mut sum = 0u64;
650                        for r in ctx.all(branches).await {
651                            sum = sum.wrapping_add(r?);
652                        }
653                        let t = ctx.now_ms()?;
654                        let total = call(&ctx, &svc, "total", sum ^ (t % 7), 0).await?;
655                        Ok(total)
656                    }
657                    Job::Pinger { target } => {
658                        let v = call(&ctx, &svc, "prep", 1, 0).await?;
659                        ctx.signal_workflow(&target, "approve", &v)?;
660                        Ok(v)
661                    }
662                    Job::Counter => {
663                        let total = Rc::new(Cell::new(0u64));
664                        let t = total.clone();
665                        ctx.on_query("total", move |_: serde_json::Value| t.get());
666                        let (t, c, s2) = (total.clone(), ctx.clone(), svc.clone());
667                        ctx.on_update(
668                            "add",
669                            |v: &u64| {
670                                if *v >= 1000 {
671                                    Err(format!("{v} is too large"))
672                                } else {
673                                    Ok(())
674                                }
675                            },
676                            move |v: u64| {
677                                let (t, c, svc) = (t.clone(), c.clone(), s2.clone());
678                                async move {
679                                    let r = call(&c, &svc, "apply", v, 0).await?;
680                                    t.set(t.get() + r % 100);
681                                    Ok(t.get())
682                                }
683                            },
684                        );
685                        let _ = ctx
686                            .wait_for::<u64>("close")
687                            .timeout(Duration::from_millis(150))
688                            .await?;
689                        Ok(total.get())
690                    }
691                    Job::Looper { left, acc } => {
692                        let v =
693                            call(&ctx, &svc, "loop", acc.wrapping_add(u64::from(left)), 0).await?;
694                        if left == 0 {
695                            return Ok(acc ^ v);
696                        }
697                        ctx.continue_as_new(&Job::Looper {
698                            left: left - 1,
699                            acc: acc ^ v,
700                        })
701                        .await
702                    }
703                    Job::Patchy { n } => {
704                        let a = call(&ctx, &svc, "a", n, 0).await?;
705                        ctx.sleep("gap", Duration::from_millis(5)).await?;
706                        if deployed2.load(Ordering::Relaxed) && ctx.patched("fix")? {
707                            call(&ctx, &svc, "new", a, 0).await
708                        } else {
709                            call(&ctx, &svc, "old", a, 0).await
710                        }
711                    }
712                    Job::Export => {
713                        let opts = StepOptions::default()
714                            .retry(RetryPolicy::exponential(
715                                5,
716                                Duration::from_millis(1),
717                                Duration::from_millis(2),
718                            ))
719                            .heartbeat_timeout(Duration::from_millis(20));
720                        let (c, s2) = (ctx.clone(), svc.clone());
721                        let pages: u32 = ctx
722                            .step("export", move || {
723                                let (c, svc) = (c.clone(), s2.clone());
724                                async move {
725                                    let key = c.key();
726                                    let start: u32 = c
727                                        .progress::<u32>()
728                                        .map_err(|e| e.to_string())?
729                                        .unwrap_or(0);
730                                    svc.lock()
731                                        .map_err(|_| "poisoned".to_string())?
732                                        .resume(&key, start);
733                                    for p in start..8 {
734                                        let stall = p == 3
735                                            && svc
736                                                .lock()
737                                                .map_err(|_| "poisoned".to_string())?
738                                                .stall_once(&key);
739                                        sim_sleep(if stall { 40 } else { 2 }).await;
740                                        svc.lock()
741                                            .map_err(|_| "poisoned".to_string())?
742                                            .report_progress(&key, p);
743                                        c.heartbeat(&(p + 1)).map_err(|e| e.to_string())?;
744                                    }
745                                    Ok::<u32, String>(8)
746                                }
747                            })
748                            .options(opts)
749                            .await?;
750                        Ok(u64::from(pages))
751                    }
752                    Job::Approval => {
753                        let (s2, id) = (svc.clone(), ctx.workflow_id().to_owned());
754                        let v: u64 = ctx
755                            .await_completion("approval", move |c: fusee::Completion| {
756                                let token = c.token;
757                                let (svc, id) = (s2.clone(), id.clone());
758                                async move {
759                                    svc.lock()
760                                        .map_err(|_| "poisoned".to_string())?
761                                        .issue(&id, &token)
762                                }
763                            })
764                            .options(StepOptions::default().retry(RetryPolicy::exponential(
765                                3,
766                                Duration::from_millis(1),
767                                Duration::from_millis(2),
768                            )))
769                            .await?;
770                        call(&ctx, &svc, "after", v, 0).await
771                    }
772                    Job::Parent { kids } => {
773                        for k in 0..kids {
774                            let close = if k % 2 == 0 {
775                                ParentClose::Terminate
776                            } else {
777                                ParentClose::Abandon
778                            };
779                            ctx.start_child(
780                                "kid",
781                                &format!("{}/kid{k}", ctx.workflow_id()),
782                                &u64::from(k),
783                            )
784                            .options(ChildOptions {
785                                close,
786                                timeout: None,
787                            })
788                            .await?;
789                        }
790                        let v = call(&ctx, &svc, "parent", 1, 0).await?;
791                        let _ = ctx
792                            .wait_for::<u64>("done")
793                            .timeout(Duration::from_millis(30))
794                            .await?;
795                        Ok(v)
796                    }
797                    Job::Reader { n } => {
798                        // Each read's input comes from the read before it,
799                        // which may not be durable yet. `store` has effects:
800                        // its gate waits for every read before it.
801                        let a = read(&ctx, &svc, "fetch", n, 0).await?;
802                        let b = read(&ctx, &svc, "enrich", a, 1).await?;
803                        let c = call(&ctx, &svc, "store", b, 1).await?;
804                        let d = read(&ctx, &svc, "verify", c, 0).await?;
805                        Ok(d)
806                    }
807                    Job::Talker { n } => {
808                        let progress = ctx.stream("progress")?;
809                        progress.append(&TALKER_PROGRESS[0])?;
810                        let (c, s2) = (ctx.clone(), svc.clone());
811                        let opts = StepOptions::default().retry(RetryPolicy::exponential(
812                            4,
813                            Duration::from_millis(1),
814                            Duration::from_millis(2),
815                        ));
816                        let tokens: Vec<u64> = ctx
817                            .step("talk", move || {
818                                let (c, svc) = (c.clone(), s2.clone());
819                                async move {
820                                    let key = c.key();
821                                    let out = c.stream("tokens").map_err(|e| e.to_string())?;
822                                    // Continue after what was recorded.
823                                    let mut toks: Vec<u64> =
824                                        out.recorded().map_err(|e| e.to_string())?;
825                                    for i in toks.len()..n as usize {
826                                        sim_sleep(1).await;
827                                        let t = svc
828                                            .lock()
829                                            .map_err(|_| "poisoned".to_string())?
830                                            .token(&key, i)?;
831                                        out.append(&t).map_err(|e| e.to_string())?;
832                                        toks.push(t);
833                                    }
834                                    Ok::<_, String>(toks)
835                                }
836                            })
837                            .options(opts)
838                            .await?;
839                        progress.append(&TALKER_PROGRESS[1])?;
840                        ctx.sleep("rest", Duration::from_millis(4)).await?;
841                        progress.append(&TALKER_PROGRESS[2])?;
842                        progress.close()?;
843                        Ok(tokens_hash(&tokens))
844                    }
845                    Job::Resettable { n } => {
846                        let a = call(&ctx, &svc, "a", n, 0).await?;
847                        let b = call(&ctx, &svc, "b", a, 0).await?;
848                        let c = call(&ctx, &svc, "c", b, 0).await?;
849                        let _ = ctx
850                            .wait_for::<u64>("go")
851                            .timeout(Duration::from_millis(80))
852                            .await?;
853                        Ok(a ^ b ^ c)
854                    }
855                }
856            }
857        }),
858    );
859    let s = svc.clone();
860    r.register(
861        "kid",
862        1,
863        erase(move |ctx: Ctx, k: u64| {
864            let svc = s.clone();
865            async move {
866                let wait = if k.is_multiple_of(2) { 400 } else { 60 };
867                let _ = ctx
868                    .wait_for::<u64>("never")
869                    .timeout(Duration::from_millis(wait))
870                    .await?;
871                call(&ctx, &svc, "kid", k, 0).await
872            }
873        }),
874    );
875    let s = svc.clone();
876    r.register(
877        "tick",
878        1,
879        erase(move |ctx: Ctx, slow: bool| {
880            let svc = s.clone();
881            async move {
882                let (id, run) = (ctx.workflow_id().to_owned(), ctx.run_id());
883                let v: u64 = ctx
884                    .step("tick", || {
885                        let svc = svc.clone();
886                        let id = id.clone();
887                        async move {
888                            svc.lock()
889                                .map_err(|_| "poisoned".to_string())?
890                                .tick(&id, run);
891                            Ok::<u64, String>(run)
892                        }
893                    })
894                    .await?;
895                if slow {
896                    ctx.sleep("work", Duration::from_millis(35)).await?;
897                }
898                Ok(v)
899            }
900        }),
901    );
902    let s = svc.clone();
903    r.register(
904        "ship",
905        1,
906        erase(move |ctx: Ctx, x: u64| {
907            let svc = s.clone();
908            async move { call(&ctx, &svc, "ship", x, 1).await }
909        }),
910    );
911    // What debounce and batch windows start: returns its input.
912    r.register(
913        "echo",
914        1,
915        erase(move |ctx: Ctx, v: serde_json::Value| async move {
916            ctx.step("echo", || {
917                let v = v.clone();
918                async move { Ok::<_, Error>(v) }
919            })
920            .await
921        }),
922    );
923}
924
925// ---------------------------------------------------------------------------
926// The client and checkers.
927
928#[derive(Clone, Debug)]
929enum Action {
930    Start {
931        id: String,
932        job: Job,
933    },
934    /// A start of a user scenario's workflow type.
935    StartAs {
936        id: String,
937        wtype: String,
938        input: Vec<u8>,
939    },
940    Signal {
941        id: String,
942        name: String,
943        payload: Vec<u8>,
944        dedup: u64,
945    },
946    Cancel {
947        id: String,
948    },
949    Terminate {
950        id: String,
951    },
952    Update {
953        id: String,
954        update_id: String,
955        value: u64,
956    },
957    Query {
958        id: String,
959    },
960    Reset {
961        id: String,
962    },
963    Schedule {
964        name: String,
965        spec: ScheduleSpec,
966    },
967    Complete {
968        wf: String,
969        token: String,
970    },
971    /// A call to a key of an object type ([`fusee::object`]): update-with-
972    /// start on the key's run, retried with one call ID until answered.
973    ObjectCall {
974        id: String,
975        wtype: String,
976        handler: String,
977        call_id: String,
978        arg: Vec<u8>,
979    },
980    /// A debounced start or batched event: update-with-start on the key's
981    /// coordinator, retried with one event ID until it has a receipt.
982    Enqueue {
983        coordinator: String,
984        wtype: &'static str,
985        /// The workflow type its window starts.
986        workflow: String,
987        state: Vec<u8>,
988        event_id: String,
989        event: Vec<u8>,
990    },
991}
992
993#[derive(Clone, Debug)]
994enum WorldEv {
995    Client(usize),
996    Deliver {
997        to: u32,
998        from: u32,
999        id: u64,
1000        msg: Message,
1001        epoch: u64,
1002    },
1003    Ack {
1004        to: u32,
1005        id: u64,
1006        epoch: u64,
1007    },
1008    Crash,
1009    /// A supervisor restarts a process whose shard stopped (not a fault).
1010    Restart,
1011    /// Reader `n` reads its stream again.
1012    Read(usize),
1013}
1014
1015/// A client reading one stream from start to end, reconnecting at its
1016/// offset after every crash.
1017#[derive(Clone, Debug)]
1018struct SimReader {
1019    id: String,
1020    name: String,
1021    run: Option<u64>,
1022    next: u64,
1023    done: bool,
1024}
1025
1026/// What readers saw of one stream: every chunk by offset, and its end.
1027#[derive(Clone, Debug, Default)]
1028struct SeenStream {
1029    chunks: Vec<serde_json::Value>,
1030    end: Option<String>,
1031}
1032
1033pub struct World {
1034    cfg: SimConfig,
1035    m: MachineRef,
1036    rng: Rng,
1037    shards: Vec<Option<ShardRuntime<SimEnv>>>,
1038    /// The machine's shared log, reopened with the process.
1039    log: Option<SimLogRef>,
1040    registry: SharedRegistry,
1041    svc: ServiceRef,
1042    /// New code (a patch) is deployed at the first crash.
1043    deployed: Arc<AtomicBool>,
1044    limits: Arc<LocalLimits>,
1045    max_permits: u32,
1046    codec: Option<Arc<dyn Codec>>,
1047    /// Replies per update ID (every call must get the same result).
1048    update_replies: BTreeMap<String, (Status, Vec<u8>)>,
1049    /// The latest query answer per workflow (must never go back).
1050    query_answers: BTreeMap<String, u64>,
1051    /// Every query answer per workflow.
1052    all_answers: BTreeMap<String, BTreeSet<u64>>,
1053    /// Completions acknowledged per workflow.
1054    completions: BTreeMap<String, Vec<String>>,
1055    tokens_seen: usize,
1056    /// Workflows with a reset in flight: their result may change.
1057    resetting: BTreeSet<String>,
1058    parents: Vec<(String, u32)>,
1059    events: BTreeMap<(u64, u64), WorldEv>,
1060    ev_seq: u64,
1061    actions: Vec<Action>,
1062    /// Action index -> acknowledged.
1063    acked: Vec<bool>,
1064    /// Debounced or batched event ID -> the run its receipt named.
1065    receipts: BTreeMap<String, String>,
1066    /// Object call ID -> its answer, when the client first sent it, and
1067    /// when the first answer came.
1068    calls: BTreeMap<String, crate::scenario::CallReply>,
1069    call_sent: BTreeMap<String, u64>,
1070    tokens: BTreeMap<u64, usize>,
1071    next_token: u64,
1072    results: BTreeMap<String, (Status, Vec<u8>)>,
1073    faults: bool,
1074    report: Report,
1075    trace: Vec<String>,
1076    /// A user scenario's requests (taken by `plan`) and checks.
1077    user_actions: Vec<(u64, UserAction)>,
1078    checks: Vec<Check>,
1079    /// Stream readers, their reads in flight, and what they saw by
1080    /// `(workflow ID, run, stream)`.
1081    readers: Vec<SimReader>,
1082    read_tokens: BTreeMap<u64, usize>,
1083    seen: BTreeMap<(String, u64, String), SeenStream>,
1084}
1085
1086impl World {
1087    pub fn new(cfg: SimConfig) -> Self {
1088        let mut rng = Rng::new(cfg.seed);
1089        let m = Rc::new(RefCell::new(Machine {
1090            disk: Default::default(),
1091            now: 1_000_000,
1092            rng: rng.fork(),
1093            io: BTreeMap::new(),
1094            io_seq: 0,
1095            epoch: 0,
1096            flush_fail_per_mille: cfg.flush_fail_per_mille,
1097            max_io_ms: cfg.max_io_ms,
1098            log: Vec::new(),
1099        }));
1100        let svc: ServiceRef = Arc::new(Mutex::new(Service {
1101            seed: cfg.seed,
1102            ..Default::default()
1103        }));
1104        let registry: SharedRegistry = Arc::new(RwLock::new(Default::default()));
1105        let deployed = Arc::new(AtomicBool::new(false));
1106        let mut setup = None;
1107        match &cfg.user {
1108            Some(user) => {
1109                let mut s = Setup::new(cfg.seed, registry.clone());
1110                (user.0)(&mut s);
1111                setup = Some(s);
1112            }
1113            None => register(&registry, &svc, deployed.clone()),
1114        }
1115        if let Ok(mut r) = registry.write() {
1116            fusee::schedule::register_builtins(&mut r);
1117        }
1118        sim_timers_clear();
1119        NOW.with(|n| n.set(1_000_000));
1120        let mut w = Self {
1121            cfg: cfg.clone(),
1122            m,
1123            rng,
1124            shards: Vec::new(),
1125            log: None,
1126            registry,
1127            svc,
1128            deployed,
1129            limits: Arc::new(LocalLimits::new()),
1130            max_permits: 0,
1131            codec: if cfg.seed % 3 == 1 {
1132                Some(Arc::new(SimCodec))
1133            } else {
1134                None
1135            },
1136            update_replies: BTreeMap::new(),
1137            query_answers: BTreeMap::new(),
1138            all_answers: BTreeMap::new(),
1139            completions: BTreeMap::new(),
1140            tokens_seen: 0,
1141            resetting: BTreeSet::new(),
1142            parents: Vec::new(),
1143            events: BTreeMap::new(),
1144            ev_seq: 0,
1145            actions: Vec::new(),
1146            acked: Vec::new(),
1147            receipts: BTreeMap::new(),
1148            calls: BTreeMap::new(),
1149            call_sent: BTreeMap::new(),
1150            tokens: BTreeMap::new(),
1151            next_token: 1,
1152            results: BTreeMap::new(),
1153            faults: true,
1154            report: Report {
1155                seed: cfg.seed,
1156                workflows: cfg.workflows,
1157                ..Default::default()
1158            },
1159            trace: Vec::new(),
1160            user_actions: Vec::new(),
1161            checks: Vec::new(),
1162            readers: Vec::new(),
1163            read_tokens: BTreeMap::new(),
1164            seen: BTreeMap::new(),
1165        };
1166        if let Some(s) = setup {
1167            w.report.workflows = s
1168                .actions
1169                .iter()
1170                .filter(|a| matches!(a.1, UserAction::Start { .. }))
1171                .count() as u32;
1172            w.user_actions = s.actions;
1173            w.checks = s.checks;
1174            w.plan_user();
1175        } else {
1176            w.plan();
1177        }
1178        w
1179    }
1180
1181    /// A user scenario's requests, then crashes as for the built-in
1182    /// workload.
1183    fn plan_user(&mut self) {
1184        let start = self.now();
1185        for (at, a) in std::mem::take(&mut self.user_actions) {
1186            let action = match a {
1187                UserAction::Start { id, wtype, input } => Action::StartAs { id, wtype, input },
1188                UserAction::Signal { id, name, payload } => Action::Signal {
1189                    id,
1190                    name,
1191                    payload,
1192                    dedup: 1 + self.rng.next() % (1 << 40),
1193                },
1194                UserAction::Cancel { id } => Action::Cancel { id },
1195                UserAction::Terminate { id } => Action::Terminate { id },
1196                UserAction::ReadStream { id, name } => {
1197                    let n = self.readers.len();
1198                    self.readers.push(SimReader {
1199                        id,
1200                        name,
1201                        run: None,
1202                        next: 0,
1203                        done: false,
1204                    });
1205                    self.at(start + at, WorldEv::Read(n));
1206                    continue;
1207                }
1208                UserAction::Call {
1209                    wtype,
1210                    key,
1211                    handler,
1212                    call_id,
1213                    arg,
1214                } => Action::ObjectCall {
1215                    id: fusee::object::object_id(&wtype, &key),
1216                    wtype,
1217                    handler,
1218                    call_id,
1219                    arg,
1220                },
1221                UserAction::Enqueue {
1222                    workflow,
1223                    key,
1224                    event_id,
1225                    input,
1226                    rule,
1227                } => {
1228                    // A third of the seeds make coordinators continue as new
1229                    // every few events, carrying windows and event IDs.
1230                    let continue_at = [0, 12, 30][(self.cfg.seed % 3) as usize];
1231                    enqueue_action(&workflow, &key, &event_id, input, rule, continue_at)
1232                }
1233            };
1234            let ix = self.actions.len();
1235            self.actions.push(action);
1236            self.at(start + at, WorldEv::Client(ix));
1237        }
1238        self.acked = vec![false; self.actions.len()];
1239        self.plan_crashes(start);
1240    }
1241
1242    fn plan_crashes(&mut self, start: u64) {
1243        if self.cfg.crash_every_ms > 0 {
1244            let mut t = start;
1245            loop {
1246                t += 1 + self.rng.below(self.cfg.crash_every_ms * 2);
1247                if t > start + self.cfg.duration_ms {
1248                    break;
1249                }
1250                self.at(t, WorldEv::Crash);
1251            }
1252        }
1253    }
1254
1255    fn now(&self) -> u64 {
1256        self.m.borrow().now
1257    }
1258
1259    fn log(&mut self, s: String) {
1260        let t = self.now();
1261        self.trace.push(format!("[{t}] {s}"));
1262        if self.trace.len() > 400 {
1263            self.trace.drain(..100);
1264        }
1265    }
1266
1267    fn at(&mut self, t: u64, ev: WorldEv) {
1268        let s = self.ev_seq;
1269        self.ev_seq += 1;
1270        self.events.insert((t, s), ev);
1271    }
1272
1273    fn plan(&mut self) {
1274        let start = self.now();
1275        let n = self.cfg.workflows;
1276        let span = self.cfg.duration_ms / 2;
1277        let mut orders = Vec::new();
1278        for i in 0..n {
1279            let id = format!("wf-{i}");
1280            let job = match self.rng.below(20) {
1281                0..=7 | 18 => {
1282                    orders.push(id.clone());
1283                    Job::Order { n: u64::from(i) }
1284                }
1285                19 => Job::Reader { n: u64::from(i) },
1286                8..=9 => Job::Fanout {
1287                    n: 2 + self.rng.below(5) as u32,
1288                },
1289                10 => Job::Pinger {
1290                    target: orders.last().cloned().unwrap_or_else(|| "nobody".into()),
1291                },
1292                11 => Job::Counter,
1293                12 => Job::Looper {
1294                    left: 1 + self.rng.below(4) as u32,
1295                    acc: u64::from(i),
1296                },
1297                13 => Job::Patchy { n: u64::from(i) },
1298                14 => Job::Export,
1299                15 => Job::Approval,
1300                16 => {
1301                    let kids = 1 + self.rng.below(4) as u32;
1302                    self.parents.push((id.clone(), kids));
1303                    Job::Parent { kids }
1304                }
1305                _ => Job::Resettable { n: u64::from(i) },
1306            };
1307            let t = start + self.rng.below(span);
1308            let ix = self.actions.len();
1309            self.actions.push(Action::Start {
1310                id: id.clone(),
1311                job: job.clone(),
1312            });
1313            self.at(t, WorldEv::Client(ix));
1314            match &job {
1315                Job::Counter => {
1316                    for u in 0..1 + self.rng.below(4) {
1317                        let ix = self.actions.len();
1318                        self.actions.push(Action::Update {
1319                            id: id.clone(),
1320                            update_id: format!("{id}/u{u}"),
1321                            value: self.rng.below(1200),
1322                        });
1323                        let t2 = t + 1 + self.rng.below(120);
1324                        self.at(t2, WorldEv::Client(ix));
1325                        // A retried call with the same update ID.
1326                        if self.rng.chance(300) {
1327                            let t3 = t2 + self.rng.below(60);
1328                            self.at(t3, WorldEv::Client(ix));
1329                        }
1330                        // A query while the update's handler may be running.
1331                        let ix = self.actions.len();
1332                        self.actions.push(Action::Query { id: id.clone() });
1333                        let t4 = t2 + 1 + self.rng.below(5);
1334                        self.at(t4, WorldEv::Client(ix));
1335                    }
1336                    for _ in 0..2 + self.rng.below(6) {
1337                        let ix = self.actions.len();
1338                        self.actions.push(Action::Query { id: id.clone() });
1339                        let t2 = t + 1 + self.rng.below(150);
1340                        self.at(t2, WorldEv::Client(ix));
1341                    }
1342                }
1343                Job::Parent { .. } if self.rng.chance(400) => {
1344                    let ix = self.actions.len();
1345                    self.actions.push(Action::Terminate { id: id.clone() });
1346                    let t2 = t + self.rng.below(40);
1347                    self.at(t2, WorldEv::Client(ix));
1348                }
1349                Job::Resettable { .. } if self.rng.chance(600) => {
1350                    let ix = self.actions.len();
1351                    self.actions.push(Action::Reset { id: id.clone() });
1352                    let t2 = t + self.rng.below(80);
1353                    self.at(t2, WorldEv::Client(ix));
1354                }
1355                _ => {}
1356            }
1357            if matches!(job, Job::Order { .. }) {
1358                if self.rng.chance(500) {
1359                    let ix = self.actions.len();
1360                    let dedup = 1 + self.rng.next() % (1 << 40);
1361                    let value = self.rng.below(1000);
1362                    self.actions.push(Action::Signal {
1363                        id: id.clone(),
1364                        name: "approve".into(),
1365                        payload: serde_json::to_vec(&value).unwrap_or_default(),
1366                        dedup,
1367                    });
1368                    let t2 = t + 1 + self.rng.below(80);
1369                    self.at(t2, WorldEv::Client(ix));
1370                }
1371                if self.rng.chance(120) {
1372                    let ix = self.actions.len();
1373                    self.actions.push(Action::Cancel { id: id.clone() });
1374                    let t3 = t + self.rng.below(40);
1375                    self.at(t3, WorldEv::Client(ix));
1376                }
1377            }
1378        }
1379        if self.cfg.seed.is_multiple_of(2) {
1380            let overlap = [
1381                Overlap::Skip,
1382                Overlap::BufferOne,
1383                Overlap::AllowAll,
1384                Overlap::CancelOther,
1385            ][self.rng.below(4) as usize];
1386            let spec = ScheduleSpec {
1387                every_ms: Some([23, 41][self.rng.below(2) as usize]),
1388                overlap,
1389                jitter_ms: self.rng.below(5),
1390                workflow: "tick".into(),
1391                input: serde_json::json!(self.rng.chance(300)),
1392                ..Default::default()
1393            };
1394            let ix = self.actions.len();
1395            self.actions.push(Action::Schedule {
1396                name: "tick".into(),
1397                spec,
1398            });
1399            let t = start + self.rng.below(span);
1400            self.at(t, WorldEv::Client(ix));
1401            // The schedule is deleted when the client is done.
1402            let ix = self.actions.len();
1403            self.actions.push(Action::Terminate {
1404                id: schedule_id("tick"),
1405            });
1406            self.at(start + self.cfg.duration_ms, WorldEv::Client(ix));
1407        }
1408        if self.cfg.seed % 4 == 3 {
1409            self.plan_queues(start, span);
1410        }
1411        self.plan_streams(start, span);
1412        self.acked = vec![false; self.actions.len()];
1413        self.plan_crashes(start);
1414    }
1415
1416    /// Model calls that stream their tokens, and readers of their streams
1417    /// that start before, during and after them. A random stream of its
1418    /// own, so the rest of the seed's plan stays as it was.
1419    fn plan_streams(&mut self, start: u64, span: u64) {
1420        let mut r = Rng::new(self.cfg.seed ^ 0x5717_ea35_c0de_0001);
1421        for k in 0..1 + r.below(3) {
1422            let id = format!("talk-{k}");
1423            let t = start + r.below(span.max(1));
1424            let ix = self.actions.len();
1425            self.actions.push(Action::Start {
1426                id: id.clone(),
1427                job: Job::Talker {
1428                    n: 3 + r.below(10) as u32,
1429                },
1430            });
1431            self.at(t, WorldEv::Client(ix));
1432            for name in ["tokens", "progress", "tokens"] {
1433                if name == "tokens" && r.chance(300) {
1434                    continue;
1435                }
1436                let n = self.readers.len();
1437                self.readers.push(SimReader {
1438                    id: id.clone(),
1439                    name: name.into(),
1440                    run: None,
1441                    next: 0,
1442                    done: false,
1443                });
1444                self.at(t.saturating_sub(5) + r.below(80), WorldEv::Read(n));
1445            }
1446        }
1447    }
1448
1449    /// Debounced starts and batched events of `echo`, in bursts, each with
1450    /// its own event ID (the input is the ID, so checks can find it).
1451    fn plan_queues(&mut self, start: u64, span: u64) {
1452        let continue_at = [0, 12, 30][(self.cfg.seed / 4 % 3) as usize];
1453        for k in 0..4u64 {
1454            let rule = if k % 2 == 1 {
1455                Rule::Batch(fusee::Batch::new(
1456                    2 + self.rng.below(3) as u32,
1457                    Duration::from_millis(20 + self.rng.below(60)),
1458                ))
1459            } else {
1460                Rule::Debounce(
1461                    fusee::Debounce::new(Duration::from_millis(5 + self.rng.below(40)))
1462                        .max_wait(Duration::from_millis(30 + self.rng.below(80))),
1463                )
1464            };
1465            let mut t = start + self.rng.below(span);
1466            for i in 0..3 + self.rng.below(6) {
1467                t += if self.rng.chance(300) {
1468                    40 + self.rng.below(100)
1469                } else {
1470                    self.rng.below(15)
1471                };
1472                let id = format!("q{k}/e{i}");
1473                let ix = self.actions.len();
1474                self.actions.push(enqueue_action(
1475                    "echo",
1476                    &format!("key-{k}"),
1477                    &id,
1478                    serde_json::json!(id),
1479                    rule.clone(),
1480                    continue_at,
1481                ));
1482                self.at(t, WorldEv::Client(ix));
1483            }
1484        }
1485    }
1486
1487    /// The built-in workload's windows: a batch run's input holds each of
1488    /// its events once, and no event is in two runs; a debounced run's
1489    /// input is one of its own window's starts.
1490    fn check_queue_contents(&self) -> Result<(), Failure> {
1491        for a in &self.actions {
1492            let Action::Enqueue {
1493                wtype, event_id, ..
1494            } = a
1495            else {
1496                continue;
1497            };
1498            let Some(run) = self.receipts.get(event_id) else {
1499                continue;
1500            };
1501            let got: serde_json::Value = match self.results.get(run) {
1502                Some((Status::Ok, p)) => serde_json::from_slice(p).unwrap_or_default(),
1503                other => {
1504                    return Err(self.fail(format!(
1505                        "window run {run} of event {event_id} ended with {other:?}"
1506                    )));
1507                }
1508            };
1509            // The events whose receipts name this run.
1510            let mates: Vec<&String> = self
1511                .receipts
1512                .iter()
1513                .filter(|(_, r)| *r == run)
1514                .map(|(e, _)| e)
1515                .collect();
1516            if *wtype == BATCH_TYPE {
1517                // Each event once in its own run, and nothing else there:
1518                // so no event is in two runs.
1519                let events = got.as_array().cloned().unwrap_or_default();
1520                let n = events
1521                    .iter()
1522                    .filter(|e| e.as_str() == Some(event_id))
1523                    .count();
1524                if n != 1 || events.len() != mates.len() {
1525                    return Err(self.fail(format!(
1526                        "batch run {run} holds event {event_id} {n} times and {} events \
1527                         in all, but {} receipts name it: {got}",
1528                        events.len(),
1529                        mates.len()
1530                    )));
1531                }
1532            } else if !mates.iter().any(|e| got.as_str() == Some(e.as_str())) {
1533                return Err(self.fail(format!(
1534                    "debounced run {run} took {got}, not a start of its window"
1535                )));
1536            }
1537        }
1538        Ok(())
1539    }
1540
1541    fn runtime_config(&self, shard: u32) -> RuntimeConfig {
1542        RuntimeConfig {
1543            shard,
1544            shards: self.cfg.shards,
1545            retention_ms: 3_600_000,
1546            park_after_ms: self.cfg.park_after_ms,
1547            // A third of the seeds park every suspended run at each sweep
1548            // through the memory budget instead of waiting `park_after_ms`.
1549            memory_budget: if self.cfg.seed % 3 == 1 { 1 } else { 0 },
1550            checkpoint_interval_ms: self.cfg.checkpoint_interval_ms,
1551            max_loaded: 100_000,
1552            segment_bytes: self.cfg.segment_bytes,
1553            inline_flush: false,
1554            inline_max_us: 0,
1555            keep_history: self.cfg.seed.is_multiple_of(2),
1556            // From bits of the seed the other knobs do not use: index bases
1557            // written a few runs at a time between checkpoints, and often,
1558            // so that they interleave with parks, wakes and crashes.
1559            index_chunk_records: [1, 3, 64, fusee::runtime::INDEX_CHUNK_RECORDS]
1560                [(self.cfg.seed / 72 % 4) as usize],
1561            index_rebase_deltas: [1, 2, 8, 64][(self.cfg.seed / 288 % 4) as usize],
1562            sabotage: self.cfg.sabotage,
1563            ..RuntimeConfig::default()
1564        }
1565    }
1566
1567    fn open_all(&mut self) -> Result<(), String> {
1568        self.shards.clear();
1569        self.log = None;
1570        if self.cfg.shared_log {
1571            self.log = Some(
1572                SimLog::open(
1573                    &self.m,
1574                    self.cfg.shards,
1575                    self.cfg.segment_bytes,
1576                    // From bits of the seed the other knobs do not use.
1577                    1 + (self.cfg.seed / 2 % 3) as usize,
1578                    (self.cfg.seed / 6 % 3) as usize,
1579                    crate::env::Offload {
1580                        threshold: self.cfg.offload_bytes,
1581                        early: self.cfg.early_blobs,
1582                    },
1583                )
1584                .map_err(|e| format!("the shared log failed to recover: {e}"))?,
1585            );
1586        }
1587        for shard in 0..self.cfg.shards {
1588            let env = match &self.log {
1589                Some(log) => SimEnv::with_log(shard, self.m.clone(), log.clone()),
1590                None => SimEnv::new(shard, self.m.clone()),
1591            };
1592            let notify: fusee::env::Notify = Arc::new(|| {});
1593            let services = Services {
1594                codec: self.codec.clone(),
1595                limits: self.limits.clone(),
1596            };
1597            self.limits.define(
1598                "svc",
1599                LimitSpec {
1600                    concurrent: 2,
1601                    per_second: 0.0,
1602                },
1603            );
1604            match ShardRuntime::open_with(
1605                env,
1606                self.runtime_config(shard),
1607                self.registry.clone(),
1608                false,
1609                services,
1610                notify,
1611            ) {
1612                Ok(rt) => self.shards.push(Some(rt)),
1613                Err(e) => return Err(format!("shard {shard} failed to recover: {e}")),
1614            }
1615        }
1616        Ok(())
1617    }
1618
1619    fn shard_rt(&self, shard: u32) -> Option<&ShardRuntime<SimEnv>> {
1620        self.shards.get(shard as usize).and_then(|s| s.as_ref())
1621    }
1622
1623    fn fail(&self, message: String) -> Failure {
1624        let mut trace = self.trace.clone();
1625        trace.extend(self.m.borrow().log.iter().rev().take(20).rev().cloned());
1626        Failure {
1627            seed: self.cfg.seed,
1628            message,
1629            trace,
1630            scenario: self.cfg.user.is_some(),
1631        }
1632    }
1633
1634    fn do_action(&mut self, ix: usize) {
1635        if self.acked[ix] {
1636            return;
1637        }
1638        let token = self.next_token;
1639        self.next_token += 1;
1640        let action = self.actions[ix].clone();
1641        let shards = self.cfg.shards;
1642        let r = match &action {
1643            Action::Start { id, job } => {
1644                let shard = shard_of(id, shards);
1645                // A client that retries uses the existing run.
1646                let mut req = StartRequest::new(
1647                    id,
1648                    "job",
1649                    Arc::from(serde_json::to_vec(job).unwrap_or_default()),
1650                    IdPolicy::ATTACH,
1651                );
1652                req.token = Some(token);
1653                self.shard_rt(shard).map(|rt| rt.start(req).map(|_| ()))
1654            }
1655            Action::StartAs { id, wtype, input } => {
1656                let shard = shard_of(id, shards);
1657                let mut req = StartRequest::new(id, wtype, Arc::from(&input[..]), IdPolicy::ATTACH);
1658                req.token = Some(token);
1659                self.shard_rt(shard).map(|rt| rt.start(req).map(|_| ()))
1660            }
1661            Action::Schedule { name, spec } => {
1662                let id = schedule_id(name);
1663                let state = ScheduleState {
1664                    name: name.clone(),
1665                    spec: spec.clone(),
1666                    ..Default::default()
1667                };
1668                let mut req = StartRequest::new(
1669                    &id,
1670                    SCHEDULE_TYPE,
1671                    Arc::from(serde_json::to_vec(&state).unwrap_or_default()),
1672                    IdPolicy::ATTACH,
1673                );
1674                req.token = Some(token);
1675                self.shard_rt(shard_of(&id, shards))
1676                    .map(|rt| rt.start(req).map(|_| ()))
1677            }
1678            Action::Terminate { id } => self
1679                .shard_rt(shard_of(id, shards))
1680                .map(|rt| rt.terminate(id, "client terminated", Some(token))),
1681            Action::Update {
1682                id,
1683                update_id,
1684                value,
1685            } => {
1686                let payload: Payload = Arc::from(serde_json::to_vec(value).unwrap_or_default());
1687                self.shard_rt(shard_of(id, shards))
1688                    .map(|rt| rt.update(id, "add", update_id, payload, Some(token)))
1689            }
1690            Action::Query { id } => self
1691                .shard_rt(shard_of(id, shards))
1692                .map(|rt| rt.query(id, "total", Arc::from(&b"null"[..]), token)),
1693            Action::Reset { id } => {
1694                self.resetting.insert(id.clone());
1695                self.results.remove(id);
1696                self.shard_rt(shard_of(id, shards))
1697                    .map(|rt| rt.reset(id, "b", true, None, Some(token)).map(|_| ()))
1698            }
1699            Action::Complete { wf, token: t } => {
1700                let payload: Payload =
1701                    Arc::from(serde_json::to_vec(&(wf.len() as u64 * 7)).unwrap_or_default());
1702                self.shard_rt(shard_of(wf, shards))
1703                    .map(|rt| rt.complete(t, Status::Ok, payload, Some(token)))
1704            }
1705            Action::Signal {
1706                id,
1707                name,
1708                payload,
1709                dedup,
1710            } => {
1711                // Only signal workflows whose start was acknowledged.
1712                let started = self
1713                    .actions
1714                    .iter()
1715                    .zip(&self.acked)
1716                    .any(|(a, ok)| {
1717                        matches!(a, Action::Start { id: s, .. } | Action::StartAs { id: s, .. } if s == id)
1718                            && *ok
1719                    });
1720                if !started {
1721                    let t = self.now() + 5;
1722                    self.at(t, WorldEv::Client(ix));
1723                    return;
1724                }
1725                let shard = shard_of(id, shards);
1726                let payload: Payload = Arc::from(&payload[..]);
1727                self.shard_rt(shard)
1728                    .map(|rt| rt.signal(id, name, payload, *dedup, Some(token)))
1729            }
1730            Action::Cancel { id } => {
1731                let shard = shard_of(id, shards);
1732                self.shard_rt(shard)
1733                    .map(|rt| rt.cancel(id, "client cancelled", Some(token)))
1734            }
1735            Action::ObjectCall {
1736                id,
1737                wtype,
1738                handler,
1739                call_id,
1740                arg,
1741            } => {
1742                // As `ObjectRef::call_with_id` sends it: update-with-start.
1743                let now = self.now();
1744                self.call_sent.entry(call_id.clone()).or_insert(now);
1745                let mut req = StartRequest::new(
1746                    id,
1747                    wtype,
1748                    Arc::from(&b"null"[..]),
1749                    IdPolicy {
1750                        running: OnRunning::UseExisting,
1751                        finished: OnFinished::Allow,
1752                    },
1753                );
1754                req.update = Some(UpdateRequest {
1755                    name: handler.clone(),
1756                    id: call_id.clone(),
1757                    payload: Arc::from(&arg[..]),
1758                    token: Some(token),
1759                });
1760                let r = self
1761                    .shard_rt(shard_of(id, shards))
1762                    .map(|rt| rt.start(req).map(|_| ()));
1763                self.log(format!("call {call_id} to {id}.{handler}: {r:?}"));
1764                if !matches!(r, Some(Ok(()))) {
1765                    // Transient (busy, recovering): send it again.
1766                    let t = self.now() + 10;
1767                    self.at(t, WorldEv::Client(ix));
1768                    return;
1769                }
1770                r
1771            }
1772            Action::Enqueue {
1773                coordinator,
1774                wtype,
1775                state,
1776                event_id,
1777                event,
1778                ..
1779            } => {
1780                // As `Engine::debounce_raw` sends it: update-with-start.
1781                let mut req = StartRequest::new(
1782                    coordinator,
1783                    wtype,
1784                    Arc::from(&state[..]),
1785                    IdPolicy {
1786                        running: OnRunning::UseExisting,
1787                        finished: OnFinished::Allow,
1788                    },
1789                );
1790                req.update = Some(UpdateRequest {
1791                    name: EVENT.into(),
1792                    id: event_id.clone(),
1793                    payload: Arc::from(&event[..]),
1794                    token: Some(token),
1795                });
1796                let r = self
1797                    .shard_rt(shard_of(coordinator, shards))
1798                    .map(|rt| rt.start(req).map(|_| ()));
1799                self.log(format!("event {event_id} to {coordinator}: {r:?}"));
1800                if !matches!(r, Some(Ok(()))) {
1801                    // Every refusal here is transient (busy, recovering).
1802                    let t = self.now() + 10;
1803                    self.at(t, WorldEv::Client(ix));
1804                    return;
1805                }
1806                r
1807            }
1808        };
1809        if let Action::Update { id, update_id, .. } = &action {
1810            self.log(format!("update {update_id} to {id}: {r:?} (token {token})"));
1811        }
1812        match r {
1813            Some(Ok(())) => {
1814                self.tokens.insert(token, ix);
1815            }
1816            Some(Err(Error::NotFound(_) | Error::Failed(_) | Error::Rejected(_))) => {
1817                // Signal or cancel for a run that finished or was never
1818                // started: there is nothing to deliver.
1819                self.acked[ix] = true;
1820                if let Action::Reset { id } = &action {
1821                    self.resetting.remove(id);
1822                }
1823            }
1824            _ => {
1825                let t = self.now() + 10;
1826                self.at(t, WorldEv::Client(ix));
1827            }
1828        }
1829    }
1830
1831    fn on_output(&mut self, shard: u32, out: Output) -> Result<(), Failure> {
1832        match out {
1833            Output::Ack(t) | Output::Reject(t) => {
1834                if let Some(ix) = self.tokens.remove(&t) {
1835                    self.acked[ix] = true;
1836                    match self.actions[ix].clone() {
1837                        Action::Reset { id } => {
1838                            let found = self
1839                                .shard_rt(shard_of(&id, self.cfg.shards))
1840                                .and_then(|rt| rt.find(&id));
1841                            self.log(format!(
1842                                "reset {id} acknowledged; the ID now names run {found:?}"
1843                            ));
1844                            self.report.resets += 1;
1845                            self.resetting.remove(&id);
1846                            self.results.remove(&id);
1847                        }
1848                        Action::Complete { wf, token } => {
1849                            let accepted = self.completions.entry(wf.clone()).or_default();
1850                            if !accepted.contains(&token) {
1851                                accepted.push(token);
1852                            }
1853                            if accepted.len() > 1 {
1854                                let all = accepted.clone();
1855                                return Err(self.fail(format!(
1856                                    "two completions were accepted for {wf}: {all:?}"
1857                                )));
1858                            }
1859                        }
1860                        _ => {}
1861                    }
1862                }
1863            }
1864            Output::Refuse { token, message } => {
1865                if let Some(ix) = self.tokens.remove(&token) {
1866                    if let Action::ObjectCall { call_id, .. } = &self.actions[ix] {
1867                        // The key could not take it now (its run was ending
1868                        // or not running code yet): the client tries again.
1869                        self.log(format!(
1870                            "call {call_id} refused: {}; again",
1871                            String::from_utf8_lossy(&message)
1872                        ));
1873                        let t = self.now() + 10;
1874                        self.at(t, WorldEv::Client(ix));
1875                        return Ok(());
1876                    }
1877                    if let Action::Enqueue { event_id, .. } = &self.actions[ix] {
1878                        return Err(self.fail(format!(
1879                            "event {event_id} was refused: {}",
1880                            String::from_utf8_lossy(&message)
1881                        )));
1882                    }
1883                    self.acked[ix] = true;
1884                }
1885            }
1886            Output::Reply {
1887                token,
1888                status,
1889                payload,
1890            } if self.read_tokens.contains_key(&token) => {
1891                if let Some(n) = self.read_tokens.remove(&token) {
1892                    self.on_read(n, status, &payload)?;
1893                }
1894            }
1895            Output::Reply {
1896                token,
1897                status,
1898                payload,
1899            } => {
1900                if let Some(ix) = self.tokens.remove(&token) {
1901                    if let Action::Enqueue { event_id, .. } = self.actions[ix].clone() {
1902                        return self.on_receipt(ix, event_id, status, &payload);
1903                    }
1904                    if let Action::ObjectCall { call_id, .. } = self.actions[ix].clone() {
1905                        return self.on_call_reply(ix, call_id, status, &payload);
1906                    }
1907                    self.acked[ix] = true;
1908                    match self.actions[ix].clone() {
1909                        Action::Update { update_id, .. } => {
1910                            let got = (status, payload.to_vec());
1911                            match self.update_replies.get(&update_id) {
1912                                Some(prev) if *prev != got => {
1913                                    return Err(self.fail(format!(
1914                                        "update {update_id} answered {:?} {:?} and later {:?} {:?}",
1915                                        prev.0,
1916                                        String::from_utf8_lossy(&prev.1),
1917                                        got.0,
1918                                        String::from_utf8_lossy(&got.1)
1919                                    )));
1920                                }
1921                                Some(_) => {}
1922                                None => {
1923                                    self.update_replies.insert(update_id, got);
1924                                }
1925                            }
1926                        }
1927                        Action::Query { id } => {
1928                            let v: u64 = serde_json::from_slice(&payload).unwrap_or(0);
1929                            let prev = self.query_answers.get(&id).copied().unwrap_or(0);
1930                            if v < prev {
1931                                return Err(self.fail(format!(
1932                                    "query of {id} answered {v} after answering {prev}: an answer \
1933                                     left before the state it read was durable"
1934                                )));
1935                            }
1936                            self.all_answers.entry(id.clone()).or_default().insert(v);
1937                            self.query_answers.insert(id, v);
1938                        }
1939                        _ => {}
1940                    }
1941                }
1942            }
1943            Output::Result { status, .. } if status.is_handover() => {}
1944            Output::Result {
1945                run,
1946                status,
1947                payload,
1948            } => {
1949                // Recovery regenerates the outputs of older runs too; only
1950                // the ID's current run speaks for the ID.
1951                let id = self
1952                    .shard_rt(shard)
1953                    .and_then(|rt| rt.summary(run).filter(|s| rt.find(&s.id) == Some(run)))
1954                    .map(|s| s.id);
1955                if let Some(id) = id {
1956                    self.log(format!("released result of {id} run {run}: {status:?}"));
1957                    self.record_result(&id, status, payload.to_vec())?;
1958                }
1959            }
1960            _ => {}
1961        }
1962        Ok(())
1963    }
1964
1965    /// A key answered a call. Every answer to one call ID must be the same:
1966    /// a call runs once, however often the client sent it.
1967    fn on_call_reply(
1968        &mut self,
1969        ix: usize,
1970        call_id: String,
1971        status: Status,
1972        payload: &[u8],
1973    ) -> Result<(), Failure> {
1974        let now = self.now();
1975        let status = status.as_str().to_owned();
1976        if let Some(prev) = self.calls.get(&call_id) {
1977            if prev.status != status || prev.payload != payload {
1978                return Err(self.fail(format!(
1979                    "call {call_id} was answered {} {:?} and later {status} {:?}: it ran twice",
1980                    prev.status,
1981                    String::from_utf8_lossy(&prev.payload),
1982                    String::from_utf8_lossy(payload)
1983                )));
1984            }
1985        } else {
1986            let sent_ms = self.call_sent.get(&call_id).copied().unwrap_or(now);
1987            self.log(format!(
1988                "call {call_id} answered {status} {}",
1989                String::from_utf8_lossy(payload)
1990            ));
1991            self.calls.insert(
1992                call_id,
1993                crate::scenario::CallReply {
1994                    status,
1995                    payload: payload.to_vec(),
1996                    sent_ms,
1997                    answered_ms: now,
1998                },
1999            );
2000        }
2001        self.acked[ix] = true;
2002        Ok(())
2003    }
2004
2005    /// A coordinator answered an event: a receipt, or "ended first", which
2006    /// the client answers by sending the event again.
2007    fn on_receipt(
2008        &mut self,
2009        ix: usize,
2010        event_id: String,
2011        status: Status,
2012        payload: &[u8],
2013    ) -> Result<(), Failure> {
2014        match status {
2015            Status::Ok => {
2016                let r: Receipt = serde_json::from_slice(payload).map_err(|e| {
2017                    self.fail(format!("event {event_id} got an unreadable receipt: {e}"))
2018                })?;
2019                self.log(format!("event {event_id} joined {}", r.id));
2020                if let Some(prev) = self.receipts.get(&event_id)
2021                    && *prev != r.id
2022                {
2023                    return Err(self.fail(format!(
2024                        "event {event_id} joined run {prev} and later run {}",
2025                        r.id
2026                    )));
2027                }
2028                self.receipts.insert(event_id, r.id);
2029                self.acked[ix] = true;
2030            }
2031            Status::Failed if payload == ENDED => {
2032                self.log(format!("event {event_id}: its coordinator ended; again"));
2033                let t = self.now() + 1;
2034                self.at(t, WorldEv::Client(ix));
2035            }
2036            s => {
2037                return Err(self.fail(format!(
2038                    "event {event_id} failed: {s:?} {}",
2039                    String::from_utf8_lossy(payload)
2040                )));
2041            }
2042        }
2043        Ok(())
2044    }
2045
2046    fn record_result(&mut self, id: &str, status: Status, payload: Vec<u8>) -> Result<(), Failure> {
2047        if self.resetting.contains(id) {
2048            return Ok(());
2049        }
2050        match self.results.get(id) {
2051            Some((s, p)) if *s != status || *p != payload => {
2052                return Err(self.fail(format!(
2053                    "workflow {id} reported result {:?} {:?} and later {:?} {:?}",
2054                    s,
2055                    String::from_utf8_lossy(p),
2056                    status,
2057                    String::from_utf8_lossy(&payload)
2058                )));
2059            }
2060            Some(_) => {}
2061            None => {
2062                self.log(format!("result {id} {status:?}"));
2063                self.results.insert(id.to_owned(), (status, payload));
2064            }
2065        }
2066        Ok(())
2067    }
2068
2069    /// Ticks every shard until nothing moves.
2070    fn settle(&mut self) -> Result<(), Failure> {
2071        for _ in 0..100_000 {
2072            let mut progress = false;
2073            let now = self.now();
2074            progress |= sim_timers_fire(now);
2075            for shard in 0..self.cfg.shards {
2076                let Some(rt) = self.shard_rt(shard) else {
2077                    continue;
2078                };
2079                let tick = rt.tick();
2080                let released = rt.take_released();
2081                let outgoing = rt.take_outgoing();
2082                let acks = rt.take_message_acks();
2083                let failed = rt.with(|i| i.failed().map(str::to_owned));
2084                progress |=
2085                    tick.busy || !released.is_empty() || !outgoing.is_empty() || !acks.is_empty();
2086                for out in released {
2087                    self.on_output(shard, out)?;
2088                }
2089                let epoch = self.m.borrow().epoch;
2090                for o in outgoing {
2091                    let d = self.rng.below(self.cfg.max_msg_ms + 1);
2092                    self.at(
2093                        now + d,
2094                        WorldEv::Deliver {
2095                            to: o.target,
2096                            from: shard,
2097                            id: o.id,
2098                            msg: o.msg,
2099                            epoch,
2100                        },
2101                    );
2102                }
2103                for (to, id) in acks {
2104                    let d = self.rng.below(self.cfg.max_msg_ms + 1);
2105                    self.at(now + d, WorldEv::Ack { to, id, epoch });
2106                }
2107                if let Some(f) = failed
2108                    && !self
2109                        .events
2110                        .values()
2111                        .any(|e| matches!(e, WorldEv::Crash | WorldEv::Restart))
2112                {
2113                    self.log(format!(
2114                        "shard {shard} stopped: {f}; restarting the process"
2115                    ));
2116                    self.at(now + 1, WorldEv::Restart);
2117                }
2118            }
2119            progress |= self.new_tokens();
2120            if !progress {
2121                return Ok(());
2122            }
2123        }
2124        Err(self.fail("the shards never settled".into()))
2125    }
2126
2127    /// The client completes every token the approval system handed out,
2128    /// stale ones included (those must be refused).
2129    fn new_tokens(&mut self) -> bool {
2130        let fresh: Vec<(String, String)> = {
2131            let s = match self.svc.lock() {
2132                Ok(s) => s,
2133                Err(_) => return false,
2134            };
2135            s.tokens[self.tokens_seen.min(s.tokens.len())..].to_vec()
2136        };
2137        if fresh.is_empty() {
2138            return false;
2139        }
2140        self.tokens_seen += fresh.len();
2141        let now = self.now();
2142        for (wf, token) in fresh {
2143            let ix = self.actions.len();
2144            self.actions.push(Action::Complete { wf, token });
2145            self.acked.push(false);
2146            let t = now + self.rng.below(20);
2147            self.at(t, WorldEv::Client(ix));
2148        }
2149        true
2150    }
2151
2152    fn next_time(&self) -> Option<u64> {
2153        let now = self.now();
2154        let mut t: Option<u64> = None;
2155        let mut take = |x: Option<u64>| {
2156            if let Some(x) = x {
2157                t = Some(t.map_or(x, |y: u64| y.min(x)));
2158            }
2159        };
2160        take(self.events.keys().next().map(|k| k.0));
2161        take(self.m.borrow().io.keys().next().map(|k| k.0));
2162        take(sim_timers_next());
2163        for s in self.shards.iter().flatten() {
2164            if s.with(|i| i.failed().is_none()) {
2165                take(s.tick().next_wake_ms);
2166            }
2167        }
2168        t.map(|t| t.max(now))
2169    }
2170
2171    fn crash(&mut self) -> Result<(), Failure> {
2172        self.report.crashes += 1;
2173        self.report.offloaded += self
2174            .log
2175            .as_ref()
2176            .map_or(0, |l| l.borrow().wal.blob_stats().records);
2177        self.log("crash: process dies; unflushed writes are lost or torn".into());
2178        self.check_limits()?;
2179        self.shards.clear();
2180        // The restarted process runs new code (a patch) and has fresh
2181        // limits: permits held by the dead process are gone with it.
2182        self.deployed.store(true, Ordering::Relaxed);
2183        self.limits = Arc::new(LocalLimits::new());
2184        sim_timers_clear();
2185        {
2186            let mut m = self.m.borrow_mut();
2187            let tear = self.cfg.tear;
2188            let mut rng = m.rng.fork();
2189            m.disk.crash(&mut rng, tear);
2190            m.io.clear();
2191            m.epoch += 1;
2192        }
2193        self.events
2194            .retain(|_, e| !matches!(e, WorldEv::Deliver { .. } | WorldEv::Ack { .. }));
2195        self.tokens.clear();
2196        self.read_tokens.clear();
2197        self.open_all().map_err(|e| self.fail(e))?;
2198        self.log(format!("recovered {} shards", self.cfg.shards));
2199        // Readers connect again at their offsets.
2200        let now = self.now();
2201        for n in 0..self.readers.len() {
2202            if !self.readers[n].done {
2203                self.at(now + 1 + n as u64 % 4, WorldEv::Read(n));
2204            }
2205        }
2206        // Client retries everything not acknowledged.
2207        let now = self.now();
2208        for ix in 0..self.actions.len() {
2209            if !self.acked[ix]
2210                && self
2211                    .events
2212                    .values()
2213                    .all(|e| !matches!(e, WorldEv::Client(i) if *i == ix))
2214            {
2215                let started_before = true;
2216                if started_before {
2217                    self.at(now + 1, WorldEv::Client(ix));
2218                }
2219            }
2220        }
2221        self.check_after_recovery()
2222    }
2223
2224    fn check_after_recovery(&mut self) -> Result<(), Failure> {
2225        let shards = self.cfg.shards;
2226        for ix in 0..self.actions.len() {
2227            if !self.acked[ix] {
2228                continue;
2229            }
2230            match self.actions[ix].clone() {
2231                Action::Start { id, .. } | Action::StartAs { id, .. } => {
2232                    let shard = shard_of(&id, shards);
2233                    if self.shard_rt(shard).and_then(|rt| rt.find(&id)).is_none() {
2234                        return Err(self.fail(format!("acknowledged start of {id} was lost")));
2235                    }
2236                }
2237                Action::Signal { id, dedup, .. } => {
2238                    let shard = shard_of(&id, shards);
2239                    let Some(rt) = self.shard_rt(shard) else {
2240                        continue;
2241                    };
2242                    match rt.inspect(&id) {
2243                        Ok(Some(d)) if d.summary.finished_ms.is_none() => {
2244                            let found = d.journal.iter().any(
2245                                |r| matches!(r, Record::Signal { dedup: x, .. } if *x == dedup),
2246                            );
2247                            if !found {
2248                                return Err(self.fail(format!(
2249                                    "acknowledged signal {dedup} to {id} is missing from its journal: {:?} {:?}",
2250                                    d.summary, d.journal
2251                                )));
2252                            }
2253                        }
2254                        Ok(_) => {}
2255                        Err(e) => return Err(self.fail(format!("inspect {id}: {e}"))),
2256                    }
2257                }
2258                Action::Enqueue {
2259                    coordinator,
2260                    event_id,
2261                    ..
2262                } => {
2263                    // Its run has started, or the coordinator that holds
2264                    // its window (or sends the run's start) still exists.
2265                    let Some(run) = self.receipts.get(&event_id) else {
2266                        continue;
2267                    };
2268                    let exists = |id: &str| {
2269                        self.shard_rt(shard_of(id, shards))
2270                            .and_then(|rt| rt.find(id))
2271                            .is_some()
2272                    };
2273                    if !exists(run) && !exists(&coordinator) {
2274                        return Err(self.fail(format!(
2275                            "acknowledged event {event_id} was lost: neither its run {run} nor \
2276                             its coordinator {coordinator} exists"
2277                        )));
2278                    }
2279                }
2280                _ => {}
2281            }
2282        }
2283        self.check_answers()?;
2284        // Results seen before must not change.
2285        let known: Vec<String> = self.results.keys().cloned().collect();
2286        for id in known {
2287            let shard = shard_of(&id, shards);
2288            let r = self.shard_rt(shard).map(|rt| rt.result(&id));
2289            match r {
2290                Some(Ok(ResultState::Done {
2291                    status,
2292                    payload,
2293                    run,
2294                })) => {
2295                    self.log(format!("after recovery {id} is run {run}: {status:?}"));
2296                    self.record_result(&id, status, payload.to_vec())?
2297                }
2298                Some(Ok(ResultState::NotFound)) => {
2299                    return Err(
2300                        self.fail(format!("finished workflow {id} vanished after recovery"))
2301                    );
2302                }
2303                Some(Ok(ResultState::Running(_))) => {
2304                    let job = self.actions.iter().find_map(|a| match a {
2305                        Action::Start { id: x, job } if *x == id => Some(format!("{job:?}")),
2306                        Action::StartAs { id: x, wtype, .. } if *x == id => Some(wtype.clone()),
2307                        _ => None,
2308                    });
2309                    let detail = self
2310                        .shard_rt(shard)
2311                        .and_then(|rt| rt.inspect(&id).ok().flatten());
2312                    return Err(self.fail(format!(
2313                        "workflow {id} ({}) had reported a result but runs again after recovery: {detail:?}",
2314                        job.unwrap_or_default()
2315                    )));
2316                }
2317                Some(Err(e)) => return Err(self.fail(format!("result {id}: {e}"))),
2318                None => {}
2319            }
2320        }
2321        self.check_service()
2322    }
2323
2324    /// Every query answer must be a state the journal recorded: the total
2325    /// after some update (each handler returns the total it set).
2326    fn check_answers(&mut self) -> Result<(), Failure> {
2327        let shards = self.cfg.shards;
2328        for (id, answers) in self.all_answers.clone() {
2329            let Some(rt) = self.shard_rt(shard_of(&id, shards)) else {
2330                continue;
2331            };
2332            let Ok(Some(d)) = rt.inspect(&id) else {
2333                continue;
2334            };
2335            let full = d
2336                .journal
2337                .iter()
2338                .any(|r| !matches!(r, Record::Start { .. } | Record::Done { .. }));
2339            if !full && d.summary.finished_ms.is_some() {
2340                continue;
2341            }
2342            let mut states: BTreeSet<u64> = [0].into();
2343            for r in &d.journal {
2344                if let Record::UpdateDone {
2345                    status: Status::Ok,
2346                    payload,
2347                    ..
2348                } = r
2349                {
2350                    let p = self
2351                        .codec
2352                        .as_ref()
2353                        .and_then(|c| c.decode(payload).ok())
2354                        .unwrap_or_else(|| payload.to_vec());
2355                    if let Ok(v) = serde_json::from_slice::<u64>(&p) {
2356                        states.insert(v);
2357                    }
2358                }
2359            }
2360            if let Some(a) = answers.iter().find(|a| !states.contains(a)) {
2361                return Err(self.fail(format!(
2362                    "query of {id} answered {a}, a state its journal never recorded ({states:?}): \
2363                     the answer left before that state was durable"
2364                )));
2365            }
2366        }
2367        Ok(())
2368    }
2369
2370    fn check_limits(&mut self) -> Result<(), Failure> {
2371        self.max_permits = self.max_permits.max(self.limits.max_in_use("svc"));
2372        if self.max_permits > 2 {
2373            return Err(self.fail(format!(
2374                "limit 'svc' (2 permits) had {} permits out at once",
2375                self.max_permits
2376            )));
2377        }
2378        Ok(())
2379    }
2380
2381    /// End-of-run checks: no run stopped on a determinism error (replaying
2382    /// patched, continued and reset runs reproduced their journals), every
2383    /// accepted update ran once, children followed their close policy.
2384    fn check_final(&mut self) -> Result<(), Failure> {
2385        self.check_limits()?;
2386        self.check_streams()?;
2387        if self.cfg.user.is_none() {
2388            self.check_queue_contents()?;
2389        }
2390        // Runs a debounce or batch window started: one per window, and only
2391        // for windows some acknowledged event joined.
2392        let mut targets = BTreeSet::new();
2393        let mut direct = BTreeSet::new();
2394        for a in &self.actions {
2395            match a {
2396                Action::Enqueue { workflow, .. } => {
2397                    targets.insert(workflow.clone());
2398                }
2399                Action::StartAs { id, .. } => {
2400                    direct.insert(id.clone());
2401                }
2402                _ => {}
2403            }
2404        }
2405        let windows: BTreeSet<&String> = self.receipts.values().collect();
2406        for shard in 0..self.cfg.shards {
2407            let Some(rt) = self.shard_rt(shard) else {
2408                continue;
2409            };
2410            for s in rt.list(1_000_000) {
2411                if s.status == "stuck" {
2412                    return Err(self.fail(format!(
2413                        "{} stopped on a determinism error: {}",
2414                        s.id,
2415                        s.error.unwrap_or_default()
2416                    )));
2417                }
2418                if targets.contains(&s.wtype) && !direct.contains(&s.id) && !windows.contains(&s.id)
2419                {
2420                    return Err(self.fail(format!(
2421                        "run {} of {} was started by a window no acknowledged event joined",
2422                        s.id, s.wtype
2423                    )));
2424                }
2425            }
2426        }
2427        let shards = self.cfg.shards;
2428        let counters: Vec<String> = self
2429            .actions
2430            .iter()
2431            .filter_map(|a| match a {
2432                Action::Start {
2433                    id,
2434                    job: Job::Counter,
2435                } => Some(id.clone()),
2436                _ => None,
2437            })
2438            .collect();
2439        for id in &counters {
2440            // A total only grows, so no answer may exceed the final one.
2441            if let (Some(answer), Some((Status::Ok, p))) =
2442                (self.query_answers.get(id), self.results.get(id))
2443            {
2444                let last: u64 = serde_json::from_slice(p).unwrap_or(u64::MAX);
2445                if *answer > last {
2446                    return Err(self.fail(format!(
2447                        "query of {id} answered {answer} but its final total is {last}: an \
2448                         answer reflected state a crash took back"
2449                    )));
2450                }
2451            }
2452        }
2453        for id in counters {
2454            let Some(rt) = self.shard_rt(shard_of(&id, shards)) else {
2455                continue;
2456            };
2457            if let Ok(Some(d)) = rt.inspect(&id) {
2458                let mut seen = BTreeSet::new();
2459                for r in &d.journal {
2460                    if let Record::Update { id: u, .. } = r
2461                        && !seen.insert(u.to_string())
2462                    {
2463                        return Err(self.fail(format!(
2464                            "update {u} was accepted twice by {id}: {:?}",
2465                            d.journal
2466                        )));
2467                    }
2468                }
2469            }
2470        }
2471        for (parent, kids) in self.parents.clone() {
2472            for k in 0..kids {
2473                let kid = format!("{parent}/kid{k}");
2474                let Some(rt) = self.shard_rt(shard_of(&kid, shards)) else {
2475                    continue;
2476                };
2477                let Some(s) = rt.find(&kid).and_then(|r| rt.summary(r)) else {
2478                    continue;
2479                };
2480                let ok = match k % 2 {
2481                    0 => matches!(s.status.as_str(), "terminated" | "ok"),
2482                    _ => s.status == "ok",
2483                };
2484                if !ok {
2485                    return Err(self.fail(format!(
2486                        "child {kid} ({}) ended '{}' against its parent close policy",
2487                        if k % 2 == 0 { "terminate" } else { "abandon" },
2488                        s.status
2489                    )));
2490                }
2491            }
2492        }
2493        Ok(())
2494    }
2495
2496    fn check_service(&self) -> Result<(), Failure> {
2497        let v = self
2498            .svc
2499            .lock()
2500            .map(|s| s.violations.clone())
2501            .unwrap_or_default();
2502        if let Some(first) = v.first() {
2503            return Err(self.fail(first.clone()));
2504        }
2505        Ok(())
2506    }
2507
2508    fn handle(&mut self, ev: WorldEv) -> Result<(), Failure> {
2509        let epoch = self.m.borrow().epoch;
2510        match ev {
2511            WorldEv::Client(ix) => self.do_action(ix),
2512            WorldEv::Deliver {
2513                to,
2514                from,
2515                id,
2516                msg,
2517                epoch: e,
2518            } => {
2519                if e == epoch
2520                    && let Some(rt) = self.shard_rt(to)
2521                {
2522                    let _ = rt.message(msg, Some((from, id)));
2523                }
2524            }
2525            WorldEv::Ack { to, id, epoch: e } => {
2526                if e == epoch
2527                    && let Some(rt) = self.shard_rt(to)
2528                {
2529                    rt.outbox_ack(id);
2530                }
2531            }
2532            WorldEv::Crash => {
2533                if self.faults {
2534                    self.crash()?;
2535                }
2536            }
2537            WorldEv::Restart => self.crash()?,
2538            WorldEv::Read(n) => self.do_read(n),
2539        }
2540        Ok(())
2541    }
2542
2543    /// Reader `n` asks for its stream from its offset (a long poll of 20
2544    /// simulated ms).
2545    fn do_read(&mut self, n: usize) {
2546        let Some(r) = self.readers.get(n).cloned() else {
2547            return;
2548        };
2549        if r.done || self.read_tokens.values().any(|x| *x == n) {
2550            return;
2551        }
2552        let token = self.next_token;
2553        self.next_token += 1;
2554        let res = self
2555            .shard_rt(shard_of(&r.id, self.cfg.shards))
2556            .map(|rt| rt.stream_read(&r.id, r.run, &r.name, r.next, 20, token));
2557        match res {
2558            Some(Ok(())) => {
2559                self.read_tokens.insert(token, n);
2560            }
2561            // Not started yet, or the shard is down: ask again soon.
2562            _ => {
2563                let t = self.now() + 5;
2564                self.at(t, WorldEv::Read(n));
2565            }
2566        }
2567    }
2568
2569    /// A read's answer: every chunk must be the one readers saw at its
2570    /// offset before (a chunk a reader saw never changes or goes away),
2571    /// with no offset skipped, and a stream ends once.
2572    fn on_read(&mut self, n: usize, status: Status, payload: &[u8]) -> Result<(), Failure> {
2573        let now = self.now();
2574        if status != Status::Ok {
2575            self.at(now + 5, WorldEv::Read(n));
2576            return Ok(());
2577        }
2578        let b = fusee::StreamBatch::parse(payload)
2579            .map_err(|e| self.fail(format!("a stream read answered something unreadable: {e}")))?;
2580        let r = self.readers[n].clone();
2581        if b.from != r.next || r.run.is_some_and(|run| run != b.run) {
2582            return Err(self.fail(format!(
2583                "reader {n} of {} {} asked for offset {} of run {:?} and got offset {} of run {}",
2584                r.id, r.name, r.next, r.run, b.from, b.run
2585            )));
2586        }
2587        let key = (r.id.clone(), b.run, r.name.clone());
2588        let mut seen = self.seen.remove(&key).unwrap_or_default();
2589        for c in &b.chunks {
2590            let v = match &c.value {
2591                fusee::ChunkValue::Json(v) => v.clone(),
2592                fusee::ChunkValue::Bytes(b) => serde_json::json!(b),
2593            };
2594            match seen.chunks.get(c.offset as usize) {
2595                Some(prev) if *prev != v => {
2596                    return Err(self.fail(format!(
2597                        "chunk {} of {} {} was {prev} for one reader and {v} for another: a reader saw a chunk a crash took back",
2598                        c.offset, r.id, r.name
2599                    )));
2600                }
2601                Some(_) => {}
2602                None if c.offset as usize == seen.chunks.len() => seen.chunks.push(v),
2603                None => {
2604                    return Err(self.fail(format!(
2605                        "a reader of {} {} got chunk {} while readers had seen {}",
2606                        r.id,
2607                        r.name,
2608                        c.offset,
2609                        seen.chunks.len()
2610                    )));
2611                }
2612            }
2613        }
2614        self.report.chunks_read += b.chunks.len() as u64;
2615        if let Some(end) = &b.end {
2616            if b.next < seen.chunks.len() as u64 {
2617                return Err(self.fail(format!(
2618                    "{} {} ended at {} chunks after readers saw {}",
2619                    r.id,
2620                    r.name,
2621                    b.next,
2622                    seen.chunks.len()
2623                )));
2624            }
2625            match &seen.end {
2626                Some(prev) if *prev != end.status => {
2627                    return Err(self.fail(format!(
2628                        "{} {} ended '{prev}' for one reader and '{}' for another",
2629                        r.id, r.name, end.status
2630                    )));
2631                }
2632                _ => seen.end = Some(end.status.clone()),
2633            }
2634            self.report.streams_read += 1;
2635        }
2636        self.seen.insert(key, seen);
2637        let reader = &mut self.readers[n];
2638        reader.run = Some(b.run);
2639        reader.next = b.next;
2640        if b.end.is_some() {
2641            reader.done = true;
2642        } else {
2643            self.at(now + (n as u64 % 3), WorldEv::Read(n));
2644        }
2645        Ok(())
2646    }
2647
2648    /// Every finished stream holds exactly what its workflow made: the
2649    /// tokens its step returned (a step that ran again continued after the
2650    /// recorded prefix) and its progress, appended once across replays.
2651    fn check_streams(&self) -> Result<(), Failure> {
2652        for a in &self.actions {
2653            let Action::Start {
2654                id,
2655                job: Job::Talker { .. },
2656            } = a
2657            else {
2658                continue;
2659            };
2660            let Some((Status::Ok, p)) = self.results.get(id) else {
2661                continue;
2662            };
2663            let hash: u64 = serde_json::from_slice(p).unwrap_or(0);
2664            for ((sid, _, name), s) in &self.seen {
2665                if sid != id {
2666                    continue;
2667                }
2668                if s.end.as_deref() != Some("ok") {
2669                    return Err(self.fail(format!(
2670                        "{id} {name} ended {:?}, not ok: {:?}",
2671                        s.end, s.chunks
2672                    )));
2673                }
2674                match name.as_str() {
2675                    "tokens" => {
2676                        let toks: Vec<u64> = s
2677                            .chunks
2678                            .iter()
2679                            .map(|v| v.as_u64().unwrap_or(u64::MAX))
2680                            .collect();
2681                        if tokens_hash(&toks) != hash {
2682                            return Err(self.fail(format!(
2683                                "readers of {id} saw tokens {toks:?}, not the ones its step returned"
2684                            )));
2685                        }
2686                    }
2687                    _ => {
2688                        let want: Vec<serde_json::Value> = TALKER_PROGRESS
2689                            .iter()
2690                            .map(|s| serde_json::json!(s))
2691                            .collect();
2692                        if s.chunks != want {
2693                            return Err(self.fail(format!(
2694                                "readers of {id} saw progress {:?}: a replay appended again, or an append was lost",
2695                                s.chunks
2696                            )));
2697                        }
2698                    }
2699                }
2700            }
2701        }
2702        Ok(())
2703    }
2704
2705    fn handle_io(&mut self, io: Io) {
2706        let epoch = self.m.borrow().epoch;
2707        match io {
2708            Io::Flush { shard, epoch: e } if e == epoch => {
2709                if let Some(rt) = self.shard_rt(shard) {
2710                    rt.with(|i| i.env.complete_flush());
2711                }
2712            }
2713            Io::Checkpoint { shard, epoch: e } if e == epoch => {
2714                if let Some(rt) = self.shard_rt(shard) {
2715                    rt.with(|i| i.env.complete_checkpoint());
2716                }
2717            }
2718            Io::LogFlush { epoch: e, seq } if e == epoch => {
2719                if let Some(log) = self.log.clone() {
2720                    SimLog::complete(&log, &self.m, seq);
2721                    // The narrow window of two writes in flight: one is
2722                    // durable, an earlier or later one is not. Crash in it
2723                    // now and then.
2724                    if self.faults
2725                        && self.cfg.crash_every_ms > 0
2726                        && log.borrow().in_flight() > 0
2727                        && self.rng.below(20) == 0
2728                    {
2729                        let now = self.now();
2730                        self.at(now, WorldEv::Crash);
2731                    }
2732                }
2733            }
2734            Io::BlobFlush { epoch: e, id } if e == epoch => {
2735                if let Some(log) = self.log.clone() {
2736                    SimLog::complete_blobs(&log, &self.m, id);
2737                    // Blobs durable, the batches that reference them not
2738                    // yet: crash in that window now and then.
2739                    if self.faults && self.cfg.crash_every_ms > 0 && self.rng.below(20) == 0 {
2740                        let now = self.now();
2741                        self.at(now, WorldEv::Crash);
2742                    }
2743                }
2744            }
2745            _ => {}
2746        }
2747    }
2748
2749    /// Advances to the next moment anything happens.
2750    fn step(&mut self) -> Result<bool, Failure> {
2751        self.settle()?;
2752        let shared_block = self
2753            .log
2754            .as_ref()
2755            .and_then(|l| l.borrow().violation().map(str::to_owned));
2756        if let Some(v) = shared_block {
2757            return Err(self.fail(v));
2758        }
2759        let Some(t) = self.next_time() else {
2760            return Ok(false);
2761        };
2762        self.m.borrow_mut().now = t;
2763        NOW.with(|n| n.set(t));
2764        loop {
2765            let io = {
2766                let mut m = self.m.borrow_mut();
2767                match m.io.keys().next().copied() {
2768                    Some(k) if k.0 <= t => m.io.remove(&k),
2769                    _ => None,
2770                }
2771            };
2772            match io {
2773                Some(io) => self.handle_io(io),
2774                None => break,
2775            }
2776        }
2777        loop {
2778            let ev = match self.events.keys().next().copied() {
2779                Some(k) if k.0 <= t => self.events.remove(&k),
2780                _ => None,
2781            };
2782            match ev {
2783                Some(ev) => self.handle(ev)?,
2784                None => break,
2785            }
2786        }
2787        Ok(true)
2788    }
2789
2790    pub fn run(mut self) -> Result<Report, Failure> {
2791        self.open_all().map_err(|e| self.fail(e))?;
2792        let start = self.now();
2793        let fault_end = start + self.cfg.duration_ms;
2794        while self.now() < fault_end {
2795            if !self.step()? {
2796                break;
2797            }
2798            self.check_service()?;
2799        }
2800        // Faults stop. Every acknowledged workflow must now finish.
2801        self.faults = false;
2802        self.m.borrow_mut().flush_fail_per_mille = 0;
2803        self.events.retain(|_, e| !matches!(e, WorldEv::Crash));
2804        if self
2805            .shards
2806            .iter()
2807            .flatten()
2808            .any(|s| s.with(|i| i.failed().is_some()))
2809        {
2810            self.faults = true;
2811            self.crash()?;
2812            self.faults = false;
2813        }
2814        let deadline = fault_end + self.cfg.settle_ms;
2815        loop {
2816            let all_done = self.all_finished();
2817            if all_done {
2818                break;
2819            }
2820            // No more crashes change the code: a stuck run stays stuck.
2821            if let Some((id, why)) = self.first_stuck() {
2822                return Err(self.fail(format!("{id} stopped on a determinism error: {why}")));
2823            }
2824            if self.now() > deadline {
2825                let pending = self.unfinished();
2826                return Err(self.fail(format!(
2827                    "{} workflows did not finish after faults stopped: {}",
2828                    pending.len(),
2829                    pending.join(", ")
2830                )));
2831            }
2832            if !self.step()? {
2833                // Nothing scheduled: give retransmission timers a chance.
2834                let t = self.now() + 500;
2835                self.m.borrow_mut().now = t;
2836                NOW.with(|n| n.set(t));
2837            }
2838        }
2839        self.check_after_recovery()?;
2840        self.check_final()?;
2841        self.run_user_checks()?;
2842        self.report.finished = self.results.len() as u32;
2843        self.report.updates = self.update_replies.len() as u32;
2844        self.report.queries = self.query_answers.len() as u32;
2845        self.report.completions = self.completions.values().map(|v| v.len() as u32).sum();
2846        self.report.queued = self.receipts.len() as u32;
2847        self.report.windows = self.receipts.values().collect::<BTreeSet<_>>().len() as u32;
2848        if let Ok(s) = self.svc.lock() {
2849            self.report.scheduled = s.ticks.len() as u32;
2850            self.report.heartbeats = s.progress.values().map(|p| p.len() as u32).sum();
2851            self.report.reads_rerun = s.reads_rerun;
2852            self.report.shared_log = self.log.is_some();
2853            self.report.log_batches = self
2854                .log
2855                .as_ref()
2856                .map_or(0, |l| l.borrow().wal.position() - 1);
2857        }
2858        self.report.offloaded += self
2859            .log
2860            .as_ref()
2861            .map_or(0, |l| l.borrow().wal.blob_stats().records);
2862        self.report.sim_ms = self.now() - start;
2863        self.report.service_calls = self.svc.lock().map(|s| s.total).unwrap_or(0);
2864        self.report.records = self
2865            .shards
2866            .iter()
2867            .flatten()
2868            .map(|s| s.metrics().records)
2869            .sum();
2870        self.report.flush_failures = self
2871            .m
2872            .borrow()
2873            .log
2874            .iter()
2875            .filter(|l| l.contains("flush failed"))
2876            .count() as u32;
2877        Ok(self.report)
2878    }
2879
2880    fn run_user_checks(&mut self) -> Result<(), Failure> {
2881        if self.checks.is_empty() {
2882            return Ok(());
2883        }
2884        let mut streams = BTreeMap::new();
2885        // Keys sort by run, so each ID's newest run read comes last.
2886        for ((id, _, name), s) in &self.seen {
2887            streams.insert(
2888                (id.clone(), name.clone()),
2889                (s.chunks.clone(), s.end.clone()),
2890            );
2891        }
2892        let outcome = Outcome {
2893            seed: self.cfg.seed,
2894            crashes: self.report.crashes,
2895            results: self.results.clone(),
2896            receipts: self.receipts.clone(),
2897            streams,
2898            calls: self.calls.clone(),
2899        };
2900        for check in std::mem::take(&mut self.checks) {
2901            let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check(&outcome)));
2902            let message = match r {
2903                Ok(Ok(())) => continue,
2904                Ok(Err(m)) => m,
2905                Err(p) => p
2906                    .downcast_ref::<String>()
2907                    .cloned()
2908                    .or_else(|| p.downcast_ref::<&str>().map(|s| (*s).to_owned()))
2909                    .unwrap_or_else(|| "the check panicked".into()),
2910            };
2911            return Err(self.fail(format!("check failed: {message}")));
2912        }
2913        Ok(())
2914    }
2915
2916    fn first_stuck(&self) -> Option<(String, String)> {
2917        let shards = self.cfg.shards;
2918        self.started_ids()
2919            .into_iter()
2920            .filter(|id| !self.results.contains_key(id))
2921            .find_map(|id| {
2922                let rt = self.shard_rt(shard_of(&id, shards))?;
2923                let why = rt.stuck_reason(rt.find(&id)?)?;
2924                Some((id, why))
2925            })
2926    }
2927
2928    fn started_ids(&self) -> Vec<String> {
2929        let mut ids: Vec<String> = self
2930            .actions
2931            .iter()
2932            .zip(&self.acked)
2933            .filter_map(|(a, ok)| match a {
2934                Action::Start { id, .. } | Action::StartAs { id, .. } if *ok => Some(id.clone()),
2935                _ => None,
2936            })
2937            .collect();
2938        // The run every acknowledged debounced or batched event joined must
2939        // start and finish too.
2940        let runs: BTreeSet<&String> = self.receipts.values().collect();
2941        ids.extend(runs.into_iter().cloned());
2942        ids
2943    }
2944
2945    fn all_finished(&mut self) -> bool {
2946        if self.acked.iter().any(|a| !a) || self.readers.iter().any(|r| !r.done) {
2947            return false;
2948        }
2949        let shards = self.cfg.shards;
2950        // Children that exist must have ended too (an abandoned child may
2951        // start late, after its start message was resent).
2952        for (parent, kids) in self.parents.clone() {
2953            for k in 0..kids {
2954                let kid = format!("{parent}/kid{k}");
2955                let running = self.shard_rt(shard_of(&kid, shards)).is_some_and(|rt| {
2956                    rt.find(&kid)
2957                        .and_then(|r| rt.summary(r))
2958                        .is_some_and(|s| s.finished_ms.is_none())
2959                });
2960                if running {
2961                    return false;
2962                }
2963            }
2964        }
2965        for id in self.started_ids() {
2966            if self.results.contains_key(&id) {
2967                continue;
2968            }
2969            let r = self
2970                .shard_rt(shard_of(&id, shards))
2971                .and_then(|rt| rt.result(&id).ok());
2972            if let Some(ResultState::Done {
2973                status, payload, ..
2974            }) = r
2975            {
2976                if self.record_result(&id, status, payload.to_vec()).is_err() {
2977                    return false;
2978                }
2979            } else {
2980                return false;
2981            }
2982        }
2983        true
2984    }
2985
2986    fn unfinished(&self) -> Vec<String> {
2987        let shards = self.cfg.shards;
2988        let mut out = Vec::new();
2989        for (ix, a) in self.actions.iter().enumerate() {
2990            if !self.acked[ix] {
2991                out.push(format!("{a:?} (unacknowledged)"));
2992            }
2993        }
2994        for r in self.readers.iter().filter(|r| !r.done) {
2995            out.push(format!("reader {r:?} (not at the end of its stream)"));
2996        }
2997        for id in self.started_ids() {
2998            if !self.results.contains_key(&id) {
2999                let rt = self.shard_rt(shard_of(&id, shards));
3000                let s = rt.and_then(|rt| rt.find(&id).and_then(|r| rt.summary(r)));
3001                let j = rt
3002                    .and_then(|rt| rt.inspect(&id).ok().flatten())
3003                    .map(|d| d.journal);
3004                let timers = rt.map(|rt| {
3005                    rt.with(|i| {
3006                        (
3007                            i.core.timers_len(),
3008                            i.core.next_timer(),
3009                            fusee::env::Env::now_ms(&i.env),
3010                        )
3011                    })
3012                });
3013                let child = format!("{id}/ship");
3014                let crt = self.shard_rt(shard_of(&child, shards));
3015                let cs = crt.and_then(|rt| rt.find(&child).and_then(|r| rt.summary(r)));
3016                let outboxes: Vec<usize> = self
3017                    .shards
3018                    .iter()
3019                    .flatten()
3020                    .map(|s| s.outbox_len())
3021                    .collect();
3022                out.push(format!("{id}: {s:?}\n    timers {timers:?}\n    journal {j:?}\n    child {cs:?}\n    outboxes {outboxes:?}"));
3023            }
3024        }
3025        out
3026    }
3027}
3028
3029/// Runs one seed.
3030pub fn run_seed(cfg: SimConfig) -> Result<Report, Failure> {
3031    let _ = SimFile::default();
3032    World::new(cfg).run()
3033}