1use 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#[derive(Clone, Debug)]
34pub struct SimConfig {
35 pub seed: u64,
36 pub shards: u32,
37 pub workflows: u32,
38 pub duration_ms: u64,
40 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 pub shared_log: bool,
52 pub offload_bytes: u64,
55 pub settle_ms: u64,
58 pub user: Option<crate::scenario::UserSetup>,
61 #[doc(hidden)]
62 pub sabotage: fusee::runtime::Sabotage,
63 #[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 shared_log: seed % 2 == 1,
87 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 pub updates: u32,
111 pub queries: u32,
112 pub completions: u32,
113 pub scheduled: u32,
114 pub resets: u32,
115 pub heartbeats: u32,
116 pub reads_rerun: u32,
119 pub queued: u32,
122 pub windows: u32,
123 pub chunks_read: u64,
126 pub streams_read: u32,
127 pub shared_log: bool,
130 pub log_batches: u64,
131 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 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
159thread_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
169pub 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#[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 pub progress: BTreeMap<String, BTreeSet<u32>>,
252 pub stalled: BTreeSet<String>,
253 pub tokens: Vec<(String, String)>,
255 pub issued: BTreeMap<String, u32>,
256 pub ticks: BTreeMap<String, BTreeSet<u64>>,
258 pub reads: BTreeMap<String, Vec<u64>>,
262 pub reads_rerun: u32,
263 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 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 pub fn stall_once(&mut self, key: &str) -> bool {
297 self.stalled.insert(key.to_owned())
298 }
299
300 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 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 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#[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 Counter,
405 Looper {
407 left: u32,
408 acc: u64,
409 },
410 Patchy {
412 n: u64,
413 },
414 Export,
416 Approval,
418 Parent {
420 kids: u32,
421 },
422 Resettable {
424 n: u64,
425 },
426 Reader {
428 n: u64,
429 },
430 Talker {
434 n: u32,
435 },
436}
437
438pub 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
446pub const TALKER_PROGRESS: [&str; 3] = ["start", "generated", "end"];
448
449#[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
498fn 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 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
569async 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 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 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 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#[derive(Clone, Debug)]
929enum Action {
930 Start {
931 id: String,
932 job: Job,
933 },
934 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 ObjectCall {
974 id: String,
975 wtype: String,
976 handler: String,
977 call_id: String,
978 arg: Vec<u8>,
979 },
980 Enqueue {
983 coordinator: String,
984 wtype: &'static str,
985 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 Restart,
1011 Read(usize),
1013}
1014
1015#[derive(Clone, Debug)]
1018struct SimReader {
1019 id: String,
1020 name: String,
1021 run: Option<u64>,
1022 next: u64,
1023 done: bool,
1024}
1025
1026#[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 log: Option<SimLogRef>,
1040 registry: SharedRegistry,
1041 svc: ServiceRef,
1042 deployed: Arc<AtomicBool>,
1044 limits: Arc<LocalLimits>,
1045 max_permits: u32,
1046 codec: Option<Arc<dyn Codec>>,
1047 update_replies: BTreeMap<String, (Status, Vec<u8>)>,
1049 query_answers: BTreeMap<String, u64>,
1051 all_answers: BTreeMap<String, BTreeSet<u64>>,
1053 completions: BTreeMap<String, Vec<String>>,
1055 tokens_seen: usize,
1056 resetting: BTreeSet<String>,
1058 parents: Vec<(String, u32)>,
1059 events: BTreeMap<(u64, u64), WorldEv>,
1060 ev_seq: u64,
1061 actions: Vec<Action>,
1062 acked: Vec<bool>,
1064 receipts: BTreeMap<String, String>,
1066 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 user_actions: Vec<(u64, UserAction)>,
1078 checks: Vec<Check>,
1079 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(®istry, &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 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 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 if self.rng.chance(300) {
1327 let t3 = t2 + self.rng.below(60);
1328 self.at(t3, WorldEv::Client(ix));
1329 }
1330 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 _ => {
2563 let t = self.now() + 5;
2564 self.at(t, WorldEv::Read(n));
2565 }
2566 }
2567 }
2568
2569 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 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 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 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 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 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 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 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 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 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 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
3029pub fn run_seed(cfg: SimConfig) -> Result<Report, Failure> {
3031 let _ = SimFile::default();
3032 World::new(cfg).run()
3033}