Skip to main content

commonware_runtime/
lib.rs

1//! Execute asynchronous tasks with a configurable scheduler.
2//!
3//! This crate provides a collection of runtimes that can be
4//! used to execute asynchronous tasks in a variety of ways. For production use,
5//! the `tokio` module provides a runtime backed by [Tokio](https://tokio.rs).
6//! For testing and simulation, the `deterministic` module provides a runtime
7//! that allows for deterministic execution of tasks (given a fixed seed).
8//!
9//! # Terminology
10//!
11//! Each runtime is typically composed of an `Executor` and a `Context`. The `Executor` implements the
12//! `Runner` trait and drives execution of a runtime. The `Context` implements any number of the
13//! other traits to provide core functionality.
14//!
15//! # Status
16//!
17//! Stability varies by primitive. See [README](https://github.com/commonwarexyz/monorepo#stability) for details.
18
19#![doc(
20    html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
21    html_favicon_url = "https://commonware.xyz/favicon.ico"
22)]
23
24use commonware_macros::stability_scope;
25
26#[macro_use]
27mod macros;
28
29mod network;
30mod process;
31mod storage;
32
33stability_scope!(ALPHA {
34    #[cfg(feature = "arbitrary")]
35    pub mod conformance;
36    pub mod deterministic;
37    pub mod mocks;
38});
39stability_scope!(ALPHA, cfg(not(target_arch = "wasm32")) {
40    pub mod benchmarks;
41});
42stability_scope!(ALPHA, cfg(any(feature = "iouring-storage", feature = "iouring-network")) {
43    mod iouring;
44});
45stability_scope!(BETA, cfg(not(target_arch = "wasm32")) {
46    pub mod tokio;
47});
48stability_scope!(BETA {
49    /// Re-export of `Buf` and `BufMut` traits for usage with [I/O buffers](iobuf).
50    pub use bytes::{Buf, BufMut};
51    use commonware_macros::select;
52    use commonware_parallel::Rayon;
53    /// Re-export of [governor::Quota] for rate limiting configuration.
54    pub use governor::Quota;
55    use iobuf::PoolError;
56    use std::{
57        future::Future,
58        io::Error as IoError,
59        net::SocketAddr,
60        num::NonZeroUsize,
61        sync::Arc,
62        time::{Duration, SystemTime},
63    };
64    pub(crate) use telemetry::metrics::{child_label, prefixed_name, METRICS_PREFIX};
65    use thiserror::Error;
66
67    pub mod iobuf;
68    pub use iobuf::{
69        cache_line_size, page_size, BufferPool, BufferPoolConfig, BufferPoolThreadCache,
70        Builder as IoBufsBuilder, IoBuf, IoBufMut, IoBufs, IoBufsMut,
71    };
72
73    pub mod utils;
74    pub use utils::*;
75
76    pub mod telemetry;
77
78    /// Default [`Blob`] version used when no version is specified via [`Storage::open`].
79    pub const DEFAULT_BLOB_VERSION: u16 = 0;
80
81    /// Errors that can occur when interacting with the runtime.
82    #[derive(Error, Debug, Clone)]
83    pub enum Error {
84        #[error("exited")]
85        Exited,
86        #[error("closed")]
87        Closed,
88        #[error("aborted")]
89        Aborted,
90        #[error("timeout")]
91        Timeout,
92        #[error("bind failed")]
93        BindFailed,
94        #[error("connection failed")]
95        ConnectionFailed,
96        #[error("write failed")]
97        WriteFailed,
98        #[error("read failed")]
99        ReadFailed,
100        #[error("send failed")]
101        SendFailed,
102        #[error("recv failed")]
103        RecvFailed,
104        #[error("dns resolution failed: {0}")]
105        ResolveFailed(String),
106        #[error("partition name invalid, must only contain alphanumeric, dash ('-'), or underscore ('_') characters: {0}")]
107        PartitionNameInvalid(String),
108        #[error("partition creation failed: {0}")]
109        PartitionCreationFailed(String),
110        #[error("partition missing: {0}")]
111        PartitionMissing(String),
112        #[error("partition corrupt: {0}")]
113        PartitionCorrupt(String),
114        #[error("blob open failed: {0}/{1} error: {2}")]
115        BlobOpenFailed(String, String, Arc<IoError>),
116        #[error("blob missing: {0}/{1}")]
117        BlobMissing(String, String),
118        #[error("blob resize failed: {0}/{1} error: {2}")]
119        BlobResizeFailed(String, String, Arc<IoError>),
120        #[error("blob sync failed: {0}/{1} error: {2}")]
121        BlobSyncFailed(String, String, Arc<IoError>),
122        #[error("blob insufficient length")]
123        BlobInsufficientLength,
124        #[error("blob corrupt: {0}/{1} reason: {2}")]
125        BlobCorrupt(String, String, String),
126        #[error("blob version mismatch: expected one of {expected:?}, found {found}")]
127        BlobVersionMismatch {
128            expected: std::ops::RangeInclusive<u16>,
129            found: u16,
130        },
131        #[error("invalid or missing checksum")]
132        InvalidChecksum,
133        #[error("offset overflow")]
134        OffsetOverflow,
135        #[error("io error: {0}")]
136        Io(Arc<IoError>),
137        #[error("buffer pool: {0}")]
138        Pool(#[from] PoolError),
139    }
140
141    impl From<IoError> for Error {
142        fn from(err: IoError) -> Self {
143            Self::Io(Arc::new(err))
144        }
145    }
146
147    /// Interface that any task scheduler must implement to start
148    /// running tasks.
149    pub trait Runner {
150        /// Context defines the environment available to tasks.
151        type Context;
152
153        /// Start running a root task.
154        ///
155        /// When this function returns, all spawned tasks will be canceled. If clean
156        /// shutdown cannot be implemented via `Drop`, consider using [Spawner::stop] and
157        /// [Spawner::stopped] to coordinate clean shutdown.
158        fn start<F, Fut>(self, f: F) -> Fut::Output
159        where
160            F: FnOnce(Self::Context) -> Fut,
161            Fut: Future;
162    }
163
164    /// The full identity of a [`Supervisor`] handle.
165    #[derive(Clone, Debug, Default)]
166    pub struct Name {
167        /// Label prefix built by successive [`Supervisor::child`] calls.
168        pub label: String,
169        /// Attributes attached via [`Supervisor::with_attribute`].
170        pub attributes: Vec<(String, String)>,
171    }
172
173    /// Interface to track task hierarchy and identity.
174    pub trait Supervisor: Send + Sync + 'static {
175        /// Return the current label prefix and attributes.
176        fn name(&self) -> Name;
177
178        /// Create a named child context with a new supervision-tree node.
179        ///
180        /// This appends `label` to the current metric prefix and creates a
181        /// child in the supervision tree. Use static role names like
182        /// `"engine"`, `"worker"`, or `"resolver"`. Dynamic values belong in
183        /// [`Supervisor::with_attribute`] so metric names remain bounded.
184        ///
185        /// Labels must start with `[a-zA-Z]` and contain only
186        /// `[a-zA-Z0-9_]`. Runtime-reserved metric prefixes must not be used.
187        #[must_use]
188        fn child(&self, label: &'static str) -> Self;
189
190        /// Add a key-value attribute to this context's identity.
191        ///
192        /// Attributes are attached to metrics registered in this context and
193        /// any child contexts. Unlike [`Supervisor::child`], attributes do not
194        /// affect metric names and do not create a supervision-tree edge. This
195        /// makes them the right place for dynamic values like epochs, rounds,
196        /// shards, or peer identifiers.
197        ///
198        /// Keys must start with `[a-zA-Z]` and contain only `[a-zA-Z0-9_]`.
199        /// Values can be any string. If the key already exists, its value is
200        /// replaced.
201        ///
202        /// ```text
203        /// context
204        ///   |-- child("orchestrator")
205        ///         |-- with_attribute("epoch", "5")
206        ///               |-- counter: votes      -> orchestrator_votes{epoch="5"}
207        ///               |-- counter: proposals  -> orchestrator_proposals{epoch="5"}
208        ///               |-- child("engine")
209        ///                     |-- gauge: height -> orchestrator_engine_height{epoch="5"}
210        /// ```
211        ///
212        /// This pattern avoids wrapping every metric in a `Family` and avoids
213        /// putting dynamic values in metric names like
214        /// `orchestrator_epoch_5_votes`.
215        ///
216        /// Attributes do not reduce cardinality. N epochs still means N time
217        /// series. They just make metrics easier to query, filter, and
218        /// aggregate.
219        ///
220        /// # Family Label Conflicts
221        ///
222        /// When using `Family` metrics, avoid attribute keys that match the
223        /// family's label field names. A conflict produces duplicate labels in
224        /// the encoded output, which is invalid Prometheus format.
225        ///
226        /// ```ignore
227        /// #[derive(EncodeLabelSet)]
228        /// struct Labels { env: String }
229        ///
230        /// // Bad: attribute "env" conflicts with Family field "env".
231        /// let ctx = context.child("api").with_attribute("env", "prod");
232        /// let family: Family<Labels, Counter> = Family::default();
233        /// ctx.register("requests", "help", family);
234        ///
235        /// // Good: use distinct names.
236        /// let ctx = context.child("api").with_attribute("region", "us_east");
237        /// ```
238        ///
239        /// # Querying The Latest Attribute
240        ///
241        /// To query the latest attribute value dynamically, create a gauge to
242        /// track the current value:
243        ///
244        /// ```ignore
245        /// let latest_epoch = context
246        ///     .child("orchestrator")
247        ///     .register("latest_epoch", "current epoch", Gauge::default());
248        /// latest_epoch.set(current_epoch);
249        /// ```
250        ///
251        /// A dashboard can then query `max(orchestrator_latest_epoch)` and use
252        /// the result as a variable in queries such as
253        /// `consensus_engine_votes_total{epoch="$latest_epoch"}`.
254        #[must_use]
255        fn with_attribute(self, key: &'static str, value: impl std::fmt::Display) -> Self;
256    }
257
258    /// Interface that any task scheduler must implement to spawn tasks.
259    pub trait Spawner: Supervisor {
260        /// Return a [`Spawner`] that schedules the next task onto the runtime's shared executor.
261        ///
262        /// Set `blocking` to `true` when the task may hold the thread for a short, blocking operation.
263        /// Runtimes can use this hint to move the work to a blocking-friendly pool so asynchronous
264        /// tasks on a work-stealing executor are not starved. For long-lived, blocking work, use
265        /// [`Spawner::dedicated`] instead.
266        ///
267        /// The shared executor with `blocking == false` is the default spawn mode.
268        #[must_use]
269        fn shared(self, blocking: bool) -> Self;
270
271        /// Return a [`Spawner`] that runs the next task on a dedicated thread when the runtime supports it.
272        ///
273        /// Reserve this for long-lived or prioritized tasks that should not compete for resources in the
274        /// shared executor.
275        ///
276        /// This is not the default behavior. See [`Spawner::shared`] for more information.
277        #[must_use]
278        fn dedicated(self) -> Self;
279
280        /// Spawn a task with the current context.
281        ///
282        /// Unlike directly awaiting a future, the task starts running immediately even if the caller
283        /// never awaits the returned [`Handle`].
284        ///
285        /// # Mandatory Supervision
286        ///
287        /// All tasks are supervised. When a parent task finishes or is aborted, all its descendants are aborted.
288        ///
289        /// Spawn consumes the current context and runs the task at a new child node. Additional supervised
290        /// children are created explicitly with [`Supervisor::child`].
291        ///
292        /// ```txt
293        /// ctx_a
294        ///   |
295        ///   +-- child("worker") ---> ctx_c
296        ///   |                  |
297        ///   |                  +-- spawn() ---> Task C (ctx_d)
298        ///   |
299        ///   +-- spawn() ---> Task A (ctx_b)
300        ///                              |
301        ///                              +-- spawn() ---> Task B (ctx_e)
302        ///
303        /// Task A finishes or aborts --> Task B and Task C are aborted
304        /// ```
305        ///
306        /// # Spawn Configuration
307        ///
308        /// [`Spawner::dedicated`] and [`Spawner::shared`] only affect the
309        /// handle they return. [`Supervisor::child`] and [`Spawner::spawn`]
310        /// both start child task contexts from a clean spawn configuration.
311        ///
312        /// Child tasks should assume they start from a clean configuration without needing to inspect how their
313        /// parent was configured.
314        fn spawn<F, Fut, T>(self, f: F) -> Handle<T>
315        where
316            Self: Sized,
317            F: FnOnce(Self) -> Fut + Send + 'static,
318            Fut: Future<Output = T> + Send + 'static,
319            T: Send + 'static;
320
321        /// Signals the runtime to stop execution and waits for all outstanding tasks
322        /// to perform any required cleanup and exit.
323        ///
324        /// This method does not actually kill any tasks but rather signals to them, using
325        /// the [signal::Signal] returned by [Spawner::stopped], that they should exit.
326        /// It then waits for all [signal::Signal] references to be dropped before returning.
327        ///
328        /// ## Multiple Stop Calls
329        ///
330        /// This method is idempotent and safe to call multiple times concurrently (on
331        /// different instances of the same context since it consumes `self`). The first
332        /// call initiates shutdown with the provided `value`, and all subsequent calls
333        /// will wait for the same completion regardless of their `value` parameter, i.e.
334        /// the original `value` from the first call is preserved.
335        ///
336        /// ## Timeout
337        ///
338        /// If a timeout is provided, the method will return an error if all [signal::Signal]
339        /// references have not been dropped within the specified duration.
340        fn stop(
341            self,
342            value: i32,
343            timeout: Option<Duration>,
344        ) -> impl Future<Output = Result<(), Error>> + Send;
345
346        /// Returns an instance of a [signal::Signal] that resolves when [Spawner::stop] is called by
347        /// any task.
348        ///
349        /// If [Spawner::stop] has already been called, the [signal::Signal] returned will resolve
350        /// immediately. The [signal::Signal] returned will always resolve to the value of the
351        /// first [Spawner::stop] call.
352        fn stopped(&self) -> signal::Signal;
353    }
354
355    /// Interface that runtimes implement to provide parallel execution strategies.
356    pub trait Strategizer: Spawner {
357        /// Returns a new [Rayon] strategy with the requested parallelism.
358        ///
359        /// # Arguments
360        /// - `parallelism`: The number of tasks to execute concurrently in the pool.
361        ///
362        /// # Panics
363        ///
364        /// Panics if the runtime cannot initialize the strategy's backing Rayon thread pool.
365        fn strategy(&self, parallelism: NonZeroUsize) -> Rayon;
366    }
367
368    /// Interface to register and encode metrics.
369    pub trait Metrics: Supervisor {
370        /// Register a metric with the runtime.
371        ///
372        /// Any registered metric includes the current context label as its name
373        /// prefix and the current context attributes as Prometheus labels. See
374        /// [`Supervisor`] for the identity model and examples.
375        ///
376        /// The returned [`telemetry::metrics::Registered`] value must be retained for as long as the
377        /// metric should remain exposed. Dropping the returned handle unregisters
378        /// the metric immediately.
379        ///
380        /// Re-registering the same metric key (same prefixed name and attributes)
381        /// returns another handle to the existing metric when the concrete metric
382        /// type matches. Registering the same key with a different metric type
383        /// panics.
384        ///
385        /// Names must start with `[a-zA-Z]` and contain only `[a-zA-Z0-9_]`.
386        fn register<N: Into<String>, H: Into<String>, M: telemetry::metrics::Metric>(
387            &self,
388            name: N,
389            help: H,
390            metric: M,
391        ) -> telemetry::metrics::Registered<M>;
392
393        /// Encode all metrics into a buffer.
394        fn encode(&self) -> String;
395    }
396
397    /// A direct (non-keyed) rate limiter using the provided [governor::clock::Clock] `C`.
398    ///
399    /// This is a convenience type alias for creating single-entity rate limiters.
400    /// For per-key rate limiting, use [KeyedRateLimiter].
401    pub type RateLimiter<C> = governor::RateLimiter<
402        governor::state::NotKeyed,
403        governor::state::InMemoryState,
404        C,
405        governor::middleware::NoOpMiddleware<<C as governor::clock::Clock>::Instant>,
406    >;
407
408    /// A rate limiter keyed by `K` using the provided [governor::clock::Clock] `C`.
409    ///
410    /// This is a convenience type alias for creating per-peer rate limiters
411    /// using governor's [HashMapStateStore].
412    ///
413    /// [HashMapStateStore]: governor::state::keyed::HashMapStateStore
414    pub type KeyedRateLimiter<K, C> = governor::RateLimiter<
415        K,
416        governor::state::keyed::HashMapStateStore<K>,
417        C,
418        governor::middleware::NoOpMiddleware<<C as governor::clock::Clock>::Instant>,
419    >;
420
421    /// Interface that any task scheduler must implement to provide
422    /// time-based operations.
423    ///
424    /// It is necessary to mock time to provide deterministic execution
425    /// of arbitrary tasks.
426    pub trait Clock:
427        governor::clock::Clock<Instant = SystemTime>
428        + governor::clock::ReasonablyRealtime
429        + Send
430        + Sync
431        + 'static
432    {
433        /// Returns the current time.
434        fn current(&self) -> SystemTime;
435
436        /// Sleep for the given duration.
437        fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + Send + 'static;
438
439        /// Sleep until the given deadline.
440        fn sleep_until(&self, deadline: SystemTime) -> impl Future<Output = ()> + Send + 'static;
441
442        /// Await a future with a timeout, returning `Error::Timeout` if it expires.
443        ///
444        /// # Examples
445        ///
446        /// ```
447        /// use std::time::Duration;
448        /// use commonware_runtime::{deterministic, Error, Runner, Clock};
449        ///
450        /// let executor = deterministic::Runner::default();
451        /// executor.start(|context| async move {
452        ///     match context
453        ///         .timeout(Duration::from_millis(100), async { 42 })
454        ///         .await
455        ///     {
456        ///         Ok(value) => assert_eq!(value, 42),
457        ///         Err(Error::Timeout) => panic!("should not timeout"),
458        ///         Err(e) => panic!("unexpected error: {:?}", e),
459        ///     }
460        /// });
461        /// ```
462        fn timeout<F, T>(
463            &self,
464            duration: Duration,
465            future: F,
466        ) -> impl Future<Output = Result<T, Error>> + Send + '_
467        where
468            F: Future<Output = T> + Send + 'static,
469            T: Send + 'static,
470        {
471            async move {
472                select! {
473                    result = future => Ok(result),
474                    _ = self.sleep(duration) => Err(Error::Timeout),
475                }
476            }
477        }
478    }
479
480    /// Syntactic sugar for the type of [Sink] used by a given [Network] N.
481    pub type SinkOf<N> = <<N as Network>::Listener as Listener>::Sink;
482
483    /// Syntactic sugar for the type of [Stream] used by a given [Network] N.
484    pub type StreamOf<N> = <<N as Network>::Listener as Listener>::Stream;
485
486    /// Syntactic sugar for the type of [Listener] used by a given [Network] N.
487    pub type ListenerOf<N> = <N as crate::Network>::Listener;
488
489    /// Interface that any runtime must implement to create
490    /// network connections.
491    pub trait Network: Send + Sync + 'static {
492        /// The type of [Listener] that's returned when binding to a socket.
493        /// Accepting a connection returns a [Sink] and [Stream] which are defined
494        /// by the [Listener] and used to send and receive data over the connection.
495        type Listener: Listener;
496
497        /// Bind to the given socket address.
498        fn bind(
499            &self,
500            socket: SocketAddr,
501        ) -> impl Future<Output = Result<Self::Listener, Error>> + Send;
502
503        /// Dial the given socket address.
504        fn dial(
505            &self,
506            socket: SocketAddr,
507        ) -> impl Future<Output = Result<(SinkOf<Self>, StreamOf<Self>), Error>> + Send;
508    }
509
510    /// Interface for DNS resolution.
511    pub trait Resolver: Send + Sync + 'static {
512        /// Resolve a hostname to IP addresses.
513        ///
514        /// Returns a list of IP addresses that the hostname resolves to.
515        fn resolve(
516            &self,
517            host: &str,
518        ) -> impl Future<Output = Result<Vec<std::net::IpAddr>, Error>> + Send;
519    }
520
521    /// Interface that any runtime must implement to handle
522    /// incoming network connections.
523    pub trait Listener: Sync + Send + 'static {
524        /// The type of [Sink] that's returned when accepting a connection.
525        /// This is used to send data to the remote connection.
526        type Sink: Sink;
527        /// The type of [Stream] that's returned when accepting a connection.
528        /// This is used to receive data from the remote connection.
529        type Stream: Stream;
530
531        /// Accept an incoming connection.
532        fn accept(
533            &mut self,
534        ) -> impl Future<Output = Result<(SocketAddr, Self::Sink, Self::Stream), Error>> + Send;
535
536        /// Returns the local address of the listener.
537        fn local_addr(&self) -> Result<SocketAddr, std::io::Error>;
538    }
539
540    /// Interface that any runtime must implement to send
541    /// messages over a network connection.
542    pub trait Sink: Sync + Send + 'static {
543        /// Send a message to the sink.
544        ///
545        /// # Warning
546        ///
547        /// If the sink returns an error, part of the message may still be delivered.
548        /// After any error, the sink is no longer reusable and subsequent sends will
549        /// return [`Error::Closed`].
550        ///
551        /// Dropping the future (e.g. via `select!`) also poisons the sink, since a
552        /// partial write may have occurred.
553        fn send(
554            &mut self,
555            bufs: impl Into<IoBufs> + Send,
556        ) -> impl Future<Output = Result<(), Error>> + Send;
557    }
558
559    /// Interface that any runtime must implement to receive
560    /// messages over a network connection.
561    pub trait Stream: Sync + Send + 'static {
562        /// Receive exactly `len` bytes from the stream.
563        ///
564        /// The runtime allocates the buffer and returns it as `IoBufs`.
565        ///
566        /// # Warning
567        ///
568        /// If the stream returns an error, partially read data may be discarded.
569        /// After any error, the stream is no longer reusable and subsequent receives
570        /// will return [`Error::Closed`].
571        ///
572        /// Dropping the future (e.g. via `select!`) also poisons the stream, since
573        /// partially read data may be lost.
574        fn recv(&mut self, len: usize) -> impl Future<Output = Result<IoBufs, Error>> + Send;
575
576        /// Peek at buffered data without consuming.
577        ///
578        /// Returns up to `max_len` bytes from the internal buffer, or an empty slice
579        /// if no data is currently buffered. This does not perform any I/O or block.
580        ///
581        /// This is useful e.g. for parsing length prefixes without committing to a read
582        /// or paying the cost of async.
583        fn peek(&self, max_len: usize) -> &[u8];
584    }
585
586    /// Interface to interact with storage.
587    ///
588    /// To support storage implementations that enable concurrent reads and
589    /// writes, blobs are responsible for maintaining synchronization.
590    ///
591    /// Storage can be backed by a local filesystem, cloud storage, etc.
592    ///
593    /// # Durability
594    ///
595    /// Runtimes must ensure that any data readable when user code starts
596    /// executing is crash-durable. Storage structures may rely on this during
597    /// recovery: data read at initialization can be assumed to survive a
598    /// subsequent crash without an explicit [`Blob::sync`].
599    ///
600    /// # Partition Names
601    ///
602    /// Partition names must be non-empty and contain only ASCII alphanumeric
603    /// characters, dashes (`-`), or underscores (`_`). Names containing other
604    /// characters (e.g., `/`, `.`, spaces) will return an error.
605    pub trait Storage: Send + Sync + 'static {
606        /// The readable/writeable storage buffer that can be opened by this Storage.
607        type Blob: Blob;
608
609        /// [`Storage::open_versioned`] with [`DEFAULT_BLOB_VERSION`] as the only value
610        /// in the versions range. The blob version is omitted from the return value.
611        fn open(
612            &self,
613            partition: &str,
614            name: &[u8],
615        ) -> impl Future<Output = Result<(Self::Blob, u64), Error>> + Send {
616            async move {
617                let (blob, size, _) = self
618                    .open_versioned(partition, name, DEFAULT_BLOB_VERSION..=DEFAULT_BLOB_VERSION)
619                    .await?;
620                Ok((blob, size))
621            }
622        }
623
624        /// Open an existing blob in a given partition or create a new one, returning
625        /// the blob and its length.
626        ///
627        /// Multiple instances of the same blob can be opened concurrently, however,
628        /// writing to the same blob concurrently may lead to undefined behavior.
629        ///
630        /// An Ok result indicates the blob is durably created (or already exists).
631        ///
632        /// # Versions
633        ///
634        /// Blobs are versioned. If the blob's version is not in `versions`, returns
635        /// [Error::BlobVersionMismatch].
636        ///
637        /// # Returns
638        ///
639        /// A tuple of (blob, logical_size, blob_version).
640        fn open_versioned(
641            &self,
642            partition: &str,
643            name: &[u8],
644            versions: std::ops::RangeInclusive<u16>,
645        ) -> impl Future<Output = Result<(Self::Blob, u64, u16), Error>> + Send;
646
647        /// Remove a blob from a given partition.
648        ///
649        /// If no `name` is provided, the entire partition is removed.
650        ///
651        /// An Ok result indicates the blob is durably removed.
652        ///
653        /// # Read-after-remove
654        ///
655        /// Removal unlinks the blob's name but does not invalidate previously opened handles:
656        /// they remain fully readable until dropped, whether the blob was removed by name or by
657        /// removing its entire partition. This includes bytes written but never synced. Physical
658        /// resources are reclaimed once the last handle is dropped.
659        ///
660        /// Re-opening a removed blob's name creates a new, independent blob; handles opened
661        /// before the removal continue to observe the removed blob's contents.
662        ///
663        /// Mutating a removed blob (e.g. via [`Blob::write_at`], [`Blob::resize`], or
664        /// [`Blob::sync`]) is unspecified: implementations may succeed or return an error.
665        fn remove(
666            &self,
667            partition: &str,
668            name: Option<&[u8]>,
669        ) -> impl Future<Output = Result<(), Error>> + Send;
670
671        /// Return all blobs in a given partition.
672        fn scan(&self, partition: &str)
673            -> impl Future<Output = Result<Vec<Vec<u8>>, Error>> + Send;
674    }
675
676    /// Interface to read and write to a blob.
677    ///
678    /// To support blob implementations that enable concurrent reads and
679    /// writes, blobs are responsible for maintaining synchronization.
680    ///
681    /// Cloning a blob is similar to wrapping a single file descriptor in
682    /// a lock whereas opening a new blob (of the same name) is similar to
683    /// opening a new file descriptor. If multiple blobs are opened with the same
684    /// name, they are not expected to coordinate access to underlying storage
685    /// and writing to both is undefined behavior.
686    ///
687    /// When a blob is dropped, any unsynced changes may be discarded. Implementations
688    /// may attempt to sync during drop but errors will go unhandled. Call `sync`
689    /// before dropping to ensure all changes are durably persisted.
690    #[allow(clippy::len_without_is_empty)]
691    pub trait Blob: Clone + Send + Sync + 'static {
692        /// Read `len` bytes at `offset` into caller-provided buffer(s).
693        ///
694        /// The caller provides the buffer(s), and the implementation fills it with
695        /// exactly `len` bytes of data read from the blob starting at `offset`.
696        /// Returns the same buffer(s), filled with data.
697        ///
698        /// # Contract
699        ///
700        /// - The returned buffers reuse caller-provided storage, with exactly `len`
701        ///   bytes filled from `offset`.
702        /// - Caller-provided chunk layout is preserved.
703        ///
704        /// # Panics
705        ///
706        /// Panics if `len` exceeds the total capacity of `bufs`.
707        fn read_at_buf(
708            &self,
709            offset: u64,
710            len: usize,
711            bufs: impl Into<IoBufsMut> + Send,
712        ) -> impl Future<Output = Result<IoBufsMut, Error>> + Send;
713
714        /// Read `len` bytes at `offset`, returning a buffer(s) with exactly `len` bytes
715        /// of data read from the blob starting at `offset`.
716        ///
717        /// To reuse a buffer(s), use [`Blob::read_at_buf`].
718        fn read_at(
719            &self,
720            offset: u64,
721            len: usize,
722        ) -> impl Future<Output = Result<IoBufsMut, Error>> + Send;
723
724        /// Write `bufs` to the blob at the given offset.
725        fn write_at(
726            &self,
727            offset: u64,
728            bufs: impl Into<IoBufs> + Send,
729        ) -> impl Future<Output = Result<(), Error>> + Send;
730
731        /// Write `bufs` to the blob at the given offset and durably persist that write.
732        ///
733        /// This is not a durability barrier for previous operations. When it completes,
734        /// only the bytes submitted to this call are guaranteed durable. Earlier unsynced
735        /// [`Blob::write_at`] or [`Blob::resize`] calls require [`Blob::sync`] to become
736        /// durable.
737        fn write_at_sync(
738            &self,
739            offset: u64,
740            bufs: impl Into<IoBufs> + Send,
741        ) -> impl Future<Output = Result<(), Error>> + Send;
742
743        /// Resize the blob to the given length.
744        ///
745        /// If the length is greater than the current length, the blob is extended with zeros.
746        /// If the length is less than the current length, the blob is resized.
747        fn resize(&self, len: u64) -> impl Future<Output = Result<(), Error>> + Send;
748
749        /// Ensure all pending data is durably persisted.
750        fn sync(&self) -> impl Future<Output = Result<(), Error>> + Send;
751
752        /// Request that all pending data is durably persisted.
753        ///
754        /// Awaiting this future waits until the sync has started. Awaiting the returned
755        /// [`Handle`] waits for the same durability guarantee as [`Blob::sync`].
756        fn start_sync(&self) -> impl Future<Output = Handle<()>> + Send;
757    }
758
759    /// Interface that any runtime must implement to provide buffer pools.
760    pub trait BufferPooler: Send + Sync + 'static {
761        /// Returns the network [BufferPool].
762        fn network_buffer_pool(&self) -> &BufferPool;
763
764        /// Returns the storage [BufferPool].
765        fn storage_buffer_pool(&self) -> &BufferPool;
766    }
767});
768stability_scope!(BETA, cfg(feature = "external") {
769    /// Interface that runtimes can implement to constrain the execution latency of a future.
770    pub trait Pacer: Clock + Send + Sync + 'static {
771        /// Defer completion of a future until a specified `latency` has elapsed. If the future is
772        /// not yet ready at the desired time of completion, the runtime will block until the future
773        /// is ready.
774        ///
775        /// In [crate::deterministic], this is used to ensure interactions with external systems can
776        /// be interacted with deterministically. In [crate::tokio], this is a no-op (allows
777        /// multiple runtimes to be tested with no code changes).
778        ///
779        /// # Setting Latency
780        ///
781        /// `pace` is not meant to be a time penalty applied to awaited futures and should be set to
782        /// the expected resolution latency of the future. To better explore the possible behavior of an
783        /// application, users can set latency to a randomly chosen value in the range of
784        /// `[expected latency / 2, expected latency * 2]`.
785        ///
786        /// # Warning
787        ///
788        /// Because `pace` blocks if the future is not ready, it is important that the future's completion
789        /// doesn't require anything in the current thread to complete (or else it will deadlock).
790        fn pace<'a, F, T>(
791            &'a self,
792            latency: Duration,
793            future: F,
794        ) -> impl Future<Output = T> + Send + 'a
795        where
796            F: Future<Output = T> + Send + 'a,
797            T: Send + 'a;
798    }
799
800    /// Extension trait that makes it more ergonomic to use [Pacer].
801    ///
802    /// This inverts the call-site of [`Pacer::pace`] by letting the future itself request how the
803    /// runtime should delay completion relative to the clock.
804    pub trait FutureExt: Future + Send + Sized {
805        /// Delay completion of the future until a specified `latency` on `pacer`.
806        fn pace<'a, E>(
807            self,
808            pacer: &'a E,
809            latency: Duration,
810        ) -> impl Future<Output = Self::Output> + Send + 'a
811        where
812            E: Pacer + 'a,
813            Self: Send + 'a,
814            Self::Output: Send + 'a,
815        {
816            pacer.pace(latency, self)
817        }
818    }
819
820    impl<F> FutureExt for F where F: Future + Send {}
821});
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826    use crate::telemetry::metrics::{
827        count_running_tasks,
828        raw::{Counter, Family},
829        EncodeLabelKey, EncodeLabelSetTrait as EncodeLabelSet,
830        EncodeLabelValueTrait as EncodeLabelValue, LabelSetEncoder,
831    };
832    use bytes::Bytes;
833    use commonware_macros::select;
834    use commonware_parallel::Strategy as _;
835    use commonware_utils::{
836        channel::{mpsc, oneshot},
837        futures::Pool as FuturesPool,
838        sync::Mutex,
839        NZUsize, SystemTimeExt, NZU32,
840    };
841    use futures::{
842        future::{pending, ready},
843        join, pin_mut, FutureExt,
844    };
845    use std::{
846        collections::HashMap,
847        net::{IpAddr, Ipv4Addr, Ipv6Addr},
848        pin::Pin,
849        str::FromStr,
850        sync::{
851            atomic::{AtomicU32, Ordering},
852            Arc,
853        },
854        task::{Context as TContext, Poll, Waker},
855    };
856    use tracing::{error, Level};
857    use utils::reschedule;
858
859    fn test_error_future<R: Runner>(runner: R) {
860        #[allow(clippy::unused_async)]
861        async fn error_future() -> Result<&'static str, &'static str> {
862            Err("An error occurred")
863        }
864        let result = runner.start(|_| error_future());
865        assert_eq!(result, Err("An error occurred"));
866    }
867
868    #[test]
869    fn test_handle_can_use_futures_pool() {
870        deterministic::Runner::default().start(|_| async move {
871            let mut pool = FuturesPool::<Result<(), Error>>::default();
872            pool.push(Handle::ready(Ok(())));
873            assert!(pool.next_completed().await.is_ok());
874        });
875    }
876
877    fn test_clock_sleep<R: Runner>(runner: R)
878    where
879        R::Context: Spawner + Clock,
880    {
881        runner.start(|context| async move {
882            // Capture initial time
883            let start = context.current();
884            let sleep_duration = Duration::from_millis(10);
885            context.sleep(sleep_duration).await;
886
887            // After run, time should have advanced
888            let end = context.current();
889            assert!(end.duration_since(start).unwrap() >= sleep_duration);
890        });
891    }
892
893    fn test_clock_sleep_until<R: Runner>(runner: R)
894    where
895        R::Context: Spawner + Clock + Metrics,
896    {
897        runner.start(|context| async move {
898            // Trigger sleep
899            let now = context.current();
900            context.sleep_until(now + Duration::from_millis(100)).await;
901
902            // Ensure slept duration has elapsed
903            let elapsed = now.elapsed().unwrap();
904            assert!(elapsed >= Duration::from_millis(100));
905        });
906    }
907
908    fn test_clock_sleep_until_far_future<R: Runner>(runner: R)
909    where
910        R::Context: Spawner + Clock,
911    {
912        runner.start(|context| async move {
913            let sleep = context.sleep_until(SystemTime::limit());
914            let result = context.timeout(Duration::from_millis(1), sleep).await;
915            assert!(matches!(result, Err(Error::Timeout)));
916        });
917    }
918
919    fn test_clock_timeout<R: Runner>(runner: R)
920    where
921        R::Context: Spawner + Clock,
922    {
923        runner.start(|context| async move {
924            // Future completes before timeout
925            let result = context
926                .timeout(Duration::from_millis(100), async { "success" })
927                .await;
928            assert_eq!(result.unwrap(), "success");
929
930            // Future exceeds timeout duration
931            let result = context
932                .timeout(Duration::from_millis(50), pending::<()>())
933                .await;
934            assert!(matches!(result, Err(Error::Timeout)));
935
936            // Future completes within timeout
937            let result = context
938                .timeout(
939                    Duration::from_millis(100),
940                    context.sleep(Duration::from_millis(50)),
941                )
942                .await;
943            assert!(result.is_ok());
944        });
945    }
946
947    fn test_root_finishes<R: Runner>(runner: R)
948    where
949        R::Context: Spawner,
950    {
951        runner.start(|context| async move {
952            context.spawn(|_| async move {
953                loop {
954                    reschedule().await;
955                }
956            });
957        });
958    }
959
960    fn test_spawn_after_abort<R>(runner: R)
961    where
962        R: Runner,
963        R::Context: Spawner,
964    {
965        runner.start(|context| async move {
966            // Create a child context
967            let child = context.child("child");
968
969            // Spawn parent and abort
970            let parent_handle = context.spawn(move |_| async move {
971                pending::<()>().await;
972            });
973            parent_handle.abort();
974
975            // Spawn child and ensure it aborts
976            let child_handle = child.spawn(move |_| async move {
977                pending::<()>().await;
978            });
979            assert!(matches!(child_handle.await, Err(Error::Closed)));
980        });
981    }
982
983    fn test_spawn_abort<R: Runner>(runner: R, dedicated: bool, blocking: bool)
984    where
985        R::Context: Spawner,
986    {
987        runner.start(|context| async move {
988            let context = if dedicated {
989                assert!(!blocking);
990                context.dedicated()
991            } else {
992                context.shared(blocking)
993            };
994
995            let handle = context.spawn(|_| async move {
996                loop {
997                    reschedule().await;
998                }
999            });
1000            handle.abort();
1001            assert!(matches!(handle.await, Err(Error::Closed)));
1002        });
1003    }
1004
1005    fn test_panic_aborts_root<R: Runner>(runner: R) {
1006        let result: Result<(), Error> = runner.start(|_| async move {
1007            panic!("blah");
1008        });
1009        result.unwrap_err();
1010    }
1011
1012    fn test_panic_aborts_spawn<R: Runner>(runner: R)
1013    where
1014        R::Context: Spawner + Clock,
1015    {
1016        runner.start(|context| async move {
1017            context.child("panic").spawn(|_| async move {
1018                panic!("blah");
1019            });
1020
1021            // Loop until panic
1022            loop {
1023                context.sleep(Duration::from_millis(100)).await;
1024            }
1025        });
1026    }
1027
1028    fn test_panic_aborts_spawn_caught<R: Runner>(runner: R)
1029    where
1030        R::Context: Spawner + Clock,
1031    {
1032        let result: Result<(), Error> = runner.start(|context| async move {
1033            let result = context.child("panic").spawn(|_| async move {
1034                panic!("blah");
1035            });
1036            result.await
1037        });
1038        assert!(matches!(result, Err(Error::Exited)));
1039    }
1040
1041    fn test_multiple_panics<R: Runner>(runner: R)
1042    where
1043        R::Context: Spawner + Clock,
1044    {
1045        runner.start(|context| async move {
1046            context.child("panic").spawn(|_| async move {
1047                panic!("boom 1");
1048            });
1049            context.child("panic").spawn(|_| async move {
1050                panic!("boom 2");
1051            });
1052            context.child("panic").spawn(|_| async move {
1053                panic!("boom 3");
1054            });
1055
1056            // Loop until panic
1057            loop {
1058                context.sleep(Duration::from_millis(100)).await;
1059            }
1060        });
1061    }
1062
1063    fn test_multiple_panics_caught<R: Runner>(runner: R)
1064    where
1065        R::Context: Spawner + Clock,
1066    {
1067        let (res1, res2, res3) = runner.start(|context| async move {
1068            let handle1 = context.child("panic").spawn(|_| async move {
1069                panic!("boom 1");
1070            });
1071            let handle2 = context.child("panic").spawn(|_| async move {
1072                panic!("boom 2");
1073            });
1074            let handle3 = context.child("panic").spawn(|_| async move {
1075                panic!("boom 3");
1076            });
1077
1078            join!(handle1, handle2, handle3)
1079        });
1080        assert!(matches!(res1, Err(Error::Exited)));
1081        assert!(matches!(res2, Err(Error::Exited)));
1082        assert!(matches!(res3, Err(Error::Exited)));
1083    }
1084
1085    fn test_select<R: Runner>(runner: R) {
1086        runner.start(|_| async move {
1087            // Test first branch
1088            let output = Mutex::new(0);
1089            select! {
1090                v1 = ready(1) => {
1091                    *output.lock() = v1;
1092                },
1093                v2 = ready(2) => {
1094                    *output.lock() = v2;
1095                },
1096            };
1097            assert_eq!(*output.lock(), 1);
1098
1099            // Test second branch
1100            select! {
1101                v1 = std::future::pending::<i32>() => {
1102                    *output.lock() = v1;
1103                },
1104                v2 = ready(2) => {
1105                    *output.lock() = v2;
1106                },
1107            };
1108            assert_eq!(*output.lock(), 2);
1109        });
1110    }
1111
1112    /// Ensure future fusing works as expected.
1113    fn test_select_loop<R: Runner>(runner: R)
1114    where
1115        R::Context: Clock,
1116    {
1117        runner.start(|context| async move {
1118            // Should hit timeout
1119            let (sender, mut receiver) = mpsc::unbounded_channel();
1120            for _ in 0..2 {
1121                select! {
1122                    v = receiver.recv() => {
1123                        panic!("unexpected value: {v:?}");
1124                    },
1125                    _ = context.sleep(Duration::from_millis(100)) => {
1126                        continue;
1127                    },
1128                };
1129            }
1130
1131            // Populate channel
1132            sender.send(0).unwrap();
1133            sender.send(1).unwrap();
1134
1135            // Prefer not reading channel without losing messages
1136            select! {
1137                _ = async {} => {
1138                    // Skip reading from channel even though populated
1139                },
1140                v = receiver.recv() => {
1141                    panic!("unexpected value: {v:?}");
1142                },
1143            };
1144
1145            // Process messages
1146            for i in 0..2 {
1147                select! {
1148                    _ = context.sleep(Duration::from_millis(100)) => {
1149                        panic!("timeout");
1150                    },
1151                    v = receiver.recv() => {
1152                        assert_eq!(v.unwrap(), i);
1153                    },
1154                };
1155            }
1156        });
1157    }
1158
1159    fn test_storage_operations<R: Runner>(runner: R)
1160    where
1161        R::Context: Storage,
1162    {
1163        runner.start(|context| async move {
1164            let partition = "test_partition";
1165            let name = b"test_blob";
1166
1167            // Open a new blob
1168            let (blob, size) = context
1169                .open(partition, name)
1170                .await
1171                .expect("Failed to open blob");
1172            assert_eq!(size, 0, "new blob should have size 0");
1173
1174            // Write data to the blob
1175            let data = b"Hello, Storage!";
1176            blob.write_at(0, data)
1177                .await
1178                .expect("Failed to write to blob");
1179
1180            // Sync the blob
1181            blob.sync().await.expect("Failed to sync blob");
1182
1183            // Read data from the blob
1184            let read = blob
1185                .read_at(0, data.len())
1186                .await
1187                .expect("Failed to read from blob");
1188            assert_eq!(read.coalesce(), data);
1189
1190            // Sync the blob
1191            blob.sync().await.expect("Failed to sync blob");
1192
1193            // Scan blobs in the partition
1194            let blobs = context
1195                .scan(partition)
1196                .await
1197                .expect("Failed to scan partition");
1198            assert!(blobs.contains(&name.to_vec()));
1199
1200            // Reopen the blob
1201            let (blob, len) = context
1202                .open(partition, name)
1203                .await
1204                .expect("Failed to reopen blob");
1205            assert_eq!(len, data.len() as u64);
1206
1207            // Read data part of message back
1208            let read = blob.read_at(7, 7).await.expect("Failed to read data");
1209            assert_eq!(read.coalesce(), b"Storage");
1210
1211            // Sync the blob
1212            blob.sync().await.expect("Failed to sync blob");
1213
1214            // Remove the blob
1215            context
1216                .remove(partition, Some(name))
1217                .await
1218                .expect("Failed to remove blob");
1219
1220            // Ensure the blob is removed
1221            let blobs = context
1222                .scan(partition)
1223                .await
1224                .expect("Failed to scan partition");
1225            assert!(!blobs.contains(&name.to_vec()));
1226
1227            // Remove the partition
1228            context
1229                .remove(partition, None)
1230                .await
1231                .expect("Failed to remove partition");
1232
1233            // Scan the partition
1234            let result = context.scan(partition).await;
1235            assert!(matches!(result, Err(Error::PartitionMissing(_))));
1236        });
1237    }
1238
1239    fn test_blob_read_write<R: Runner>(runner: R)
1240    where
1241        R::Context: Storage,
1242    {
1243        runner.start(|context| async move {
1244            let partition = "test_partition";
1245            let name = b"test_blob_rw";
1246
1247            // Open a new blob
1248            let (blob, _) = context
1249                .open(partition, name)
1250                .await
1251                .expect("Failed to open blob");
1252
1253            // Write data at different offsets
1254            let data1 = b"Hello";
1255            let data2 = b"World";
1256            blob.write_at(0, data1)
1257                .await
1258                .expect("Failed to write data1");
1259            blob.write_at(5, data2)
1260                .await
1261                .expect("Failed to write data2");
1262
1263            // Read data back
1264            let read = blob.read_at(0, 10).await.expect("Failed to read data");
1265            let read = read.coalesce();
1266            assert_eq!(&read.as_ref()[..5], data1);
1267            assert_eq!(&read.as_ref()[5..], data2);
1268
1269            // Read past end of blob
1270            let result = blob.read_at(10, 10).await;
1271            assert!(result.is_err());
1272
1273            // Rewrite data without affecting length
1274            let data3 = b"Store";
1275            blob.write_at(5, data3)
1276                .await
1277                .expect("Failed to write data3");
1278
1279            // Read data back
1280            let read = blob.read_at(0, 10).await.expect("Failed to read data");
1281            let read = read.coalesce();
1282            assert_eq!(&read.as_ref()[..5], data1);
1283            assert_eq!(&read.as_ref()[5..], data3);
1284
1285            // Read past end of blob
1286            let result = blob.read_at(10, 10).await;
1287            assert!(result.is_err());
1288        });
1289    }
1290
1291    fn test_blob_resize<R: Runner>(runner: R)
1292    where
1293        R::Context: Storage,
1294    {
1295        runner.start(|context| async move {
1296            let partition = "test_partition_resize";
1297            let name = b"test_blob_resize";
1298
1299            // Open and write to a new blob
1300            let (blob, _) = context
1301                .open(partition, name)
1302                .await
1303                .expect("Failed to open blob");
1304
1305            let data = b"some data";
1306            blob.write_at(0, data.to_vec())
1307                .await
1308                .expect("Failed to write");
1309            blob.sync().await.expect("Failed to sync after write");
1310
1311            // Re-open and check length
1312            let (blob, len) = context.open(partition, name).await.unwrap();
1313            assert_eq!(len, data.len() as u64);
1314
1315            // Resize to extend the file
1316            let new_len = (data.len() as u64) * 2;
1317            blob.resize(new_len)
1318                .await
1319                .expect("Failed to resize to extend");
1320            blob.sync().await.expect("Failed to sync after resize");
1321
1322            // Re-open and check length again
1323            let (blob, len) = context.open(partition, name).await.unwrap();
1324            assert_eq!(len, new_len);
1325
1326            // Read original data
1327            let read_buf = blob.read_at(0, data.len()).await.unwrap();
1328            assert_eq!(read_buf.coalesce(), data);
1329
1330            // Read extended part (should be zeros)
1331            let extended_part = blob.read_at(data.len() as u64, data.len()).await.unwrap();
1332            assert_eq!(extended_part.coalesce(), vec![0; data.len()].as_slice());
1333
1334            // Truncate the blob
1335            blob.resize(data.len() as u64).await.unwrap();
1336            blob.sync().await.unwrap();
1337
1338            // Reopen to check truncation
1339            let (blob, size) = context.open(partition, name).await.unwrap();
1340            assert_eq!(size, data.len() as u64);
1341
1342            // Read truncated data
1343            let read_buf = blob.read_at(0, data.len()).await.unwrap();
1344            assert_eq!(read_buf.coalesce(), data);
1345            blob.sync().await.unwrap();
1346        });
1347    }
1348
1349    fn test_many_partition_read_write<R: Runner>(runner: R)
1350    where
1351        R::Context: Storage,
1352    {
1353        runner.start(|context| async move {
1354            let partitions = ["partition1", "partition2", "partition3"];
1355            let name = b"test_blob_rw";
1356            let data1 = b"Hello";
1357            let data2 = b"World";
1358
1359            for (additional, partition) in partitions.iter().enumerate() {
1360                // Open a new blob
1361                let (blob, _) = context
1362                    .open(partition, name)
1363                    .await
1364                    .expect("Failed to open blob");
1365
1366                // Write data at different offsets
1367                blob.write_at(0, data1)
1368                    .await
1369                    .expect("Failed to write data1");
1370                blob.write_at(5 + additional as u64, data2)
1371                    .await
1372                    .expect("Failed to write data2");
1373
1374                // Sync the blob
1375                blob.sync().await.expect("Failed to sync blob");
1376            }
1377
1378            for (additional, partition) in partitions.iter().enumerate() {
1379                // Open a new blob
1380                let (blob, len) = context
1381                    .open(partition, name)
1382                    .await
1383                    .expect("Failed to open blob");
1384                assert_eq!(len, (data1.len() + data2.len() + additional) as u64);
1385
1386                // Read data back
1387                let read = blob
1388                    .read_at(0, 10 + additional)
1389                    .await
1390                    .expect("Failed to read data");
1391                let read = read.coalesce();
1392                assert_eq!(&read.as_ref()[..5], b"Hello");
1393                assert_eq!(&read.as_ref()[5 + additional..], b"World");
1394            }
1395        });
1396    }
1397
1398    fn test_blob_read_past_length<R: Runner>(runner: R)
1399    where
1400        R::Context: Storage,
1401    {
1402        runner.start(|context| async move {
1403            let partition = "test_partition";
1404            let name = b"test_blob_rw";
1405
1406            // Open a new blob
1407            let (blob, _) = context
1408                .open(partition, name)
1409                .await
1410                .expect("Failed to open blob");
1411
1412            // Read data past file length (empty file)
1413            let result = blob.read_at(0, 10).await;
1414            assert!(result.is_err());
1415
1416            // Write data to the blob
1417            let data = b"Hello, Storage!".to_vec();
1418            blob.write_at(0, data)
1419                .await
1420                .expect("Failed to write to blob");
1421
1422            // Read data past file length (non-empty file)
1423            let result = blob.read_at(0, 20).await;
1424            assert!(result.is_err());
1425        })
1426    }
1427
1428    fn test_blob_clone_and_concurrent_read<R: Runner>(runner: R)
1429    where
1430        R::Context: Spawner + Storage + Metrics,
1431    {
1432        runner.start(|context| async move {
1433            let partition = "test_partition";
1434            let name = b"test_blob_rw";
1435
1436            // Open a new blob
1437            let (blob, _) = context
1438                .open(partition, name)
1439                .await
1440                .expect("Failed to open blob");
1441
1442            // Write data to the blob
1443            let data = b"Hello, Storage!";
1444            blob.write_at(0, data)
1445                .await
1446                .expect("Failed to write to blob");
1447
1448            // Sync the blob
1449            blob.sync().await.expect("Failed to sync blob");
1450
1451            // Read data from the blob in clone
1452            let check1 = context.child("check1").spawn({
1453                let blob = blob.clone();
1454                let data_len = data.len();
1455                move |_| async move {
1456                    let read = blob
1457                        .read_at(0, data_len)
1458                        .await
1459                        .expect("Failed to read from blob");
1460                    assert_eq!(read.coalesce(), data);
1461                }
1462            });
1463            let check2 = context.child("check2").spawn({
1464                let blob = blob.clone();
1465                let data_len = data.len();
1466                move |_| async move {
1467                    let read = blob
1468                        .read_at(0, data_len)
1469                        .await
1470                        .expect("Failed to read from blob");
1471                    assert_eq!(read.coalesce(), data);
1472                }
1473            });
1474
1475            // Wait for both reads to complete
1476            let result = join!(check1, check2);
1477            assert!(result.0.is_ok());
1478            assert!(result.1.is_ok());
1479
1480            // Read data from the blob
1481            let read = blob
1482                .read_at(0, data.len())
1483                .await
1484                .expect("Failed to read from blob");
1485            assert_eq!(read.coalesce(), data);
1486
1487            // Drop the blob
1488            drop(blob);
1489
1490            // Ensure no blobs still open
1491            let buffer = context.encode();
1492            assert!(buffer.contains("open_blobs 0"));
1493        });
1494    }
1495
1496    fn test_shutdown<R: Runner>(runner: R)
1497    where
1498        R::Context: Spawner + Metrics + Clock,
1499    {
1500        let kill = 9;
1501        runner.start(|context| async move {
1502            // Spawn a task that waits for signal
1503            let before = context.child("before").spawn(move |context| async move {
1504                let mut signal = context.stopped();
1505                let value = (&mut signal).await.unwrap();
1506                assert_eq!(value, kill);
1507                drop(signal);
1508            });
1509
1510            // Signal the tasks and wait for them to stop
1511            let result = context.child("stop").stop(kill, None).await;
1512            assert!(result.is_ok());
1513
1514            // Spawn a task after stop is called
1515            let after = context.child("after").spawn(move |context| async move {
1516                // A call to `stopped()` after `stop()` resolves immediately
1517                let value = context.stopped().await.unwrap();
1518                assert_eq!(value, kill);
1519            });
1520
1521            // Ensure both tasks complete
1522            let result = join!(before, after);
1523            assert!(result.0.is_ok());
1524            assert!(result.1.is_ok());
1525        });
1526    }
1527
1528    fn test_shutdown_multiple_signals<R: Runner>(runner: R)
1529    where
1530        R::Context: Spawner + Metrics + Clock,
1531    {
1532        let kill = 42;
1533        runner.start(|context| async move {
1534            let (started_tx, mut started_rx) = mpsc::channel(3);
1535            let counter = Arc::new(AtomicU32::new(0));
1536
1537            // Spawn 3 tasks that do cleanup work after receiving stop signal
1538            // and increment a shared counter
1539            let task = |context: R::Context, cleanup_duration: Duration| {
1540                let counter = counter.clone();
1541                let started_tx = started_tx.clone();
1542                context.spawn(move |context| async move {
1543                    // Wait for signal to be acquired
1544                    let mut signal = context.stopped();
1545                    started_tx.send(()).await.unwrap();
1546
1547                    // Increment once killed
1548                    let value = (&mut signal).await.unwrap();
1549                    assert_eq!(value, kill);
1550                    context.sleep(cleanup_duration).await;
1551                    counter.fetch_add(1, Ordering::SeqCst);
1552
1553                    // Wait to drop signal until work has been done
1554                    drop(signal);
1555                })
1556            };
1557
1558            let task1 = task(context.child("cleanup"), Duration::from_millis(10));
1559            let task2 = task(context.child("cleanup"), Duration::from_millis(20));
1560            let task3 = task(context.child("cleanup"), Duration::from_millis(30));
1561
1562            // Give tasks time to start
1563            for _ in 0..3 {
1564                started_rx.recv().await.unwrap();
1565            }
1566
1567            // Stop and verify all cleanup completed
1568            context.stop(kill, None).await.unwrap();
1569            assert_eq!(counter.load(Ordering::SeqCst), 3);
1570
1571            // Ensure all tasks completed
1572            let result = join!(task1, task2, task3);
1573            assert!(result.0.is_ok());
1574            assert!(result.1.is_ok());
1575            assert!(result.2.is_ok());
1576        });
1577    }
1578
1579    fn test_shutdown_timeout<R: Runner>(runner: R)
1580    where
1581        R::Context: Spawner + Metrics + Clock,
1582    {
1583        let kill = 42;
1584        runner.start(|context| async move {
1585            // Setup startup coordinator
1586            let (started_tx, started_rx) = oneshot::channel();
1587
1588            // Spawn a task that never completes its cleanup
1589            context.child("signal").spawn(move |context| async move {
1590                let signal = context.stopped();
1591                started_tx.send(()).unwrap();
1592                pending::<()>().await;
1593                signal.await.unwrap();
1594            });
1595
1596            // Try to stop with a timeout
1597            started_rx.await.unwrap();
1598            let result = context.stop(kill, Some(Duration::from_millis(100))).await;
1599
1600            // Assert that we got a timeout error
1601            assert!(matches!(result, Err(Error::Timeout)));
1602        });
1603    }
1604
1605    fn test_shutdown_multiple_stop_calls<R: Runner>(runner: R)
1606    where
1607        R::Context: Spawner + Metrics + Clock,
1608    {
1609        let kill1 = 42;
1610        let kill2 = 43;
1611
1612        runner.start(|context| async move {
1613            let (started_tx, started_rx) = oneshot::channel();
1614            let counter = Arc::new(AtomicU32::new(0));
1615
1616            // Spawn a task that delays completion to test timing
1617            let task = context.child("blocking_task").spawn({
1618                let counter = counter.clone();
1619                move |context| async move {
1620                    // Wait for signal to be acquired
1621                    let mut signal = context.stopped();
1622                    started_tx.send(()).unwrap();
1623
1624                    // Wait for signal to be resolved
1625                    let value = (&mut signal).await.unwrap();
1626                    assert_eq!(value, kill1);
1627                    context.sleep(Duration::from_millis(50)).await;
1628
1629                    // Increment counter
1630                    counter.fetch_add(1, Ordering::SeqCst);
1631                    drop(signal);
1632                }
1633            });
1634
1635            // Give task time to start
1636            started_rx.await.unwrap();
1637
1638            // Issue two separate stop calls
1639            // The second stop call uses a different stop value that should be ignored
1640            let stop_task1 = context.child("stop").stop(kill1, None);
1641            pin_mut!(stop_task1);
1642            let stop_task2 = context.child("stop").stop(kill2, None);
1643            pin_mut!(stop_task2);
1644
1645            // Both of them should be awaiting completion
1646            assert!(stop_task1.as_mut().now_or_never().is_none());
1647            assert!(stop_task2.as_mut().now_or_never().is_none());
1648
1649            // Wait for both stop calls to complete
1650            assert!(stop_task1.await.is_ok());
1651            assert!(stop_task2.await.is_ok());
1652
1653            // Verify first stop value wins
1654            let sig = context.stopped().await;
1655            assert_eq!(sig.unwrap(), kill1);
1656
1657            // Wait for blocking task to complete
1658            let result = task.await;
1659            assert!(result.is_ok());
1660            assert_eq!(counter.load(Ordering::SeqCst), 1);
1661
1662            // Post-completion stop should return immediately
1663            assert!(context.stop(kill2, None).now_or_never().unwrap().is_ok());
1664        });
1665    }
1666
1667    fn test_unfulfilled_shutdown<R: Runner>(runner: R)
1668    where
1669        R::Context: Spawner + Metrics,
1670    {
1671        runner.start(|context| async move {
1672            // Spawn a task that waits for signal
1673            context.child("before").spawn(move |context| async move {
1674                let mut signal = context.stopped();
1675                let value = (&mut signal).await.unwrap();
1676
1677                // We should never reach this point
1678                assert_eq!(value, 42);
1679                drop(signal);
1680            });
1681
1682            // Ensure waker is registered
1683            reschedule().await;
1684        });
1685    }
1686
1687    fn test_spawn_dedicated<R: Runner>(runner: R)
1688    where
1689        R::Context: Spawner,
1690    {
1691        runner.start(|context| async move {
1692            let handle = context.dedicated().spawn(|_| async move { 42 });
1693            assert!(matches!(handle.await, Ok(42)));
1694        });
1695    }
1696
1697    fn test_spawn<R: Runner>(runner: R)
1698    where
1699        R::Context: Spawner + Clock,
1700    {
1701        runner.start(|context| async move {
1702            let child_handle = Arc::new(Mutex::new(None));
1703            let child_handle2 = child_handle.clone();
1704
1705            let (parent_initialized_tx, parent_initialized_rx) = oneshot::channel();
1706            let (parent_complete_tx, parent_complete_rx) = oneshot::channel();
1707            let parent_handle = context.spawn(move |context| async move {
1708                // Spawn child that completes immediately
1709                let handle = context.spawn(|_| async {});
1710
1711                // Store child handle so we can test it later
1712                *child_handle2.lock() = Some(handle);
1713
1714                parent_initialized_tx.send(()).unwrap();
1715
1716                // Parent task completes
1717                parent_complete_rx.await.unwrap();
1718            });
1719
1720            // Wait for parent task to spawn the children
1721            parent_initialized_rx.await.unwrap();
1722
1723            // Child task completes successfully
1724            let child_handle = child_handle.lock().take().unwrap();
1725            assert!(child_handle.await.is_ok());
1726
1727            // Complete the parent task
1728            parent_complete_tx.send(()).unwrap();
1729
1730            // Wait for parent task to complete successfully
1731            assert!(parent_handle.await.is_ok());
1732        });
1733    }
1734
1735    fn test_spawn_abort_on_parent_abort<R: Runner>(runner: R)
1736    where
1737        R::Context: Spawner + Clock,
1738    {
1739        runner.start(|context| async move {
1740            let child_handle = Arc::new(Mutex::new(None));
1741            let child_handle2 = child_handle.clone();
1742
1743            let (parent_initialized_tx, parent_initialized_rx) = oneshot::channel();
1744            let parent_handle = context.spawn(move |context| async move {
1745                // Spawn child task that hangs forever, should be aborted when parent aborts
1746                let handle = context.spawn(|_| pending::<()>());
1747
1748                // Store child task handle so we can test it later
1749                *child_handle2.lock() = Some(handle);
1750
1751                parent_initialized_tx.send(()).unwrap();
1752
1753                // Parent task runs until aborted
1754                pending::<()>().await
1755            });
1756
1757            // Wait for parent task to spawn the children
1758            parent_initialized_rx.await.unwrap();
1759
1760            // Abort parent task
1761            parent_handle.abort();
1762            assert!(matches!(parent_handle.await, Err(Error::Closed)));
1763
1764            // Child task should also resolve with error since its parent aborted
1765            let child_handle = child_handle.lock().take().unwrap();
1766            assert!(matches!(child_handle.await, Err(Error::Closed)));
1767        });
1768    }
1769
1770    fn test_spawn_abort_on_parent_completion<R: Runner>(runner: R)
1771    where
1772        R::Context: Spawner + Clock,
1773    {
1774        runner.start(|context| async move {
1775            let child_handle = Arc::new(Mutex::new(None));
1776            let child_handle2 = child_handle.clone();
1777
1778            let (parent_complete_tx, parent_complete_rx) = oneshot::channel();
1779            let parent_handle = context.spawn(move |context| async move {
1780                // Spawn child task that hangs forever, should be aborted when parent completes
1781                let handle = context.spawn(|_| pending::<()>());
1782
1783                // Store child task handle so we can test it later
1784                *child_handle2.lock() = Some(handle);
1785
1786                // Parent task completes
1787                parent_complete_rx.await.unwrap();
1788            });
1789
1790            // Fire parent completion
1791            parent_complete_tx.send(()).unwrap();
1792
1793            // Wait for parent task to complete
1794            assert!(parent_handle.await.is_ok());
1795
1796            // Child task should resolve with error since its parent has completed
1797            let child_handle = child_handle.lock().take().unwrap();
1798            assert!(matches!(child_handle.await, Err(Error::Closed)));
1799        });
1800    }
1801
1802    fn test_spawn_cascading_abort<R: Runner>(runner: R)
1803    where
1804        R::Context: Spawner + Clock,
1805    {
1806        runner.start(|context| async move {
1807            // We create the following tree of tasks. All tasks will run
1808            // indefinitely (until aborted).
1809            //
1810            //          root
1811            //     /     |     \
1812            //    /      |      \
1813            //   c0      c1      c2
1814            //  /  \    /  \    /  \
1815            // g0  g1  g2  g3  g4  g5
1816            let c0 = context.child("c0");
1817            let g0 = c0.child("g0");
1818            let g1 = c0.child("g1");
1819            let c1 = context.child("c1");
1820            let g2 = c1.child("g2");
1821            let g3 = c1.child("g3");
1822            let c2 = context.child("c2");
1823            let g4 = c2.child("g4");
1824            let g5 = c2.child("g5");
1825
1826            // Spawn tasks
1827            let handles = Arc::new(Mutex::new(Vec::new()));
1828            let (initialized_tx, mut initialized_rx) = mpsc::channel(9);
1829            let root_task = context.spawn({
1830                let handles = handles.clone();
1831                move |_| async move {
1832                    for (context, grandchildren) in [(c0, [g0, g1]), (c1, [g2, g3]), (c2, [g4, g5])]
1833                    {
1834                        let handle = context.spawn({
1835                            let handles = handles.clone();
1836                            let initialized_tx = initialized_tx.clone();
1837                            move |_| async move {
1838                                for grandchild in grandchildren {
1839                                    let handle = grandchild.spawn(|_| async {
1840                                        pending::<()>().await;
1841                                    });
1842                                    handles.lock().push(handle);
1843                                    initialized_tx.send(()).await.unwrap();
1844                                }
1845
1846                                pending::<()>().await;
1847                            }
1848                        });
1849                        handles.lock().push(handle);
1850                        initialized_tx.send(()).await.unwrap();
1851                    }
1852
1853                    pending::<()>().await;
1854                }
1855            });
1856
1857            // Wait for tasks to initialize
1858            for _ in 0..9 {
1859                initialized_rx.recv().await.unwrap();
1860            }
1861
1862            // Verify we have all 9 handles (3 children + 6 grandchildren)
1863            assert_eq!(handles.lock().len(), 9);
1864
1865            // Abort root task
1866            root_task.abort();
1867            assert!(matches!(root_task.await, Err(Error::Closed)));
1868
1869            // All handles should resolve with error due to cascading abort
1870            let handles = handles.lock().drain(..).collect::<Vec<_>>();
1871            for handle in handles {
1872                assert!(matches!(handle.await, Err(Error::Closed)));
1873            }
1874        });
1875    }
1876
1877    fn test_child_survives_sibling_completion<R: Runner>(runner: R)
1878    where
1879        R::Context: Spawner + Clock,
1880    {
1881        runner.start(|context| async move {
1882            let (child_started_tx, child_started_rx) = oneshot::channel();
1883            let (child_complete_tx, child_complete_rx) = oneshot::channel();
1884            let (child_handle_tx, child_handle_rx) = oneshot::channel();
1885            let (sibling_started_tx, sibling_started_rx) = oneshot::channel();
1886            let (sibling_complete_tx, sibling_complete_rx) = oneshot::channel();
1887            let (sibling_handle_tx, sibling_handle_rx) = oneshot::channel();
1888            let (parent_complete_tx, parent_complete_rx) = oneshot::channel();
1889
1890            let parent = context.spawn(move |context| async move {
1891                // Spawn a child task
1892                let child_handle = context.child("child").spawn(|_| async move {
1893                    child_started_tx.send(()).unwrap();
1894                    // Wait for signal to complete
1895                    child_complete_rx.await.unwrap();
1896                });
1897                assert!(
1898                    child_handle_tx.send(child_handle).is_ok(),
1899                    "child handle receiver dropped"
1900                );
1901
1902                // Spawn an independent sibling task
1903                let sibling_handle = context.child("sibling").spawn(move |_| async move {
1904                    sibling_started_tx.send(()).unwrap();
1905                    // Wait for signal to complete
1906                    sibling_complete_rx.await.unwrap();
1907                });
1908                assert!(
1909                    sibling_handle_tx.send(sibling_handle).is_ok(),
1910                    "sibling handle receiver dropped"
1911                );
1912
1913                // Wait for signal to complete
1914                parent_complete_rx.await.unwrap();
1915            });
1916
1917            // Wait for both to start
1918            child_started_rx.await.unwrap();
1919            sibling_started_rx.await.unwrap();
1920
1921            // Kill the sibling
1922            sibling_complete_tx.send(()).unwrap();
1923            assert!(sibling_handle_rx.await.is_ok());
1924
1925            // The child task should still be alive
1926            child_complete_tx.send(()).unwrap();
1927            assert!(child_handle_rx.await.is_ok());
1928
1929            // As well as the parent
1930            parent_complete_tx.send(()).unwrap();
1931            assert!(parent.await.is_ok());
1932        });
1933    }
1934
1935    fn test_spawn_clone_chain<R: Runner>(runner: R)
1936    where
1937        R::Context: Spawner + Clock,
1938    {
1939        runner.start(|context| async move {
1940            let (parent_started_tx, parent_started_rx) = oneshot::channel();
1941            let (child_started_tx, child_started_rx) = oneshot::channel();
1942            let (grandchild_started_tx, grandchild_started_rx) = oneshot::channel();
1943            let (child_handle_tx, child_handle_rx) = oneshot::channel();
1944            let (grandchild_handle_tx, grandchild_handle_rx) = oneshot::channel();
1945
1946            let parent = context.child("parent").spawn({
1947                move |context| async move {
1948                    let child = context.child("child").spawn({
1949                        move |context| async move {
1950                            let grandchild = context.child("grandchild").spawn({
1951                                move |_| async move {
1952                                    grandchild_started_tx.send(()).unwrap();
1953                                    pending::<()>().await;
1954                                }
1955                            });
1956                            assert!(
1957                                grandchild_handle_tx.send(grandchild).is_ok(),
1958                                "grandchild handle receiver dropped"
1959                            );
1960                            child_started_tx.send(()).unwrap();
1961                            pending::<()>().await;
1962                        }
1963                    });
1964                    assert!(
1965                        child_handle_tx.send(child).is_ok(),
1966                        "child handle receiver dropped"
1967                    );
1968                    parent_started_tx.send(()).unwrap();
1969                    pending::<()>().await;
1970                }
1971            });
1972
1973            parent_started_rx.await.unwrap();
1974            child_started_rx.await.unwrap();
1975            grandchild_started_rx.await.unwrap();
1976
1977            let child_handle = child_handle_rx.await.unwrap();
1978            let grandchild_handle = grandchild_handle_rx.await.unwrap();
1979
1980            parent.abort();
1981            assert!(parent.await.is_err());
1982
1983            assert!(child_handle.await.is_err());
1984            assert!(grandchild_handle.await.is_err());
1985        });
1986    }
1987
1988    fn test_spawn_sparse_clone_chain<R: Runner>(runner: R)
1989    where
1990        R::Context: Spawner + Clock,
1991    {
1992        runner.start(|context| async move {
1993            let (leaf_started_tx, leaf_started_rx) = oneshot::channel();
1994            let (leaf_handle_tx, leaf_handle_rx) = oneshot::channel();
1995
1996            let parent = context.child("parent").spawn({
1997                move |context| async move {
1998                    let clone1 = context.child("clone1");
1999                    let clone2 = clone1.child("clone2");
2000                    let clone3 = clone2.child("clone3");
2001
2002                    let leaf = clone3.spawn({
2003                        move |_| async move {
2004                            leaf_started_tx.send(()).unwrap();
2005                            pending::<()>().await;
2006                        }
2007                    });
2008
2009                    leaf_handle_tx
2010                        .send(leaf)
2011                        .unwrap_or_else(|_| panic!("leaf handle receiver dropped"));
2012                    pending::<()>().await;
2013                }
2014            });
2015
2016            leaf_started_rx.await.unwrap();
2017            let leaf_handle = leaf_handle_rx.await.unwrap();
2018
2019            parent.abort();
2020            assert!(parent.await.is_err());
2021            assert!(leaf_handle.await.is_err());
2022        });
2023    }
2024
2025    fn test_spawn_blocking<R: Runner>(runner: R, dedicated: bool)
2026    where
2027        R::Context: Spawner,
2028    {
2029        runner.start(|context| async move {
2030            let context = if dedicated {
2031                context.dedicated()
2032            } else {
2033                context.shared(true)
2034            };
2035
2036            let handle = context.spawn(|_| async move { 42 });
2037            let result = handle.await;
2038            assert!(matches!(result, Ok(42)));
2039        });
2040    }
2041
2042    fn test_spawn_blocking_panic<R: Runner>(runner: R, dedicated: bool)
2043    where
2044        R::Context: Spawner + Clock,
2045    {
2046        runner.start(|context| async move {
2047            if dedicated {
2048                context.child("blocking").dedicated().spawn(|_| async move {
2049                    panic!("blocking task panicked");
2050                });
2051            } else {
2052                context
2053                    .child("blocking")
2054                    .shared(true)
2055                    .spawn(|_| async move {
2056                        panic!("blocking task panicked");
2057                    });
2058            }
2059
2060            // Loop until panic
2061            loop {
2062                context.sleep(Duration::from_millis(100)).await;
2063            }
2064        });
2065    }
2066
2067    fn test_spawn_blocking_panic_caught<R: Runner>(runner: R, dedicated: bool)
2068    where
2069        R::Context: Spawner + Clock,
2070    {
2071        let result: Result<(), Error> = runner.start(|context| async move {
2072            let handle = if dedicated {
2073                context.child("blocking").dedicated().spawn(|_| async move {
2074                    panic!("blocking task panicked");
2075                })
2076            } else {
2077                context
2078                    .child("blocking")
2079                    .shared(true)
2080                    .spawn(|_| async move {
2081                        panic!("blocking task panicked");
2082                    })
2083            };
2084            handle.await
2085        });
2086        assert!(matches!(result, Err(Error::Exited)));
2087    }
2088
2089    fn test_circular_reference_prevents_cleanup<R: Runner>(runner: R) {
2090        runner.start(|_| async move {
2091            // Setup tracked resource
2092            let dropper = Arc::new(());
2093            let executor = deterministic::Runner::default();
2094            executor.start({
2095                let dropper = dropper.clone();
2096                move |context| async move {
2097                    // Create tasks with circular dependencies through channels
2098                    let (setup_tx, mut setup_rx) = mpsc::unbounded_channel::<()>();
2099                    let (tx1, mut rx1) = mpsc::unbounded_channel::<()>();
2100                    let (tx2, mut rx2) = mpsc::unbounded_channel::<()>();
2101
2102                    // Task 1 holds tx2 and waits on rx1
2103                    context.child("task1").spawn({
2104                        let setup_tx = setup_tx.clone();
2105                        let dropper = dropper.clone();
2106                        move |_| async move {
2107                            // Setup deadlock and mark ready
2108                            tx2.send(()).unwrap();
2109                            rx1.recv().await.unwrap();
2110                            setup_tx.send(()).unwrap();
2111
2112                            // Wait forever
2113                            while rx1.recv().await.is_some() {}
2114                            drop(tx2);
2115                            drop(dropper);
2116                        }
2117                    });
2118
2119                    // Task 2 holds tx1 and waits on rx2
2120                    context.child("task2").spawn(move |_| async move {
2121                        // Setup deadlock and mark ready
2122                        tx1.send(()).unwrap();
2123                        rx2.recv().await.unwrap();
2124                        setup_tx.send(()).unwrap();
2125
2126                        // Wait forever
2127                        while rx2.recv().await.is_some() {}
2128                        drop(tx1);
2129                        drop(dropper);
2130                    });
2131
2132                    // Wait for tasks to start
2133                    setup_rx.recv().await.unwrap();
2134                    setup_rx.recv().await.unwrap();
2135                }
2136            });
2137
2138            // After runtime drop, both tasks should be cleaned up
2139            Arc::try_unwrap(dropper).expect("references remaining");
2140        });
2141    }
2142
2143    fn test_late_waker<R: Runner>(runner: R)
2144    where
2145        R::Context: Metrics + Spawner,
2146    {
2147        // A future that captures its waker and sends it to the caller, then
2148        // stays pending forever.
2149        struct CaptureWaker {
2150            tx: Option<oneshot::Sender<Waker>>,
2151            sent: bool,
2152        }
2153        impl Future for CaptureWaker {
2154            type Output = ();
2155            fn poll(mut self: Pin<&mut Self>, cx: &mut TContext<'_>) -> Poll<Self::Output> {
2156                if !self.sent {
2157                    if let Some(tx) = self.tx.take() {
2158                        // Send a clone of the current task's waker to the root
2159                        let _ = tx.send(cx.waker().clone());
2160                    }
2161                    self.sent = true;
2162                }
2163                Poll::Pending
2164            }
2165        }
2166
2167        // A guard that wakes the captured waker on drop.
2168        struct WakeOnDrop(Option<Waker>);
2169        impl Drop for WakeOnDrop {
2170            fn drop(&mut self) {
2171                if let Some(w) = self.0.take() {
2172                    w.wake_by_ref();
2173                }
2174            }
2175        }
2176
2177        // Run the executor to completion
2178        let holder = runner.start(|context| async move {
2179            // Wire a oneshot to receive the task waker.
2180            let (tx, rx) = oneshot::channel::<Waker>();
2181
2182            // Spawn a task that registers its waker and then stays pending.
2183            context.child("capture_waker").spawn(move |_| async move {
2184                CaptureWaker {
2185                    tx: Some(tx),
2186                    sent: false,
2187                }
2188                .await;
2189            });
2190
2191            // Ensure the spawned task runs and registers its waker.
2192            utils::reschedule().await;
2193
2194            // Receive the waker from the spawned task.
2195            let waker = rx.await.expect("waker not received");
2196
2197            // Return a guard that will wake after the runtime has dropped.
2198            WakeOnDrop(Some(waker))
2199        });
2200
2201        // Dropping the guard after the runtime has torn down will trigger a wake on
2202        // a task whose executor has been dropped.
2203        drop(holder);
2204    }
2205
2206    fn test_metrics<R: Runner>(runner: R)
2207    where
2208        R::Context: Metrics,
2209    {
2210        runner.start(|context| async move {
2211            // Assert label
2212            assert_eq!(context.name().label, "");
2213
2214            // Register a metric
2215            let counter = Counter::<u64>::default();
2216            let _registered = context.register("test", "test", counter.clone());
2217
2218            // Increment the counter
2219            counter.inc();
2220
2221            // Encode metrics
2222            let buffer = context.encode();
2223            assert!(buffer.contains("test_total 1"));
2224
2225            // Nested context
2226            let context = context.child("nested");
2227            let nested_counter = Counter::<u64>::default();
2228            let _nested_registered = context.register("test", "test", nested_counter.clone());
2229
2230            // Increment the counter
2231            nested_counter.inc();
2232
2233            // Encode metrics
2234            let buffer = context.encode();
2235            assert!(buffer.contains("nested_test_total 1"));
2236            assert!(buffer.contains("test_total 1"));
2237        });
2238    }
2239
2240    fn test_metrics_with_attribute<R: Runner>(runner: R)
2241    where
2242        R::Context: Metrics,
2243    {
2244        runner.start(|context| async move {
2245            // Create context with a attribute
2246            let ctx_epoch5 = context.child("consensus").with_attribute("epoch", "e5");
2247
2248            // Register a metric with the attribute
2249            let counter = Counter::<u64>::default();
2250            let _epoch5 = ctx_epoch5.register("votes", "vote count", counter.clone());
2251            counter.inc();
2252
2253            // Encode and verify the attribute appears as a label
2254            let buffer = context.encode();
2255            assert!(
2256                buffer.contains("consensus_votes_total{epoch=\"e5\"} 1"),
2257                "Expected metric with epoch attribute, got: {}",
2258                buffer
2259            );
2260
2261            // Create context with different epoch attribute (same metric name)
2262            let ctx_epoch6 = context.child("consensus").with_attribute("epoch", "e6");
2263            let counter2 = Counter::<u64>::default();
2264            let _epoch6 = ctx_epoch6.register("votes", "vote count", counter2.clone());
2265            counter2.inc();
2266            counter2.inc();
2267
2268            // Both should appear in encoded output with canonical format (single HELP/TYPE)
2269            let buffer = context.encode();
2270            assert!(
2271                buffer.contains("consensus_votes_total{epoch=\"e5\"} 1"),
2272                "Expected metric with epoch=e5, got: {}",
2273                buffer
2274            );
2275            assert!(
2276                buffer.contains("consensus_votes_total{epoch=\"e6\"} 2"),
2277                "Expected metric with epoch=e6, got: {}",
2278                buffer
2279            );
2280
2281            // Verify canonical format: HELP and TYPE should appear exactly once
2282            assert_eq!(
2283                buffer.matches("# HELP consensus_votes").count(),
2284                1,
2285                "HELP should appear exactly once, got: {}",
2286                buffer
2287            );
2288            assert_eq!(
2289                buffer.matches("# TYPE consensus_votes").count(),
2290                1,
2291                "TYPE should appear exactly once, got: {}",
2292                buffer
2293            );
2294
2295            // Multiple attributes
2296            let ctx_multi = context
2297                .child("engine")
2298                .with_attribute("region", "us")
2299                .with_attribute("instance", "i1");
2300            let counter3 = Counter::<u64>::default();
2301            let _multi = ctx_multi.register("requests", "request count", counter3.clone());
2302            counter3.inc();
2303
2304            let buffer = context.encode();
2305            assert!(
2306                buffer.contains("engine_requests_total{instance=\"i1\",region=\"us\"} 1"),
2307                "Expected metric with sorted attributes, got: {}",
2308                buffer
2309            );
2310        });
2311    }
2312
2313    #[test]
2314    fn test_deterministic_metrics_with_attribute() {
2315        let executor = deterministic::Runner::default();
2316        test_metrics_with_attribute(executor);
2317    }
2318
2319    #[test]
2320    fn test_tokio_metrics_with_attribute() {
2321        let runner = tokio::Runner::default();
2322        test_metrics_with_attribute(runner);
2323    }
2324
2325    fn test_metrics_attribute_with_nested_label<R: Runner>(runner: R)
2326    where
2327        R::Context: Metrics,
2328    {
2329        runner.start(|context| async move {
2330            // Create context with attribute, then nest a label
2331            let ctx = context
2332                .child("orchestrator")
2333                .with_attribute("epoch", "e5")
2334                .child("engine");
2335
2336            // Register a metric
2337            let counter = Counter::<u64>::default();
2338            let _registered = ctx.register("votes", "vote count", counter.clone());
2339            counter.inc();
2340
2341            // Verify the attribute is preserved through the nested label
2342            let buffer = context.encode();
2343            assert!(
2344                buffer.contains("orchestrator_engine_votes_total{epoch=\"e5\"} 1"),
2345                "Expected metric with preserved epoch attribute, got: {}",
2346                buffer
2347            );
2348
2349            // Multiple levels of nesting with attributes at different levels
2350            let ctx2 = context
2351                .child("outer")
2352                .with_attribute("region", "us")
2353                .child("middle")
2354                .with_attribute("az", "east")
2355                .child("inner");
2356
2357            let counter2 = Counter::<u64>::default();
2358            let _registered2 = ctx2.register("requests", "request count", counter2.clone());
2359            counter2.inc();
2360            counter2.inc();
2361
2362            let buffer = context.encode();
2363            assert!(
2364                buffer.contains("outer_middle_inner_requests_total{az=\"east\",region=\"us\"} 2"),
2365                "Expected metric with all attributes preserved and sorted, got: {}",
2366                buffer
2367            );
2368        });
2369    }
2370
2371    #[test]
2372    fn test_deterministic_metrics_attribute_with_nested_label() {
2373        let executor = deterministic::Runner::default();
2374        test_metrics_attribute_with_nested_label(executor);
2375    }
2376
2377    #[test]
2378    fn test_tokio_metrics_attribute_with_nested_label() {
2379        let runner = tokio::Runner::default();
2380        test_metrics_attribute_with_nested_label(runner);
2381    }
2382
2383    fn test_metrics_attributes_isolated_between_contexts<R: Runner>(runner: R)
2384    where
2385        R::Context: Metrics,
2386    {
2387        runner.start(|context| async move {
2388            // Create two separate sub-contexts, each with their own attribute
2389            let ctx_a = context.child("component_a").with_attribute("epoch", 1);
2390            let ctx_b = context.child("component_b").with_attribute("epoch", 2);
2391
2392            // Register metrics in ctx_a
2393            let c1 = Counter::<u64>::default();
2394            let _ctx_a_requests = ctx_a.register("requests", "help", c1);
2395
2396            // Register metrics in ctx_b
2397            let c2 = Counter::<u64>::default();
2398            let _ctx_b_requests = ctx_b.register("requests", "help", c2);
2399
2400            // Register another metric in ctx_a AFTER ctx_b was used
2401            let c3 = Counter::<u64>::default();
2402            let _ctx_a_errors = ctx_a.register("errors", "help", c3);
2403
2404            let output = context.encode();
2405
2406            // ctx_a metrics should only have epoch=1
2407            assert!(
2408                output.contains("component_a_requests_total{epoch=\"1\"} 0"),
2409                "ctx_a requests should have epoch=1: {output}"
2410            );
2411            assert!(
2412                output.contains("component_a_errors_total{epoch=\"1\"} 0"),
2413                "ctx_a errors should have epoch=1: {output}"
2414            );
2415            assert!(
2416                !output.contains("component_a_requests_total{epoch=\"2\"}"),
2417                "ctx_a requests should not have epoch=2: {output}"
2418            );
2419
2420            // ctx_b metrics should only have epoch=2
2421            assert!(
2422                output.contains("component_b_requests_total{epoch=\"2\"} 0"),
2423                "ctx_b should have epoch=2: {output}"
2424            );
2425            assert!(
2426                !output.contains("component_b_requests_total{epoch=\"1\"}"),
2427                "ctx_b should not have epoch=1: {output}"
2428            );
2429        });
2430    }
2431
2432    #[test]
2433    fn test_deterministic_metrics_attributes_isolated_between_contexts() {
2434        let executor = deterministic::Runner::default();
2435        test_metrics_attributes_isolated_between_contexts(executor);
2436    }
2437
2438    #[test]
2439    fn test_tokio_metrics_attributes_isolated_between_contexts() {
2440        let runner = tokio::Runner::default();
2441        test_metrics_attributes_isolated_between_contexts(runner);
2442    }
2443
2444    /// Regression test for https://github.com/commonwarexyz/monorepo/issues/3485.
2445    ///
2446    /// Verifies the documented guarantee that runtime task metrics ignore context
2447    /// attributes: spawning with a varying `with_attribute` (as the marshaled
2448    /// consensus code does for each round) must not create per-value entries in
2449    /// `runtime_tasks_spawned` / `runtime_tasks_running`.
2450    fn test_metrics_spawn_attribute_cardinality<R: Runner>(runner: R)
2451    where
2452        R::Context: Spawner + Metrics + Clock,
2453    {
2454        runner.start(|context| async move {
2455            const ROUNDS: u64 = 128;
2456
2457            let mut handles = Vec::with_capacity(ROUNDS as usize);
2458            for round in 0..ROUNDS {
2459                let handle = context
2460                    .child("deferred_verify")
2461                    .with_attribute("round", round)
2462                    .spawn(move |_| async move { round });
2463                handles.push(handle);
2464            }
2465            for (expected, handle) in handles.into_iter().enumerate() {
2466                assert_eq!(handle.await.expect("task failed"), expected as u64);
2467            }
2468
2469            // handle.await resolves when the task's output is ready, but
2470            // the running-gauge decrement fires on task-struct drop which
2471            // may lag slightly. Yield to the executor so it can run cleanup.
2472            while count_running_tasks(&context, "deferred_verify") > 0 {
2473                context.sleep(Duration::from_millis(10)).await;
2474            }
2475            let buffer = context.encode();
2476
2477            // Count occurrences of each runtime task metric for our label. If
2478            // attributes were incorrectly folded into the task family key, we
2479            // would see ROUNDS distinct time series instead of one.
2480            let spawned_lines = buffer
2481                .lines()
2482                .filter(|line| {
2483                    line.starts_with("runtime_tasks_spawned_total{")
2484                        && line.contains("name=\"deferred_verify\"")
2485                })
2486                .count();
2487            let running_lines = buffer
2488                .lines()
2489                .filter(|line| {
2490                    line.starts_with("runtime_tasks_running{")
2491                        && line.contains("name=\"deferred_verify\"")
2492                })
2493                .count();
2494            assert_eq!(
2495                spawned_lines, 1,
2496                "expected exactly 1 runtime_tasks_spawned entry for deferred_verify, got {spawned_lines}: {buffer}",
2497            );
2498            assert_eq!(
2499                running_lines, 1,
2500                "expected exactly 1 runtime_tasks_running entry for deferred_verify, got {running_lines}: {buffer}",
2501            );
2502
2503            // The single spawned-counter entry should reflect every round.
2504            let spawned_value = format!(
2505                "runtime_tasks_spawned_total{{name=\"deferred_verify\",kind=\"Task\",execution=\"Shared\"}} {ROUNDS}"
2506            );
2507            assert!(
2508                buffer.contains(&spawned_value),
2509                "expected accumulated spawned counter `{spawned_value}`, got: {buffer}",
2510            );
2511            let running_value = "runtime_tasks_running{name=\"deferred_verify\",kind=\"Task\",execution=\"Shared\"} 0";
2512            assert!(
2513                buffer.contains(running_value),
2514                "expected running gauge to return to 0, got: {buffer}",
2515            );
2516
2517            // The per-round attribute must not surface on task metrics (the
2518            // task `Label` does not include context attributes).
2519            assert!(
2520                !buffer
2521                    .lines()
2522                    .any(|line| line.starts_with("runtime_tasks_")
2523                        && line.contains("round=")),
2524                "task metrics must not carry `round` attribute: {buffer}",
2525            );
2526        });
2527    }
2528
2529    #[test]
2530    fn test_deterministic_metrics_spawn_attribute_cardinality() {
2531        let executor = deterministic::Runner::default();
2532        test_metrics_spawn_attribute_cardinality(executor);
2533    }
2534
2535    #[test]
2536    fn test_tokio_metrics_spawn_attribute_cardinality() {
2537        let runner = tokio::Runner::default();
2538        test_metrics_spawn_attribute_cardinality(runner);
2539    }
2540
2541    fn test_metrics_attributes_sorted_deterministically<R: Runner>(runner: R)
2542    where
2543        R::Context: Metrics,
2544    {
2545        runner.start(|context| async move {
2546            // Create two contexts with same attributes but different order
2547            let ctx_ab = context
2548                .child("service")
2549                .with_attribute("region", "us")
2550                .with_attribute("env", "prod");
2551
2552            let ctx_ba = context
2553                .child("service")
2554                .with_attribute("env", "prod")
2555                .with_attribute("region", "us");
2556
2557            // Register via first context
2558            let c1 = Counter::<u64>::default();
2559            let _requests = ctx_ab.register("requests", "help", c1.clone());
2560            c1.inc();
2561
2562            // Register via second context - same attributes, different metric
2563            let c2 = Counter::<u64>::default();
2564            let _errors = ctx_ba.register("errors", "help", c2.clone());
2565            c2.inc();
2566            c2.inc();
2567
2568            let output = context.encode();
2569
2570            // Both should have the same label order (alphabetically sorted: env, region)
2571            assert!(
2572                output.contains("service_requests_total{env=\"prod\",region=\"us\"} 1"),
2573                "requests should have sorted labels: {output}"
2574            );
2575            assert!(
2576                output.contains("service_errors_total{env=\"prod\",region=\"us\"} 2"),
2577                "errors should have sorted labels: {output}"
2578            );
2579
2580            // Should NOT have reverse order
2581            assert!(
2582                !output.contains("region=\"us\",env=\"prod\""),
2583                "should not have unsorted label order: {output}"
2584            );
2585        });
2586    }
2587
2588    #[test]
2589    fn test_deterministic_metrics_attributes_sorted_deterministically() {
2590        let executor = deterministic::Runner::default();
2591        test_metrics_attributes_sorted_deterministically(executor);
2592    }
2593
2594    #[test]
2595    fn test_tokio_metrics_attributes_sorted_deterministically() {
2596        let runner = tokio::Runner::default();
2597        test_metrics_attributes_sorted_deterministically(runner);
2598    }
2599
2600    fn test_metrics_nested_labels_with_attributes<R: Runner>(runner: R)
2601    where
2602        R::Context: Metrics,
2603    {
2604        runner.start(|context| async move {
2605            // Service A: plain, no nested labels
2606            let svc_a = context.child("service_a");
2607
2608            // Service A with attribute (same top-level label, different context)
2609            let svc_a_v2 = context.child("service_a").with_attribute("version", 2);
2610
2611            // Service B with nested label: service_b_worker
2612            let svc_b_worker = context.child("service_b").child("worker");
2613
2614            // Service B with nested label AND attribute
2615            let svc_b_worker_shard = context
2616                .child("service_b")
2617                .child("worker")
2618                .with_attribute("shard", 99);
2619
2620            // Service B different nested label: service_b_manager
2621            let svc_b_manager = context.child("service_b").child("manager");
2622
2623            // Service C: plain, proves no cross-service contamination
2624            let svc_c = context.child("service_c");
2625
2626            // Register metrics in all contexts
2627            let c1 = Counter::<u64>::default();
2628            let _svc_a = svc_a.register("requests", "help", c1);
2629
2630            let c2 = Counter::<u64>::default();
2631            let _svc_a_v2 = svc_a_v2.register("requests", "help", c2);
2632
2633            let c3 = Counter::<u64>::default();
2634            let _svc_b_worker = svc_b_worker.register("tasks", "help", c3);
2635
2636            let c4 = Counter::<u64>::default();
2637            let _svc_b_worker_shard = svc_b_worker_shard.register("tasks", "help", c4);
2638
2639            let c5 = Counter::<u64>::default();
2640            let _svc_b_manager = svc_b_manager.register("decisions", "help", c5);
2641
2642            let c6 = Counter::<u64>::default();
2643            let _svc_c = svc_c.register("requests", "help", c6);
2644
2645            let output = context.encode();
2646
2647            // Service A plain and attributed both exist independently
2648            assert!(
2649                output.contains("service_a_requests_total 0"),
2650                "svc_a plain should exist: {output}"
2651            );
2652            assert!(
2653                output.contains("service_a_requests_total{version=\"2\"} 0"),
2654                "svc_a_v2 should have version=2: {output}"
2655            );
2656
2657            // Service B worker: plain and attributed versions
2658            assert!(
2659                output.contains("service_b_worker_tasks_total 0"),
2660                "svc_b_worker plain should exist: {output}"
2661            );
2662            assert!(
2663                output.contains("service_b_worker_tasks_total{shard=\"99\"} 0"),
2664                "svc_b_worker_shard should have shard=99: {output}"
2665            );
2666
2667            // Service B manager: no attributes
2668            assert!(
2669                output.contains("service_b_manager_decisions_total 0"),
2670                "svc_b_manager should have no attributes: {output}"
2671            );
2672            assert!(
2673                !output.contains("service_b_manager_decisions_total{"),
2674                "svc_b_manager should have no attributes at all: {output}"
2675            );
2676
2677            // Service C: no attributes, no contamination
2678            assert!(
2679                output.contains("service_c_requests_total 0"),
2680                "svc_c should have no attributes: {output}"
2681            );
2682            assert!(
2683                !output.contains("service_c_requests_total{"),
2684                "svc_c should have no attributes at all: {output}"
2685            );
2686
2687            // Cross-contamination checks
2688            assert!(
2689                !output.contains("service_b_manager_decisions_total{shard="),
2690                "svc_b_manager should not have shard: {output}"
2691            );
2692            assert!(
2693                !output.contains("service_a_requests_total{shard="),
2694                "svc_a should not have shard: {output}"
2695            );
2696            assert!(
2697                !output.contains("service_c_requests_total{version="),
2698                "svc_c should not have version: {output}"
2699            );
2700        });
2701    }
2702
2703    #[test]
2704    fn test_deterministic_metrics_nested_labels_with_attributes() {
2705        let executor = deterministic::Runner::default();
2706        test_metrics_nested_labels_with_attributes(executor);
2707    }
2708
2709    #[test]
2710    fn test_tokio_metrics_nested_labels_with_attributes() {
2711        let runner = tokio::Runner::default();
2712        test_metrics_nested_labels_with_attributes(runner);
2713    }
2714
2715    fn test_metrics_family_with_attributes<R: Runner>(runner: R)
2716    where
2717        R::Context: Metrics,
2718    {
2719        runner.start(|context| async move {
2720            #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
2721            struct RequestLabels {
2722                method: String,
2723                status: u16,
2724            }
2725
2726            // Create context with attribute
2727            let ctx = context
2728                .child("api")
2729                .with_attribute("region", "us_east")
2730                .with_attribute("env", "prod");
2731
2732            // Register a Family metric
2733            let requests: Family<RequestLabels, Counter<u64>> = Family::default();
2734            let _requests = ctx.register("requests", "HTTP requests", requests.clone());
2735
2736            // Increment counters for different label combinations
2737            requests
2738                .get_or_create(&RequestLabels {
2739                    method: "GET".to_string(),
2740                    status: 200,
2741                })
2742                .inc();
2743            requests
2744                .get_or_create(&RequestLabels {
2745                    method: "POST".to_string(),
2746                    status: 201,
2747                })
2748                .inc();
2749            requests
2750                .get_or_create(&RequestLabels {
2751                    method: "GET".to_string(),
2752                    status: 404,
2753                })
2754                .inc();
2755
2756            let output = context.encode();
2757
2758            // Context attributes appear first (alphabetically sorted), then Family labels
2759            // Context attributes: env="prod", region="us_east"
2760            // Family labels: method, status
2761            assert!(
2762                output.contains(
2763                    "api_requests_total{env=\"prod\",region=\"us_east\",method=\"GET\",status=\"200\"} 1"
2764                ),
2765                "GET 200 should have merged labels: {output}"
2766            );
2767            assert!(
2768                output.contains(
2769                    "api_requests_total{env=\"prod\",region=\"us_east\",method=\"POST\",status=\"201\"} 1"
2770                ),
2771                "POST 201 should have merged labels: {output}"
2772            );
2773            assert!(
2774                output.contains(
2775                    "api_requests_total{env=\"prod\",region=\"us_east\",method=\"GET\",status=\"404\"} 1"
2776                ),
2777                "GET 404 should have merged labels: {output}"
2778            );
2779
2780            // Create another context WITHOUT attributes to verify isolation
2781            let ctx_plain = context.child("api_plain");
2782            let plain_requests: Family<RequestLabels, Counter<u64>> = Family::default();
2783            let _plain_requests =
2784                ctx_plain.register("requests", "HTTP requests", plain_requests.clone());
2785
2786            plain_requests
2787                .get_or_create(&RequestLabels {
2788                    method: "DELETE".to_string(),
2789                    status: 204,
2790                })
2791                .inc();
2792
2793            let output = context.encode();
2794
2795            // Plain context should have Family labels but no context attributes
2796            assert!(
2797                output.contains("api_plain_requests_total{method=\"DELETE\",status=\"204\"} 1"),
2798                "plain DELETE should have only family labels: {output}"
2799            );
2800            assert!(
2801                !output.contains("api_plain_requests_total{env="),
2802                "plain should not have env attribute: {output}"
2803            );
2804            assert!(
2805                !output.contains("api_plain_requests_total{region="),
2806                "plain should not have region attribute: {output}"
2807            );
2808        });
2809    }
2810
2811    #[test]
2812    fn test_deterministic_metrics_family_with_attributes() {
2813        let executor = deterministic::Runner::default();
2814        test_metrics_family_with_attributes(executor);
2815    }
2816
2817    #[test]
2818    fn test_tokio_metrics_family_with_attributes() {
2819        let runner = tokio::Runner::default();
2820        test_metrics_family_with_attributes(runner);
2821    }
2822
2823    fn test_register_and_encode<R: Runner>(runner: R)
2824    where
2825        R::Context: Metrics,
2826    {
2827        runner.start(|context| async move {
2828            let counter =
2829                context
2830                    .child("engine")
2831                    .register("votes", "vote count", Counter::<u64>::default());
2832            counter.inc();
2833
2834            let buffer = context.encode();
2835            assert!(
2836                buffer.contains("engine_votes_total 1"),
2837                "registered metric should appear in encode: {buffer}"
2838            );
2839        });
2840    }
2841
2842    #[test]
2843    fn test_deterministic_register_and_encode() {
2844        let executor = deterministic::Runner::default();
2845        test_register_and_encode(executor);
2846    }
2847
2848    #[test]
2849    fn test_tokio_register_and_encode() {
2850        let runner = tokio::Runner::default();
2851        test_register_and_encode(runner);
2852    }
2853
2854    fn test_register_drop_removes_metrics<R: Runner>(runner: R)
2855    where
2856        R::Context: Metrics,
2857    {
2858        runner.start(|context| async move {
2859            let permanent = context.child("permanent").register(
2860                "counter",
2861                "permanent counter",
2862                Counter::<u64>::default(),
2863            );
2864            permanent.inc();
2865
2866            let counter =
2867                context
2868                    .child("engine")
2869                    .register("votes", "vote count", Counter::<u64>::default());
2870            counter.inc();
2871
2872            let buffer = context.encode();
2873            assert!(buffer.contains("permanent_counter_total 1"));
2874            assert!(buffer.contains("engine_votes_total 1"));
2875
2876            drop(counter);
2877
2878            let buffer = context.encode();
2879            assert!(
2880                buffer.contains("permanent_counter_total 1"),
2881                "other registered metrics should survive handle drop: {buffer}"
2882            );
2883            assert!(
2884                !buffer.contains("engine_votes"),
2885                "metric should be removed after handle drop: {buffer}"
2886            );
2887        });
2888    }
2889
2890    #[test]
2891    fn test_deterministic_register_drop_removes_metrics() {
2892        let executor = deterministic::Runner::default();
2893        test_register_drop_removes_metrics(executor);
2894    }
2895
2896    #[test]
2897    fn test_tokio_register_drop_removes_metrics() {
2898        let runner = tokio::Runner::default();
2899        test_register_drop_removes_metrics(runner);
2900    }
2901
2902    fn test_register_with_attributes<R: Runner>(runner: R)
2903    where
2904        R::Context: Metrics,
2905    {
2906        runner.start(|context| async move {
2907            let epoch1 = context.child("engine").with_attribute("epoch", 1).register(
2908                "votes",
2909                "vote count",
2910                Counter::<u64>::default(),
2911            );
2912            epoch1.inc();
2913
2914            let epoch2 = context.child("engine").with_attribute("epoch", 2).register(
2915                "votes",
2916                "vote count",
2917                Counter::<u64>::default(),
2918            );
2919            epoch2.inc();
2920            epoch2.inc();
2921
2922            let buffer = context.encode();
2923            assert!(buffer.contains("engine_votes_total{epoch=\"1\"} 1"));
2924            assert!(buffer.contains("engine_votes_total{epoch=\"2\"} 2"));
2925
2926            assert_eq!(
2927                buffer.matches("# HELP engine_votes").count(),
2928                1,
2929                "HELP should appear once: {buffer}"
2930            );
2931            assert_eq!(
2932                buffer.matches("# TYPE engine_votes").count(),
2933                1,
2934                "TYPE should appear once: {buffer}"
2935            );
2936
2937            drop(epoch1);
2938            let buffer = context.encode();
2939            assert!(
2940                !buffer.contains("epoch=\"1\""),
2941                "epoch 1 should be gone: {buffer}"
2942            );
2943            assert!(buffer.contains("engine_votes_total{epoch=\"2\"} 2"));
2944
2945            drop(epoch2);
2946            let buffer = context.encode();
2947            assert!(
2948                !buffer.contains("engine_votes"),
2949                "all epoch metrics should be gone: {buffer}"
2950            );
2951        });
2952    }
2953
2954    #[test]
2955    fn test_deterministic_register_with_attributes() {
2956        let executor = deterministic::Runner::default();
2957        test_register_with_attributes(executor);
2958    }
2959
2960    #[test]
2961    fn test_tokio_register_with_attributes() {
2962        let runner = tokio::Runner::default();
2963        test_register_with_attributes(runner);
2964    }
2965
2966    fn test_reregister_after_drop<R: Runner>(runner: R)
2967    where
2968        R::Context: Metrics,
2969    {
2970        runner.start(|context| async move {
2971            let votes = context.child("engine").with_attribute("epoch", 1).register(
2972                "votes",
2973                "vote count",
2974                Counter::<u64>::default(),
2975            );
2976            drop(votes);
2977
2978            let replacement = context.child("engine").with_attribute("epoch", 1).register(
2979                "votes",
2980                "vote count",
2981                Counter::<u64>::default(),
2982            );
2983            drop(replacement);
2984        });
2985    }
2986
2987    #[test]
2988    fn test_deterministic_reregister_after_drop() {
2989        let executor = deterministic::Runner::default();
2990        test_reregister_after_drop(executor);
2991    }
2992
2993    #[test]
2994    fn test_tokio_reregister_after_drop() {
2995        let runner = tokio::Runner::default();
2996        test_reregister_after_drop(runner);
2997    }
2998
2999    fn test_register_clone_keeps_metric_alive<R: Runner>(runner: R)
3000    where
3001        R::Context: Metrics,
3002    {
3003        runner.start(|context| async move {
3004            let registered =
3005                context
3006                    .child("engine")
3007                    .register("votes", "vote count", Counter::<u64>::default());
3008            registered.inc();
3009            let clone = registered.clone();
3010
3011            let buffer = context.encode();
3012            assert!(
3013                buffer.contains("engine_votes_total 1"),
3014                "metric should remain registered while any handle exists: {buffer}"
3015            );
3016
3017            drop(registered);
3018            let buffer = context.encode();
3019            assert!(
3020                buffer.contains("engine_votes_total 1"),
3021                "metric should survive while clone is retained: {buffer}"
3022            );
3023
3024            drop(clone);
3025            let buffer = context.encode();
3026            assert!(
3027                !buffer.contains("engine_votes"),
3028                "metric should be removed when all handle clones are dropped: {buffer}"
3029            );
3030        });
3031    }
3032
3033    #[test]
3034    fn test_deterministic_register_clone_keeps_metric_alive() {
3035        let executor = deterministic::Runner::default();
3036        test_register_clone_keeps_metric_alive(executor);
3037    }
3038
3039    #[test]
3040    fn test_tokio_register_clone_keeps_metric_alive() {
3041        let runner = tokio::Runner::default();
3042        test_register_clone_keeps_metric_alive(runner);
3043    }
3044
3045    fn test_encode_single_eof<R: Runner>(runner: R)
3046    where
3047        R::Context: Metrics,
3048    {
3049        runner.start(|context| async move {
3050            let root_counter = context.register("root", "root metric", Counter::<u64>::default());
3051            root_counter.inc();
3052
3053            let child =
3054                context
3055                    .child("engine")
3056                    .register("ops", "child metric", Counter::<u64>::default());
3057            child.inc();
3058
3059            let buffer = context.encode();
3060            assert!(
3061                buffer.contains("root_total 1"),
3062                "root metric missing: {buffer}"
3063            );
3064            assert!(
3065                buffer.contains("engine_ops_total 1"),
3066                "child metric missing: {buffer}"
3067            );
3068            assert_eq!(
3069                buffer.matches("# EOF").count(),
3070                1,
3071                "expected exactly one EOF marker: {buffer}"
3072            );
3073            assert!(
3074                buffer.ends_with("# EOF\n"),
3075                "EOF must be the last line: {buffer}"
3076            );
3077        });
3078    }
3079
3080    #[test]
3081    fn test_deterministic_encode_single_eof() {
3082        let executor = deterministic::Runner::default();
3083        test_encode_single_eof(executor);
3084    }
3085
3086    #[test]
3087    fn test_tokio_encode_single_eof() {
3088        let runner = tokio::Runner::default();
3089        test_encode_single_eof(runner);
3090    }
3091
3092    fn test_family_with_attributes<R: Runner>(runner: R)
3093    where
3094        R::Context: Metrics,
3095    {
3096        #[derive(Clone, Debug, Hash, PartialEq, Eq)]
3097        struct Peer {
3098            name: String,
3099        }
3100        impl EncodeLabelSet for Peer {
3101            fn encode(&self, encoder: &mut LabelSetEncoder<'_>) -> Result<(), std::fmt::Error> {
3102                let mut label = encoder.encode_label();
3103                let mut key = label.encode_label_key()?;
3104                EncodeLabelKey::encode(&"peer", &mut key)?;
3105                let mut value = key.encode_label_value()?;
3106                EncodeLabelValue::encode(&self.name.as_str(), &mut value)?;
3107                value.finish()
3108            }
3109        }
3110
3111        runner.start(|context| async move {
3112            let family = context
3113                .child("batcher")
3114                .with_attribute("epoch", 1)
3115                .register(
3116                    "votes",
3117                    "votes per peer",
3118                    Family::<Peer, Counter>::default(),
3119                );
3120            family
3121                .get_or_create(&Peer {
3122                    name: "alice".into(),
3123                })
3124                .inc();
3125            family.get_or_create(&Peer { name: "bob".into() }).inc();
3126
3127            let buffer = context.encode();
3128            assert!(
3129                buffer.contains("batcher_votes_total{epoch=\"1\",peer=\"alice\"} 1"),
3130                "family with attributes should combine labels: {buffer}"
3131            );
3132            assert!(
3133                buffer.contains("batcher_votes_total{epoch=\"1\",peer=\"bob\"} 1"),
3134                "family with attributes should combine labels: {buffer}"
3135            );
3136
3137            drop(family);
3138            let buffer = context.encode();
3139            assert!(
3140                !buffer.contains("batcher_votes"),
3141                "family metrics should be removed: {buffer}"
3142            );
3143        });
3144    }
3145
3146    #[test]
3147    fn test_deterministic_family_with_attributes() {
3148        let executor = deterministic::Runner::default();
3149        test_family_with_attributes(executor);
3150    }
3151
3152    #[test]
3153    fn test_tokio_family_with_attributes() {
3154        let runner = tokio::Runner::default();
3155        test_family_with_attributes(runner);
3156    }
3157
3158    #[test]
3159    fn test_deterministic_future() {
3160        let runner = deterministic::Runner::default();
3161        test_error_future(runner);
3162    }
3163
3164    #[test]
3165    fn test_deterministic_clock_sleep() {
3166        let executor = deterministic::Runner::default();
3167        test_clock_sleep(executor);
3168    }
3169
3170    #[test]
3171    fn test_deterministic_clock_sleep_until() {
3172        let executor = deterministic::Runner::default();
3173        test_clock_sleep_until(executor);
3174    }
3175
3176    #[test]
3177    fn test_deterministic_clock_sleep_until_far_future() {
3178        let executor = deterministic::Runner::default();
3179        test_clock_sleep_until_far_future(executor);
3180    }
3181
3182    #[test]
3183    fn test_deterministic_clock_timeout() {
3184        let executor = deterministic::Runner::default();
3185        test_clock_timeout(executor);
3186    }
3187
3188    #[test]
3189    fn test_deterministic_root_finishes() {
3190        let executor = deterministic::Runner::default();
3191        test_root_finishes(executor);
3192    }
3193
3194    #[test]
3195    fn test_deterministic_spawn_after_abort() {
3196        let executor = deterministic::Runner::default();
3197        test_spawn_after_abort(executor);
3198    }
3199
3200    #[test]
3201    fn test_deterministic_spawn_abort() {
3202        let executor = deterministic::Runner::default();
3203        test_spawn_abort(executor, false, false);
3204    }
3205
3206    #[test]
3207    #[should_panic(expected = "blah")]
3208    fn test_deterministic_panic_aborts_root() {
3209        let runner = deterministic::Runner::default();
3210        test_panic_aborts_root(runner);
3211    }
3212
3213    #[test]
3214    #[should_panic(expected = "blah")]
3215    fn test_deterministic_panic_aborts_root_caught() {
3216        let cfg = deterministic::Config::default().with_catch_panics(true);
3217        let runner = deterministic::Runner::new(cfg);
3218        test_panic_aborts_root(runner);
3219    }
3220
3221    #[test]
3222    #[should_panic(expected = "blah")]
3223    fn test_deterministic_panic_aborts_spawn() {
3224        let executor = deterministic::Runner::default();
3225        test_panic_aborts_spawn(executor);
3226    }
3227
3228    #[test]
3229    fn test_deterministic_panic_aborts_spawn_caught() {
3230        let cfg = deterministic::Config::default().with_catch_panics(true);
3231        let executor = deterministic::Runner::new(cfg);
3232        test_panic_aborts_spawn_caught(executor);
3233    }
3234
3235    #[test]
3236    #[should_panic(expected = "boom")]
3237    fn test_deterministic_multiple_panics() {
3238        let executor = deterministic::Runner::default();
3239        test_multiple_panics(executor);
3240    }
3241
3242    #[test]
3243    fn test_deterministic_multiple_panics_caught() {
3244        let cfg = deterministic::Config::default().with_catch_panics(true);
3245        let executor = deterministic::Runner::new(cfg);
3246        test_multiple_panics_caught(executor);
3247    }
3248
3249    #[test]
3250    fn test_deterministic_select() {
3251        let executor = deterministic::Runner::default();
3252        test_select(executor);
3253    }
3254
3255    #[test]
3256    fn test_deterministic_select_loop() {
3257        let executor = deterministic::Runner::default();
3258        test_select_loop(executor);
3259    }
3260
3261    #[test]
3262    fn test_deterministic_storage_operations() {
3263        let executor = deterministic::Runner::default();
3264        test_storage_operations(executor);
3265    }
3266
3267    #[test]
3268    fn test_deterministic_blob_read_write() {
3269        let executor = deterministic::Runner::default();
3270        test_blob_read_write(executor);
3271    }
3272
3273    #[test]
3274    fn test_deterministic_blob_resize() {
3275        let executor = deterministic::Runner::default();
3276        test_blob_resize(executor);
3277    }
3278
3279    #[test]
3280    fn test_deterministic_many_partition_read_write() {
3281        let executor = deterministic::Runner::default();
3282        test_many_partition_read_write(executor);
3283    }
3284
3285    #[test]
3286    fn test_deterministic_blob_read_past_length() {
3287        let executor = deterministic::Runner::default();
3288        test_blob_read_past_length(executor);
3289    }
3290
3291    #[test]
3292    fn test_deterministic_blob_clone_and_concurrent_read() {
3293        // Run test
3294        let executor = deterministic::Runner::default();
3295        test_blob_clone_and_concurrent_read(executor);
3296    }
3297
3298    #[test]
3299    fn test_deterministic_shutdown() {
3300        let executor = deterministic::Runner::default();
3301        test_shutdown(executor);
3302    }
3303
3304    #[test]
3305    fn test_deterministic_shutdown_multiple_signals() {
3306        let executor = deterministic::Runner::default();
3307        test_shutdown_multiple_signals(executor);
3308    }
3309
3310    #[test]
3311    fn test_deterministic_shutdown_timeout() {
3312        let executor = deterministic::Runner::default();
3313        test_shutdown_timeout(executor);
3314    }
3315
3316    #[test]
3317    fn test_deterministic_shutdown_multiple_stop_calls() {
3318        let executor = deterministic::Runner::default();
3319        test_shutdown_multiple_stop_calls(executor);
3320    }
3321
3322    #[test]
3323    fn test_deterministic_unfulfilled_shutdown() {
3324        let executor = deterministic::Runner::default();
3325        test_unfulfilled_shutdown(executor);
3326    }
3327
3328    #[test]
3329    fn test_deterministic_spawn_dedicated() {
3330        let executor = deterministic::Runner::default();
3331        test_spawn_dedicated(executor);
3332    }
3333
3334    #[test]
3335    fn test_deterministic_spawn() {
3336        let runner = deterministic::Runner::default();
3337        test_spawn(runner);
3338    }
3339
3340    #[test]
3341    fn test_deterministic_spawn_abort_on_parent_abort() {
3342        let runner = deterministic::Runner::default();
3343        test_spawn_abort_on_parent_abort(runner);
3344    }
3345
3346    #[test]
3347    fn test_deterministic_spawn_abort_on_parent_completion() {
3348        let runner = deterministic::Runner::default();
3349        test_spawn_abort_on_parent_completion(runner);
3350    }
3351
3352    #[test]
3353    fn test_deterministic_spawn_cascading_abort() {
3354        let runner = deterministic::Runner::default();
3355        test_spawn_cascading_abort(runner);
3356    }
3357
3358    #[test]
3359    fn test_deterministic_child_survives_sibling_completion() {
3360        let runner = deterministic::Runner::default();
3361        test_child_survives_sibling_completion(runner);
3362    }
3363
3364    #[test]
3365    fn test_deterministic_spawn_clone_chain() {
3366        let runner = deterministic::Runner::default();
3367        test_spawn_clone_chain(runner);
3368    }
3369
3370    #[test]
3371    fn test_deterministic_spawn_sparse_clone_chain() {
3372        let runner = deterministic::Runner::default();
3373        test_spawn_sparse_clone_chain(runner);
3374    }
3375
3376    #[test]
3377    fn test_deterministic_spawn_blocking() {
3378        for dedicated in [false, true] {
3379            let executor = deterministic::Runner::default();
3380            test_spawn_blocking(executor, dedicated);
3381        }
3382    }
3383
3384    #[test]
3385    #[should_panic(expected = "blocking task panicked")]
3386    fn test_deterministic_spawn_blocking_panic() {
3387        for dedicated in [false, true] {
3388            let executor = deterministic::Runner::default();
3389            test_spawn_blocking_panic(executor, dedicated);
3390        }
3391    }
3392
3393    #[test]
3394    fn test_deterministic_spawn_blocking_panic_caught() {
3395        for dedicated in [false, true] {
3396            let cfg = deterministic::Config::default().with_catch_panics(true);
3397            let executor = deterministic::Runner::new(cfg);
3398            test_spawn_blocking_panic_caught(executor, dedicated);
3399        }
3400    }
3401
3402    #[test]
3403    fn test_deterministic_spawn_blocking_abort() {
3404        for (dedicated, blocking) in [(false, true), (true, false)] {
3405            let executor = deterministic::Runner::default();
3406            test_spawn_abort(executor, dedicated, blocking);
3407        }
3408    }
3409
3410    #[test]
3411    fn test_deterministic_circular_reference_prevents_cleanup() {
3412        let executor = deterministic::Runner::default();
3413        test_circular_reference_prevents_cleanup(executor);
3414    }
3415
3416    #[test]
3417    fn test_deterministic_late_waker() {
3418        let executor = deterministic::Runner::default();
3419        test_late_waker(executor);
3420    }
3421
3422    #[test]
3423    fn test_deterministic_metrics() {
3424        let executor = deterministic::Runner::default();
3425        test_metrics(executor);
3426    }
3427
3428    #[test]
3429    fn test_deterministic_resolver() {
3430        let executor = deterministic::Runner::default();
3431        executor.start(|context| async move {
3432            // Register DNS mappings
3433            let ip1: IpAddr = "192.168.1.1".parse().unwrap();
3434            let ip2: IpAddr = "192.168.1.2".parse().unwrap();
3435            context.resolver_register("example.com", Some(vec![ip1, ip2]));
3436
3437            // Resolve registered hostname
3438            let addrs = context.resolve("example.com").await.unwrap();
3439            assert_eq!(addrs, vec![ip1, ip2]);
3440
3441            // Resolve unregistered hostname
3442            let result = context.resolve("unknown.com").await;
3443            assert!(matches!(result, Err(Error::ResolveFailed(_))));
3444
3445            // Remove mapping
3446            context.resolver_register("example.com", None);
3447            let result = context.resolve("example.com").await;
3448            assert!(matches!(result, Err(Error::ResolveFailed(_))));
3449        });
3450    }
3451
3452    #[test]
3453    fn test_tokio_error_future() {
3454        let runner = tokio::Runner::default();
3455        test_error_future(runner);
3456    }
3457
3458    #[test]
3459    fn test_tokio_clock_sleep() {
3460        let executor = tokio::Runner::default();
3461        test_clock_sleep(executor);
3462    }
3463
3464    #[test]
3465    fn test_tokio_clock_sleep_until() {
3466        let executor = tokio::Runner::default();
3467        test_clock_sleep_until(executor);
3468    }
3469
3470    #[test]
3471    fn test_tokio_clock_sleep_until_far_future() {
3472        let executor = tokio::Runner::default();
3473        test_clock_sleep_until_far_future(executor);
3474    }
3475
3476    #[test]
3477    fn test_tokio_clock_timeout() {
3478        let executor = tokio::Runner::default();
3479        test_clock_timeout(executor);
3480    }
3481
3482    #[test]
3483    fn test_tokio_root_finishes() {
3484        let executor = tokio::Runner::default();
3485        test_root_finishes(executor);
3486    }
3487
3488    #[test]
3489    fn test_tokio_spawn_after_abort() {
3490        let executor = tokio::Runner::default();
3491        test_spawn_after_abort(executor);
3492    }
3493
3494    #[test]
3495    fn test_tokio_spawn_abort() {
3496        let executor = tokio::Runner::default();
3497        test_spawn_abort(executor, false, false);
3498    }
3499
3500    #[test]
3501    #[should_panic(expected = "blah")]
3502    fn test_tokio_panic_aborts_root() {
3503        let executor = tokio::Runner::default();
3504        test_panic_aborts_root(executor);
3505    }
3506
3507    #[test]
3508    #[should_panic(expected = "blah")]
3509    fn test_tokio_panic_aborts_root_caught() {
3510        let cfg = tokio::Config::default().with_catch_panics(true);
3511        let executor = tokio::Runner::new(cfg);
3512        test_panic_aborts_root(executor);
3513    }
3514
3515    #[test]
3516    #[should_panic(expected = "blah")]
3517    fn test_tokio_panic_aborts_spawn() {
3518        let executor = tokio::Runner::default();
3519        test_panic_aborts_spawn(executor);
3520    }
3521
3522    #[test]
3523    fn test_tokio_panic_aborts_spawn_caught() {
3524        let cfg = tokio::Config::default().with_catch_panics(true);
3525        let executor = tokio::Runner::new(cfg);
3526        test_panic_aborts_spawn_caught(executor);
3527    }
3528
3529    #[test]
3530    #[should_panic(expected = "boom")]
3531    fn test_tokio_multiple_panics() {
3532        let executor = tokio::Runner::default();
3533        test_multiple_panics(executor);
3534    }
3535
3536    #[test]
3537    fn test_tokio_multiple_panics_caught() {
3538        let cfg = tokio::Config::default().with_catch_panics(true);
3539        let executor = tokio::Runner::new(cfg);
3540        test_multiple_panics_caught(executor);
3541    }
3542
3543    #[test]
3544    fn test_tokio_select() {
3545        let executor = tokio::Runner::default();
3546        test_select(executor);
3547    }
3548
3549    #[test]
3550    fn test_tokio_select_loop() {
3551        let executor = tokio::Runner::default();
3552        test_select_loop(executor);
3553    }
3554
3555    #[test]
3556    fn test_tokio_storage_operations() {
3557        let executor = tokio::Runner::default();
3558        test_storage_operations(executor);
3559    }
3560
3561    #[test]
3562    fn test_tokio_blob_read_write() {
3563        let executor = tokio::Runner::default();
3564        test_blob_read_write(executor);
3565    }
3566
3567    #[test]
3568    fn test_tokio_blob_resize() {
3569        let executor = tokio::Runner::default();
3570        test_blob_resize(executor);
3571    }
3572
3573    #[test]
3574    fn test_tokio_many_partition_read_write() {
3575        let executor = tokio::Runner::default();
3576        test_many_partition_read_write(executor);
3577    }
3578
3579    #[test]
3580    fn test_tokio_blob_read_past_length() {
3581        let executor = tokio::Runner::default();
3582        test_blob_read_past_length(executor);
3583    }
3584
3585    #[test]
3586    fn test_tokio_blob_clone_and_concurrent_read() {
3587        // Run test
3588        let executor = tokio::Runner::default();
3589        test_blob_clone_and_concurrent_read(executor);
3590    }
3591
3592    #[test]
3593    fn test_tokio_shutdown() {
3594        let executor = tokio::Runner::default();
3595        test_shutdown(executor);
3596    }
3597
3598    #[test]
3599    fn test_tokio_shutdown_multiple_signals() {
3600        let executor = tokio::Runner::default();
3601        test_shutdown_multiple_signals(executor);
3602    }
3603
3604    #[test]
3605    fn test_tokio_shutdown_timeout() {
3606        let executor = tokio::Runner::default();
3607        test_shutdown_timeout(executor);
3608    }
3609
3610    #[test]
3611    fn test_tokio_shutdown_multiple_stop_calls() {
3612        let executor = tokio::Runner::default();
3613        test_shutdown_multiple_stop_calls(executor);
3614    }
3615
3616    #[test]
3617    fn test_tokio_unfulfilled_shutdown() {
3618        let executor = tokio::Runner::default();
3619        test_unfulfilled_shutdown(executor);
3620    }
3621
3622    #[test]
3623    fn test_tokio_spawn_dedicated() {
3624        let executor = tokio::Runner::default();
3625        test_spawn_dedicated(executor);
3626    }
3627
3628    #[test]
3629    fn test_tokio_spawn() {
3630        let runner = tokio::Runner::default();
3631        test_spawn(runner);
3632    }
3633
3634    #[test]
3635    fn test_tokio_spawn_abort_on_parent_abort() {
3636        let runner = tokio::Runner::default();
3637        test_spawn_abort_on_parent_abort(runner);
3638    }
3639
3640    #[test]
3641    fn test_tokio_spawn_abort_on_parent_completion() {
3642        let runner = tokio::Runner::default();
3643        test_spawn_abort_on_parent_completion(runner);
3644    }
3645
3646    #[test]
3647    fn test_tokio_spawn_cascading_abort() {
3648        let runner = tokio::Runner::default();
3649        test_spawn_cascading_abort(runner);
3650    }
3651
3652    #[test]
3653    fn test_tokio_child_survives_sibling_completion() {
3654        let runner = tokio::Runner::default();
3655        test_child_survives_sibling_completion(runner);
3656    }
3657
3658    #[test]
3659    fn test_tokio_spawn_clone_chain() {
3660        let runner = tokio::Runner::default();
3661        test_spawn_clone_chain(runner);
3662    }
3663
3664    #[test]
3665    fn test_tokio_spawn_sparse_clone_chain() {
3666        let runner = tokio::Runner::default();
3667        test_spawn_sparse_clone_chain(runner);
3668    }
3669
3670    #[test]
3671    fn test_tokio_spawn_blocking() {
3672        for dedicated in [false, true] {
3673            let executor = tokio::Runner::default();
3674            test_spawn_blocking(executor, dedicated);
3675        }
3676    }
3677
3678    #[test]
3679    #[should_panic(expected = "blocking task panicked")]
3680    fn test_tokio_spawn_blocking_panic() {
3681        for dedicated in [false, true] {
3682            let executor = tokio::Runner::default();
3683            test_spawn_blocking_panic(executor, dedicated);
3684        }
3685    }
3686
3687    #[test]
3688    fn test_tokio_spawn_blocking_panic_caught() {
3689        for dedicated in [false, true] {
3690            let cfg = tokio::Config::default().with_catch_panics(true);
3691            let executor = tokio::Runner::new(cfg);
3692            test_spawn_blocking_panic_caught(executor, dedicated);
3693        }
3694    }
3695
3696    #[test]
3697    fn test_tokio_spawn_blocking_abort() {
3698        for (dedicated, blocking) in [(false, true), (true, false)] {
3699            let executor = tokio::Runner::default();
3700            test_spawn_abort(executor, dedicated, blocking);
3701        }
3702    }
3703
3704    #[test]
3705    fn test_tokio_circular_reference_prevents_cleanup() {
3706        let executor = tokio::Runner::default();
3707        test_circular_reference_prevents_cleanup(executor);
3708    }
3709
3710    #[test]
3711    fn test_tokio_late_waker() {
3712        let executor = tokio::Runner::default();
3713        test_late_waker(executor);
3714    }
3715
3716    #[test]
3717    fn test_tokio_metrics() {
3718        let executor = tokio::Runner::default();
3719        test_metrics(executor);
3720    }
3721
3722    #[test]
3723    fn test_tokio_process_rss_metric() {
3724        let executor = tokio::Runner::default();
3725        executor.start(|context| async move {
3726            loop {
3727                // Wait for RSS metric to be available
3728                let metrics = context.encode();
3729                if !metrics.contains("runtime_process_rss") {
3730                    context.sleep(Duration::from_millis(100)).await;
3731                    continue;
3732                }
3733
3734                // Verify the RSS value is eventually populated (greater than 0)
3735                for line in metrics.lines() {
3736                    if line.starts_with("runtime_process_rss")
3737                        && !line.starts_with("runtime_process_rss{")
3738                    {
3739                        let parts: Vec<&str> = line.split_whitespace().collect();
3740                        if parts.len() >= 2 {
3741                            let rss_value: i64 =
3742                                parts[1].parse().expect("Failed to parse RSS value");
3743                            if rss_value > 0 {
3744                                return;
3745                            }
3746                        }
3747                    }
3748                }
3749            }
3750        });
3751    }
3752
3753    #[test]
3754    fn test_tokio_telemetry() {
3755        let executor = tokio::Runner::default();
3756        executor.start(|context| async move {
3757            // Define the server address
3758            let address = SocketAddr::from_str("127.0.0.1:8000").unwrap();
3759
3760            // Configure telemetry
3761            tokio::telemetry::init(
3762                context.child("metrics"),
3763                tokio::telemetry::Logs {
3764                    level: Level::INFO,
3765                    json: false,
3766                },
3767                Some(address),
3768                None,
3769            );
3770
3771            // Register a test metric
3772            let counter: Counter<u64> = Counter::default();
3773            let _registered = context.register("test_counter", "Test counter", counter.clone());
3774            counter.inc();
3775
3776            // Helper functions to parse HTTP response
3777            async fn read_line<St: Stream>(stream: &mut St) -> Result<String, Error> {
3778                let mut line = Vec::new();
3779                loop {
3780                    let received = stream.recv(1).await?;
3781                    let byte = received.coalesce().as_ref()[0];
3782                    if byte == b'\n' {
3783                        if line.last() == Some(&b'\r') {
3784                            line.pop(); // Remove trailing \r
3785                        }
3786                        break;
3787                    }
3788                    line.push(byte);
3789                }
3790                String::from_utf8(line).map_err(|_| Error::ReadFailed)
3791            }
3792
3793            async fn read_headers<St: Stream>(
3794                stream: &mut St,
3795            ) -> Result<HashMap<String, String>, Error> {
3796                let mut headers = HashMap::new();
3797                loop {
3798                    let line = read_line(stream).await?;
3799                    if line.is_empty() {
3800                        break;
3801                    }
3802                    let parts: Vec<&str> = line.splitn(2, ": ").collect();
3803                    if parts.len() == 2 {
3804                        headers.insert(parts[0].to_string(), parts[1].to_string());
3805                    }
3806                }
3807                Ok(headers)
3808            }
3809
3810            async fn read_body<St: Stream>(
3811                stream: &mut St,
3812                content_length: usize,
3813            ) -> Result<String, Error> {
3814                let received = stream.recv(content_length).await?;
3815                String::from_utf8(received.coalesce().into()).map_err(|_| Error::ReadFailed)
3816            }
3817
3818            // Simulate a client connecting to the server
3819            let client_handle = context.child("client").spawn(move |context| async move {
3820                let (mut sink, mut stream) = loop {
3821                    match context.dial(address).await {
3822                        Ok((sink, stream)) => break (sink, stream),
3823                        Err(e) => {
3824                            // The client may be polled before the server is ready, that's alright!
3825                            error!(err =?e, "failed to connect");
3826                            context.sleep(Duration::from_millis(10)).await;
3827                        }
3828                    }
3829                };
3830
3831                // Send a GET request to the server
3832                let request = format!(
3833                    "GET /metrics HTTP/1.1\r\nHost: {address}\r\nConnection: close\r\n\r\n"
3834                );
3835                sink.send(Bytes::from(request)).await.unwrap();
3836
3837                // Read and verify the HTTP status line
3838                let status_line = read_line(&mut stream).await.unwrap();
3839                assert_eq!(status_line, "HTTP/1.1 200 OK");
3840
3841                // Read and parse headers
3842                let headers = read_headers(&mut stream).await.unwrap();
3843                println!("Headers: {headers:?}");
3844                let content_length = headers
3845                    .get("content-length")
3846                    .unwrap()
3847                    .parse::<usize>()
3848                    .unwrap();
3849
3850                // Read and verify the body
3851                let body = read_body(&mut stream, content_length).await.unwrap();
3852                assert!(body.contains("test_counter_total 1"));
3853            });
3854
3855            // Wait for the client task to complete
3856            client_handle.await.unwrap();
3857        });
3858    }
3859
3860    #[test]
3861    fn test_tokio_resolver() {
3862        let executor = tokio::Runner::default();
3863        executor.start(|context| async move {
3864            let addrs = context.resolve("localhost").await.unwrap();
3865            assert!(!addrs.is_empty());
3866            for addr in addrs {
3867                assert!(
3868                    addr == IpAddr::V4(Ipv4Addr::LOCALHOST)
3869                        || addr == IpAddr::V6(Ipv6Addr::LOCALHOST)
3870                );
3871            }
3872        });
3873    }
3874
3875    #[test]
3876    fn test_strategy_tokio() {
3877        let executor = tokio::Runner::default();
3878        executor.start(|context| async move {
3879            // Create a strategy backed by a pool with 4 threads.
3880            let strategy = context.child("pool").strategy(NZUsize!(4));
3881            assert_eq!(strategy.manual().parallelism(), 4);
3882
3883            // Use the strategy to sum a vector of numbers.
3884            let sum = strategy.fold(0..10000, || 0i32, |acc, n| acc + n, |a, b| a + b);
3885            assert_eq!(sum, 10000 * 9999 / 2);
3886        });
3887    }
3888
3889    #[test]
3890    fn test_strategy_deterministic() {
3891        let executor = deterministic::Runner::default();
3892        executor.start(|context| async move {
3893            // Create a strategy that plans for a parallelism of 4.
3894            let strategy = context.child("pool").strategy(NZUsize!(4));
3895            assert_eq!(strategy.manual().parallelism(), 4);
3896
3897            // Use the strategy to sum a vector of numbers.
3898            let sum = strategy.fold(0..10000, || 0i32, |acc, n| acc + n, |a, b| a + b);
3899            assert_eq!(sum, 10000 * 9999 / 2);
3900        });
3901    }
3902
3903    #[test]
3904    fn test_deterministic_nested_strategy_runs_inline() {
3905        let executor = deterministic::Runner::default();
3906        executor.start(|context| async move {
3907            let strategy = context.child("pool").strategy(NZUsize!(1)).manual();
3908
3909            let output = strategy
3910                .spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
3911                .await;
3912
3913            assert_eq!(output, vec![1, 2]);
3914        });
3915    }
3916
3917    /// A strategy with parallelism greater than one must behave as configured under the
3918    /// deterministic runtime even though no worker threads exist.
3919    #[test]
3920    fn test_deterministic_parallel_strategy_spawn_completes() {
3921        let executor = deterministic::Runner::default();
3922        executor.start(|context| async move {
3923            let strategy = context.child("pool").strategy(NZUsize!(2)).manual();
3924            assert_eq!(strategy.parallelism(), 2);
3925
3926            let output = strategy
3927                .spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
3928                .await;
3929
3930            assert_eq!(output, vec![1, 2]);
3931        });
3932    }
3933
3934    /// Strategies share the pool registered with the executor thread, but each request must
3935    /// retain its own planning parallelism and execute work. This covers multiple strategies
3936    /// within one runner and a later runner on the same thread.
3937    #[test]
3938    fn test_deterministic_strategies_reuse_pool_across_runners() {
3939        let executor = deterministic::Runner::default();
3940        executor.start(|context| async move {
3941            let first = context.child("pool_a").strategy(NZUsize!(1)).manual();
3942            assert_eq!(first.parallelism(), 1);
3943            assert_eq!(first.run(2, || "serial", || "parallel"), "serial");
3944            let output = first
3945                .spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
3946                .now_or_never()
3947                .expect("single-threaded pool should run spawned work inline");
3948            assert_eq!(output, vec![1, 2]);
3949
3950            let second = context.child("pool_b").strategy(NZUsize!(3)).manual();
3951            assert_eq!(second.parallelism(), 3);
3952            assert_eq!(second.run(2, || "serial", || "parallel"), "parallel");
3953            let output = second
3954                .spawn(|strategy| strategy.map_collect_vec(0..3, |i| i + 1))
3955                .now_or_never()
3956                .expect("single-threaded pool should run spawned work inline");
3957            assert_eq!(output, vec![1, 2, 3]);
3958        });
3959
3960        let executor = deterministic::Runner::default();
3961        executor.start(|context| async move {
3962            let third = context.child("pool_c").strategy(NZUsize!(4)).manual();
3963            assert_eq!(third.parallelism(), 4);
3964            assert_eq!(third.run(2, || "serial", || "parallel"), "parallel");
3965            let output = third
3966                .spawn(|strategy| strategy.map_collect_vec(0..4, |i| i + 1))
3967                .now_or_never()
3968                .expect("single-threaded pool should run spawned work inline");
3969            assert_eq!(output, vec![1, 2, 3, 4]);
3970        });
3971    }
3972
3973    /// Tasks may suspend while a pool exists: pools have no worker tasks for the executor
3974    /// to poll (a polled rayon worker loop would block or abort the runtime), so suspension
3975    /// must leave the pool usable.
3976    #[test]
3977    fn test_deterministic_pool_survives_suspension() {
3978        let executor = deterministic::Runner::default();
3979        executor.start(|context| async move {
3980            let strategy = context.child("pool").strategy(NZUsize!(2)).manual();
3981            context.sleep(Duration::from_millis(10)).await;
3982
3983            let output = strategy
3984                .spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
3985                .await;
3986            assert_eq!(output, vec![1, 2]);
3987
3988            context.sleep(Duration::from_millis(10)).await;
3989            let sum = strategy.fold(0..100u64, || 0u64, |acc, i| acc + i, |a, b| a + b);
3990            assert_eq!(sum, 4950);
3991        });
3992    }
3993
3994    #[test]
3995    fn test_tokio_nested_strategy_runs_inline() {
3996        let executor = tokio::Runner::default();
3997        executor.start(|context| async move {
3998            let strategy = context.child("pool").strategy(NZUsize!(1)).manual();
3999
4000            let output = strategy
4001                .spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
4002                .await;
4003
4004            assert_eq!(output, vec![1, 2]);
4005        });
4006    }
4007
4008    fn test_buffer_pooler<R: Runner>(
4009        runner: R,
4010        expected_network_max_per_class: u32,
4011        expected_storage_max_per_class: u32,
4012    ) where
4013        R::Context: BufferPooler,
4014    {
4015        runner.start(|context| async move {
4016            // Verify network pool is accessible and works (cache-line aligned)
4017            let net_buf = context.network_buffer_pool().try_alloc(1024).unwrap();
4018            assert!(net_buf.capacity() >= 1024);
4019
4020            // Verify storage pool is accessible and works (page-aligned)
4021            let storage_buf = context.storage_buffer_pool().try_alloc(1024).unwrap();
4022            assert!(storage_buf.capacity() >= 4096);
4023
4024            // Verify pools have expected configurations
4025            assert_eq!(
4026                context.network_buffer_pool().config().max_per_class.get(),
4027                expected_network_max_per_class
4028            );
4029            assert_eq!(
4030                context.storage_buffer_pool().config().max_per_class.get(),
4031                expected_storage_max_per_class
4032            );
4033        });
4034    }
4035
4036    #[test]
4037    fn test_deterministic_buffer_pooler() {
4038        test_buffer_pooler(deterministic::Runner::default(), 4096, 64);
4039
4040        let runner = deterministic::Runner::new(
4041            deterministic::Config::default()
4042                .with_network_buffer_pool_config(
4043                    BufferPoolConfig::for_network().with_max_per_class(NZU32!(64)),
4044                )
4045                .with_storage_buffer_pool_config(
4046                    BufferPoolConfig::for_storage().with_max_per_class(NZU32!(8)),
4047                ),
4048        );
4049        test_buffer_pooler(runner, 64, 8);
4050    }
4051
4052    #[test]
4053    fn test_tokio_buffer_pooler() {
4054        test_buffer_pooler(tokio::Runner::default(), 4096, 64);
4055
4056        let runner = tokio::Runner::new(
4057            tokio::Config::default()
4058                .with_network_buffer_pool_config(
4059                    BufferPoolConfig::for_network().with_max_per_class(NZU32!(64)),
4060                )
4061                .with_storage_buffer_pool_config(
4062                    BufferPoolConfig::for_storage().with_max_per_class(NZU32!(8)),
4063                ),
4064        );
4065        test_buffer_pooler(runner, 64, 8);
4066    }
4067}