Skip to main content

ergon_io/
endpoint.rs

1//! What a `Driver` drives: connection ids, the sealed endpoint traits and the
2//! application callbacks. Platform-independent.
3
4use crate::error::Error;
5
6/// Identifies a connection within one `Driver`.
7///
8/// A slot index plus a generation: once a connection is closed its id never matches again, even when
9/// the slot is reused, so a stale id cannot reach a newer connection.
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
11pub struct ConnId {
12    idx: u32,
13    generation: u32,
14}
15
16#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
17impl ConnId {
18    pub(crate) const fn new(idx: u32, generation: u32) -> Self {
19        Self { idx, generation }
20    }
21
22    pub(crate) const fn generation(self) -> u32 {
23        self.generation
24    }
25
26    /// The slot index (dense, reused after close; for per-connection arrays).
27    #[must_use]
28    pub const fn index(self) -> usize {
29        self.idx as usize
30    }
31
32    /// Test lever: an id from its parts, for stand-in drivers in tests. A
33    /// made-up id never reaches a connection: the driver checks the generation.
34    #[doc(hidden)]
35    #[must_use]
36    pub const fn from_parts(index: u32, generation: u32) -> Self {
37        Self::new(index, generation)
38    }
39}
40
41/// The driver-facing side of endpoints and dials. The module is private, so
42/// code outside this crate can neither implement these traits nor call their
43/// methods: only the driver moves bytes for a connection it owns.
44pub(crate) mod sealed {
45    use std::net::SocketAddr;
46    use std::os::fd::RawFd;
47    use std::sync::{Arc, OnceLock};
48
49    use super::ConnId;
50    use crate::RxMeta;
51    use crate::error::Result;
52
53    /// Socket, receive buffer and send queue of a connection.
54    pub trait Io {
55        /// Whether [`Io::expire`] must be called (request timeouts). The
56        /// driver's periodic deadline check is compiled out when `false`.
57        const DEADLINES: bool = false;
58        /// Fails the connection if something it waits for is overdue, and
59        /// runs its timers (WebSocket pings).
60        fn expire(&mut self, now_ns: i64) -> Result<()> {
61            let _ = now_ns;
62            Ok(())
63        }
64        /// The error to report when the connection ends with `error`: a
65        /// WebSocket whose close handshake finished reports the Close, an
66        /// HTTP connection with requests in flight which of them were sent.
67        fn close_reason(&mut self, error: crate::error::Error) -> crate::error::Error {
68            error
69        }
70        /// The kernel took `n` bytes from the front of [`Io::tx_queue`]
71        /// (`io_uring`: they were submitted, an upper bound). One add per
72        /// send on the HTTP connection; nothing elsewhere.
73        #[inline]
74        fn on_handed(&mut self, n: usize) {
75            let _ = n;
76        }
77        /// The socket.
78        fn fd(&self) -> RawFd;
79        /// The free part of the receive buffer. With `io_uring` the kernel
80        /// writes into it after the call returns, so it must stay at the same
81        /// address until `on_received`.
82        fn rx_space(&mut self) -> &mut [u8];
83        /// Bytes waiting to be sent; the driver drains from the front.
84        fn tx_queue(&mut self) -> &mut Vec<u8>;
85        /// Input received before the endpoint was added, not yet decoded.
86        fn has_buffered_input(&self) -> bool;
87        /// Records what the quick-ACK route check found, or the cell
88        /// ergon-io's warning thread fills in (once, when the driver takes
89        /// the connection, or at TCP connect under `require`).
90        fn set_route(&mut self, route: RouteSlot);
91        /// What the connection holds of its route check.
92        fn route_state(&self) -> &RouteSlot;
93    }
94
95    /// Decoding received bytes for handler `H`.
96    pub trait Deliver<H: ?Sized>: Io {
97        /// Accounts for `n` bytes written into `rx_space`, decodes and delivers
98        /// every complete message. `n == 0` decodes buffered input.
99        fn on_received(
100            &mut self,
101            id: ConnId,
102            n: usize,
103            meta: &RxMeta,
104            handler: &mut H,
105        ) -> Result<usize>;
106    }
107
108    /// What a connection holds of its route check: the outcome, or the cell
109    /// ergon-io's warning thread fills in. Reading it is one atomic load, and
110    /// nothing on the read or write path reads it.
111    #[derive(Debug, Clone)]
112    #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
113    pub enum RouteSlot {
114        /// Decided where the connection was set up.
115        Ready(crate::route::RouteCheck),
116        /// Filled in by the warning thread.
117        Pending(Arc<OnceLock<crate::route::RouteCheck>>),
118    }
119
120    impl Default for RouteSlot {
121        fn default() -> Self {
122            Self::Ready(crate::route::RouteCheck::NotChecked)
123        }
124    }
125
126    /// What a connection reads while its lookup is queued.
127    static PENDING: crate::route::RouteCheck = crate::route::RouteCheck::Pending;
128
129    #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
130    impl RouteSlot {
131        /// The outcome, or [`crate::route::RouteCheck::Pending`].
132        pub fn get(&self) -> &crate::route::RouteCheck {
133            match self {
134                Self::Ready(c) => c,
135                Self::Pending(cell) => cell.get().unwrap_or(&PENDING),
136            }
137        }
138
139        /// Whether the route was checked where the connection was set up (a
140        /// gated dial under `require`).
141        pub const fn decided(&self) -> bool {
142            !matches!(
143                self,
144                Self::Ready(crate::route::RouteCheck::NotChecked) | Self::Pending(_)
145            )
146        }
147    }
148
149    /// A connection being set up.
150    pub trait DialIo {
151        /// The connection it produces.
152        type Conn: Io;
153        /// Host to resolve.
154        fn host(&self) -> &str;
155        /// TCP port.
156        fn port(&self) -> u16;
157        /// A fixed address, if DNS should be skipped.
158        fn addr(&self) -> Option<SocketAddr>;
159        /// Starts the non-blocking TCP connect.
160        fn start(&mut self, addr: SocketAddr) -> Result<()>;
161        /// Advances as far as the socket allows; `Some` once ready.
162        fn step(&mut self) -> Result<Option<Self::Conn>>;
163        /// Stop once TCP is connected, before the TLS handshake, until
164        /// [`DialIo::route_checked`]: the driver checks the route there
165        /// under `quickack_check=require`, so a refused route costs no
166        /// handshake.
167        fn gate_route(&mut self);
168        /// The socket, while the dial waits at that point.
169        fn awaiting_route(&self) -> Option<RawFd>;
170        /// The route check's outcome: the dial goes on to TLS, and the
171        /// connection it produces carries it.
172        fn route_checked(&mut self, check: crate::route::RouteCheck);
173    }
174}
175
176/// A connection a driver can own: `WsConn`, `HttpConn` or `AnyConn`.
177///
178/// Sealed: with `io_uring` the kernel writes into the receive buffer after the
179/// submitting call returns, so only this crate's types, whose buffers never move
180/// while owned by a driver, can be endpoints.
181#[allow(private_bounds)] // sealed on purpose: the bound is unnameable outside the crate
182pub trait Endpoint: sealed::Io {}
183impl<T: sealed::Io> Endpoint for T {}
184
185/// An endpoint that can decode for application handler `H`.
186#[allow(private_bounds)]
187pub trait Receive<H: ?Sized>: Endpoint + sealed::Deliver<H> {}
188impl<H: ?Sized, T: sealed::Deliver<H>> Receive<H> for T {}
189
190/// A connection being set up without blocking (`WsDial`, `HttpDial`,
191/// `AnyDial`): DNS is the driver's job; the dial does TCP, TLS and any
192/// protocol upgrade.
193#[allow(private_bounds)]
194pub trait Dial: sealed::DialIo {}
195impl<T: sealed::DialIo> Dial for T {}
196
197/// Mutable access to a connection a `Driver` owns.
198///
199/// It offers every method of the connection but no way to replace, swap or
200/// move it out. With `io_uring` a receive is always armed into the
201/// connection's receive buffer, and every backend tracks the connection by its
202/// socket descriptor and slot, so the connection stays where it is until the
203/// driver closes it.
204///
205/// ```compile_fail,E0594
206/// fn replace(mut c: ergon_io::ConnMut<'_, ergon_io::WsConn>, other: ergon_io::WsConn) {
207///     *c = other;
208/// }
209/// ```
210///
211/// ```compile_fail,E0596
212/// fn swap(mut a: ergon_io::ConnMut<'_, ergon_io::WsConn>, mut b: ergon_io::ConnMut<'_, ergon_io::WsConn>) {
213///     std::mem::swap(&mut *a, &mut *b);
214/// }
215/// ```
216pub struct ConnMut<'a, E>(&'a mut E);
217
218impl<'a, E> ConnMut<'a, E> {
219    #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
220    pub(crate) const fn new(conn: &'a mut E) -> Self {
221        Self(conn)
222    }
223
224    /// The connection itself, for this crate's forwarding methods only.
225    pub(crate) const fn inner(&mut self) -> &mut E {
226        self.0
227    }
228}
229
230impl<E> std::ops::Deref for ConnMut<'_, E> {
231    type Target = E;
232    fn deref(&self) -> &E {
233        self.0
234    }
235}
236
237/// Connection lifecycle callbacks.
238pub trait Handler {
239    /// A connection started with `Driver::connect` is ready.
240    fn on_open(&mut self, id: ConnId) {
241        let _ = id;
242    }
243    /// The driver closed `id` because of `error`: [`Error::Closed`] for a peer
244    /// close, [`Error::WsClose`] after a WebSocket close handshake. The
245    /// connection is gone when this is called.
246    fn on_closed(&mut self, id: ConnId, error: Error) {
247        let _ = (id, error);
248    }
249}
250
251/// Kernel-interface counters.
252#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
253pub struct DriverStats {
254    /// `poll` calls.
255    pub polls: u64,
256    /// `recvmsg` calls.
257    pub recvmsg_calls: u64,
258    /// `recvmsg` calls that returned `EAGAIN`.
259    pub recvmsg_eagain: u64,
260    /// `epoll_wait` / `epoll_pwait2` calls.
261    pub epoll_wait_calls: u64,
262    /// `send` syscalls (epoll backends; `io_uring` sends are submitted through
263    /// `io_uring_enter` or the `SQPOLL` thread and are not syscalls of their own).
264    pub send_calls: u64,
265    /// Sends started, one per batch of queued bytes, on every backend.
266    pub sends: u64,
267    /// `io_uring_enter` calls.
268    pub enter_calls: u64,
269    /// Receive completions with data.
270    pub completions: u64,
271    /// Messages delivered.
272    pub messages: u64,
273    /// Connections closed because of an error or peer close.
274    pub closed: u64,
275    /// Connections opened by `Driver::connect`.
276    pub connects: u64,
277    /// `Driver::connect` attempts that failed or timed out.
278    pub connect_failures: u64,
279    /// Longest single connection-setup step on the polling thread, in
280    /// nanoseconds (the TLS handshake's crypto lands here).
281    pub connect_step_max_ns: u64,
282    /// Quick-ACK route lookups the polling thread made itself: under
283    /// `quickack_check=require`, one per connection it set up
284    /// (`Driver::connect` at TCP connect, before the TLS handshake;
285    /// `Driver::add`). Under `warn` the lookup runs on ergon-io's warning
286    /// thread, and `Driver::connect_background` makes it on the helper
287    /// thread: neither is counted.
288    pub route_lookups: u64,
289    /// Polling-thread time spent on the route check, in nanoseconds: the
290    /// lookups above, or under `warn` reading the connection's addresses
291    /// and queuing the lookup (see [`crate::route`]).
292    pub route_check_ns: u64,
293    /// The longest single one of those, in nanoseconds.
294    pub route_check_max_ns: u64,
295}
296
297impl DriverStats {
298    /// Total syscalls made by the driver.
299    #[must_use]
300    pub const fn syscalls(&self) -> u64 {
301        self.recvmsg_calls + self.epoll_wait_calls + self.send_calls + self.enter_calls
302    }
303}