Skip to main content

simoom_rt/
lib.rs

1//! simoom runtime: one event loop per core, timer wheel, buffer pools, cross-thread wakeups,
2//! offload pool.
3//!
4//! # Model
5//!
6//! - **One loop thread per core, shared-nothing.** Each loop owns an I/O driver
7//!   ([`simoom_sys::driver::Driver`]: epoll or io_uring on Linux, IOCP on Windows, kqueue on
8//!   macOS), a buffer pool ([`pool::BufPool`]), a timer wheel ([`timer::TimerWheel`]) and its
9//!   connections. Nothing on the hot path is shared between loops.
10//! - **Completion style.** The driver receives into pooled buffers and sends owned buffers; a
11//!   connection sees bytes in and hands bytes out. Readiness is never exposed.
12//! - **An idle connection holds no buffer.** Buffers are lent to the driver, taken only when
13//!   data arrives, and returned to the pool as soon as a handler has consumed the data and the
14//!   reply is written.
15//! - **Accepting.** Linux: one `SO_REUSEPORT` listener per loop. Windows: one listener with a
16//!   queue of `AcceptEx` calls on loop 0, which hands connections to the less busy of two loops.
17//!   macOS: one shared listener, handed out the same way.
18//! - **UDP.** One endpoint per loop (`SO_REUSEPORT` sockets on Linux; loop 0 elsewhere) with
19//!   batched receive and send, GRO/GSO, ECN and packet info, for QUIC.
20//! - **Offload.** Blocking work goes to a bounded shared pool ([`offload::OffloadPool`]) with
21//!   CoDel admission; results come back to the connection's loop as messages.
22//! - **Caps and backpressure.** Connection caps (total and per loop: over the cap a connection is
23//!   accepted and closed at once), a cap on unconsumed input per connection, and write
24//!   high/low-water marks that pause and resume reading.
25//! - **Graceful stop** with a deadline ([`RuntimeHandle::stop`]).
26//!
27//! # Driving a protocol
28//!
29//! Protocol crates are sans-io: they take received bytes and produce bytes to send. The glue is
30//! a [`Connection`] per connection, created by an [`Acceptor`]:
31//!
32//! ```no_run
33//! use simoom_rt::{Connection, ConnInfo, Ctx, Runtime};
34//!
35//! struct Echo;
36//! impl Connection for Echo {
37//!     fn on_data(&mut self, cx: &mut Ctx<'_>, data: &[u8]) -> usize {
38//!         let mut out = cx.buffer(data.len()); // pooled
39//!         out.extend_from_slice(data);
40//!         cx.send(out); // written at the end of the loop turn, batched with other output
41//!         data.len()    // consumed everything: the input buffer goes back to the pool
42//!     }
43//! }
44//!
45//! let rt = Runtime::builder()
46//!     .threads(0) // one loop per CPU
47//!     .listen("127.0.0.1:8080", |_: &ConnInfo| Some(Box::new(Echo) as Box<dyn Connection>))
48//!     .start()
49//!     .unwrap();
50//! println!("listening on {:?}", rt.local_addrs());
51//! rt.stop(std::time::Duration::from_secs(5));
52//! rt.join();
53//! ```
54//!
55//! - [`Connection::on_data`] gets every unconsumed byte and returns how many it consumed. A
56//!   partial message stays buffered for the next call.
57//! - [`Ctx::send`] / [`Ctx::send_static`] queue output; [`Ctx::close`] closes after flushing.
58//! - [`Ctx::set_timer`] arms a timer ([`Connection::on_timer`]), for request deadlines and
59//!   idle timeouts.
60//! - [`Ctx::pause_reading`] / [`Ctx::resume_reading`] for backpressure; [`Ctx::notify_drain`]
61//!   and [`Connection::on_drain`] for streaming output.
62//! - [`Ctx::handle`] gives a `Send` [`ConnHandle`] to deliver a [`Message`] from any thread
63//!   (a deferred reply); [`Ctx::offload`] runs a closure on the offload pool and delivers its
64//!   result the same way.
65//!
66//! UDP protocols (QUIC) implement [`Endpoint`], created per loop by a [`UdpService`].
67
68#![forbid(unsafe_code)]
69
70mod event_loop;
71mod handler;
72pub mod offload;
73pub mod pool;
74mod runtime;
75pub mod timer;
76
77/// Spawns a thread. Every thread of the runtime starts here, so std's spawn code is
78/// instantiated once instead of once per closure type (1-3 KB each).
79#[inline(never)]
80pub(crate) fn spawn(tb: std::thread::Builder, f: Box<dyn FnOnce() + Send>) -> std::io::Result<std::thread::JoinHandle<()>> {
81    tb.spawn(f)
82}
83
84pub use event_loop::{ConnHandle, Ctx, EndpointHandle, LoopCtx, LoopHandle, UdpCtx};
85pub use handler::{Acceptor, CloseReason, ConnInfo, Connection, Endpoint, Message, UdpInfo, UdpService};
86pub use runtime::{Builder, Config, LoopSnapshot, RuntimeHandle, Stats};
87
88pub use simoom_sys::driver::{Backend, DriverConfig, DriverStats, OutBuf};
89pub use simoom_sys::net::{ListenOptions, Socket, UdpOptions};
90pub use simoom_sys::udp::{RecvMeta, Transmit};
91
92/// Entry point: `Runtime::builder()`.
93#[derive(Debug)]
94pub struct Runtime;
95
96impl Runtime {
97    /// A [`Builder`] with the default [`Config`].
98    pub fn builder() -> Builder {
99        Builder::new()
100    }
101}