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}
96
97tokio::task_local! {
98 static CURRENT_SIM_CONNECTIONS: RefCell<SimConnections>;
99}
100
101pub struct CompiledSim {
103 pub(super) _path: TempPath,
104 pub(super) lib: Library,
105 pub(super) externals_port_registry: SimExternalPortRegistry,
106 pub(super) unit_test_fuzz_iterations: usize,
107}
108
109#[sealed::sealed]
110pub trait Instantiator<'a>: RefUnwindSafe + Fn() -> CompiledSimInstance<'a> {}
114#[sealed::sealed]
115impl<'a, T: RefUnwindSafe + Fn() -> CompiledSimInstance<'a>> Instantiator<'a> for T {}
116
117fn null_handler(_args: fmt::Arguments) {}
118
119fn println_handler(args: fmt::Arguments) {
120 println!("{}", args);
121}
122
123fn eprintln_handler(args: fmt::Arguments) {
124 eprintln!("{}", args);
125}
126
127type SimLoaded<'a> = libloading::Symbol<
133 'a,
134 unsafe extern "Rust" fn(
135 should_color: bool,
136 external_out: &mut HashMap<usize, UnboundedReceiverStream<Bytes>>,
137 external_in: &mut HashMap<usize, UnboundedSender<Bytes>>,
138 cluster_external_out: &mut HashMap<usize, HashMap<u32, UnboundedReceiverStream<Bytes>>>,
139 cluster_external_in: &mut HashMap<usize, HashMap<u32, UnboundedSender<Bytes>>>,
140 println_handler: fn(fmt::Arguments<'_>),
141 eprintln_handler: fn(fmt::Arguments<'_>),
142 ) -> (
143 Vec<(&'static str, Option<u32>, DfirErased)>,
144 Vec<(&'static str, Option<u32>, DfirErased)>,
145 Hooks<&'static str>,
146 InlineHooks<&'static str>,
147 ),
148>;
149
150impl CompiledSim {
151 pub fn with_instance<T>(&self, thunk: impl FnOnce(CompiledSimInstance) -> T) -> T {
153 self.with_instantiator(|instantiator| thunk(instantiator()), true)
154 }
155
156 pub fn with_instantiator<T>(
164 &self,
165 thunk: impl FnOnce(&dyn Instantiator) -> T,
166 always_log: bool,
167 ) -> T {
168 let func: SimLoaded = unsafe { self.lib.get(b"__hydro_runtime").unwrap() };
169 let log = always_log || std::env::var("HYDRO_SIM_LOG").is_ok_and(|v| v == "1");
170 thunk(
171 &(|| CompiledSimInstance {
172 func: func.clone(),
173 externals_port_registry: self.externals_port_registry.clone(),
174 dylib_result: None,
175 log,
176 }),
177 )
178 }
179
180 pub fn fuzz(&self, mut thunk: impl AsyncFn() + RefUnwindSafe) {
191 let caller_fn = crate::compile::ir::backtrace::Backtrace::get_backtrace(0)
192 .elements()
193 .into_iter()
194 .find(|e| {
195 !e.fn_name.starts_with("hydro_lang::sim::compiled")
196 && !e.fn_name.starts_with("hydro_lang::sim::flow")
197 && !e.fn_name.starts_with("fuzz<")
198 && !e.fn_name.starts_with("<hydro_lang::sim")
199 })
200 .unwrap();
201
202 let caller_path = Path::new(&caller_fn.filename.unwrap()).to_path_buf();
203 let repro_folder = caller_path.parent().unwrap().join("sim-failures");
204
205 let caller_fuzz_repro_path = repro_folder
206 .join(caller_fn.fn_name.replace("::", "__"))
207 .with_extension("bin");
208
209 if std::env::var("BOLERO_FUZZER").is_ok() {
210 let corpus_dir = std::env::current_dir().unwrap().join(".fuzz-corpus");
211 std::fs::create_dir_all(&corpus_dir).unwrap();
212 let libfuzzer_args = format!(
213 "{} {} -artifact_prefix={}/ -handle_abrt=0",
214 corpus_dir.to_str().unwrap(),
215 corpus_dir.to_str().unwrap(),
216 corpus_dir.to_str().unwrap(),
217 );
218
219 std::fs::create_dir_all(&repro_folder).unwrap();
220
221 if !std::env::var("HYDRO_NO_FAILURE_OUTPUT").is_ok_and(|v| v == "1") {
222 unsafe {
223 std::env::set_var(
224 "BOLERO_FAILURE_OUTPUT",
225 caller_fuzz_repro_path.to_str().unwrap(),
226 );
227 }
228 }
229
230 unsafe {
231 std::env::set_var("BOLERO_LIBFUZZER_ARGS", libfuzzer_args);
232 }
233
234 self.with_instantiator(
235 |instantiator| {
236 bolero::test(bolero::TargetLocation {
237 package_name: "",
238 manifest_dir: "",
239 module_path: "",
240 file: "",
241 line: 0,
242 item_path: "<unknown>::__bolero_item_path__",
243 test_name: None,
244 })
245 .run_with_replay(move |is_replay| {
246 let mut instance = instantiator();
247
248 if instance.log {
249 eprintln!(
250 "{}",
251 "\n==== New Simulation Instance ===="
252 .color(colored::Color::Cyan)
253 .bold()
254 );
255 }
256
257 if is_replay {
258 instance.log = true;
259 }
260
261 tokio::runtime::Builder::new_current_thread()
262 .build()
263 .unwrap()
264 .block_on(async { instance.run(&mut thunk).await })
265 })
266 },
267 false,
268 );
269 } else if let Ok(existing_bytes) = std::fs::read(&caller_fuzz_repro_path) {
270 self.fuzz_repro(existing_bytes, async |compiled| {
271 compiled.launch();
272 thunk().await
273 });
274 } else {
275 eprintln!(
276 "Running a fuzz test without `cargo sim` and no reproducer found at {}, using {} iterations with random inputs.",
277 caller_fuzz_repro_path.display(),
278 self.unit_test_fuzz_iterations,
279 );
280 self.with_instantiator(
281 |instantiator| {
282 bolero::test(bolero::TargetLocation {
283 package_name: "",
284 manifest_dir: "",
285 module_path: "",
286 file: ".",
287 line: 0,
288 item_path: "<unknown>::__bolero_item_path__",
289 test_name: None,
290 })
291 .with_iterations(self.unit_test_fuzz_iterations)
292 .run(move || {
293 let instance = instantiator();
294 tokio::runtime::Builder::new_current_thread()
295 .build()
296 .unwrap()
297 .block_on(async { instance.run(&mut thunk).await })
298 })
299 },
300 false,
301 );
302 }
303 }
304
305 pub fn fuzz_repro<'a>(
309 &'a self,
310 bytes: Vec<u8>,
311 thunk: impl AsyncFnOnce(CompiledSimInstance) + RefUnwindSafe,
312 ) {
313 self.with_instance(|instance| {
314 bolero::bolero_engine::any::scope::with(
315 Box::new(bolero::bolero_engine::driver::object::Object(
316 bolero::bolero_engine::driver::bytes::Driver::new(bytes, &Default::default()),
317 )),
318 || {
319 tokio::runtime::Builder::new_current_thread()
320 .build()
321 .unwrap()
322 .block_on(async { instance.run_without_launching(thunk).await })
323 },
324 )
325 });
326 }
327
328 pub fn exhaustive(&self, mut thunk: impl AsyncFnMut() + RefUnwindSafe) -> usize {
339 if std::env::var("BOLERO_FUZZER").is_ok() {
340 eprintln!(
341 "Cannot run exhaustive tests with a fuzzer. Please use `cargo test` instead of `cargo sim`."
342 );
343 std::process::abort();
344 }
345
346 let mut count = 0;
347 let count_mut = &mut count;
348
349 let _span = tracing::debug_span!(target: "hydro_build", "sim_exhaustive").entered();
350
351 self.with_instantiator(
352 |instantiator| {
353 bolero::test(bolero::TargetLocation {
354 package_name: "",
355 manifest_dir: "",
356 module_path: "",
357 file: "",
358 line: 0,
359 item_path: "<unknown>::__bolero_item_path__",
360 test_name: None,
361 })
362 .exhaustive()
363 .run_with_replay(move |is_replay| {
364 *count_mut += 1;
365
366 let mut instance = instantiator();
367 if instance.log {
368 eprintln!(
369 "{}",
370 "\n==== New Simulation Instance ===="
371 .color(colored::Color::Cyan)
372 .bold()
373 );
374 }
375
376 if is_replay {
377 instance.log = true;
378 }
379
380 tokio::runtime::Builder::new_current_thread()
381 .build()
382 .unwrap()
383 .block_on(async { instance.run(&mut thunk).await })
384 })
385 },
386 false,
387 );
388
389 count
390 }
391}
392
393type DylibResult = (
395 Vec<(&'static str, Option<u32>, DfirErased)>,
396 Vec<(&'static str, Option<u32>, DfirErased)>,
397 Hooks<&'static str>,
398 InlineHooks<&'static str>,
399);
400
401pub struct CompiledSimInstance<'a> {
404 func: SimLoaded<'a>,
405 externals_port_registry: SimExternalPortRegistry,
406 dylib_result: Option<DylibResult>,
407 log: bool,
408}
409
410impl<'a> CompiledSimInstance<'a> {
411 async fn run(self, thunk: impl AsyncFnOnce() + RefUnwindSafe) {
412 self.run_without_launching(async |instance| {
413 instance.launch();
414 thunk().await;
415 })
416 .await;
417 }
418
419 async fn run_without_launching(
420 mut self,
421 thunk: impl AsyncFnOnce(CompiledSimInstance) + RefUnwindSafe,
422 ) {
423 let mut external_out: HashMap<usize, UnboundedReceiverStream<Bytes>> = HashMap::new();
424 let mut external_in: HashMap<usize, UnboundedSender<Bytes>> = HashMap::new();
425 let mut cluster_external_out: HashMap<usize, HashMap<u32, UnboundedReceiverStream<Bytes>>> =
426 HashMap::new();
427 let mut cluster_external_in: HashMap<usize, HashMap<u32, UnboundedSender<Bytes>>> =
428 HashMap::new();
429
430 let dylib_result = unsafe {
431 (self.func)(
432 colored::control::SHOULD_COLORIZE.should_colorize(),
433 &mut external_out,
434 &mut external_in,
435 &mut cluster_external_out,
436 &mut cluster_external_in,
437 if self.log {
438 println_handler
439 } else {
440 null_handler
441 },
442 if self.log {
443 eprintln_handler
444 } else {
445 null_handler
446 },
447 )
448 };
449
450 let registered = &self.externals_port_registry.registered;
451
452 let quiescence = Rc::new(QuiescenceState {
453 quiescent: Cell::new(false),
454 quiescence_notify: Notify::new(),
455 resume_notify: Notify::new(),
456 });
457
458 let mut input_senders = HashMap::new();
459 let mut output_receivers = HashMap::new();
460 let mut cluster_input_senders = HashMap::new();
461 let mut cluster_output_receivers = HashMap::new();
462
463 #[expect(
464 clippy::disallowed_methods,
465 reason = "inserts into maps also unordered"
466 )]
467 for sim_port in registered.values() {
468 let usize_key = sim_port.into_inner();
469 if let Some(sender) = external_in.remove(&usize_key) {
470 input_senders.insert(*sim_port, Rc::new(sender));
471 }
472 if let Some(receiver) = external_out.remove(&usize_key) {
473 output_receivers.insert(*sim_port, Rc::new(Mutex::new(receiver)));
474 }
475 if let Some(senders) = cluster_external_in.remove(&usize_key) {
476 cluster_input_senders.insert(
477 *sim_port,
478 senders
479 .into_iter()
480 .map(|(member, s)| (member, Rc::new(s)))
481 .collect(),
482 );
483 }
484 if let Some(receivers) = cluster_external_out.remove(&usize_key) {
485 cluster_output_receivers.insert(
486 *sim_port,
487 receivers
488 .into_iter()
489 .map(|(member, r)| (member, Rc::new(Mutex::new(r))))
490 .collect(),
491 );
492 }
493 }
494
495 self.dylib_result = Some(dylib_result);
496
497 let local_set = tokio::task::LocalSet::new();
498 local_set
499 .run_until(CURRENT_SIM_CONNECTIONS.scope(
500 RefCell::new(SimConnections {
501 input_senders,
502 output_receivers,
503 cluster_input_senders,
504 cluster_output_receivers,
505 external_registered: self.externals_port_registry.registered.clone(),
506 quiescence: quiescence.clone(),
507 }),
508 async move {
509 thunk(self).await;
510 },
511 ))
512 .await;
513 }
514
515 fn launch(self) {
518 tokio::task::spawn_local(self.schedule_with_maybe_logger::<std::io::Empty>(None));
519 }
520
521 pub fn schedule_with_logger<W: std::io::Write>(
524 self,
525 log_writer: W,
526 ) -> impl use<W> + Future<Output = ()> {
527 self.schedule_with_maybe_logger(Some(log_writer))
528 }
529
530 fn schedule_with_maybe_logger<W: std::io::Write>(
531 mut self,
532 log_override: Option<W>,
533 ) -> impl use<W> + Future<Output = ()> {
534 let (async_dfirs, tick_dfirs, hooks, inline_hooks) = self.dylib_result.take().unwrap();
535
536 let not_ready_observation = async_dfirs
537 .iter()
538 .map(|(lid, c_id, _)| (serde_json::from_str(lid).unwrap(), *c_id))
539 .collect();
540
541 let quiescence = CURRENT_SIM_CONNECTIONS.with(|connections| {
542 let connections = connections.borrow();
543 connections.quiescence.clone()
544 });
545
546 let mut launched = LaunchedSim {
547 async_dfirs: async_dfirs
548 .into_iter()
549 .map(|(lid, c_id, dfir)| (serde_json::from_str(lid).unwrap(), c_id, dfir))
550 .collect(),
551 possibly_ready_ticks: vec![],
552 not_ready_ticks: tick_dfirs
553 .into_iter()
554 .map(|(lid, c_id, dfir)| (serde_json::from_str(lid).unwrap(), c_id, dfir))
555 .collect(),
556 possibly_ready_observation: vec![],
557 not_ready_observation,
558 hooks: hooks
559 .into_iter()
560 .map(|((lid, cid), hs)| ((serde_json::from_str(lid).unwrap(), cid), hs))
561 .collect(),
562 inline_hooks: inline_hooks
563 .into_iter()
564 .map(|((lid, cid), hs)| ((serde_json::from_str(lid).unwrap(), cid), hs))
565 .collect(),
566 log: if self.log {
567 if let Some(w) = log_override {
568 LogKind::Custom(w)
569 } else {
570 LogKind::Stderr
571 }
572 } else {
573 LogKind::Null
574 },
575 quiescence,
576 };
577
578 async move { launched.scheduler().await }
579 }
580}
581
582impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Clone for SimReceiver<T, O, R> {
583 fn clone(&self) -> Self {
584 *self
585 }
586}
587
588impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Copy for SimReceiver<T, O, R> {}
589
590impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimReceiver<T, O, R> {
591 async fn with_stream<Out>(
592 &self,
593 thunk: impl AsyncFnOnce(&mut Pin<&mut dyn Stream<Item = T>>) -> Out,
594 ) -> Out {
595 let (receiver, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
596 let connections = connections.borrow();
597 let port = connections.external_registered.get(&self.0).unwrap();
598 (
599 connections.output_receivers.get(port).unwrap().clone(),
600 connections.quiescence.clone(),
601 )
602 });
603
604 let mut receiver_stream = receiver.lock().await;
605 let mut notified_fut = pin!(quiescence.notified());
606 let mut quiescence_aware = futures::stream::poll_fn(|cx| {
607 use std::task::Poll;
608 match receiver_stream.poll_next_unpin(cx) {
609 Poll::Ready(Some(bytes)) => {
610 return Poll::Ready(Some(bincode::deserialize(&bytes).unwrap()));
611 }
612 Poll::Ready(None) => return Poll::Ready(None),
613 Poll::Pending => {}
614 }
615 if quiescence.is_quiescent() {
616 return Poll::Ready(None);
617 }
618 let () = ready!(notified_fut.as_mut().poll(cx));
619 notified_fut.set(quiescence.notified());
620 Poll::Ready(None)
621 });
622 thunk(&mut pin!(&mut quiescence_aware)).await
623 }
624
625 pub fn assert_no_more(self) -> impl Future<Output = ()>
627 where
628 T: Debug,
629 {
630 FutureTrackingCaller {
631 future: async move {
632 self.with_stream(async |stream| {
633 if let Some(next) = stream.next().await {
634 return Err(format!(
635 "Stream yielded unexpected message: {:?}, expected termination",
636 next
637 ));
638 }
639 Ok(())
640 })
641 .await
642 },
643 }
644 }
645}
646
647impl<T: Serialize + DeserializeOwned> SimReceiver<T, TotalOrder, ExactlyOnce> {
648 pub async fn next(&self) -> Option<T> {
651 self.with_stream(async |stream| stream.next().await).await
652 }
653
654 pub async fn collect<C: Default + Extend<T>>(self) -> C {
657 self.with_stream(async |stream| stream.collect().await)
658 .await
659 }
660
661 pub fn assert_yields<T2: Debug, I: IntoIterator<Item = T2>>(
664 &self,
665 expected: I,
666 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
667 where
668 T: Debug + PartialEq<T2>,
669 {
670 FutureTrackingCaller {
671 future: async {
672 let mut expected: VecDeque<T2> = expected.into_iter().collect();
673
674 while !expected.is_empty() {
675 if let Some(next) = self.next().await {
676 let next_expected = expected.pop_front().unwrap();
677 if next != next_expected {
678 return Err(format!(
679 "Stream yielded unexpected message: {:?}, expected: {:?}",
680 next, next_expected
681 ));
682 }
683 } else {
684 return Err(format!(
685 "Stream ended early, still expected: {:?}",
686 expected
687 ));
688 }
689 }
690
691 Ok(())
692 },
693 }
694 }
695
696 pub fn assert_yields_only<T2: Debug, I: IntoIterator<Item = T2>>(
699 &self,
700 expected: I,
701 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
702 where
703 T: Debug + PartialEq<T2>,
704 {
705 ChainedFuture {
706 first: self.assert_yields(expected),
707 second: self.assert_no_more(),
708 first_done: false,
709 }
710 }
711}
712
713pin_project_lite::pin_project! {
714 struct FutureTrackingCaller<F: Future<Output = Result<(), String>>> {
724 #[pin]
725 future: F,
726 }
727}
728
729impl<F: Future<Output = Result<(), String>>> Future for FutureTrackingCaller<F> {
730 type Output = ();
731
732 #[track_caller]
733 fn poll(
734 mut self: Pin<&mut Self>,
735 cx: &mut std::task::Context<'_>,
736 ) -> std::task::Poll<Self::Output> {
737 match ready!(self.as_mut().project().future.poll(cx)) {
738 Ok(()) => std::task::Poll::Ready(()),
739 Err(e) => panic!("{}", e),
740 }
741 }
742}
743
744pin_project_lite::pin_project! {
745 struct ChainedFuture<F1: Future<Output = ()>, F2: Future<Output = ()>> {
749 #[pin]
750 first: F1,
751 #[pin]
752 second: F2,
753 first_done: bool,
754 }
755}
756
757impl<F1: Future<Output = ()>, F2: Future<Output = ()>> Future for ChainedFuture<F1, F2> {
758 type Output = ();
759
760 #[track_caller]
761 fn poll(
762 mut self: Pin<&mut Self>,
763 cx: &mut std::task::Context<'_>,
764 ) -> std::task::Poll<Self::Output> {
765 if !self.first_done {
766 ready!(self.as_mut().project().first.poll(cx));
767 *self.as_mut().project().first_done = true;
768 }
769
770 self.as_mut().project().second.poll(cx)
771 }
772}
773
774impl<T: Serialize + DeserializeOwned> SimReceiver<T, NoOrder, ExactlyOnce> {
775 pub async fn collect_sorted<C: Default + Extend<T> + AsMut<[T]>>(self) -> C
778 where
779 T: Ord,
780 {
781 self.with_stream(async |stream| {
782 let mut collected: C = stream.collect().await;
783 collected.as_mut().sort();
784 collected
785 })
786 .await
787 }
788
789 pub fn assert_yields_unordered<T2: Debug, I: IntoIterator<Item = T2>>(
792 &self,
793 expected: I,
794 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
795 where
796 T: Debug + PartialEq<T2>,
797 {
798 FutureTrackingCaller {
799 future: async {
800 self.with_stream(async |stream| {
801 let mut expected: Vec<T2> = expected.into_iter().collect();
802
803 while !expected.is_empty() {
804 if let Some(next) = stream.next().await {
805 let idx = expected.iter().enumerate().find(|(_, e)| &next == *e);
806 if let Some((i, _)) = idx {
807 expected.swap_remove(i);
808 } else {
809 return Err(format!(
810 "Stream yielded unexpected message: {:?}",
811 next
812 ));
813 }
814 } else {
815 return Err(format!(
816 "Stream ended early, still expected: {:?}",
817 expected
818 ));
819 }
820 }
821
822 Ok(())
823 })
824 .await
825 },
826 }
827 }
828
829 pub fn assert_yields_only_unordered<T2: Debug, I: IntoIterator<Item = T2>>(
832 &self,
833 expected: I,
834 ) -> impl use<'_, T, T2, I> + Future<Output = ()>
835 where
836 T: Debug + PartialEq<T2>,
837 {
838 ChainedFuture {
839 first: self.assert_yields_unordered(expected),
840 second: self.assert_no_more(),
841 first_done: false,
842 }
843 }
844}
845
846impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimSender<T, O, R> {
847 fn with_sink<Out>(
848 &self,
849 thunk: impl FnOnce(&dyn Fn(T) -> Result<(), tokio::sync::mpsc::error::SendError<Bytes>>) -> Out,
850 ) -> Out {
851 let (sender, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
852 let connections = connections.borrow();
853 (
854 connections
855 .input_senders
856 .get(connections.external_registered.get(&self.0).unwrap())
857 .unwrap()
858 .clone(),
859 connections.quiescence.clone(),
860 )
861 });
862
863 thunk(&move |t| {
864 let res = sender.send(bincode::serialize(&t).unwrap().into());
865 quiescence.resume();
866 res
867 })
868 }
869}
870
871impl<T: Serialize + DeserializeOwned, O: Ordering> SimSender<T, O, ExactlyOnce> {
872 pub fn send_many_unordered<I: IntoIterator<Item = T>>(&self, iter: I) {
875 self.with_sink(|send| {
876 for t in iter {
877 send(t).unwrap();
878 }
879 })
880 }
881}
882
883impl<T: Serialize + DeserializeOwned> SimSender<T, TotalOrder, ExactlyOnce> {
884 pub fn send(&self, t: T) {
887 self.with_sink(|send| send(t)).unwrap();
888 }
889
890 pub fn send_many<I: IntoIterator<Item = T>>(&self, iter: I) {
893 self.with_sink(|send| {
894 for t in iter {
895 send(t).unwrap();
896 }
897 })
898 }
899}
900
901impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Clone
902 for SimClusterReceiver<T, O, R>
903{
904 fn clone(&self) -> Self {
905 *self
906 }
907}
908
909impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> Copy
910 for SimClusterReceiver<T, O, R>
911{
912}
913
914impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimClusterReceiver<T, O, R> {
915 async fn with_member_stream<Out>(
916 &self,
917 member_id: u32,
918 thunk: impl AsyncFnOnce(&mut Pin<&mut dyn Stream<Item = T>>) -> Out,
919 ) -> Out {
920 let (receiver, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
921 let connections = connections.borrow();
922 let port = connections.external_registered.get(&self.0).unwrap();
923 let receivers = connections.cluster_output_receivers.get(port).unwrap();
924 (
925 receivers[&member_id].clone(),
926 connections.quiescence.clone(),
927 )
928 });
929
930 let mut lock = receiver.lock().await;
931 let mut notified_fut = pin!(quiescence.notified());
932 let mut quiescence_aware = futures::stream::poll_fn(|cx| {
933 use std::task::Poll;
934 match lock.poll_next_unpin(cx) {
935 Poll::Ready(Some(bytes)) => {
936 return Poll::Ready(Some(bincode::deserialize(&bytes).unwrap()));
937 }
938 Poll::Ready(None) => return Poll::Ready(None),
939 Poll::Pending => {}
940 }
941 if quiescence.is_quiescent() {
942 return Poll::Ready(None);
943 }
944 let () = ready!(notified_fut.as_mut().poll(cx));
945 notified_fut.set(quiescence.notified());
946 Poll::Ready(None)
947 });
948 thunk(&mut pin!(&mut quiescence_aware)).await
949 }
950}
951
952impl<T: Serialize + DeserializeOwned> SimClusterReceiver<T, TotalOrder, ExactlyOnce> {
953 pub async fn next(&self, member_id: u32) -> Option<T> {
955 self.with_member_stream(member_id, async |stream| stream.next().await)
956 .await
957 }
958
959 pub async fn collect<C: Default + Extend<T>>(self, member_id: u32) -> C {
961 self.with_member_stream(member_id, async |stream| stream.collect().await)
962 .await
963 }
964}
965
966impl<T: Serialize + DeserializeOwned> SimClusterReceiver<T, NoOrder, ExactlyOnce> {
967 pub async fn collect_sorted<C: Default + Extend<T> + AsMut<[T]>>(self, member_id: u32) -> C
969 where
970 T: Ord,
971 {
972 self.with_member_stream(member_id, async |stream| {
973 let mut collected: C = stream.collect().await;
974 collected.as_mut().sort();
975 collected
976 })
977 .await
978 }
979}
980
981impl<T: Serialize + DeserializeOwned, O: Ordering, R: Retries> SimClusterSender<T, O, R> {
982 fn with_sink<Out>(
983 &self,
984 thunk: impl FnOnce(
985 &dyn Fn(u32, T) -> Result<(), tokio::sync::mpsc::error::SendError<Bytes>>,
986 ) -> Out,
987 ) -> Out {
988 let (senders, quiescence) = CURRENT_SIM_CONNECTIONS.with(|connections| {
989 let connections = connections.borrow();
990 (
991 connections
992 .cluster_input_senders
993 .get(connections.external_registered.get(&self.0).unwrap())
994 .unwrap()
995 .clone(),
996 connections.quiescence.clone(),
997 )
998 });
999
1000 thunk(&move |member_id: u32, t: T| {
1001 let payload = bincode::serialize(&t).unwrap();
1002 let res = senders[&member_id].send(Bytes::from(payload));
1003 quiescence.resume();
1004 res
1005 })
1006 }
1007}
1008
1009impl<T: Serialize + DeserializeOwned, O: Ordering> SimClusterSender<T, O, ExactlyOnce> {
1010 pub fn send_many_unordered<I: IntoIterator<Item = (u32, T)>>(&self, iter: I) {
1013 self.with_sink(|send| {
1014 for (member_id, t) in iter {
1015 send(member_id, t).unwrap();
1016 }
1017 })
1018 }
1019}
1020
1021impl<T: Serialize + DeserializeOwned> SimClusterSender<T, TotalOrder, ExactlyOnce> {
1022 pub fn send(&self, member_id: u32, t: T) {
1024 self.with_sink(|send| send(member_id, t)).unwrap();
1025 }
1026
1027 pub fn send_many<I: IntoIterator<Item = (u32, T)>>(&self, iter: I) {
1029 self.with_sink(|send| {
1030 for (member_id, t) in iter {
1031 send(member_id, t).unwrap();
1032 }
1033 })
1034 }
1035}
1036
1037enum LogKind<W: std::io::Write> {
1038 Null,
1039 Stderr,
1040 Custom(W),
1041}
1042
1043impl<W: std::io::Write> std::fmt::Write for LogKind<W> {
1045 fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
1046 match self {
1047 LogKind::Null => Ok(()),
1048 LogKind::Stderr => {
1049 eprint!("{}", s);
1050 Ok(())
1051 }
1052 LogKind::Custom(w) => w.write_all(s.as_bytes()).map_err(|_| std::fmt::Error),
1053 }
1054 }
1055}
1056
1057struct LaunchedSim<W: std::io::Write> {
1069 async_dfirs: Vec<(LocationId, Option<u32>, DfirErased)>,
1072 possibly_ready_ticks: Vec<(LocationId, Option<u32>, DfirErased)>,
1075 not_ready_ticks: Vec<(LocationId, Option<u32>, DfirErased)>,
1077 possibly_ready_observation: Vec<(LocationId, Option<u32>)>,
1081 not_ready_observation: Vec<(LocationId, Option<u32>)>,
1083 hooks: Hooks<LocationId>,
1086 inline_hooks: InlineHooks<LocationId>,
1090 log: LogKind<W>,
1091 quiescence: Rc<QuiescenceState>,
1093}
1094
1095impl<W: std::io::Write> LaunchedSim<W> {
1096 async fn scheduler(&mut self) {
1097 loop {
1098 tokio::task::yield_now().await;
1099 let mut any_made_progress = false;
1100 for (loc, c_id, dfir) in &mut self.async_dfirs {
1101 if dfir.run_tick().await {
1102 any_made_progress = true;
1103 let (now_ready, still_not_ready): (Vec<_>, Vec<_>) = self
1104 .not_ready_ticks
1105 .drain(..)
1106 .partition(|(tick_loc, tick_c_id, _)| {
1107 let LocationId::Tick(_, outer) = tick_loc else {
1108 unreachable!()
1109 };
1110 outer.as_ref() == loc && tick_c_id == c_id
1111 });
1112
1113 self.possibly_ready_ticks.extend(now_ready);
1114 self.not_ready_ticks.extend(still_not_ready);
1115
1116 let (now_ready_obs, still_not_ready_obs): (Vec<_>, Vec<_>) = self
1117 .not_ready_observation
1118 .drain(..)
1119 .partition(|(obs_loc, obs_c_id)| obs_loc == loc && obs_c_id == c_id);
1120
1121 self.possibly_ready_observation.extend(now_ready_obs);
1122 self.not_ready_observation.extend(still_not_ready_obs);
1123 }
1124 }
1125
1126 if any_made_progress {
1127 continue;
1128 } else {
1129 use bolero::generator::*;
1130
1131 let (ready_tick, mut not_ready_tick): (Vec<_>, Vec<_>) = self
1132 .possibly_ready_ticks
1133 .drain(..)
1134 .partition(|(name, cid, _)| {
1135 let hooks = self.hooks.get(&(name.clone(), *cid)).unwrap();
1136 hooks.iter().all(|hook| hook.is_ready())
1138 && hooks.iter().any(|hook| {
1140 hook.current_decision().unwrap_or(false)
1141 || hook.can_make_nontrivial_decision()
1142 })
1143 });
1144
1145 self.possibly_ready_ticks = ready_tick;
1146 self.not_ready_ticks.append(&mut not_ready_tick);
1147
1148 let (ready_obs, mut not_ready_obs): (Vec<_>, Vec<_>) = self
1149 .possibly_ready_observation
1150 .drain(..)
1151 .partition(|(name, cid)| {
1152 self.hooks
1153 .get(&(name.clone(), *cid))
1154 .into_iter()
1155 .flatten()
1156 .any(|hook| {
1157 hook.current_decision().unwrap_or(false)
1158 || hook.can_make_nontrivial_decision()
1159 })
1160 });
1161
1162 self.possibly_ready_observation = ready_obs;
1163 self.not_ready_observation.append(&mut not_ready_obs);
1164
1165 if self.possibly_ready_ticks.is_empty()
1166 && self.possibly_ready_observation.is_empty()
1167 {
1168 for (name, cid, _) in &self.not_ready_ticks {
1171 let hooks = self.hooks.get(&(name.clone(), *cid)).unwrap();
1172 abort_assert!(
1173 hooks.iter().all(|hook| hook.is_ready()),
1174 "tick has a hook that never became ready"
1175 );
1176 }
1177
1178 self.quiescence.wait_for_resume().await;
1180 } else {
1181 let next_tick_or_obs = (0..(self.possibly_ready_ticks.len()
1182 + self.possibly_ready_observation.len()))
1183 .any();
1184
1185 if next_tick_or_obs < self.possibly_ready_ticks.len() {
1186 let next_tick = next_tick_or_obs;
1187 let mut removed = self.possibly_ready_ticks.remove(next_tick);
1188
1189 match &mut self.log {
1190 LogKind::Null => {}
1191 LogKind::Stderr => {
1192 if let Some(cid) = &removed.1 {
1193 eprintln!(
1194 "\n{}",
1195 format!("Running Tick (Cluster Member {})", cid)
1196 .color(colored::Color::Magenta)
1197 .bold()
1198 )
1199 } else {
1200 eprintln!(
1201 "\n{}",
1202 "Running Tick".color(colored::Color::Magenta).bold()
1203 )
1204 }
1205 }
1206 LogKind::Custom(writer) => {
1207 writeln!(
1208 writer,
1209 "\n{}",
1210 "Running Tick".color(colored::Color::Magenta).bold()
1211 )
1212 .unwrap();
1213 }
1214 }
1215
1216 let mut asterisk_indenter = |_line_no, write: &mut dyn std::fmt::Write| {
1217 write.write_str(&"*".color(colored::Color::Magenta).bold())?;
1218 write.write_str(" ")
1219 };
1220
1221 let mut tick_decision_writer = indenter::indented(&mut self.log)
1222 .with_format(indenter::Format::Custom {
1223 inserter: &mut asterisk_indenter,
1224 });
1225
1226 let hooks = self.hooks.get_mut(&(removed.0.clone(), removed.1)).unwrap();
1227 run_hooks(&mut tick_decision_writer, hooks);
1228
1229 let run_tick_future = removed.2.run_tick();
1230 if let Some(inline_hooks) =
1231 self.inline_hooks.get_mut(&(removed.0.clone(), removed.1))
1232 {
1233 let mut run_tick_future_pinned = pin!(run_tick_future);
1234
1235 loop {
1236 tokio::select! {
1237 biased;
1238 r = &mut run_tick_future_pinned => {
1239 abort_assert!(r, "tick DFIR run_tick() returned false");
1240 break;
1241 }
1242 _ = async {} => {
1243 bolero_generator::any::scope::borrow_with(|driver| {
1244 for hook in inline_hooks.iter_mut() {
1245 if hook.pending_decision() {
1246 if !hook.has_decision() {
1247 hook.autonomous_decision(driver);
1248 }
1249
1250 hook.release_decision(&mut tick_decision_writer);
1251 }
1252 }
1253 });
1254 }
1255 }
1256 }
1257 } else {
1258 abort_assert!(
1259 run_tick_future.await,
1260 "tick DFIR run_tick() returned false"
1261 );
1262 }
1263
1264 self.possibly_ready_ticks.push(removed);
1265 } else {
1266 let next_obs = next_tick_or_obs - self.possibly_ready_ticks.len();
1267 let mut default_hooks = vec![];
1268 let hooks = self
1269 .hooks
1270 .get_mut(&self.possibly_ready_observation[next_obs])
1271 .unwrap_or(&mut default_hooks);
1272
1273 run_hooks(&mut self.log, hooks);
1274 }
1275 }
1276 }
1277 }
1278 }
1279}
1280
1281fn run_hooks(tick_decision_writer: &mut impl std::fmt::Write, hooks: &mut Vec<Box<dyn SimHook>>) {
1282 let mut remaining_decision_count = hooks.len();
1283 let mut made_nontrivial_decision = false;
1284
1285 bolero::generator::bolero_generator::any::scope::borrow_with(|driver| {
1286 hooks.iter_mut().for_each(|hook| {
1288 if let Some(is_nontrivial) = hook.current_decision() {
1289 made_nontrivial_decision |= is_nontrivial;
1290 remaining_decision_count -= 1;
1291 } else if !hook.can_make_nontrivial_decision() {
1292 hook.autonomous_decision(driver, false);
1296 remaining_decision_count -= 1;
1297 }
1298 });
1299
1300 hooks.iter_mut().for_each(|hook| {
1301 if hook.current_decision().is_none() {
1302 made_nontrivial_decision |= hook.autonomous_decision(
1303 driver,
1304 !made_nontrivial_decision && remaining_decision_count == 1,
1305 );
1306 remaining_decision_count -= 1;
1307 }
1308
1309 hook.release_decision(tick_decision_writer);
1310 });
1311 });
1312}