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