Skip to main content

commonware_runtime/
deterministic.rs

1//! A deterministic runtime that randomly selects tasks to run based on a seed
2//!
3//! # Panics
4//!
5//! Unless configured otherwise, any task panic will lead to a runtime panic.
6//!
7//! # External Processes
8//!
9//! When testing an application that interacts with some external process, it can appear to
10//! the runtime that progress has stalled because no pending tasks can make progress and/or
11//! that futures resolve at variable latency (which in turn triggers non-deterministic execution).
12//!
13//! To support such applications, the runtime can be built with the `external` feature to both
14//! sleep for each [Config::cycle] (opting to wait if all futures are pending) and to constrain
15//! the resolution latency of any future (with `pace()`).
16//!
17//! **Applications that do not interact with external processes (or are able to mock them) should never
18//! need to enable this feature. It is commonly used when testing consensus with external execution environments
19//! that use their own runtime (but are deterministic over some set of inputs).**
20//!
21//! # Metrics
22//!
23//! This runtime enforces metrics are unique and well-formed:
24//! - Labels must start with `[a-zA-Z]` and contain only `[a-zA-Z0-9_]`
25//! - Re-registering the same metric key reuses the existing metric handle when the type matches
26//!
27//! # Example
28//!
29//! ```rust
30//! use commonware_runtime::{Spawner, Runner, deterministic, Metrics, Supervisor};
31//!
32//! let executor =  deterministic::Runner::default();
33//! executor.start(|context| async move {
34//!     println!("Parent started");
35//!     let result = context.child("child").spawn(|_| async move {
36//!         println!("Child started");
37//!         "hello"
38//!     });
39//!     println!("Child result: {:?}", result.await);
40//!     println!("Parent exited");
41//!     println!("Auditor state: {}", context.auditor().state());
42//! });
43//! ```
44
45pub use crate::storage::faulty::{
46    Config as FaultConfig, PartialWriteMode, ResizeConfig, WriteConfig,
47};
48use crate::{
49    BlobVersion, BufferPool, BufferPoolConfig, Clock, Error, Execution, Handle, IoBufs, ListenerOf,
50    METRICS_PREFIX, Name, Panicked, child_label,
51    network::{
52        audited::Network as AuditedNetwork, deterministic::Network as DeterministicNetwork,
53        metered::Network as MeteredNetwork,
54    },
55    prefixed_name,
56    storage::{
57        audited::Storage as AuditedStorage,
58        faulty::Storage as FaultyStorage,
59        memory::{Snapshot as MemStorageSnapshot, Storage as MemStorage},
60        metered::Storage as MeteredStorage,
61    },
62    telemetry::metrics::{
63        Counter, CounterFamily, GaugeFamily, Metric, Register, Registered, Registry, add_attribute,
64        raw, task::Label, validate_label,
65    },
66    utils::{
67        Panicker,
68        signal::{Signal, Stopper},
69        supervision::Tree,
70    },
71};
72#[cfg(feature = "external")]
73use crate::{Blocker, Pacer};
74use commonware_codec::Encode;
75use commonware_formatting::hex;
76use commonware_macros::select;
77use commonware_parallel::{Rayon, ThreadPool};
78use commonware_utils::{
79    Cached, SystemTimeExt,
80    sync::{Mutex, RwLock},
81    time::SYSTEM_TIME_PRECISION,
82};
83#[cfg(feature = "external")]
84use futures::task::noop_waker;
85use futures::{
86    Future,
87    task::{ArcWake, waker},
88};
89use governor::clock::{Clock as GClock, ReasonablyRealtime};
90#[cfg(feature = "external")]
91use pin_project::pin_project;
92use rand::{CryptoRng, Rng, SeedableRng, TryCryptoRng, TryRng, prelude::SliceRandom, rngs::StdRng};
93use rayon::{ThreadPoolBuildError, ThreadPoolBuilder};
94use sha2::{Digest as _, Sha256};
95use std::{
96    collections::{BTreeMap, BinaryHeap, HashMap},
97    convert::Infallible,
98    mem::{replace, take},
99    net::{IpAddr, SocketAddr},
100    num::NonZeroUsize,
101    panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
102    pin::Pin,
103    sync::{Arc, Weak},
104    task::{self, Poll, Waker},
105    time::{Duration, SystemTime, UNIX_EPOCH},
106};
107use tracing::trace;
108
109#[derive(Debug)]
110struct Metrics {
111    iterations: Counter,
112    tasks_spawned: CounterFamily<Label>,
113    tasks_running: GaugeFamily<Label>,
114    task_polls: CounterFamily<Label>,
115}
116
117impl Metrics {
118    pub fn init(registry: &mut impl Register) -> Self {
119        Self {
120            iterations: registry.register(
121                "iterations",
122                "Total number of iterations",
123                raw::Counter::default(),
124            ),
125            tasks_spawned: registry.register(
126                "tasks_spawned",
127                "Total number of tasks spawned",
128                raw::Family::default(),
129            ),
130            tasks_running: registry.register(
131                "tasks_running",
132                "Number of tasks currently running",
133                raw::Family::default(),
134            ),
135            task_polls: registry.register(
136                "task_polls",
137                "Total number of task polls",
138                raw::Family::default(),
139            ),
140        }
141    }
142}
143
144/// A SHA-256 digest.
145type Digest = [u8; 32];
146
147/// Hashes an unambiguous sequence of fields for deterministic runtime auditing.
148pub(crate) struct AuditHasher(Sha256);
149
150impl AuditHasher {
151    /// Creates an empty audit hasher.
152    pub(crate) fn new() -> Self {
153        Self(Sha256::new())
154    }
155
156    /// Adds a length-prefixed field to the audit.
157    pub(crate) fn update(&mut self, value: impl AsRef<[u8]>) {
158        let value = value.as_ref();
159        self.0.update((value.len() as u64).to_be_bytes());
160        self.0.update(value);
161    }
162
163    /// Adds the logical contents of `bufs` as one length-prefixed field.
164    ///
165    /// Physical chunk boundaries are excluded because they are not part of the storage or network
166    /// operation being audited.
167    pub(crate) fn update_bufs(&mut self, bufs: &IoBufs) {
168        self.0.update((bufs.len() as u64).to_be_bytes());
169        bufs.for_each_chunk(|chunk| self.0.update(chunk));
170    }
171
172    /// Returns the digest of all fields added to the audit.
173    pub(crate) fn finalize(self) -> Digest {
174        self.0.finalize().into()
175    }
176}
177
178/// Track the state of the runtime for determinism auditing.
179pub struct Auditor {
180    digest: Mutex<Digest>,
181}
182
183impl Default for Auditor {
184    fn default() -> Self {
185        Self {
186            digest: Digest::default().into(),
187        }
188    }
189}
190
191impl Auditor {
192    /// Record that an event happened.
193    /// This auditor's hash will be updated with the event's `label` and
194    /// whatever other data is passed in the `payload` closure.
195    pub(crate) fn event<F>(&self, label: &'static [u8], payload: F)
196    where
197        F: FnOnce(&mut AuditHasher),
198    {
199        let mut digest = self.digest.lock();
200
201        let mut hasher = AuditHasher::new();
202        hasher.update(digest.as_ref());
203        hasher.update(label);
204        payload(&mut hasher);
205
206        *digest = hasher.finalize();
207    }
208
209    /// Generate a representation of the current state of the runtime.
210    ///
211    /// This can be used to ensure that logic running on top
212    /// of the runtime is interacting deterministically.
213    pub fn state(&self) -> String {
214        let hash = self.digest.lock();
215        hex(hash.as_ref())
216    }
217}
218
219/// A dynamic RNG that can safely be sent between threads.
220pub type BoxDynRng = Box<dyn CryptoRng + Send + 'static>;
221
222/// Configuration for the `deterministic` runtime.
223pub struct Config {
224    /// Random number generator.
225    rng: BoxDynRng,
226
227    /// The cycle duration determines how much time is advanced after each iteration of the event
228    /// loop. This is useful to prevent starvation if some task never yields.
229    cycle: Duration,
230
231    /// Time the runtime starts at.
232    start_time: SystemTime,
233
234    /// If the runtime is still executing at this point (i.e. a test hasn't stopped), panic.
235    timeout: Option<Duration>,
236
237    /// Whether spawned tasks should catch panics instead of propagating them.
238    catch_panics: bool,
239
240    /// Configuration for deterministic storage fault injection.
241    /// Defaults to no faults being injected.
242    storage_fault_cfg: FaultConfig,
243
244    /// Buffer pool configuration for network I/O.
245    network_buffer_pool_cfg: BufferPoolConfig,
246
247    /// Buffer pool configuration for storage I/O.
248    storage_buffer_pool_cfg: BufferPoolConfig,
249}
250
251impl Config {
252    /// Returns a new [Config] with default values.
253    pub fn new() -> Self {
254        cfg_if::cfg_if! {
255            if #[cfg(miri)] {
256                // Reduce max_per_class to avoid slow atomics under Miri
257                let network_buffer_pool_cfg = BufferPoolConfig::for_network()
258                    .with_max_per_class(commonware_utils::NZU32!(32))
259                    .with_thread_cache_disabled();
260                let storage_buffer_pool_cfg = BufferPoolConfig::for_storage()
261                    .with_max_per_class(commonware_utils::NZU32!(32))
262                    .with_thread_cache_disabled();
263            } else {
264                let network_buffer_pool_cfg =
265                    BufferPoolConfig::for_network().with_thread_cache_disabled();
266                let storage_buffer_pool_cfg =
267                    BufferPoolConfig::for_storage().with_thread_cache_disabled();
268            }
269        }
270
271        Self {
272            rng: Box::new(StdRng::seed_from_u64(42)),
273            cycle: Duration::from_millis(1),
274            start_time: UNIX_EPOCH,
275            timeout: None,
276            catch_panics: false,
277            storage_fault_cfg: FaultConfig::default(),
278            network_buffer_pool_cfg,
279            storage_buffer_pool_cfg,
280        }
281    }
282
283    // Setters
284    /// See [Config]
285    pub fn with_seed(self, seed: u64) -> Self {
286        let rng: BoxDynRng = Box::new(StdRng::seed_from_u64(seed));
287        self.with_rng(rng)
288    }
289
290    /// Provide the config with a dynamic RNG directly.
291    ///
292    /// This can be useful for, e.g. fuzzing, where beyond just having randomness,
293    /// you might want to control specific bytes of the RNG. By taking in a dynamic
294    /// RNG object, any behavior is possible.
295    pub fn with_rng(mut self, rng: impl Into<BoxDynRng>) -> Self {
296        self.rng = rng.into();
297        self
298    }
299
300    /// See [Config]
301    pub const fn with_cycle(mut self, cycle: Duration) -> Self {
302        self.cycle = cycle;
303        self
304    }
305    /// See [Config]
306    pub const fn with_start_time(mut self, start_time: SystemTime) -> Self {
307        self.start_time = start_time;
308        self
309    }
310    /// See [Config]
311    pub const fn with_timeout(mut self, timeout: Option<Duration>) -> Self {
312        self.timeout = timeout;
313        self
314    }
315    /// See [Config]
316    pub const fn with_catch_panics(mut self, catch_panics: bool) -> Self {
317        self.catch_panics = catch_panics;
318        self
319    }
320    /// See [Config]
321    pub fn with_network_buffer_pool_config(mut self, cfg: BufferPoolConfig) -> Self {
322        self.network_buffer_pool_cfg = cfg;
323        self
324    }
325    /// See [Config]
326    pub fn with_storage_buffer_pool_config(mut self, cfg: BufferPoolConfig) -> Self {
327        self.storage_buffer_pool_cfg = cfg;
328        self
329    }
330
331    /// Configure storage fault injection.
332    ///
333    /// When set, the runtime will inject deterministic storage errors based on
334    /// the provided configuration. Faults are drawn from the shared RNG, ensuring
335    /// reproducible failure patterns for a given seed.
336    pub const fn with_storage_fault_config(mut self, faults: FaultConfig) -> Self {
337        self.storage_fault_cfg = faults;
338        self
339    }
340
341    // Getters
342    /// See [Config]
343    pub const fn cycle(&self) -> Duration {
344        self.cycle
345    }
346    /// See [Config]
347    pub const fn start_time(&self) -> SystemTime {
348        self.start_time
349    }
350    /// See [Config]
351    pub const fn timeout(&self) -> Option<Duration> {
352        self.timeout
353    }
354    /// See [Config]
355    pub const fn catch_panics(&self) -> bool {
356        self.catch_panics
357    }
358    /// See [Config]
359    pub const fn network_buffer_pool_config(&self) -> &BufferPoolConfig {
360        &self.network_buffer_pool_cfg
361    }
362    /// See [Config]
363    pub const fn storage_buffer_pool_config(&self) -> &BufferPoolConfig {
364        &self.storage_buffer_pool_cfg
365    }
366
367    /// Assert that the configuration is valid.
368    pub fn assert(&self) {
369        assert!(
370            self.cycle != Duration::default() || self.timeout.is_none(),
371            "cycle duration must be non-zero when timeout is set",
372        );
373        assert!(
374            self.cycle >= SYSTEM_TIME_PRECISION,
375            "cycle duration must be greater than or equal to system time precision"
376        );
377        assert!(
378            self.start_time >= UNIX_EPOCH,
379            "start time must be greater than or equal to unix epoch"
380        );
381    }
382}
383
384impl Default for Config {
385    fn default() -> Self {
386        Self::new()
387    }
388}
389
390/// Deterministic runtime that randomly selects tasks to run based on a seed.
391pub struct Executor {
392    registry: Registry,
393    cycle: Duration,
394    deadline: Option<SystemTime>,
395    metrics: Arc<Metrics>,
396    auditor: Arc<Auditor>,
397    rng: Arc<Mutex<BoxDynRng>>,
398    time: Mutex<SystemTime>,
399    tasks: Arc<Tasks>,
400    sleeping: Mutex<BinaryHeap<Alarm>>,
401    shutdown: Mutex<Stopper>,
402    panicker: Panicker,
403    dns: Mutex<HashMap<String, Vec<IpAddr>>>,
404}
405
406impl Executor {
407    /// Advance simulated time by [Config::cycle].
408    ///
409    /// When built with the `external` feature, sleep for [Config::cycle] to let
410    /// external processes make progress.
411    fn advance_time(&self) -> SystemTime {
412        #[cfg(feature = "external")]
413        std::thread::sleep(self.cycle);
414
415        let mut time = self.time.lock();
416        *time = time
417            .checked_add(self.cycle)
418            .expect("executor time overflowed");
419        let now = *time;
420        trace!(now = now.epoch_millis(), "time advanced");
421        now
422    }
423
424    /// Ensure the runtime has not reached its configured deadline.
425    fn assert_deadline(&self, current: SystemTime) {
426        if self.deadline.is_some_and(|deadline| current >= deadline) {
427            panic!("runtime timeout");
428        }
429    }
430
431    /// When idle, jump directly to the next actionable time.
432    ///
433    /// When built with the `external` feature, never skip ahead (to ensure we poll all pending tasks
434    /// every [Config::cycle]).
435    fn skip_idle_time(&self, current: SystemTime) -> SystemTime {
436        if cfg!(feature = "external") || self.tasks.ready() != 0 {
437            return current;
438        }
439
440        let mut skip_until = None;
441        {
442            let sleeping = self.sleeping.lock();
443            if let Some(next) = sleeping.peek()
444                && next.time > current
445            {
446                skip_until = Some(next.time);
447            }
448        }
449
450        skip_until.map_or(current, |deadline| {
451            let mut time = self.time.lock();
452            *time = deadline;
453            let now = *time;
454            trace!(now = now.epoch_millis(), "time skipped");
455            now
456        })
457    }
458
459    /// Wake any sleepers whose deadlines have elapsed.
460    fn wake_ready_sleepers(&self, current: SystemTime) {
461        let mut sleeping = self.sleeping.lock();
462        while let Some(next) = sleeping.peek() {
463            if next.time <= current {
464                let sleeper = sleeping.pop().unwrap();
465                sleeper.waker.wake();
466            } else {
467                break;
468            }
469        }
470    }
471
472    /// Wake sleepers until the runtime can make progress.
473    ///
474    /// Canceling a polled sleep leaves its alarm registered until its deadline. If that alarm
475    /// wakes no task, continue to later deadlines before deciding the runtime has stalled.
476    ///
477    /// When built with the `external` feature, the passage of time is sufficient to continue.
478    fn wake_until_progress(&self, mut current: SystemTime) {
479        loop {
480            // Move to the next actionable time. Check the runtime deadline before waking sleepers
481            // so timeout takes precedence over work scheduled at the deadline.
482            current = self.skip_idle_time(current);
483            self.assert_deadline(current);
484            self.wake_ready_sleepers(current);
485
486            // Continue once external work or a woken task can make progress. Without either,
487            // another alarm is the runtime's only remaining source of progress.
488            if cfg!(feature = "external") || self.tasks.ready() != 0 {
489                return;
490            }
491            if self.sleeping.lock().is_empty() {
492                panic!("runtime stalled");
493            }
494        }
495    }
496}
497
498/// An artifact that can be used to recover the state of the runtime.
499///
500/// This is useful when mocking unclean shutdown (while retaining deterministic behavior).
501pub struct Checkpoint {
502    cycle: Duration,
503    deadline: Option<SystemTime>,
504    auditor: Arc<Auditor>,
505    rng: Arc<Mutex<BoxDynRng>>,
506    time: Mutex<SystemTime>,
507    storage: MemStorageSnapshot,
508    storage_fault_cfg: FaultConfig,
509    dns: Mutex<HashMap<String, Vec<IpAddr>>>,
510    catch_panics: bool,
511    network_buffer_pool_cfg: BufferPoolConfig,
512    storage_buffer_pool_cfg: BufferPoolConfig,
513}
514
515impl Checkpoint {
516    /// Get a reference to the [Auditor].
517    pub fn auditor(&self) -> Arc<Auditor> {
518        self.auditor.clone()
519    }
520}
521
522#[allow(clippy::large_enum_variant)]
523enum State {
524    Config(Config),
525    Checkpoint(Checkpoint),
526}
527
528/// Implementation of [crate::Runner] for the `deterministic` runtime.
529pub struct Runner {
530    state: State,
531}
532
533impl From<Config> for Runner {
534    fn from(cfg: Config) -> Self {
535        Self::new(cfg)
536    }
537}
538
539impl From<Checkpoint> for Runner {
540    fn from(checkpoint: Checkpoint) -> Self {
541        Self {
542            state: State::Checkpoint(checkpoint),
543        }
544    }
545}
546
547impl Runner {
548    /// Initialize a new `deterministic` runtime with the given seed and cycle duration.
549    pub fn new(cfg: Config) -> Self {
550        // Ensure config is valid
551        cfg.assert();
552        Self {
553            state: State::Config(cfg),
554        }
555    }
556
557    /// Initialize a new `deterministic` runtime with the default configuration
558    /// and the provided seed.
559    pub fn seeded(seed: u64) -> Self {
560        Self::new(Config::default().with_seed(seed))
561    }
562
563    /// Initialize a new `deterministic` runtime with the default configuration
564    /// but exit after the given timeout.
565    pub fn timed(timeout: Duration) -> Self {
566        let cfg = Config {
567            timeout: Some(timeout),
568            ..Config::default()
569        };
570        Self::new(cfg)
571    }
572
573    /// Like [crate::Runner::start], but also returns a [Checkpoint] that can be used
574    /// to recover the state of the runtime in a subsequent run.
575    pub fn start_and_recover<F, Fut>(self, f: F) -> (Fut::Output, Checkpoint)
576    where
577        F: FnOnce(Context) -> Fut,
578        Fut: Future,
579    {
580        // Setup context and return strong reference to executor
581        let (context, executor, panicked) = match self.state {
582            State::Config(config) => Context::new(config),
583            State::Checkpoint(checkpoint) => Context::recover(checkpoint),
584        };
585
586        // Pin root task to the heap
587        let storage = context.storage.clone();
588        let network_buffer_pool_cfg = context.network_buffer_pool.config().clone();
589        let storage_buffer_pool_cfg = context.storage_buffer_pool.config().clone();
590        let mut root = Box::pin(panicked.interrupt(f(context)));
591
592        // Register the root task
593        Tasks::register_root(&executor.tasks);
594
595        // Process tasks until root task completes or progress stalls.
596        // Wrap the loop in catch_unwind to ensure task cleanup runs even if the loop or a task panics.
597        let result = catch_unwind(AssertUnwindSafe(|| {
598            loop {
599                // Ensure we have not exceeded our deadline
600                let current = *executor.time.lock();
601                executor.assert_deadline(current);
602
603                // Drain all ready tasks
604                let mut queue = executor.tasks.drain();
605
606                // Shuffle tasks (if more than one)
607                if queue.len() > 1 {
608                    let mut rng = executor.rng.lock();
609                    queue.shuffle(&mut *rng);
610                }
611
612                // Run all snapshotted tasks
613                //
614                // This approach is more efficient than randomly selecting a task one-at-a-time
615                // because it ensures we don't pull the same pending task multiple times in a row (without
616                // processing a different task required for other tasks to make progress).
617                trace!(
618                    iter = executor.metrics.iterations.get(),
619                    tasks = queue.len(),
620                    "starting loop"
621                );
622                let mut output = None;
623                for id in queue {
624                    // Lookup the task (it may have completed already)
625                    let Some(task) = executor.tasks.get(id) else {
626                        trace!(id, "skipping missing task");
627                        continue;
628                    };
629
630                    // Record task for auditing
631                    executor.auditor.event(b"process_task", |hasher| {
632                        hasher.update(task.id.to_be_bytes());
633                        hasher.update(task.label.name().as_bytes());
634                    });
635                    executor.metrics.task_polls.get_or_create(&task.label).inc();
636                    trace!(id, "processing task");
637
638                    // Prepare task for polling
639                    let waker = waker(Arc::new(TaskWaker {
640                        id,
641                        tasks: Arc::downgrade(&executor.tasks),
642                    }));
643                    let mut cx = task::Context::from_waker(&waker);
644
645                    // Poll the task
646                    match &task.mode {
647                        Mode::Root => {
648                            // Poll the root task
649                            if let Poll::Ready(result) = root.as_mut().poll(&mut cx) {
650                                trace!(id, "root task is complete");
651                                output = Some(result);
652                                break;
653                            }
654                        }
655                        Mode::Work(future) => {
656                            // Get the future (if it still exists)
657                            let mut fut_opt = future.lock();
658                            let Some(fut) = fut_opt.as_mut() else {
659                                trace!(id, "skipping already complete task");
660
661                                // Remove the future
662                                executor.tasks.remove(id);
663                                continue;
664                            };
665
666                            // Poll the task
667                            if fut.as_mut().poll(&mut cx).is_ready() {
668                                trace!(id, "task is complete");
669
670                                // Remove the future
671                                executor.tasks.remove(id);
672                                *fut_opt = None;
673                                continue;
674                            }
675                        }
676                    }
677
678                    // Try again later if task is still pending
679                    trace!(id, "task is still pending");
680                }
681
682                // If the root task has completed, exit as soon as possible
683                if let Some(output) = output {
684                    break output;
685                }
686
687                // Advance time and wake sleepers until the runtime can make progress
688                let current = executor.advance_time();
689                executor.wake_until_progress(current);
690
691                // Record that we completed another iteration of the event loop.
692                executor.metrics.iterations.inc();
693            }
694        }));
695
696        // Clear remaining tasks from the executor.
697        //
698        // It is critical that we wait to drop the strong
699        // reference to executor until after we have dropped
700        // all tasks (as they may attempt to upgrade their weak
701        // reference to the executor during drop).
702        executor.sleeping.lock().clear(); // included in tasks
703        let tasks = executor.tasks.clear();
704        for task in tasks {
705            let Mode::Work(future) = &task.mode else {
706                continue;
707            };
708            *future.lock() = None;
709        }
710
711        // Drop the root task to release any Context references it may still hold.
712        // This is necessary when the loop exits early (e.g., timeout) while the
713        // root future is still Pending and holds captured variables with Context references.
714        drop(root);
715
716        // No task can issue or make a write durable after this crash boundary.
717        storage
718            .inner()
719            .inner()
720            .crash()
721            .expect("retaining successful unsynced writes at crash should succeed");
722        let storage_fault_cfg = storage.inner().inner().config().read().clone();
723        let storage = storage.inner().inner().inner().take_snapshot();
724
725        // Assert the context doesn't escape the start() function (behavior
726        // is undefined in this case)
727        assert!(
728            Arc::weak_count(&executor) == 0,
729            "executor still has weak references"
730        );
731
732        // Handle the result — resume the original panic after cleanup if one was caught.
733        let output = match result {
734            Ok(output) => output,
735            Err(payload) => resume_unwind(payload),
736        };
737
738        // Extract the executor from the Arc
739        let executor = Arc::into_inner(executor).expect("executor still has strong references");
740
741        // Construct a checkpoint that can be used to restart the runtime
742        let checkpoint = Checkpoint {
743            cycle: executor.cycle,
744            deadline: executor.deadline,
745            auditor: executor.auditor,
746            rng: executor.rng,
747            time: executor.time,
748            storage,
749            storage_fault_cfg,
750            dns: executor.dns,
751            catch_panics: executor.panicker.catch(),
752            network_buffer_pool_cfg,
753            storage_buffer_pool_cfg,
754        };
755
756        (output, checkpoint)
757    }
758}
759
760impl Default for Runner {
761    fn default() -> Self {
762        Self::new(Config::default())
763    }
764}
765
766impl crate::Runner for Runner {
767    type Context = Context;
768
769    fn start<F, Fut>(self, f: F) -> Fut::Output
770    where
771        F: FnOnce(Self::Context) -> Fut,
772        Fut: Future,
773    {
774        let (output, _) = self.start_and_recover(f);
775        output
776    }
777}
778
779/// The mode of a [Task].
780enum Mode {
781    Root,
782    Work(Mutex<Option<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>>),
783}
784
785/// A future being executed by the [Executor].
786struct Task {
787    id: u128,
788    label: Label,
789
790    mode: Mode,
791}
792
793/// A waker for a [Task].
794struct TaskWaker {
795    id: u128,
796
797    tasks: Weak<Tasks>,
798}
799
800impl ArcWake for TaskWaker {
801    fn wake_by_ref(arc_self: &Arc<Self>) {
802        // Upgrade the weak reference to re-enqueue this task.
803        // If upgrade fails, the task queue has been dropped and no action is required.
804        //
805        // This can happen if some data is passed into the runtime and it drops after the runtime exits.
806        if let Some(tasks) = arc_self.tasks.upgrade() {
807            tasks.queue(arc_self.id);
808        }
809    }
810}
811
812/// A collection of [Task]s that are being executed by the [Executor].
813struct Tasks {
814    /// The next task id.
815    counter: Mutex<u128>,
816    /// Tasks ready to be polled.
817    ready: Mutex<Vec<u128>>,
818    /// All running tasks.
819    running: Mutex<BTreeMap<u128, Arc<Task>>>,
820}
821
822impl Tasks {
823    /// Create a new task queue.
824    const fn new() -> Self {
825        Self {
826            counter: Mutex::new(0),
827            ready: Mutex::new(Vec::new()),
828            running: Mutex::new(BTreeMap::new()),
829        }
830    }
831
832    /// Increment the task counter and return the old value.
833    fn increment(&self) -> u128 {
834        let mut counter = self.counter.lock();
835        let old = *counter;
836        *counter = counter.checked_add(1).expect("task counter overflow");
837        old
838    }
839
840    /// Register the root task.
841    ///
842    /// If the root task has already been registered, this function will panic.
843    fn register_root(arc_self: &Arc<Self>) {
844        let id = arc_self.increment();
845        let task = Arc::new(Task {
846            id,
847            label: Label::root(),
848            mode: Mode::Root,
849        });
850        arc_self.register(id, task);
851    }
852
853    /// Register a non-root task to be executed.
854    fn register_work(
855        arc_self: &Arc<Self>,
856        label: Label,
857        future: Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
858    ) {
859        let id = arc_self.increment();
860        let task = Arc::new(Task {
861            id,
862            label,
863            mode: Mode::Work(Mutex::new(Some(future))),
864        });
865        arc_self.register(id, task);
866    }
867
868    /// Register a new task to be executed.
869    fn register(&self, id: u128, task: Arc<Task>) {
870        // Track as running until completion
871        self.running.lock().insert(id, task);
872
873        // Add to ready
874        self.queue(id);
875    }
876
877    /// Enqueue an already registered task to be executed.
878    fn queue(&self, id: u128) {
879        let mut ready = self.ready.lock();
880        ready.push(id);
881    }
882
883    /// Drain all ready tasks.
884    fn drain(&self) -> Vec<u128> {
885        let mut queue = self.ready.lock();
886        let len = queue.len();
887        replace(&mut *queue, Vec::with_capacity(len))
888    }
889
890    /// The number of ready tasks.
891    fn ready(&self) -> usize {
892        self.ready.lock().len()
893    }
894
895    /// Lookup a task.
896    ///
897    /// We must return cloned here because we cannot hold the running lock while polling a task (will
898    /// deadlock if [Self::register_work] is called).
899    fn get(&self, id: u128) -> Option<Arc<Task>> {
900        let running = self.running.lock();
901        running.get(&id).cloned()
902    }
903
904    /// Remove a task.
905    fn remove(&self, id: u128) {
906        self.running.lock().remove(&id);
907    }
908
909    /// Clear all tasks.
910    fn clear(&self) -> Vec<Arc<Task>> {
911        // Clear ready
912        self.ready.lock().clear();
913
914        // Clear running tasks
915        let running: BTreeMap<u128, Arc<Task>> = {
916            let mut running = self.running.lock();
917            take(&mut *running)
918        };
919        running.into_values().collect()
920    }
921}
922
923type Network = MeteredNetwork<AuditedNetwork<DeterministicNetwork>>;
924type Storage = MeteredStorage<AuditedStorage<FaultyStorage<MemStorage>>>;
925
926fn build_storage(
927    inner: MemStorage,
928    rng: Arc<Mutex<BoxDynRng>>,
929    faults: FaultConfig,
930    auditor: Arc<Auditor>,
931    registry: &mut impl Register,
932) -> Storage {
933    MeteredStorage::new(
934        AuditedStorage::new(
935            FaultyStorage::new(inner, rng, Arc::new(RwLock::new(faults))),
936            auditor,
937        ),
938        registry,
939    )
940}
941
942/// Implementation of [crate::Spawner], [crate::Clock],
943/// [crate::Network], and [crate::Storage] for the `deterministic`
944/// runtime.
945pub struct Context {
946    name: String,
947    attributes: Vec<(String, String)>,
948    executor: Weak<Executor>,
949    network: Arc<Network>,
950    storage: Arc<Storage>,
951    network_buffer_pool: BufferPool,
952    storage_buffer_pool: BufferPool,
953    tree: Arc<Tree>,
954    execution: Execution,
955}
956
957impl Context {
958    fn new(cfg: Config) -> (Self, Arc<Executor>, Panicked) {
959        // Create a new registry
960        let mut registry = Registry::new();
961        let mut runtime_registry = registry.sub_registry(METRICS_PREFIX);
962
963        // Initialize runtime
964        let metrics = Arc::new(Metrics::init(&mut runtime_registry));
965        let start_time = cfg.start_time;
966        let deadline = cfg
967            .timeout
968            .map(|timeout| start_time.checked_add(timeout).expect("timeout overflowed"));
969        let auditor = Arc::new(Auditor::default());
970
971        // Create shared RNG (used by both executor and storage)
972        let rng = Arc::new(Mutex::new(cfg.rng));
973
974        // Initialize buffer pools
975        let network_buffer_pool = BufferPool::new(
976            cfg.network_buffer_pool_cfg.clone(),
977            &mut runtime_registry.sub_registry("network_buffer_pool"),
978        );
979        let storage_buffer_pool = BufferPool::new(
980            cfg.storage_buffer_pool_cfg.clone(),
981            &mut runtime_registry.sub_registry("storage_buffer_pool"),
982        );
983
984        let storage = build_storage(
985            MemStorage::new(storage_buffer_pool.clone()),
986            rng.clone(),
987            cfg.storage_fault_cfg,
988            auditor.clone(),
989            &mut runtime_registry,
990        );
991
992        // Create network
993        let network = AuditedNetwork::new(DeterministicNetwork::default(), auditor.clone());
994        let network = MeteredNetwork::new(network, &mut runtime_registry);
995
996        // Initialize panicker
997        let (panicker, panicked) = Panicker::new(cfg.catch_panics);
998
999        let executor = Arc::new(Executor {
1000            registry,
1001            cycle: cfg.cycle,
1002            deadline,
1003            metrics,
1004            auditor,
1005            rng,
1006            time: Mutex::new(start_time),
1007            tasks: Arc::new(Tasks::new()),
1008            sleeping: Mutex::new(BinaryHeap::new()),
1009            shutdown: Mutex::new(Stopper::default()),
1010            panicker,
1011            dns: Mutex::new(HashMap::new()),
1012        });
1013
1014        (
1015            Self {
1016                name: String::new(),
1017                attributes: Vec::new(),
1018                executor: Arc::downgrade(&executor),
1019                network: Arc::new(network),
1020                storage: Arc::new(storage),
1021                network_buffer_pool,
1022                storage_buffer_pool,
1023                tree: Tree::root(),
1024                execution: Execution::default(),
1025            },
1026            executor,
1027            panicked,
1028        )
1029    }
1030
1031    /// Recover the inner state (deadline, metrics, auditor, rng, storage, etc.) from the current
1032    /// runtime and use it to initialize a new instance of the runtime. Storage recovery includes
1033    /// durable state and any unsynchronized mutations retained by the configured crash policy. A
1034    /// recovered runtime does not inherit pending tasks, network connections, or its shutdown
1035    /// signaler.
1036    ///
1037    /// This is useful for performing a deterministic simulation that spans multiple runtime instantiations,
1038    /// like simulating unclean shutdown (which involves repeatedly halting the runtime at unexpected intervals).
1039    ///
1040    /// It is only permitted to call this method after the runtime has finished (i.e. once `start` returns)
1041    /// and only permitted to do once (otherwise multiple recovered runtimes will share the same inner state).
1042    /// If either one of these conditions is violated, this method will panic.
1043    fn recover(checkpoint: Checkpoint) -> (Self, Arc<Executor>, Panicked) {
1044        // Rebuild metrics
1045        let mut registry = Registry::new();
1046        let mut runtime_registry = registry.sub_registry(METRICS_PREFIX);
1047        let metrics = Arc::new(Metrics::init(&mut runtime_registry));
1048
1049        // Copy state
1050        let network =
1051            AuditedNetwork::new(DeterministicNetwork::default(), checkpoint.auditor.clone());
1052        let network = MeteredNetwork::new(network, &mut runtime_registry);
1053
1054        // Initialize buffer pools
1055        let network_buffer_pool = BufferPool::new(
1056            checkpoint.network_buffer_pool_cfg.clone(),
1057            &mut runtime_registry.sub_registry("network_buffer_pool"),
1058        );
1059        let storage_buffer_pool = BufferPool::new(
1060            checkpoint.storage_buffer_pool_cfg.clone(),
1061            &mut runtime_registry.sub_registry("storage_buffer_pool"),
1062        );
1063        let storage = build_storage(
1064            MemStorage::from_snapshot(checkpoint.storage, storage_buffer_pool.clone()),
1065            checkpoint.rng.clone(),
1066            checkpoint.storage_fault_cfg,
1067            checkpoint.auditor.clone(),
1068            &mut runtime_registry,
1069        );
1070
1071        // Initialize panicker
1072        let (panicker, panicked) = Panicker::new(checkpoint.catch_panics);
1073
1074        let executor = Arc::new(Executor {
1075            // Copied from the checkpoint
1076            cycle: checkpoint.cycle,
1077            deadline: checkpoint.deadline,
1078            auditor: checkpoint.auditor,
1079            rng: checkpoint.rng,
1080            time: checkpoint.time,
1081            dns: checkpoint.dns,
1082
1083            // New state for the new runtime
1084            registry,
1085            metrics,
1086            tasks: Arc::new(Tasks::new()),
1087            sleeping: Mutex::new(BinaryHeap::new()),
1088            shutdown: Mutex::new(Stopper::default()),
1089            panicker,
1090        });
1091        (
1092            Self {
1093                name: String::new(),
1094                attributes: Vec::new(),
1095                executor: Arc::downgrade(&executor),
1096                network: Arc::new(network),
1097                storage: Arc::new(storage),
1098                network_buffer_pool,
1099                storage_buffer_pool,
1100                tree: Tree::root(),
1101                execution: Execution::default(),
1102            },
1103            executor,
1104            panicked,
1105        )
1106    }
1107
1108    /// Upgrade Weak reference to [Executor].
1109    fn executor(&self) -> Arc<Executor> {
1110        self.executor.upgrade().expect("executor already dropped")
1111    }
1112
1113    /// Get a reference to [Metrics].
1114    fn metrics(&self) -> Arc<Metrics> {
1115        self.executor().metrics.clone()
1116    }
1117
1118    /// Get a reference to the [Auditor].
1119    pub fn auditor(&self) -> Arc<Auditor> {
1120        self.executor().auditor.clone()
1121    }
1122
1123    /// Compute a [Sha256] digest of all storage contents.
1124    pub fn storage_audit(&self) -> Digest {
1125        self.storage.inner().inner().inner().audit()
1126    }
1127
1128    /// Access the storage fault configuration.
1129    ///
1130    /// Changes to the returned [`FaultConfig`] take effect immediately for
1131    /// subsequent storage operations. This allows dynamically enabling or
1132    /// disabling fault injection during a test.
1133    pub fn storage_fault_config(&self) -> Arc<RwLock<FaultConfig>> {
1134        self.storage.inner().inner().config()
1135    }
1136
1137    /// Register a DNS mapping for a hostname.
1138    ///
1139    /// If `addrs` is `None`, the mapping is removed.
1140    /// If `addrs` is `Some`, the mapping is added or updated.
1141    pub fn resolver_register(&self, host: impl Into<String>, addrs: Option<Vec<IpAddr>>) {
1142        // Update the auditor
1143        let executor = self.executor();
1144        let host = host.into();
1145        executor.auditor.event(b"resolver_register", |hasher| {
1146            hasher.update(host.as_bytes());
1147            hasher.update(addrs.encode());
1148        });
1149
1150        // Update the DNS mapping
1151        let mut dns = executor.dns.lock();
1152        match addrs {
1153            Some(addrs) => {
1154                dns.insert(host, addrs);
1155            }
1156            None => {
1157                dns.remove(&host);
1158            }
1159        }
1160    }
1161}
1162
1163impl crate::Spawner for Context {
1164    fn dedicated(mut self) -> Self {
1165        self.execution = Execution::Dedicated;
1166        self
1167    }
1168
1169    fn shared(mut self, blocking: bool) -> Self {
1170        self.execution = Execution::Shared(blocking);
1171        self
1172    }
1173
1174    fn spawn<F, Fut, T>(mut self, f: F) -> Handle<T>
1175    where
1176        F: FnOnce(Self) -> Fut + Send + 'static,
1177        Fut: Future<Output = T> + Send + 'static,
1178        T: Send + 'static,
1179    {
1180        // Get metrics
1181        let (label, metric) = spawn_metrics!(self);
1182
1183        // Track supervision before resetting configuration
1184        let parent = Arc::clone(&self.tree);
1185        self.execution = Execution::default();
1186        let (child, aborted) = Tree::child(&parent);
1187        if aborted {
1188            return Handle::closed(metric);
1189        }
1190        self.tree = child;
1191
1192        // Spawn the task (we don't care about Model)
1193        let executor = self.executor();
1194        let future = f(self);
1195        let (f, handle) = Handle::init(
1196            future,
1197            metric,
1198            executor.panicker.clone(),
1199            Arc::clone(&parent),
1200        );
1201        Tasks::register_work(&executor.tasks, label, Box::pin(f));
1202
1203        // Register the task on the parent
1204        if let Some(aborter) = handle.aborter() {
1205            parent.register(aborter);
1206        }
1207
1208        handle
1209    }
1210
1211    async fn stop(self, value: i32, timeout: Option<Duration>) -> Result<(), Error> {
1212        let executor = self.executor();
1213        executor.auditor.event(b"stop", |hasher| {
1214            hasher.update(value.to_be_bytes());
1215        });
1216        let stop_resolved = {
1217            let mut shutdown = executor.shutdown.lock();
1218            shutdown.stop(value)
1219        };
1220
1221        // Wait for all tasks to complete or the timeout to fire
1222        let timeout_future = timeout.map_or_else(
1223            || futures::future::Either::Right(futures::future::pending()),
1224            |duration| futures::future::Either::Left(self.sleep(duration)),
1225        );
1226        select! {
1227            result = stop_resolved => {
1228                result.map_err(|_| Error::Closed)?;
1229                Ok(())
1230            },
1231            _ = timeout_future => Err(Error::Timeout),
1232        }
1233    }
1234
1235    fn stopped(&self) -> Signal {
1236        let executor = self.executor();
1237        executor.auditor.event(b"stopped", |_| {});
1238
1239        executor.shutdown.lock().stopped()
1240    }
1241}
1242
1243// Rayon permits one permanent registry registration per OS thread. Cache the pool that
1244// registered the executor thread so later requests and runners reuse it.
1245commonware_utils::thread_local_cache!(static THREAD_POOL: ThreadPool);
1246
1247/// Returns the single-threaded pool the executor thread registered with, created on first use.
1248///
1249/// All pool work executes inline on the executor thread, so a larger pool would only
1250/// add permanently unstarted workers.
1251fn shared_thread_pool() -> Result<ThreadPool, ThreadPoolBuildError> {
1252    let pool = Cached::take(
1253        &THREAD_POOL,
1254        || {
1255            ThreadPoolBuilder::new()
1256                .num_threads(1)
1257                .use_current_thread()
1258                .build()
1259                .map(Arc::new)
1260        },
1261        |_| Ok(()),
1262    )?;
1263    Ok(Arc::clone(&pool))
1264}
1265
1266/// Spawning threads would be nondeterministic, so the pool has no background workers. The
1267/// executor thread registers itself as its sole member and all work executes inline.
1268///
1269/// Rayon's current-thread registration is permanent and per-OS-thread, so only one pool
1270/// can ever execute work on the executor thread. Every request (including from a later
1271/// runner on the same thread) returns a strategy on that single-threaded pool with its
1272/// planning parallelism set independently. This controls adaptive decisions and manual
1273/// partitioning hints while Rayon executes on the sole registered thread. The returned
1274/// strategy is therefore tied to the executor thread.
1275impl crate::Strategizer for Context {
1276    fn strategy(&self, parallelism: NonZeroUsize) -> Rayon {
1277        Rayon::with_pool(
1278            shared_thread_pool().expect("failed to create deterministic Rayon thread pool"),
1279        )
1280        .with_parallelism(parallelism)
1281    }
1282}
1283
1284impl crate::Supervisor for Context {
1285    fn child(&self, label: &'static str) -> Self {
1286        let (tree, _) = Tree::child(&self.tree);
1287        Self {
1288            name: child_label(&self.name, label),
1289            attributes: self.attributes.clone(),
1290            executor: self.executor.clone(),
1291            network: self.network.clone(),
1292            storage: self.storage.clone(),
1293            network_buffer_pool: self.network_buffer_pool.clone(),
1294            storage_buffer_pool: self.storage_buffer_pool.clone(),
1295            tree,
1296            execution: Execution::default(),
1297        }
1298    }
1299
1300    fn with_attribute(mut self, key: &'static str, value: impl std::fmt::Display) -> Self {
1301        // Validate label format (must match [a-zA-Z][a-zA-Z0-9_]*)
1302        validate_label(key);
1303
1304        // Add the attribute to the list of attributes
1305        add_attribute(&mut self.attributes, key, value);
1306        self
1307    }
1308
1309    fn name(&self) -> Name {
1310        Name {
1311            label: self.name.clone(),
1312            attributes: self.attributes.clone(),
1313        }
1314    }
1315}
1316
1317impl crate::Metrics for Context {
1318    fn register<N: Into<String>, H: Into<String>, M: Metric>(
1319        &self,
1320        name: N,
1321        help: H,
1322        metric: M,
1323    ) -> Registered<M> {
1324        let name = name.into();
1325        let help = help.into();
1326        let executor = self.executor();
1327        executor.auditor.event(b"register", |hasher| {
1328            hasher.update(name.as_bytes());
1329            hasher.update(help.as_bytes());
1330            for (k, v) in &self.attributes {
1331                hasher.update(k.as_bytes());
1332                hasher.update(v.as_bytes());
1333            }
1334        });
1335        let metric = Arc::new(metric);
1336        executor.registry.register(
1337            prefixed_name(&self.name, &name),
1338            help,
1339            self.attributes.clone(),
1340            metric,
1341        )
1342    }
1343
1344    fn encode(&self) -> String {
1345        let executor = self.executor();
1346        executor.auditor.event(b"encode", |_| {});
1347        executor.registry.encode()
1348    }
1349}
1350
1351struct Sleeper {
1352    executor: Weak<Executor>,
1353    time: SystemTime,
1354    registered: bool,
1355}
1356
1357impl Sleeper {
1358    /// Upgrade Weak reference to [Executor].
1359    fn executor(&self) -> Arc<Executor> {
1360        self.executor.upgrade().expect("executor already dropped")
1361    }
1362}
1363
1364struct Alarm {
1365    time: SystemTime,
1366    waker: Waker,
1367}
1368
1369impl PartialEq for Alarm {
1370    fn eq(&self, other: &Self) -> bool {
1371        self.time.eq(&other.time)
1372    }
1373}
1374
1375impl Eq for Alarm {}
1376
1377impl PartialOrd for Alarm {
1378    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1379        Some(self.cmp(other))
1380    }
1381}
1382
1383impl Ord for Alarm {
1384    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1385        // Reverse the ordering for min-heap
1386        other.time.cmp(&self.time)
1387    }
1388}
1389
1390impl Future for Sleeper {
1391    type Output = ();
1392
1393    fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
1394        let executor = self.executor();
1395        {
1396            let current_time = *executor.time.lock();
1397            if current_time >= self.time {
1398                return Poll::Ready(());
1399            }
1400        }
1401        if !self.registered {
1402            self.registered = true;
1403            executor.sleeping.lock().push(Alarm {
1404                time: self.time,
1405                waker: cx.waker().clone(),
1406            });
1407        }
1408        Poll::Pending
1409    }
1410}
1411
1412impl Clock for Context {
1413    fn current(&self) -> SystemTime {
1414        *self.executor().time.lock()
1415    }
1416
1417    fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + Send + 'static {
1418        let deadline = self
1419            .current()
1420            .checked_add(duration)
1421            .expect("overflow when setting wake time");
1422        self.sleep_until(deadline)
1423    }
1424
1425    fn sleep_until(&self, deadline: SystemTime) -> impl Future<Output = ()> + Send + 'static {
1426        Sleeper {
1427            executor: self.executor.clone(),
1428
1429            time: deadline,
1430            registered: false,
1431        }
1432    }
1433}
1434
1435/// A future that resolves when a given target time is reached.
1436///
1437/// If the future is not ready at the target time, the future is blocked until the target time is reached.
1438#[cfg(feature = "external")]
1439#[pin_project]
1440struct Waiter<F: Future> {
1441    executor: Weak<Executor>,
1442    target: SystemTime,
1443    #[pin]
1444    future: F,
1445    ready: Option<F::Output>,
1446    started: bool,
1447    registered: bool,
1448}
1449
1450#[cfg(feature = "external")]
1451impl<F> Future for Waiter<F>
1452where
1453    F: Future + Send,
1454{
1455    type Output = F::Output;
1456
1457    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
1458        let mut this = self.project();
1459
1460        // Poll once with a noop waker so the future can register interest or start work
1461        // without being able to wake this task before the sampled delay expires. Any ready
1462        // value is cached and only released after the clock reaches `self.target`.
1463        if !*this.started {
1464            *this.started = true;
1465            let waker = noop_waker();
1466            let mut cx_noop = task::Context::from_waker(&waker);
1467            if let Poll::Ready(value) = this.future.as_mut().poll(&mut cx_noop) {
1468                *this.ready = Some(value);
1469            }
1470        }
1471
1472        // Only allow the task to progress once the sampled delay has elapsed.
1473        let executor = this.executor.upgrade().expect("executor already dropped");
1474        let current_time = *executor.time.lock();
1475        if current_time < *this.target {
1476            // Register exactly once with the deterministic sleeper queue so the executor
1477            // wakes us once the clock reaches the scheduled target time.
1478            if !*this.registered {
1479                *this.registered = true;
1480                executor.sleeping.lock().push(Alarm {
1481                    time: *this.target,
1482                    waker: cx.waker().clone(),
1483                });
1484            }
1485            return Poll::Pending;
1486        }
1487
1488        // If the underlying future completed during the noop pre-poll, surface the cached value.
1489        if let Some(value) = this.ready.take() {
1490            return Poll::Ready(value);
1491        }
1492
1493        // Block the current thread until the future reschedules itself, keeping polling
1494        // deterministic with respect to executor time.
1495        let blocker = Blocker::new();
1496        loop {
1497            let waker = waker(blocker.clone());
1498            let mut cx_block = task::Context::from_waker(&waker);
1499            match this.future.as_mut().poll(&mut cx_block) {
1500                Poll::Ready(value) => {
1501                    break Poll::Ready(value);
1502                }
1503                Poll::Pending => blocker.wait(),
1504            }
1505        }
1506    }
1507}
1508
1509#[cfg(feature = "external")]
1510impl Pacer for Context {
1511    fn pace<'a, F, T>(&'a self, latency: Duration, future: F) -> impl Future<Output = T> + Send + 'a
1512    where
1513        F: Future<Output = T> + Send + 'a,
1514        T: Send + 'a,
1515    {
1516        // Compute target time
1517        let target = self
1518            .executor()
1519            .time
1520            .lock()
1521            .checked_add(latency)
1522            .expect("overflow when setting wake time");
1523
1524        Waiter {
1525            executor: self.executor.clone(),
1526            target,
1527            future,
1528            ready: None,
1529            started: false,
1530            registered: false,
1531        }
1532    }
1533}
1534
1535impl GClock for Context {
1536    type Instant = SystemTime;
1537
1538    fn now(&self) -> Self::Instant {
1539        self.current()
1540    }
1541}
1542
1543impl ReasonablyRealtime for Context {}
1544
1545impl crate::Network for Context {
1546    type Listener = ListenerOf<Network>;
1547
1548    async fn bind(&self, socket: SocketAddr) -> Result<Self::Listener, Error> {
1549        self.network.bind(socket).await
1550    }
1551
1552    async fn dial(
1553        &self,
1554        socket: SocketAddr,
1555    ) -> Result<(crate::SinkOf<Self>, crate::StreamOf<Self>), Error> {
1556        self.network.dial(socket).await
1557    }
1558}
1559
1560impl crate::Resolver for Context {
1561    async fn resolve(&self, host: &str) -> Result<Vec<IpAddr>, Error> {
1562        // Get the record
1563        let executor = self.executor();
1564        let dns = executor.dns.lock();
1565        let result = dns.get(host).cloned();
1566        drop(dns);
1567
1568        // Update the auditor
1569        executor.auditor.event(b"resolve", |hasher| {
1570            hasher.update(host.as_bytes());
1571            hasher.update(result.encode());
1572        });
1573        result.ok_or_else(|| Error::ResolveFailed(host.to_string()))
1574    }
1575}
1576
1577impl TryRng for Context {
1578    type Error = Infallible;
1579
1580    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
1581        let executor = self.executor();
1582        executor.auditor.event(b"rand", |hasher| {
1583            hasher.update(b"next_u32");
1584        });
1585        let result = executor.rng.lock().next_u32();
1586        Ok(result)
1587    }
1588
1589    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
1590        let executor = self.executor();
1591        executor.auditor.event(b"rand", |hasher| {
1592            hasher.update(b"next_u64");
1593        });
1594        let result = executor.rng.lock().next_u64();
1595        Ok(result)
1596    }
1597
1598    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
1599        let executor = self.executor();
1600        executor.auditor.event(b"rand", |hasher| {
1601            hasher.update(b"fill_bytes");
1602        });
1603        executor.rng.lock().fill_bytes(dest);
1604        Ok(())
1605    }
1606}
1607
1608impl TryCryptoRng for Context {}
1609
1610impl crate::Storage for Context {
1611    type Blob = <Storage as crate::Storage>::Blob;
1612
1613    async fn open_versioned(
1614        &self,
1615        partition: &str,
1616        name: &[u8],
1617        versions: std::ops::RangeInclusive<BlobVersion>,
1618    ) -> Result<(Self::Blob, u64, BlobVersion), Error> {
1619        self.storage.open_versioned(partition, name, versions).await
1620    }
1621
1622    async fn remove(&self, partition: &str, name: Option<&[u8]>) -> Result<(), Error> {
1623        self.storage.remove(partition, name).await
1624    }
1625
1626    async fn scan(&self, partition: &str) -> Result<Vec<Vec<u8>>, Error> {
1627        self.storage.scan(partition).await
1628    }
1629}
1630
1631impl crate::BufferPooler for Context {
1632    fn network_buffer_pool(&self) -> &crate::BufferPool {
1633        &self.network_buffer_pool
1634    }
1635
1636    fn storage_buffer_pool(&self) -> &crate::BufferPool {
1637        &self.storage_buffer_pool
1638    }
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643    use super::*;
1644    #[cfg(feature = "external")]
1645    use crate::FutureExt;
1646    use crate::{
1647        Blob, Metrics as _, ReadOptions, Resolver, Runner as _, Spawner as _, Storage, Strategizer,
1648        Supervisor as _, WriteOptions, deterministic, reschedule,
1649    };
1650    use commonware_macros::test_traced;
1651    use commonware_parallel::Strategy;
1652    #[cfg(feature = "external")]
1653    use commonware_utils::channel::mpsc;
1654    use commonware_utils::{NZUsize, ScriptedRng, channel::oneshot, probability};
1655    #[cfg(feature = "external")]
1656    use futures::StreamExt;
1657    #[cfg(not(feature = "external"))]
1658    use futures::future::pending;
1659    #[cfg(not(feature = "external"))]
1660    use futures::stream::StreamExt as _;
1661    use futures::{FutureExt as _, stream::FuturesUnordered, task::noop_waker};
1662
1663    async fn task(i: usize) -> usize {
1664        for _ in 0..5 {
1665            reschedule().await;
1666        }
1667        i
1668    }
1669
1670    fn run_tasks(tasks: usize, runner: deterministic::Runner) -> (String, Vec<usize>) {
1671        runner.start(|context| async move {
1672            let mut handles = FuturesUnordered::new();
1673            for i in 0..=tasks - 1 {
1674                handles.push(context.child("task").spawn(move |_| task(i)));
1675            }
1676
1677            let mut outputs = Vec::new();
1678            while let Some(result) = handles.next().await {
1679                outputs.push(result.unwrap());
1680            }
1681            assert_eq!(outputs.len(), tasks);
1682            (context.auditor().state(), outputs)
1683        })
1684    }
1685
1686    fn run_with_seed(seed: u64) -> (String, Vec<usize>) {
1687        let executor = deterministic::Runner::seeded(seed);
1688        run_tasks(5, executor)
1689    }
1690
1691    fn run_with_metric(name: &'static str, help: &'static str) -> String {
1692        deterministic::Runner::default().start(|context| async move {
1693            let _: Registered<raw::Counter> = context.register(name, help, raw::Counter::default());
1694            context.auditor().state()
1695        })
1696    }
1697
1698    #[test]
1699    fn test_auditor_separates_metric_fields() {
1700        let state_a = run_with_metric("a", "bc");
1701        let state_b = run_with_metric("ab", "c");
1702
1703        assert_ne!(state_a, state_b);
1704    }
1705
1706    #[test]
1707    fn test_same_seed_same_order() {
1708        // Generate initial outputs
1709        let mut outputs = Vec::new();
1710        for seed in 0..1000 {
1711            let output = run_with_seed(seed);
1712            outputs.push(output);
1713        }
1714
1715        // Ensure they match
1716        for seed in 0..1000 {
1717            let output = run_with_seed(seed);
1718            assert_eq!(output, outputs[seed as usize]);
1719        }
1720    }
1721
1722    #[test_traced("TRACE")]
1723    fn test_different_seeds_different_order() {
1724        let output1 = run_with_seed(12345);
1725        let output2 = run_with_seed(54321);
1726        assert_ne!(output1, output2);
1727    }
1728
1729    #[test]
1730    fn test_alarm_min_heap() {
1731        // Populate heap
1732        let now = SystemTime::now();
1733        let alarms = vec![
1734            Alarm {
1735                time: now + Duration::new(10, 0),
1736                waker: noop_waker(),
1737            },
1738            Alarm {
1739                time: now + Duration::new(5, 0),
1740                waker: noop_waker(),
1741            },
1742            Alarm {
1743                time: now + Duration::new(15, 0),
1744                waker: noop_waker(),
1745            },
1746            Alarm {
1747                time: now + Duration::new(5, 0),
1748                waker: noop_waker(),
1749            },
1750        ];
1751        let mut heap = BinaryHeap::new();
1752        for alarm in alarms {
1753            heap.push(alarm);
1754        }
1755
1756        // Verify min-heap
1757        let mut sorted_times = Vec::new();
1758        while let Some(alarm) = heap.pop() {
1759            sorted_times.push(alarm.time);
1760        }
1761        assert_eq!(
1762            sorted_times,
1763            vec![
1764                now + Duration::new(5, 0),
1765                now + Duration::new(5, 0),
1766                now + Duration::new(10, 0),
1767                now + Duration::new(15, 0),
1768            ]
1769        );
1770    }
1771
1772    #[test]
1773    fn test_dropped_sleeper_before_live_deadline() {
1774        let executor = deterministic::Runner::default();
1775        executor.start(|context| async move {
1776            let (started_sender, started_receiver) = oneshot::channel();
1777            let sleeper = context.child("sleeper").spawn(|context| async move {
1778                let mut sleepers = FuturesUnordered::new();
1779                sleepers.push(context.sleep(Duration::from_secs(1)));
1780                started_sender.send(()).unwrap();
1781                sleepers.next().await;
1782            });
1783
1784            // Waiting for the signal ensures the child registered its alarm before being aborted.
1785            started_receiver.await.unwrap();
1786            sleeper.abort();
1787
1788            // The stale child alarm must not prevent a later live alarm from firing.
1789            context.sleep(Duration::from_secs(2)).await;
1790        });
1791    }
1792
1793    #[cfg(not(feature = "external"))]
1794    #[test]
1795    #[should_panic(expected = "runtime timeout")]
1796    fn test_dropped_sleeper_beyond_timeout() {
1797        let executor = deterministic::Runner::timed(Duration::from_secs(10));
1798        executor.start(|context| async move {
1799            let (started_sender, started_receiver) = oneshot::channel();
1800            let sleeper = context.child("sleeper").spawn(|context| async move {
1801                let mut sleep = Box::pin(context.sleep(Duration::from_secs(20)));
1802                assert!(sleep.as_mut().now_or_never().is_none());
1803                started_sender.send(()).unwrap();
1804                sleep.await;
1805            });
1806
1807            started_receiver.await.unwrap();
1808            sleeper.abort();
1809            pending::<()>().await;
1810        });
1811    }
1812
1813    #[test]
1814    #[should_panic(expected = "runtime timeout")]
1815    fn test_timeout() {
1816        let executor = deterministic::Runner::timed(Duration::from_secs(10));
1817        executor.start(|context| async move {
1818            loop {
1819                context.sleep(Duration::from_secs(1)).await;
1820            }
1821        });
1822    }
1823
1824    #[test]
1825    #[should_panic(expected = "cycle duration must be non-zero when timeout is set")]
1826    fn test_bad_timeout() {
1827        let cfg = Config {
1828            timeout: Some(Duration::default()),
1829            cycle: Duration::default(),
1830            ..Config::default()
1831        };
1832        deterministic::Runner::new(cfg);
1833    }
1834
1835    #[test]
1836    #[should_panic(
1837        expected = "cycle duration must be greater than or equal to system time precision"
1838    )]
1839    fn test_bad_cycle() {
1840        let cfg = Config {
1841            cycle: SYSTEM_TIME_PRECISION - Duration::from_nanos(1),
1842            ..Config::default()
1843        };
1844        deterministic::Runner::new(cfg);
1845    }
1846
1847    #[test]
1848    fn test_recover_synced_storage_persists() {
1849        // Initialize the first runtime
1850        let executor1 = deterministic::Runner::default();
1851        let partition = "test_partition";
1852        let name = b"test_blob";
1853        let data = b"Hello, world!";
1854
1855        // Run some tasks, sync storage, and recover the runtime
1856        let (state, checkpoint) = executor1.start_and_recover(|context| async move {
1857            let (blob, _) = context.open(partition, name).await.unwrap();
1858            blob.write_at(0, data, WriteOptions::default())
1859                .await
1860                .unwrap();
1861            blob.sync().await.unwrap();
1862            context.auditor().state()
1863        });
1864
1865        // Verify auditor state is the same
1866        assert_eq!(state, checkpoint.auditor.state());
1867
1868        // Check that synced storage persists after recovery
1869        let executor = Runner::from(checkpoint);
1870        executor.start(|context| async move {
1871            let (blob, len) = context.open(partition, name).await.unwrap();
1872            assert_eq!(len, data.len() as u64);
1873            let read = blob
1874                .read_at(0, data.len(), ReadOptions::default())
1875                .await
1876                .unwrap();
1877            assert_eq!(read.coalesce(), data);
1878        });
1879    }
1880
1881    #[test]
1882    #[should_panic(expected = "goodbye")]
1883    fn test_recover_panic_handling() {
1884        // Initialize the first runtime
1885        let executor1 = deterministic::Runner::default();
1886        let (_, checkpoint) = executor1.start_and_recover(|_| async move {
1887            reschedule().await;
1888        });
1889
1890        // Ensure that panic setting is preserved
1891        let executor = Runner::from(checkpoint);
1892        executor.start(|_| async move {
1893            panic!("goodbye");
1894        });
1895    }
1896
1897    #[test]
1898    fn test_recover_unsynced_storage_does_not_persist() {
1899        // Initialize the first runtime
1900        let executor = deterministic::Runner::default();
1901        let partition = "test_partition";
1902        let name = b"test_blob";
1903        let data = b"Hello, world!";
1904
1905        // Run some tasks without syncing storage
1906        let (_, checkpoint) = executor.start_and_recover(|context| async move {
1907            let (blob, _) = context.open(partition, name).await.unwrap();
1908            blob.write_at(0, data, WriteOptions::default())
1909                .await
1910                .unwrap();
1911        });
1912
1913        // Recover the runtime
1914        let executor = Runner::from(checkpoint);
1915
1916        // Check that unsynced storage does not persist after recovery
1917        executor.start(|context| async move {
1918            let (_, len) = context.open(partition, name).await.unwrap();
1919            assert_eq!(len, 0);
1920        });
1921    }
1922
1923    #[test]
1924    fn test_recover_snapshots_fault_configuration() {
1925        let (stale_config, checkpoint) =
1926            deterministic::Runner::default().start_and_recover(|context| async move {
1927                let config = context.storage_fault_config();
1928                *config.write() = FaultConfig::default().open(probability!(1.0));
1929                config
1930            });
1931        *stale_config.write() = FaultConfig::default();
1932
1933        deterministic::Runner::from(checkpoint).start(|context| async move {
1934            assert!(context.open("fault_config", b"blob").await.is_err());
1935        });
1936    }
1937
1938    #[test]
1939    fn test_recover_retained_successful_resize() {
1940        let retained_resize = [u64::MAX, 0];
1941        let cfg = deterministic::Config::default()
1942            .with_rng(ScriptedRng::new(retained_resize))
1943            .with_storage_fault_config(FaultConfig::default().resize(ResizeConfig {
1944                failure_rate: probability!(0.5),
1945                partial_rate: probability!(0.0),
1946            }));
1947        let (_, checkpoint) =
1948            deterministic::Runner::new(cfg).start_and_recover(|context| async move {
1949                let (blob, _) = context.open("crash_resize", b"blob").await.unwrap();
1950                blob.write_at(0, b"abcdefgh", WriteOptions::SYNC)
1951                    .await
1952                    .unwrap();
1953                blob.resize(3).await.unwrap();
1954            });
1955
1956        deterministic::Runner::from(checkpoint).start(|context| async move {
1957            let (blob, len) = context.open("crash_resize", b"blob").await.unwrap();
1958            assert_eq!(len, 3);
1959            assert_eq!(
1960                blob.read_at(0, 3, ReadOptions::default())
1961                    .await
1962                    .unwrap()
1963                    .coalesce(),
1964                b"abc"
1965            );
1966        });
1967    }
1968
1969    #[test]
1970    fn test_recover_random_crash_writes_is_seeded_and_epoch_scoped() {
1971        const STABLE_LEN: usize = 32;
1972        const PENDING_LEN: usize = 256;
1973
1974        fn run(seed: u64) -> (Vec<u8>, Digest) {
1975            let cfg = deterministic::Config::default()
1976                .with_seed(seed)
1977                .with_storage_fault_config(FaultConfig::default().write(WriteConfig {
1978                    failure_rate: probability!(0.0),
1979                    retention_rate: probability!(0.5),
1980                    mode: PartialWriteMode::Subset,
1981                }));
1982            let (_, checkpoint) =
1983                deterministic::Runner::new(cfg).start_and_recover(|context| async move {
1984                    let (blob, _) = context.open("crash_epoch", b"blob").await.unwrap();
1985                    blob.write_at(0, vec![0xA5; STABLE_LEN], WriteOptions::default())
1986                        .await
1987                        .unwrap();
1988                    blob.sync().await.unwrap();
1989                    blob.write_at(
1990                        STABLE_LEN as u64,
1991                        vec![0x5A; PENDING_LEN],
1992                        WriteOptions::default(),
1993                    )
1994                    .await
1995                    .unwrap();
1996                });
1997
1998            deterministic::Runner::from(checkpoint).start(|context| async move {
1999                let (blob, len) = context.open("crash_epoch", b"blob").await.unwrap();
2000                let mut bytes = vec![0; STABLE_LEN + PENDING_LEN];
2001                let len = usize::try_from(len).unwrap();
2002                let durable = blob
2003                    .read_at(0, len, ReadOptions::default())
2004                    .await
2005                    .unwrap()
2006                    .coalesce();
2007                bytes[..durable.len()].copy_from_slice(durable.as_ref());
2008                (bytes, context.storage_audit())
2009            })
2010        }
2011
2012        let first = run(12345);
2013        let second = run(12345);
2014        let different = run(54321);
2015        assert_eq!(first, second);
2016        assert_ne!(first.0, different.0);
2017        assert!(first.0[..STABLE_LEN].iter().all(|&byte| byte == 0xA5));
2018        assert!(first.0[STABLE_LEN..].contains(&0));
2019        assert!(first.0[STABLE_LEN..].contains(&0x5A));
2020    }
2021
2022    #[test]
2023    fn test_recover_dns_mappings_persist() {
2024        // Initialize the first runtime
2025        let executor = deterministic::Runner::default();
2026        let host = "example.com";
2027        let addrs = vec![
2028            IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 1)),
2029            IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 2)),
2030        ];
2031
2032        // Register DNS mapping and recover the runtime
2033        let (state, checkpoint) = executor.start_and_recover({
2034            let addrs = addrs.clone();
2035            |context| async move {
2036                context.resolver_register(host, Some(addrs));
2037                context.auditor().state()
2038            }
2039        });
2040
2041        // Verify auditor state is the same
2042        assert_eq!(state, checkpoint.auditor.state());
2043
2044        // Check that DNS mappings persist after recovery
2045        let executor = Runner::from(checkpoint);
2046        executor.start(move |context| async move {
2047            let resolved = context.resolve(host).await.unwrap();
2048            assert_eq!(resolved, addrs);
2049        });
2050    }
2051
2052    #[test]
2053    fn test_recover_time_persists() {
2054        // Initialize the first runtime
2055        let executor = deterministic::Runner::default();
2056        let duration_to_sleep = Duration::from_secs(10);
2057
2058        // Sleep for some time and recover the runtime
2059        let (time_before_recovery, checkpoint) = executor.start_and_recover(|context| async move {
2060            context.sleep(duration_to_sleep).await;
2061            context.current()
2062        });
2063
2064        // Check that the time advanced correctly before recovery
2065        assert_eq!(
2066            time_before_recovery.duration_since(UNIX_EPOCH).unwrap(),
2067            duration_to_sleep
2068        );
2069
2070        // Check that the time persists after recovery
2071        let executor2 = Runner::from(checkpoint);
2072        executor2.start(move |context| async move {
2073            assert_eq!(context.current(), time_before_recovery);
2074
2075            // Advance time further
2076            context.sleep(duration_to_sleep).await;
2077            assert_eq!(
2078                context.current().duration_since(UNIX_EPOCH).unwrap(),
2079                duration_to_sleep * 2
2080            );
2081        });
2082    }
2083
2084    #[test]
2085    #[should_panic(expected = "executor still has weak references")]
2086    fn test_context_return() {
2087        // Initialize runtime
2088        let executor = deterministic::Runner::default();
2089
2090        // Start runtime
2091        let context = executor.start(|context| async move {
2092            // Attempt to recover before the runtime has finished
2093            context
2094        });
2095
2096        // Should never get this far
2097        drop(context);
2098    }
2099
2100    #[test]
2101    fn test_default_time_zero() {
2102        // Initialize runtime
2103        let executor = deterministic::Runner::default();
2104
2105        executor.start(|context| async move {
2106            // Check that the time is zero
2107            assert_eq!(
2108                context.current().duration_since(UNIX_EPOCH).unwrap(),
2109                Duration::ZERO
2110            );
2111        });
2112    }
2113
2114    #[test]
2115    fn test_start_time() {
2116        // Initialize runtime with default config
2117        let executor_default = deterministic::Runner::default();
2118        executor_default.start(|context| async move {
2119            assert_eq!(context.current(), UNIX_EPOCH);
2120        });
2121
2122        // Initialize runtime with custom start time
2123        let start_time = UNIX_EPOCH + Duration::from_secs(100);
2124        let cfg = Config::default().with_start_time(start_time);
2125        let executor = deterministic::Runner::new(cfg);
2126
2127        executor.start(move |context| async move {
2128            // Check that the time matches the custom start time
2129            assert_eq!(context.current(), start_time);
2130        });
2131    }
2132
2133    #[test]
2134    #[should_panic(expected = "start time must be greater than or equal to unix epoch")]
2135    fn test_bad_start_time() {
2136        let cfg = Config::default().with_start_time(UNIX_EPOCH - Duration::from_secs(1));
2137        deterministic::Runner::new(cfg);
2138    }
2139
2140    #[cfg(not(feature = "external"))]
2141    #[test]
2142    #[should_panic(expected = "runtime stalled")]
2143    fn test_stall() {
2144        // Initialize runtime
2145        let executor = deterministic::Runner::default();
2146
2147        // Start runtime
2148        executor.start(|_| async move {
2149            pending::<()>().await;
2150        });
2151    }
2152
2153    #[cfg(not(feature = "external"))]
2154    #[test]
2155    #[should_panic(expected = "runtime stalled")]
2156    fn test_external_simulated() {
2157        // Initialize runtime
2158        let executor = deterministic::Runner::default();
2159
2160        // Create a thread that waits for 1 second
2161        let (tx, rx) = oneshot::channel();
2162        std::thread::spawn(move || {
2163            std::thread::sleep(Duration::from_secs(1));
2164            tx.send(()).unwrap();
2165        });
2166
2167        // Start runtime
2168        executor.start(|_| async move {
2169            rx.await.unwrap();
2170        });
2171    }
2172
2173    #[cfg(feature = "external")]
2174    #[test]
2175    fn test_external_realtime() {
2176        // Initialize runtime
2177        let executor = deterministic::Runner::default();
2178
2179        // Create a thread that waits for 1 second
2180        let (tx, rx) = oneshot::channel();
2181        std::thread::spawn(move || {
2182            std::thread::sleep(Duration::from_secs(1));
2183            tx.send(()).unwrap();
2184        });
2185
2186        // Start runtime
2187        executor.start(|_| async move {
2188            rx.await.unwrap();
2189        });
2190    }
2191
2192    #[cfg(feature = "external")]
2193    #[test]
2194    fn test_external_realtime_variable() {
2195        // Initialize runtime
2196        let executor = deterministic::Runner::default();
2197
2198        // Start runtime
2199        executor.start(|context| async move {
2200            // Initialize test
2201            let start_real = SystemTime::now();
2202            let start_sim = context.current();
2203            let (first_tx, first_rx) = oneshot::channel();
2204            let (second_tx, second_rx) = oneshot::channel();
2205            let (results_tx, mut results_rx) = mpsc::channel(2);
2206
2207            // Create a thread that waits for 1 second
2208            let first_wait = Duration::from_secs(1);
2209            std::thread::spawn(move || {
2210                std::thread::sleep(first_wait);
2211                first_tx.send(()).unwrap();
2212            });
2213
2214            // Create a thread
2215            std::thread::spawn(move || {
2216                std::thread::sleep(Duration::ZERO);
2217                second_tx.send(()).unwrap();
2218            });
2219
2220            // Wait for a delay sampled before the external send occurs
2221            let first = context.child("sample_before_send").spawn({
2222                let results_tx = results_tx.clone();
2223                move |context| async move {
2224                    first_rx.pace(&context, Duration::ZERO).await.unwrap();
2225                    let elapsed_real = SystemTime::now().duration_since(start_real).unwrap();
2226                    assert!(elapsed_real > first_wait);
2227                    let elapsed_sim = context.current().duration_since(start_sim).unwrap();
2228                    assert!(elapsed_sim < first_wait);
2229                    results_tx.send(1).await.unwrap();
2230                }
2231            });
2232
2233            // Wait for a delay sampled after the external send occurs
2234            let second = context
2235                .child("sample_after_send")
2236                .spawn(move |context| async move {
2237                    second_rx.pace(&context, first_wait).await.unwrap();
2238                    let elapsed_real = SystemTime::now().duration_since(start_real).unwrap();
2239                    assert!(elapsed_real >= first_wait);
2240                    let elapsed_sim = context.current().duration_since(start_sim).unwrap();
2241                    assert!(elapsed_sim >= first_wait);
2242                    results_tx.send(2).await.unwrap();
2243                });
2244
2245            // Wait for both tasks to complete
2246            second.await.unwrap();
2247            first.await.unwrap();
2248
2249            // Ensure order is correct
2250            let mut results = Vec::new();
2251            for _ in 0..2 {
2252                results.push(results_rx.recv().await.unwrap());
2253            }
2254            assert_eq!(results, vec![1, 2]);
2255        });
2256    }
2257
2258    #[cfg(not(feature = "external"))]
2259    #[test]
2260    fn test_simulated_skip() {
2261        // Initialize runtime
2262        let executor = deterministic::Runner::default();
2263
2264        // Start runtime
2265        executor.start(|context| async move {
2266            context.sleep(Duration::from_secs(1)).await;
2267
2268            // Check if we skipped
2269            let metrics = context.encode();
2270            let iterations = metrics
2271                .lines()
2272                .find_map(|line| {
2273                    line.strip_prefix("runtime_iterations_total ")
2274                        .and_then(|value| value.trim().parse::<u64>().ok())
2275                })
2276                .expect("missing runtime_iterations_total metric");
2277            assert!(iterations < 10);
2278        });
2279    }
2280
2281    #[cfg(feature = "external")]
2282    #[test]
2283    fn test_realtime_no_skip() {
2284        // Initialize runtime
2285        let executor = deterministic::Runner::default();
2286
2287        // Start runtime
2288        executor.start(|context| async move {
2289            context.sleep(Duration::from_secs(1)).await;
2290
2291            // Check if we skipped
2292            let metrics = context.encode();
2293            let iterations = metrics
2294                .lines()
2295                .find_map(|line| {
2296                    line.strip_prefix("runtime_iterations_total ")
2297                        .and_then(|value| value.trim().parse::<u64>().ok())
2298                })
2299                .expect("missing runtime_iterations_total metric");
2300            assert!(iterations > 500);
2301        });
2302    }
2303
2304    #[test]
2305    #[should_panic(expected = "label must start with [a-zA-Z]")]
2306    fn test_metrics_label_empty() {
2307        let executor = deterministic::Runner::default();
2308        executor.start(|context| async move {
2309            let _ = context.child("");
2310        });
2311    }
2312
2313    #[test]
2314    #[should_panic(expected = "label must start with [a-zA-Z]")]
2315    fn test_metrics_label_invalid_first_char() {
2316        let executor = deterministic::Runner::default();
2317        executor.start(|context| async move {
2318            let _ = context.child("1invalid");
2319        });
2320    }
2321
2322    #[test]
2323    #[should_panic(expected = "label must only contain [a-zA-Z0-9_]")]
2324    fn test_metrics_label_invalid_char() {
2325        let executor = deterministic::Runner::default();
2326        executor.start(|context| async move {
2327            let _ = context.child("invalid-label");
2328        });
2329    }
2330
2331    #[test]
2332    #[should_panic(expected = "using runtime label is not allowed")]
2333    fn test_metrics_label_reserved_prefix() {
2334        let executor = deterministic::Runner::default();
2335        executor.start(|context| async move {
2336            let _ = context.child(METRICS_PREFIX);
2337        });
2338    }
2339
2340    #[test]
2341    fn test_metrics_duplicate_attribute_overwrites() {
2342        let executor = deterministic::Runner::default();
2343        executor.start(|context| async move {
2344            let context = context
2345                .child("test")
2346                .with_attribute("epoch", "old")
2347                .with_attribute("epoch", "new");
2348            assert_eq!(
2349                context.name().attributes,
2350                vec![("epoch".to_string(), "new".to_string())]
2351            );
2352        });
2353    }
2354
2355    #[test]
2356    fn test_storage_fault_injection_and_recovery() {
2357        // Phase 1: Run with 100% sync failure rate
2358        let cfg = deterministic::Config::default().with_storage_fault_config(FaultConfig {
2359            sync_rate: Some(probability!(1.0)),
2360            ..Default::default()
2361        });
2362
2363        let (result, checkpoint) =
2364            deterministic::Runner::new(cfg).start_and_recover(|ctx| async move {
2365                let (blob, _) = ctx.open("test_fault", b"blob").await.unwrap();
2366                blob.write_at(0, b"data".to_vec(), WriteOptions::default())
2367                    .await
2368                    .unwrap();
2369                blob.sync().await // This should fail due to fault injection
2370            });
2371
2372        // Verify sync failed
2373        assert!(result.is_err());
2374
2375        // Phase 2: Recover and disable faults explicitly
2376        deterministic::Runner::from(checkpoint).start(|ctx| async move {
2377            // Explicitly disable faults for recovery verification
2378            *ctx.storage_fault_config().write() = FaultConfig::default();
2379
2380            // Data was not synced, so blob should be empty (unsynced writes are lost)
2381            let (blob, len) = ctx.open("test_fault", b"blob").await.unwrap();
2382            assert_eq!(len, 0, "unsynced data should be lost after recovery");
2383
2384            // Now we can write and sync successfully
2385            blob.write_at(0, b"recovered".to_vec(), WriteOptions::default())
2386                .await
2387                .unwrap();
2388            blob.sync()
2389                .await
2390                .expect("sync should succeed with faults disabled");
2391
2392            // Verify data persisted
2393            let read_buf = blob.read_at(0, 9, ReadOptions::default()).await.unwrap();
2394            assert_eq!(read_buf.coalesce(), b"recovered");
2395        });
2396    }
2397
2398    #[test]
2399    fn test_storage_fault_dynamic_config() {
2400        let executor = deterministic::Runner::default();
2401        executor.start(|ctx| async move {
2402            let (blob, _) = ctx.open("test_dynamic", b"blob").await.unwrap();
2403
2404            // Initially no faults - sync should succeed
2405            blob.write_at(0, b"initial".to_vec(), WriteOptions::default())
2406                .await
2407                .unwrap();
2408            blob.sync().await.expect("initial sync should succeed");
2409
2410            // Enable sync faults dynamically
2411            let storage_fault_cfg = ctx.storage_fault_config();
2412            storage_fault_cfg.write().sync_rate = Some(probability!(1.0));
2413
2414            // Now sync should fail
2415            blob.write_at(0, b"updated".to_vec(), WriteOptions::default())
2416                .await
2417                .unwrap();
2418            let result = blob.sync().await;
2419            assert!(result.is_err(), "sync should fail with faults enabled");
2420
2421            // Disable faults
2422            storage_fault_cfg.write().sync_rate = Some(probability!(0.0));
2423
2424            // Sync should succeed again
2425            blob.sync()
2426                .await
2427                .expect("sync should succeed with faults disabled");
2428        });
2429    }
2430
2431    #[test]
2432    fn test_storage_fault_determinism() {
2433        // Run the same sequence twice with the same seed
2434        fn run_with_seed(seed: u64) -> Vec<bool> {
2435            let cfg = deterministic::Config::default()
2436                .with_seed(seed)
2437                .with_storage_fault_config(FaultConfig {
2438                    open_rate: Some(probability!(0.5)),
2439                    ..Default::default()
2440                });
2441
2442            let runner = deterministic::Runner::new(cfg);
2443            runner.start(|ctx| async move {
2444                let mut results = Vec::new();
2445                for i in 0..20 {
2446                    let name = format!("blob{i}");
2447                    let result = ctx.open("test_determinism", name.as_bytes()).await;
2448                    results.push(result.is_ok());
2449                }
2450                results
2451            })
2452        }
2453
2454        let results1 = run_with_seed(12345);
2455        let results2 = run_with_seed(12345);
2456        assert_eq!(
2457            results1, results2,
2458            "same seed should produce same failure pattern"
2459        );
2460
2461        let results3 = run_with_seed(99999);
2462        assert_ne!(
2463            results1, results3,
2464            "different seeds should produce different patterns"
2465        );
2466    }
2467
2468    #[test]
2469    fn test_storage_fault_determinism_multi_task() {
2470        // Run the same multi-task sequence twice with the same seed.
2471        // This tests that task shuffling + fault decisions interleave deterministically.
2472        fn run_with_seed(seed: u64) -> Vec<u32> {
2473            let cfg = deterministic::Config::default()
2474                .with_seed(seed)
2475                .with_storage_fault_config(FaultConfig {
2476                    open_rate: Some(probability!(0.5)),
2477                    write_rate: Some(WriteConfig {
2478                        failure_rate: probability!(0.3),
2479                        retention_rate: probability!(0.0),
2480                        mode: PartialWriteMode::Prefix,
2481                    }),
2482                    sync_rate: Some(probability!(0.2)),
2483                    ..Default::default()
2484                });
2485
2486            let runner = deterministic::Runner::new(cfg);
2487            runner.start(|ctx| async move {
2488                // Spawn multiple tasks that do storage operations
2489                let mut handles = Vec::new();
2490                for i in 0..5 {
2491                    let ctx = ctx.child("task");
2492                    handles.push(ctx.spawn(move |ctx| async move {
2493                        let mut successes = 0u32;
2494                        for j in 0..4 {
2495                            let name = format!("task{i}_blob{j}");
2496                            if let Ok((blob, _)) = ctx.open("partition", name.as_bytes()).await {
2497                                successes += 1;
2498                                if blob
2499                                    .write_at(0, b"data".to_vec(), WriteOptions::default())
2500                                    .await
2501                                    .is_ok()
2502                                {
2503                                    successes += 1;
2504                                }
2505                                if blob.sync().await.is_ok() {
2506                                    successes += 1;
2507                                }
2508                            }
2509                        }
2510                        successes
2511                    }));
2512                }
2513
2514                // Collect results from all tasks
2515                let mut results = Vec::new();
2516                for handle in handles {
2517                    results.push(handle.await.unwrap());
2518                }
2519                results
2520            })
2521        }
2522
2523        let results1 = run_with_seed(42);
2524        let results2 = run_with_seed(42);
2525        assert_eq!(
2526            results1, results2,
2527            "same seed should produce same multi-task pattern"
2528        );
2529
2530        let results3 = run_with_seed(99999);
2531        assert_ne!(
2532            results1, results3,
2533            "different seeds should produce different patterns"
2534        );
2535    }
2536
2537    #[test]
2538    fn test_resolver() {
2539        let executor = deterministic::Runner::default();
2540        executor.start(|context| async move {
2541            // Register DNS mappings
2542            let ip1: IpAddr = "192.168.1.1".parse().unwrap();
2543            let ip2: IpAddr = "192.168.1.2".parse().unwrap();
2544            context.resolver_register("example.com", Some(vec![ip1, ip2]));
2545
2546            // Resolve registered hostname
2547            let addrs = context.resolve("example.com").await.unwrap();
2548            assert_eq!(addrs, vec![ip1, ip2]);
2549
2550            // Resolve unregistered hostname
2551            let result = context.resolve("unknown.com").await;
2552            assert!(matches!(result, Err(Error::ResolveFailed(_))));
2553
2554            // Remove mapping
2555            context.resolver_register("example.com", None);
2556            let result = context.resolve("example.com").await;
2557            assert!(matches!(result, Err(Error::ResolveFailed(_))));
2558        });
2559    }
2560
2561    /// A strategy with parallelism greater than one must behave as configured under the
2562    /// deterministic runtime even though no worker threads exist.
2563    #[test]
2564    fn test_parallel_strategy_spawn_completes() {
2565        let executor = deterministic::Runner::default();
2566        executor.start(|context| async move {
2567            let strategy = context.child("pool").strategy(NZUsize!(2)).manual();
2568            assert_eq!(strategy.parallelism(), 2);
2569
2570            let output = strategy
2571                .spawn(2, |strategy| strategy.map_collect_vec(0..2, |i| i + 1))
2572                .await;
2573
2574            assert_eq!(output, vec![1, 2]);
2575        });
2576    }
2577
2578    /// Strategies share the pool registered with the executor thread, but each request must
2579    /// retain its own planning parallelism and execute work. This covers multiple strategies
2580    /// within one runner and a later runner on the same thread.
2581    #[test]
2582    fn test_strategies_reuse_pool_across_runners() {
2583        let executor = deterministic::Runner::default();
2584        executor.start(|context| async move {
2585            let first = context.child("pool_a").strategy(NZUsize!(1)).manual();
2586            assert_eq!(first.parallelism(), 1);
2587            assert_eq!(first.run(2, || "serial", || "parallel"), "serial");
2588            let output = first
2589                .spawn(2, |strategy| strategy.map_collect_vec(0..2, |i| i + 1))
2590                .now_or_never()
2591                .expect("single-threaded pool should run spawned work inline");
2592            assert_eq!(output, vec![1, 2]);
2593
2594            let second = context.child("pool_b").strategy(NZUsize!(3)).manual();
2595            assert_eq!(second.parallelism(), 3);
2596            assert_eq!(second.run(2, || "serial", || "parallel"), "parallel");
2597            let output = second
2598                .spawn(3, |strategy| strategy.map_collect_vec(0..3, |i| i + 1))
2599                .now_or_never()
2600                .expect("single-threaded pool should run spawned work inline");
2601            assert_eq!(output, vec![1, 2, 3]);
2602        });
2603
2604        let executor = deterministic::Runner::default();
2605        executor.start(|context| async move {
2606            let third = context.child("pool_c").strategy(NZUsize!(4)).manual();
2607            assert_eq!(third.parallelism(), 4);
2608            assert_eq!(third.run(2, || "serial", || "parallel"), "parallel");
2609            let output = third
2610                .spawn(4, |strategy| strategy.map_collect_vec(0..4, |i| i + 1))
2611                .now_or_never()
2612                .expect("single-threaded pool should run spawned work inline");
2613            assert_eq!(output, vec![1, 2, 3, 4]);
2614        });
2615    }
2616
2617    /// Tasks may suspend while a pool exists: pools have no worker tasks for the executor
2618    /// to poll (a polled rayon worker loop would block or abort the runtime), so suspension
2619    /// must leave the pool usable.
2620    #[test]
2621    fn test_pool_survives_suspension() {
2622        let executor = deterministic::Runner::default();
2623        executor.start(|context| async move {
2624            let strategy = context.child("pool").strategy(NZUsize!(2)).manual();
2625            context.sleep(Duration::from_millis(10)).await;
2626
2627            let output = strategy
2628                .spawn(2, |strategy| strategy.map_collect_vec(0..2, |i| i + 1))
2629                .await;
2630            assert_eq!(output, vec![1, 2]);
2631
2632            context.sleep(Duration::from_millis(10)).await;
2633            let sum = strategy.fold(0..100u64, || 0u64, |acc, i| acc + i, |a, b| a + b);
2634            assert_eq!(sum, 4950);
2635        });
2636    }
2637}