Skip to main content

confium_net_quic/
runtime.rs

1//! Shared async runtime for all QUIC handles in this process.
2//!
3//! Quinn is fully async; the [`confium_net::Transport`] /
4//! [`confium_net::Listener`] traits are blocking. Rather than impose an
5//! async runtime on every Confium caller, all QUIC transport/listener
6//! handles share a single process-wide tokio runtime and drive their
7//! async work via `block_on`.
8//!
9//! A single shared runtime avoids the subtle stalls that arise when
10//! two endpoints on two separate runtimes exchange UDP packets while
11//! each runtime is parked inside its own `block_on`: with separate
12//! runtimes, neither endpoint's driver task runs unless its own
13//! `block_on` is on the stack, so a peer's packet can sit unprocessed
14//! in a kernel buffer until the owning thread happens to re-enter
15//! `block_on`. With one runtime, the multi-thread scheduler keeps
16//! every endpoint's driver task making progress regardless of which
17//! handle is currently in `block_on`.
18
19use std::sync::Arc;
20use std::sync::OnceLock;
21
22/// A handle to the shared runtime. Cloning is cheap (Arc).
23#[derive(Clone)]
24pub(crate) struct Handle {
25    rt: Arc<tokio::runtime::Runtime>,
26}
27
28/// Wraps the `OnceLock` payload so we can stash a construction error
29/// without relying on the unstable `get_or_try_init` API.
30type SharedResult = std::result::Result<Arc<tokio::runtime::Runtime>, std::io::Error>;
31
32static SHARED: OnceLock<Arc<SharedResult>> = OnceLock::new();
33
34impl Handle {
35    /// Return the process-wide shared runtime, constructing it on
36    /// first use.
37    pub(crate) fn new() -> std::io::Result<Self> {
38        let cell = SHARED.get_or_init(|| {
39            let result = tokio::runtime::Builder::new_multi_thread()
40                .enable_all()
41                .build()
42                .map(Arc::new);
43            Arc::new(result)
44        });
45        match cell.as_ref() {
46            Ok(rt) => Ok(Self { rt: Arc::clone(rt) }),
47            Err(e) => Err(std::io::Error::new(e.kind(), e.to_string())),
48        }
49    }
50
51    /// Run `fut` to completion on this runtime, blocking the caller.
52    pub(crate) fn block_on<F, T>(&self, fut: F) -> T
53    where
54        F: std::future::Future<Output = T>,
55    {
56        self.rt.block_on(fut)
57    }
58}