1macro_rules! abort_assert {
14 ($cond:expr, $($arg:tt)*) => {
15 if !$cond {
16 eprintln!("Simulator internal error: {}", format!($($arg)*));
17 std::process::abort();
18 }
19 };
20}
21
22use core::{fmt, panic};
23use std::cell::{Cell, RefCell};
24use std::collections::{HashMap, VecDeque};
25use std::fmt::Debug;
26use std::panic::RefUnwindSafe;
27use std::path::Path;
28use std::pin::{Pin, pin};
29use std::rc::Rc;
30use std::task::ready;
31
32use bytes::Bytes;
33use colored::Colorize;
34use dfir_rs::scheduled::context::DfirErased;
35use futures::{Stream, StreamExt};
36use libloading::Library;
37use serde::Serialize;
38use serde::de::DeserializeOwned;
39use tempfile::TempPath;
40use tokio::sync::mpsc::UnboundedSender;
41use tokio::sync::{Mutex, Notify};
42use tokio_stream::wrappers::UnboundedReceiverStream;
43
44use super::runtime::{Hooks, InlineHooks};
45use super::{SimClusterReceiver, SimClusterSender, SimReceiver, SimSender};
46use crate::compile::builder::ExternalPortId;
47use crate::live_collections::stream::{ExactlyOnce, NoOrder, Ordering, Retries, TotalOrder};
48use crate::location::dynamic::LocationId;
49use crate::sim::graph::{SimExternalPort, SimExternalPortRegistry};
50use crate::sim::runtime::SimHook;
51
52struct QuiescenceState {
53 quiescent: Cell<bool>,
55 quiescence_notify: Notify,
57 resume_notify: Notify,
59}
60
61impl QuiescenceState {
62 fn resume(&self) {
64 self.quiescent.set(false);
65 self.resume_notify.notify_waiters();
66 }
67
68 fn is_quiescent(&self) -> bool {
70 self.quiescent.get()
71 }
72
73 fn notified(&self) -> tokio::sync::futures::Notified<'_> {
75 self.quiescence_notify.notified()
76 }
77
78 async fn wait_for_resume(&self) {
80 self.quiescent.set(true);
81 self.quiescence_notify.notify_waiters();
82 self.resume_notify.notified().await;
83 self.quiescent.set(false);
84 }
85}
86
87struct SimConnections {
88 input_senders: HashMap<SimExternalPort, Rc<UnboundedSender<Bytes>>>,
89 output_receivers: HashMap<SimExternalPort, Rc<Mutex<UnboundedReceiverStream<Bytes>>>>,
90 cluster_input_senders: HashMap<SimExternalPort, HashMap<u32, Rc<UnboundedSender<Bytes>>>>,
91 cluster_output_receivers:
92 HashMap<SimExternalPort, HashMap<u32, Rc<Mutex<UnboundedReceiverStream<Bytes>>>>>,
93 external_registered: HashMap<ExternalPortId, SimExternalPort>,
94 quiescence: Rc<QuiescenceState>,
95 log: bool,
96}
97
98#[doc(hidden)]
106#[track_caller]
107pub fn continue_if_impl(condition: bool, message: fmt::Arguments<'_>) {
108 if condition {
109 return;
110 }
111
112 let log = CURRENT_SIM_CONNECTIONS
113 .try_with(|connections| connections.borrow().log)
114 .unwrap_or(true);
115 if log {
116 eprintln!(
117 "{}",
118 render_continue_if_failure(std::panic::Location::caller(), message)
119 );
120 }
121
122 bolero::generator::bolero_generator::any::assume(false, "simulation assumption failed");
126}
127
128fn render_continue_if_failure(
131 location: &std::panic::Location<'_>,
132 message: fmt::Arguments<'_>,
133) -> String {
134 use std::fmt::Write;
135
136 let source_line = std::env::current_dir()
140 .ok()
141 .and_then(|cwd| {
142 cwd.ancestors()
143 .find_map(|base| std::fs::read_to_string(base.join(location.file())).ok())
144 })
145 .and_then(|content| {
146 content
147 .lines()
148 .nth((location.line() as usize).saturating_sub(1))
149 .map(|line| line.to_owned())
150 })
151 .unwrap_or_default();
152
153 let caret_indent = " ".repeat((location.column() as usize).saturating_sub(1));
154
155 let mut out = String::new();
156 let _ = writeln!(
157 out,
158 "\n{}",
159 "Condition failed (discarding simulation instance):"
160 .color(colored::Color::Yellow)
161 .bold()
162 );
163 let _ = writeln!(out, "{} {}", "-->".color(colored::Color::Blue), location);
164 let _ = writeln!(out, " {}{}", "|".color(colored::Color::Blue), source_line);
165 let _ = write!(
166 out,
167 " {}{}{}",
168 "|".color(colored::Color::Blue),
169 caret_indent,
170 format!("^ {}", message).color(colored::Color::Yellow)
171 );
172 out
173}
174
175tokio::task_local! {
176 static CURRENT_SIM_CONNECTIONS: RefCell<SimConnections>;
177}
178
179pub struct CompiledSim {
181 pub(super) _path: TempPath,
182 pub(super) lib: Library,
183 pub(super) externals_port_registry: SimExternalPortRegistry,
184 pub(super) unit_test_fuzz_iterations: usize,
185}
186
187#[sealed::sealed]
188pub trait Instantiator<'a>: RefUnwindSafe + Fn() -> CompiledSimInstance<'a> {}
192#[sealed::sealed]
193impl<'a, T: RefUnwindSafe + Fn() -> CompiledSimInstance<'a>> Instantiator<'a> for T {}
194
195fn null_handler(_args: fmt::Arguments) {}
196
197fn println_handler(args: fmt::Arguments) {
198 println!("{}", args);
199}
200
201fn eprintln_handler(args: fmt::Arguments) {
202 eprintln!("{}", args);
203}
204
205type SimLoaded<'a> = libloading::Symbol<
211 'a,
212 unsafe extern "Rust" fn(
213 should_color: bool,
214 external_out: &mut HashMap<usize, UnboundedReceiverStream<Bytes>>,
215 external_in: &mut HashMap<usize, UnboundedSender<Bytes>>,
216 cluster_external_out: &mut HashMap<usize, HashMap<u32, UnboundedReceiverStream<Bytes>>>,
217 cluster_external_in: &mut HashMap<usize, HashMap<u32, UnboundedSender<Bytes>>>,
218 println_handler: fn(fmt::Arguments<'_>),
219 eprintln_handler: fn(fmt::Arguments<'_>),
220 ) -> (
221 Vec<(&'static str, Option<u32>, DfirErased)>,
222 Vec<(&'static str, Option<u32>, DfirErased)>,
223 Hooks<&'static str>,
224 InlineHooks<&'static str>,
225 ),
226>;
227
228impl CompiledSim {
229 pub fn with_instance<T>(&self, thunk: impl FnOnce(CompiledSimInstance) -> T) -> T {
231 self.with_instantiator(|instantiator| thunk(instantiator()), true)
232 }
233
234 pub fn with_instantiator<T>(
242 &self,
243 thunk: impl FnOnce(&dyn Instantiator) -> T,
244 always_log: bool,
245 ) -> T {
246 let func: SimLoaded = unsafe { self.lib.get(b"__hydro_runtime").unwrap() };
247 let log = always_log || std::env::var("HYDRO_SIM_LOG").is_ok_and(|v| v == "1");
248 thunk(
249 &(|| CompiledSimInstance {
250 func: func.clone(),
251 externals_port_registry: self.externals_port_registry.clone(),
252 dylib_result: None,
253 log,
254 }),
255 )
256 }
257
258 pub fn fuzz(&self, mut thunk: impl AsyncFnMut() + RefUnwindSafe) {
269 let caller_fn = crate::compile::ir::backtrace::Backtrace::get_backtrace(0)
270 .elements()
271 .into_iter()
272 .find(|e| {
273 !e.fn_name.starts_with("hydro_lang::sim::compiled")
274 && !e.fn_name.starts_with("hydro_lang::sim::flow")
275 && !e.fn_name.starts_with("fuzz<")
276 && !e.fn_name.starts_with("<hydro_lang::sim")
277 })
278 .unwrap();
279
280 let caller_path = Path::new(&caller_fn.filename.unwrap()).to_path_buf();
281 let repro_folder = caller_path.parent().unwrap().join("sim-failures");
282
283 let caller_fuzz_repro_path = repro_folder
284 .join(caller_fn.fn_name.replace("::", "__"))
285 .with_extension("bin");
286
287 if std::env::var("BOLERO_FUZZER").is_ok() {
288 let corpus_dir = std::env::current_dir().unwrap().join(".fuzz-corpus");
289 std::fs::create_dir_all(&corpus_dir).unwrap();
290 let libfuzzer_args = format!(
291 "{} {} -artifact_prefix={}/ -handle_abrt=0",
292 corpus_dir.to_str().unwrap(),
293 corpus_dir.to_str().unwrap(),
294 corpus_dir.to_str().unwrap(),
295 );
296
297 std::fs::create_dir_all(&repro_folder).unwrap();
298
299 if !std::env::var("HYDRO_NO_FAILURE_OUTPUT").is_ok_and(|v| v == "1") {
300 unsafe {
301 std::env::set_var(
302 "BOLERO_FAILURE_OUTPUT",
303 caller_fuzz_repro_path.to_str().unwrap(),
304 );
305 }
306 }
307
308 unsafe {
309 std::env::set_var("BOLERO_LIBFUZZER_ARGS", libfuzzer_args);
310 }
311
312 self.with_instantiator(
313 |instantiator| {
314 bolero::test(bolero::TargetLocation {
315 package_name: "",
316 manifest_dir: "",
317 module_path: "",
318 file: "",
319 line: 0,
320 item_path: "<unknown>::__bolero_item_path__",
321 test_name: None,
322 })
323 .run_with_replay(move |is_replay| {
324 let mut instance = instantiator();
325
326 if instance.log {
327 eprintln!(
328 "{}",
329 "\n==== New Simulation Instance ===="
330 .color(colored::Color::Cyan)
331 .bold()
332 );
333 }
334
335 if is_replay {
336 instance.log = true;
337 }
338
339 tokio::runtime::Builder::new_current_thread()
340 .build()
341 .unwrap()
342 .block_on(async { instance.run(&mut thunk).await })
343 })
344 },
345 false,
346 );
347 } else if let Ok(existing_bytes) = std::fs::read(&caller_fuzz_repro_path) {
348 self.fuzz_repro(existing_bytes, async |compiled| {
349 compiled.launch();
350 thunk().await
351 });
352 } else {
353 eprintln!(
354 "Running a fuzz test without `cargo sim` and no reproducer found at {}, using {} iterations with random inputs.",
355 caller_fuzz_repro_path.display(),
356 self.unit_test_fuzz_iterations,
357 );
358 self.with_instantiator(
359 |instantiator| {
360 bolero::test(bolero::TargetLocation {
361 package_name: "",
362 manifest_dir: "",
363 module_path: "",
364 file: ".",
365 line: 0,
366 item_path: "<unknown>::__bolero_item_path__",
367 test_name: None,
368 })
369 .with_iterations(self.unit_test_fuzz_iterations)
370 .run(move || {
371 let instance = instantiator();
372 tokio::runtime::Builder::new_current_thread()
373 .build()
374 .unwrap()
375 .block_on(async { instance.run(&mut thunk).await })
376 })
377 },
378 false,
379 );
380 }
381 }
382
383 pub fn fuzz_repro<'a>(
387 &'a self,
388 bytes: Vec<u8>,
389 thunk: impl AsyncFnOnce(CompiledSimInstance) + RefUnwindSafe,
390 ) {
391 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
392 self.with_instance(|instance| {
393 bolero::bolero_engine::any::scope::with(
394 Box::new(bolero::bolero_engine::driver::object::Object(
395 bolero::bolero_engine::driver::bytes::Driver::new(
396 bytes,
397 &Default::default(),
398 ),
399 )),
400 || {
401 tokio::runtime::Builder::new_current_thread()
402 .build()
403 .unwrap()
404 .block_on(async { instance.run_without_launching(thunk).await })
405 },
406 )
407 })
408 }));
409
410 if let Err(payload) = result {
411 if payload
412 .downcast_ref::<bolero::generator::bolero_generator::any::Error>()
413 .is_some()
414 {
415 panic!(
420 "simulation assumption failed while replaying recorded fuzz decisions; the reproducer may be stale or may not correspond to this program"
421 );
422 }
423 std::panic::resume_unwind(payload);
424 }
425 }
426
427 pub fn exhaustive(&self, mut thunk: impl AsyncFnMut() + RefUnwindSafe) -> usize {
438 if std::env::var("BOLERO_FUZZER").is_ok() {
439 eprintln!(
440 "Cannot run exhaustive tests with a fuzzer. Please use `cargo test` instead of `cargo sim`."
441 );
442 std::process::abort();
443 }
444
445 let mut count = 0;
446 let count_mut = &mut count;
447
448 let _span = tracing::debug_span!(target: "hydro_build", "sim_exhaustive").entered();
449
450 self.with_instantiator(
451 |instantiator| {
452 bolero::test(bolero::TargetLocation {
453 package_name: "",
454 manifest_dir: "",
455 module_path: "",
456 file: "",
457 line: 0,
458 item_path: "<unknown>::__bolero_item_path__",
459 test_name: None,
460 })
461 .exhaustive()
462 .run_with_replay(move |is_replay| {
463 *count_mut += 1;
464
465 let mut instance = instantiator();
466 if instance.log {
467 eprintln!(
468 "{}",
469 "\n==== New Simulation Instance ===="
470 .color(colored::Color::Cyan)
471 .bold()
472 );
473 }
474
475 if is_replay {
476 instance.log = true;
477 }
478
479 tokio::runtime::Builder::new_current_thread()
480 .build()
481 .unwrap()
482 .block_on(async { instance.run(&mut thunk).await })
483 })
484 },
485 false,
486 );
487
488 count
489 }
490}
491
492type DylibResult = (
494 Vec<(&'static str, Option<u32>, DfirErased)>,
495 Vec<(&'static str, Option<u32>, DfirErased)>,
496 Hooks<&'static str>,
497 InlineHooks<&'static str>,
498);
499
500pub struct CompiledSimInstance<'a> {
503 func: SimLoaded<'a>,
504 externals_port_registry: SimExternalPortRegistry,
505 dylib_result: Option<DylibResult>,
506 log: bool,
507}
508
509impl<'a> CompiledSimInstance<'a> {
510 async fn run(self, thunk: impl AsyncFnOnce() + RefUnwindSafe) {
511 self.run_without_launching(async |instance| {
512 instance.launch();
513 thunk().await;
514 })
515 .await;
516 }
517
518 async fn run_without_launching(
519 mut self,
520 thunk: impl AsyncFnOnce(CompiledSimInstance) + RefUnwindSafe,
521 ) {
522 let mut external_out: HashMap<usize, UnboundedReceiverStream<Bytes>> = HashMap::new();
523 let mut external_in: HashMap<usize, UnboundedSender<Bytes>> = HashMap::new();
524 let mut cluster_external_out: HashMap<usize, HashMap<u32, UnboundedReceiverStream<Bytes>>> =
525 HashMap::new();
526 let mut cluster_external_in: HashMap<usize, HashMap<u32, UnboundedSender<Bytes>>> =
527 HashMap::new();
528
529 let dylib_result = unsafe {
530 (self.func)(
531 colored::control::SHOULD_COLORIZE.should_colorize(),
532 &mut external_out,
533 &mut external_in,
534 &mut cluster_external_out,
535 &mut cluster_external_in,
536 if self.log {
537 println_handler
538 } else {
539 null_handler
540 },
541 if self.log {
542 eprintln_handler
543 } else {
544 null_handler
545 },
546 )
547 };
548
549 let registered = &self.externals_port_registry.registered;
550
551 let quiescence = Rc::new(QuiescenceState {
552 quiescent: Cell::new(false),
553 quiescence_notify: Notify::new(),
554 resume_notify: Notify::new(),
555 });
556
557 let mut input_senders = HashMap::new();
558 let mut output_receivers = HashMap::new();
559 let mut cluster_input_senders = HashMap::new();
560 let mut cluster_output_receivers = HashMap::new();
561
562 #[expect(
563 clippy::disallowed_methods,
564 reason = "inserts into maps also unordered"
565 )]
566 for sim_port in registered.values() {
567 let usize_key = sim_port.into_inner();
568 if let Some(sender) = external_in.remove(&usize_key) {
569 input_senders.insert(*sim_port, Rc::new(sender));
570 }
571 if let Some(receiver) = external_out.remove(&usize_key) {
572 output_receivers.insert(*sim_port, Rc::new(Mutex::new(receiver)));
573 }
574 if let Some(senders) = cluster_external_in.remove(&usize_key) {
575 cluster_input_senders.insert(
576 *sim_port,
577 senders
578 .into_iter()
579 .map(|(member, s)| (member, Rc::new(s)))
580 .collect(),
581 );
582 }
583 if let Some(receivers) = cluster_external_out.remove(&usize_key) {
584 cluster_output_receivers.insert(
585 *sim_port,
586 receivers
587 .into_iter()
588 .map(|(member, r)| (member, Rc::new(Mutex::new(r))))
589 .collect(),
590 );
591 }
592 }
593
594 self.dylib_result = Some(dylib_result);
595
596 let local_set = tokio::task::LocalSet::new();
597 local_set
598 .run_until(CURRENT_SIM_CONNECTIONS.scope(
599 RefCell::new(SimConnections {
600 input_senders,
601 output_receivers,
602 cluster_input_senders,
603 cluster_output_receivers,
604 external_registered: self.externals_port_registry.registered.clone(),
605 quiescence: quiescence.clone(),
606 log: self.log,
607 }),
608 async move {
609 thunk(self).await;
610 },
611 ))
612 .await;
613 }
614
615 fn launch(self) {
618 tokio::task::spawn_local(self.schedule_with_maybe_logger::<std::io::Empty>(None));
619 }
620
621 pub fn schedule_with_logger<W: std::io::Write>(
624 self,
625 log_writer: W,
626 ) -> impl use<W> + Future<Output = ()> {
627 self.schedule_with_maybe_logger(Some(log_writer))
628 }
629
630 fn schedule_with_maybe_logger<W: std::io::Write>(
631 mut self,
632 log_override: Option<W>,
633 ) -> impl use<W> + Future<Output = ()> {
634 let (async_dfirs, tick_dfirs, hooks, inline_hooks) = self.dylib_result.take().unwrap();
635
636 let not_ready_observation = async_dfirs
637 .iter()
638 .map(|(lid, c_id, _)| (serde_json::from_str(lid).unwrap(), *c_id))
639 .collect();
640
641 let quiescence = CURRENT_SIM_CONNECTIONS.with(|connections| {
642 let connections = connections.borrow();
643 connections.quiescence.clone()
644 });
645
646 let mut launched = LaunchedSim {
647 async_dfirs: async_dfirs
648 .into_iter()
649 .map(|(lid, c_id, dfir)| (serde_json::from_str(lid).unwrap(), c_id, dfir))
650 .collect(),
651 possibly_ready_ticks: vec![],
652 not_ready_ticks: tick_dfirs
653 .into_iter()
654 .map(|(lid, c_id, dfir)| (serde_json::from_str(lid).unwrap(), c_id, dfir))
655 .collect(),
656 possibly_ready_observation: vec![],
657 not_ready_observation,
658 hooks: hooks
659 .into_iter()
660 .map(|((lid, cid), hs)| ((serde_json::from_str(lid).unwrap(), cid), hs))
661 .collect(),
662 inline_hooks: inline_hooks
663 .into_iter()
664 .map(|((lid, cid), hs)| ((serde_json::from_str(lid).unwrap(), cid), hs))
665 .collect(),
666 log: if self.log {
667 if let Some(w) = log_override {
668 LogKind::Custom(w)
669 } else {
670 LogKind::Stderr
671 }
672 } else {
673 LogKind::Null
674 },
675 quiescence,
676 };
677
678 async move { launched.scheduler().await }
679 }
680}
681
682impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Clone for SimReceiver<T, O, R> {
683 fn clone(&self) -> Self {
684 *self
685 }
686}
687
688impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Copy for SimReceiver<T, O, R> {}
689
690impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimReceiver<T, O, R> {
691 async fn with_stream<Out>(
692 &self,
693 thunk: impl AsyncFnOnce(&mut Pin<&mut dyn Stream<Item = T>>) -> Out,
694 ) -> Out {
695 let (receiver, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
696 let connections = connections.borrow();
697 let port = connections.external_registered.get(&self.0).unwrap();
698 (
699 connections.output_receivers.get(port).unwrap().clone(),
700 connections.quiescence.clone(),
701 )
702 });
703
704 let mut receiver_stream = receiver.lock().await;
705 let mut notified_fut = pin!(quiescence.notified());
706 let mut quiescence_aware = futures::stream::poll_fn(|cx| {
707 use std::task::Poll;
708 match receiver_stream.poll_next_unpin(cx) {
709 Poll::Ready(Some(bytes)) => {
710 return Poll::Ready(Some(bincode::deserialize(&bytes).unwrap()));
711 }
712 Poll::Ready(None) => return Poll::Ready(None),
713 Poll::Pending => {}
714 }
715 if quiescence.is_quiescent() {
716 return Poll::Ready(None);
717 }
718 let () = ready!(notified_fut.as_mut().poll(cx));
719 notified_fut.set(quiescence.notified());
720 Poll::Ready(None)
721 });
722 thunk(&mut pin!(&mut quiescence_aware)).await
723 }
724
725 pub fn assert_no_more(self) -> impl Future<Output = ()>
727 where
728 T: Debug,
729 {
730 FutureTrackingCaller {
731 future: async move {
732 self.with_stream(async |stream| {
733 if let Some(next) = stream.next().await {
734 return Err(format!(
735 "Stream yielded unexpected message: {:?}, expected termination",
736 next
737 ));
738 }
739 Ok(())
740 })
741 .await
742 },
743 }
744 }
745}
746
747impl<T: Serialize + DeserializeOwned> SimReceiver<T, TotalOrder, ExactlyOnce> {
748 pub async fn next(&self) -> Option<T> {
751 self.with_stream(async |stream| stream.next().await).await
752 }
753
754 pub async fn collect<C: Default + Extend<T>>(self) -> C {
757 self.with_stream(async |stream| stream.collect().await)
758 .await
759 }
760
761 pub fn assert_yields<T2: Debug, I: IntoIterator<Item = T2>>(
764 &self,
765 expected: I,
766 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
767 where
768 T: Debug + PartialEq<T2>,
769 {
770 FutureTrackingCaller {
771 future: async {
772 let mut expected: VecDeque<T2> = expected.into_iter().collect();
773
774 while !expected.is_empty() {
775 if let Some(next) = self.next().await {
776 let next_expected = expected.pop_front().unwrap();
777 if next != next_expected {
778 return Err(format!(
779 "Stream yielded unexpected message: {:?}, expected: {:?}",
780 next, next_expected
781 ));
782 }
783 } else {
784 return Err(format!(
785 "Stream ended early, still expected: {:?}",
786 expected
787 ));
788 }
789 }
790
791 Ok(())
792 },
793 }
794 }
795
796 pub fn assert_yields_only<T2: Debug, I: IntoIterator<Item = T2>>(
799 &self,
800 expected: I,
801 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
802 where
803 T: Debug + PartialEq<T2>,
804 {
805 ChainedFuture {
806 first: self.assert_yields(expected),
807 second: self.assert_no_more(),
808 first_done: false,
809 }
810 }
811}
812
813pin_project_lite::pin_project! {
814 struct FutureTrackingCaller<F: Future<Output = Result<(), String>>> {
824 #[pin]
825 future: F,
826 }
827}
828
829impl<F: Future<Output = Result<(), String>>> Future for FutureTrackingCaller<F> {
830 type Output = ();
831
832 #[track_caller]
833 fn poll(
834 mut self: Pin<&mut Self>,
835 cx: &mut std::task::Context<'_>,
836 ) -> std::task::Poll<Self::Output> {
837 match ready!(self.as_mut().project().future.poll(cx)) {
838 Ok(()) => std::task::Poll::Ready(()),
839 Err(e) => panic!("{}", e),
840 }
841 }
842}
843
844pin_project_lite::pin_project! {
845 struct ChainedFuture<F1: Future<Output = ()>, F2: Future<Output = ()>> {
849 #[pin]
850 first: F1,
851 #[pin]
852 second: F2,
853 first_done: bool,
854 }
855}
856
857impl<F1: Future<Output = ()>, F2: Future<Output = ()>> Future for ChainedFuture<F1, F2> {
858 type Output = ();
859
860 #[track_caller]
861 fn poll(
862 mut self: Pin<&mut Self>,
863 cx: &mut std::task::Context<'_>,
864 ) -> std::task::Poll<Self::Output> {
865 if !self.first_done {
866 ready!(self.as_mut().project().first.poll(cx));
867 *self.as_mut().project().first_done = true;
868 }
869
870 self.as_mut().project().second.poll(cx)
871 }
872}
873
874impl<T: Serialize + DeserializeOwned> SimReceiver<T, NoOrder, ExactlyOnce> {
875 pub async fn collect_sorted<C: Default + Extend<T> + AsMut<[T]>>(self) -> C
878 where
879 T: Ord,
880 {
881 self.with_stream(async |stream| {
882 let mut collected: C = stream.collect().await;
883 collected.as_mut().sort();
884 collected
885 })
886 .await
887 }
888
889 pub fn assert_yields_unordered<T2: Debug, I: IntoIterator<Item = T2>>(
892 &self,
893 expected: I,
894 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
895 where
896 T: Debug + PartialEq<T2>,
897 {
898 FutureTrackingCaller {
899 future: async {
900 self.with_stream(async |stream| {
901 let mut expected: Vec<T2> = expected.into_iter().collect();
902
903 while !expected.is_empty() {
904 if let Some(next) = stream.next().await {
905 let idx = expected.iter().enumerate().find(|(_, e)| &next == *e);
906 if let Some((i, _)) = idx {
907 expected.swap_remove(i);
908 } else {
909 return Err(format!(
910 "Stream yielded unexpected message: {:?}",
911 next
912 ));
913 }
914 } else {
915 return Err(format!(
916 "Stream ended early, still expected: {:?}",
917 expected
918 ));
919 }
920 }
921
922 Ok(())
923 })
924 .await
925 },
926 }
927 }
928
929 pub fn assert_yields_only_unordered<T2: Debug, I: IntoIterator<Item = T2>>(
932 &self,
933 expected: I,
934 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
935 where
936 T: Debug + PartialEq<T2>,
937 {
938 ChainedFuture {
939 first: self.assert_yields_unordered(expected),
940 second: self.assert_no_more(),
941 first_done: false,
942 }
943 }
944}
945
946impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimSender<T, O, R> {
947 fn with_sink<Out>(
948 &self,
949 thunk: impl FnOnce(&dyn Fn(T) -> Result<(), tokio::sync::mpsc::error::SendError<Bytes>>) -> Out,
950 ) -> Out {
951 let (sender, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
952 let connections = connections.borrow();
953 (
954 connections
955 .input_senders
956 .get(connections.external_registered.get(&self.0).unwrap())
957 .unwrap()
958 .clone(),
959 connections.quiescence.clone(),
960 )
961 });
962
963 thunk(&move |t| {
964 let res = sender.send(bincode::serialize(&t).unwrap().into());
965 quiescence.resume();
966 res
967 })
968 }
969}
970
971impl<T: Serialize + DeserializeOwned, O: Ordering> SimSender<T, O, ExactlyOnce> {
972 pub fn send_many_unordered<I: IntoIterator<Item = T>>(&self, iter: I) {
975 self.with_sink(|send| {
976 for t in iter {
977 send(t).unwrap();
978 }
979 })
980 }
981}
982
983impl<T: Serialize + DeserializeOwned> SimSender<T, TotalOrder, ExactlyOnce> {
984 pub fn send(&self, t: T) {
987 self.with_sink(|send| send(t)).unwrap();
988 }
989
990 pub fn send_many<I: IntoIterator<Item = T>>(&self, iter: I) {
993 self.with_sink(|send| {
994 for t in iter {
995 send(t).unwrap();
996 }
997 })
998 }
999}
1000
1001impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Clone
1002 for SimClusterReceiver<T, O, R>
1003{
1004 fn clone(&self) -> Self {
1005 *self
1006 }
1007}
1008
1009impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Copy
1010 for SimClusterReceiver<T, O, R>
1011{
1012}
1013
1014impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimClusterReceiver<T, O, R> {
1015 async fn with_member_stream<Out>(
1016 &self,
1017 member_id: u32,
1018 thunk: impl AsyncFnOnce(&mut Pin<&mut dyn Stream<Item = T>>) -> Out,
1019 ) -> Out {
1020 let (receiver, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
1021 let connections = connections.borrow();
1022 let port = connections.external_registered.get(&self.0).unwrap();
1023 let receivers = connections.cluster_output_receivers.get(port).unwrap();
1024 (
1025 receivers[&member_id].clone(),
1026 connections.quiescence.clone(),
1027 )
1028 });
1029
1030 let mut lock = receiver.lock().await;
1031 let mut notified_fut = pin!(quiescence.notified());
1032 let mut quiescence_aware = futures::stream::poll_fn(|cx| {
1033 use std::task::Poll;
1034 match lock.poll_next_unpin(cx) {
1035 Poll::Ready(Some(bytes)) => {
1036 return Poll::Ready(Some(bincode::deserialize(&bytes).unwrap()));
1037 }
1038 Poll::Ready(None) => return Poll::Ready(None),
1039 Poll::Pending => {}
1040 }
1041 if quiescence.is_quiescent() {
1042 return Poll::Ready(None);
1043 }
1044 let () = ready!(notified_fut.as_mut().poll(cx));
1045 notified_fut.set(quiescence.notified());
1046 Poll::Ready(None)
1047 });
1048 thunk(&mut pin!(&mut quiescence_aware)).await
1049 }
1050}
1051
1052impl<T: Serialize + DeserializeOwned> SimClusterReceiver<T, TotalOrder, ExactlyOnce> {
1053 pub async fn next(&self, member_id: u32) -> Option<T> {
1055 self.with_member_stream(member_id, async |stream| stream.next().await)
1056 .await
1057 }
1058
1059 pub async fn collect<C: Default + Extend<T>>(self, member_id: u32) -> C {
1061 self.with_member_stream(member_id, async |stream| stream.collect().await)
1062 .await
1063 }
1064}
1065
1066impl<T: Serialize + DeserializeOwned> SimClusterReceiver<T, NoOrder, ExactlyOnce> {
1067 pub async fn collect_sorted<C: Default + Extend<T> + AsMut<[T]>>(self, member_id: u32) -> C
1069 where
1070 T: Ord,
1071 {
1072 self.with_member_stream(member_id, async |stream| {
1073 let mut collected: C = stream.collect().await;
1074 collected.as_mut().sort();
1075 collected
1076 })
1077 .await
1078 }
1079}
1080
1081impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimClusterSender<T, O, R> {
1082 fn with_sink<Out>(
1083 &self,
1084 thunk: impl FnOnce(
1085 &dyn Fn(u32, T) -> Result<(), tokio::sync::mpsc::error::SendError<Bytes>>,
1086 ) -> Out,
1087 ) -> Out {
1088 let (senders, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
1089 let connections = connections.borrow();
1090 (
1091 connections
1092 .cluster_input_senders
1093 .get(connections.external_registered.get(&self.0).unwrap())
1094 .unwrap()
1095 .clone(),
1096 connections.quiescence.clone(),
1097 )
1098 });
1099
1100 thunk(&move |member_id: u32, t: T| {
1101 let payload = bincode::serialize(&t).unwrap();
1102 let res = senders[&member_id].send(Bytes::from(payload));
1103 quiescence.resume();
1104 res
1105 })
1106 }
1107}
1108
1109impl<T: Serialize + DeserializeOwned, O: Ordering> SimClusterSender<T, O, ExactlyOnce> {
1110 pub fn send_many_unordered<I: IntoIterator<Item = (u32, T)>>(&self, iter: I) {
1113 self.with_sink(|send| {
1114 for (member_id, t) in iter {
1115 send(member_id, t).unwrap();
1116 }
1117 })
1118 }
1119}
1120
1121impl<T: Serialize + DeserializeOwned> SimClusterSender<T, TotalOrder, ExactlyOnce> {
1122 pub fn send(&self, member_id: u32, t: T) {
1124 self.with_sink(|send| send(member_id, t)).unwrap();
1125 }
1126
1127 pub fn send_many<I: IntoIterator<Item = (u32, T)>>(&self, iter: I) {
1129 self.with_sink(|send| {
1130 for (member_id, t) in iter {
1131 send(member_id, t).unwrap();
1132 }
1133 })
1134 }
1135}
1136
1137enum LogKind<W: std::io::Write> {
1138 Null,
1139 Stderr,
1140 Custom(W),
1141}
1142
1143impl<W: std::io::Write> std::fmt::Write for LogKind<W> {
1145 fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
1146 match self {
1147 LogKind::Null => Ok(()),
1148 LogKind::Stderr => {
1149 eprint!("{}", s);
1150 Ok(())
1151 }
1152 LogKind::Custom(w) => w.write_all(s.as_bytes()).map_err(|_| std::fmt::Error),
1153 }
1154 }
1155}
1156
1157struct LaunchedSim<W: std::io::Write> {
1169 async_dfirs: Vec<(LocationId, Option<u32>, DfirErased)>,
1172 possibly_ready_ticks: Vec<(LocationId, Option<u32>, DfirErased)>,
1175 not_ready_ticks: Vec<(LocationId, Option<u32>, DfirErased)>,
1177 possibly_ready_observation: Vec<(LocationId, Option<u32>)>,
1181 not_ready_observation: Vec<(LocationId, Option<u32>)>,
1183 hooks: Hooks<LocationId>,
1186 inline_hooks: InlineHooks<LocationId>,
1190 log: LogKind<W>,
1191 quiescence: Rc<QuiescenceState>,
1193}
1194
1195impl<W: std::io::Write> LaunchedSim<W> {
1196 async fn scheduler(&mut self) {
1197 loop {
1198 tokio::task::yield_now().await;
1199 let mut any_made_progress = false;
1200 for (loc, c_id, dfir) in &mut self.async_dfirs {
1201 if dfir.run_tick().await {
1202 any_made_progress = true;
1203 let (now_ready, still_not_ready): (Vec<_>, Vec<_>) = self
1204 .not_ready_ticks
1205 .drain(..)
1206 .partition(|(tick_loc, tick_c_id, _)| {
1207 let LocationId::Tick(_, outer) = tick_loc else {
1208 unreachable!()
1209 };
1210 outer.as_ref() == loc && tick_c_id == c_id
1211 });
1212
1213 self.possibly_ready_ticks.extend(now_ready);
1214 self.not_ready_ticks.extend(still_not_ready);
1215
1216 let (now_ready_obs, still_not_ready_obs): (Vec<_>, Vec<_>) = self
1217 .not_ready_observation
1218 .drain(..)
1219 .partition(|(obs_loc, obs_c_id)| obs_loc == loc && obs_c_id == c_id);
1220
1221 self.possibly_ready_observation.extend(now_ready_obs);
1222 self.not_ready_observation.extend(still_not_ready_obs);
1223 }
1224 }
1225
1226 if any_made_progress {
1227 continue;
1228 } else {
1229 use bolero::generator::*;
1230
1231 let (ready_tick, mut not_ready_tick): (Vec<_>, Vec<_>) = self
1232 .possibly_ready_ticks
1233 .drain(..)
1234 .partition(|(name, cid, _)| {
1235 let hooks = self.hooks.get(&(name.clone(), *cid)).unwrap();
1236 hooks.iter().all(|hook| hook.is_ready())
1238 && hooks.iter().any(|hook| {
1240 hook.current_decision().unwrap_or(false)
1241 || hook.can_make_nontrivial_decision()
1242 })
1243 });
1244
1245 self.possibly_ready_ticks = ready_tick;
1246 self.not_ready_ticks.append(&mut not_ready_tick);
1247
1248 let (ready_obs, mut not_ready_obs): (Vec<_>, Vec<_>) = self
1249 .possibly_ready_observation
1250 .drain(..)
1251 .partition(|(name, cid)| {
1252 self.hooks
1253 .get(&(name.clone(), *cid))
1254 .into_iter()
1255 .flatten()
1256 .any(|hook| {
1257 hook.current_decision().unwrap_or(false)
1258 || hook.can_make_nontrivial_decision()
1259 })
1260 });
1261
1262 self.possibly_ready_observation = ready_obs;
1263 self.not_ready_observation.append(&mut not_ready_obs);
1264
1265 if self.possibly_ready_ticks.is_empty()
1266 && self.possibly_ready_observation.is_empty()
1267 {
1268 for (name, cid, _) in &self.not_ready_ticks {
1271 let hooks = self.hooks.get(&(name.clone(), *cid)).unwrap();
1272 abort_assert!(
1273 hooks.iter().all(|hook| hook.is_ready()),
1274 "tick has a hook that never became ready"
1275 );
1276 }
1277
1278 self.quiescence.wait_for_resume().await;
1280 } else {
1281 let next_tick_or_obs = (0..(self.possibly_ready_ticks.len()
1282 + self.possibly_ready_observation.len()))
1283 .any();
1284
1285 if next_tick_or_obs < self.possibly_ready_ticks.len() {
1286 let next_tick = next_tick_or_obs;
1287 let mut removed = self.possibly_ready_ticks.remove(next_tick);
1288
1289 match &mut self.log {
1290 LogKind::Null => {}
1291 LogKind::Stderr => {
1292 if let Some(cid) = &removed.1 {
1293 eprintln!(
1294 "\n{}",
1295 format!("Running Tick (Cluster Member {})", cid)
1296 .color(colored::Color::Magenta)
1297 .bold()
1298 )
1299 } else {
1300 eprintln!(
1301 "\n{}",
1302 "Running Tick".color(colored::Color::Magenta).bold()
1303 )
1304 }
1305 }
1306 LogKind::Custom(writer) => {
1307 writeln!(
1308 writer,
1309 "\n{}",
1310 "Running Tick".color(colored::Color::Magenta).bold()
1311 )
1312 .unwrap();
1313 }
1314 }
1315
1316 let mut asterisk_indenter = |_line_no, write: &mut dyn std::fmt::Write| {
1317 write.write_str(&"*".color(colored::Color::Magenta).bold())?;
1318 write.write_str(" ")
1319 };
1320
1321 let mut tick_decision_writer = indenter::indented(&mut self.log)
1322 .with_format(indenter::Format::Custom {
1323 inserter: &mut asterisk_indenter,
1324 });
1325
1326 let hooks = self.hooks.get_mut(&(removed.0.clone(), removed.1)).unwrap();
1327 run_hooks(&mut tick_decision_writer, hooks);
1328
1329 let run_tick_future = removed.2.run_tick();
1330 if let Some(inline_hooks) =
1331 self.inline_hooks.get_mut(&(removed.0.clone(), removed.1))
1332 {
1333 let mut run_tick_future_pinned = pin!(run_tick_future);
1334
1335 loop {
1336 tokio::select! {
1337 biased;
1338 r = &mut run_tick_future_pinned => {
1339 abort_assert!(r, "tick DFIR run_tick() returned false");
1340 break;
1341 }
1342 _ = async {} => {
1343 bolero_generator::any::scope::borrow_with(|driver| {
1344 for hook in inline_hooks.iter_mut() {
1345 if hook.pending_decision() {
1346 if !hook.has_decision() {
1347 hook.autonomous_decision(driver);
1348 }
1349
1350 hook.release_decision(&mut tick_decision_writer);
1351 }
1352 }
1353 });
1354 }
1355 }
1356 }
1357 } else {
1358 abort_assert!(
1359 run_tick_future.await,
1360 "tick DFIR run_tick() returned false"
1361 );
1362 }
1363
1364 self.possibly_ready_ticks.push(removed);
1365 } else {
1366 let next_obs = next_tick_or_obs - self.possibly_ready_ticks.len();
1367 let mut default_hooks = vec![];
1368 let hooks = self
1369 .hooks
1370 .get_mut(&self.possibly_ready_observation[next_obs])
1371 .unwrap_or(&mut default_hooks);
1372
1373 run_hooks(&mut self.log, hooks);
1374 }
1375 }
1376 }
1377 }
1378 }
1379}
1380
1381fn run_hooks(tick_decision_writer: &mut impl std::fmt::Write, hooks: &mut Vec<Box<dyn SimHook>>) {
1382 let mut remaining_decision_count = hooks.len();
1383 let mut made_nontrivial_decision = false;
1384
1385 bolero::generator::bolero_generator::any::scope::borrow_with(|driver| {
1386 hooks.iter_mut().for_each(|hook| {
1388 if let Some(is_nontrivial) = hook.current_decision() {
1389 made_nontrivial_decision |= is_nontrivial;
1390 remaining_decision_count -= 1;
1391 } else if !hook.can_make_nontrivial_decision() {
1392 hook.autonomous_decision(driver, false);
1396 remaining_decision_count -= 1;
1397 }
1398 });
1399
1400 hooks.iter_mut().for_each(|hook| {
1401 if hook.current_decision().is_none() {
1402 made_nontrivial_decision |= hook.autonomous_decision(
1403 driver,
1404 !made_nontrivial_decision && remaining_decision_count == 1,
1405 );
1406 remaining_decision_count -= 1;
1407 }
1408
1409 hook.release_decision(tick_decision_writer);
1410 });
1411 });
1412}