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