Skip to main content

trillium_http/h2/
connection.rs

1//! Shared per-connection HTTP/2 state ([`H2Connection`]).
2//!
3//! [`H2Connection`] is `Arc`-shared between the driver task ([`H2Driver`]) and every conn
4//! task that holds an open stream's [`Conn`]. It owns the per-stream `StreamState` map,
5//! the cross-task wake primitive ([`AtomicWaker`]), and the [`HttpContext`] / [`Swansong`]
6//! the broader server stack reaches in through.
7//!
8//! The driver loop itself lives in [`super::acceptor`] — see that module for the
9//! per-connection state machine and how send / receive concerns are split.
10//!
11//! # Module layout
12//!
13//! Conn-task-side primitives are split across child modules so each subsystem reads
14//! independently:
15//!
16//! - [`ping`]: `PING` / `PING ACK` round-trip tracking and the [`SendPing`] future.
17//! - [`peer_settings_wait`]: the [`PeerSettings`] sync primitive that parks until the peer's first
18//!   SETTINGS frame is applied.
19//! - [`submit`]: send-staging API ([`submit_send`][H2Connection::submit_send],
20//!   [`submit_upgrade`][H2Connection::submit_upgrade]) and client-side stream-open primitives
21//!   ([`open_stream`][H2Connection::open_stream] /
22//!   [`open_connect_stream`][H2Connection::open_connect_stream]) + the [`SubmitSend`] future.
23//! - [`response`]: client-role recv-side primitives — [`ResponseHeaders`] and
24//!   [`take_trailers`][H2Connection::take_trailers].
25//!
26//! [`H2Driver`]: super::H2Driver
27
28mod peer_settings_wait;
29mod ping;
30mod response;
31mod submit;
32
33#[cfg(feature = "unstable")]
34use super::H2Initiator;
35use super::{H2Driver, H2Settings, role::Role, transport::StreamState};
36use crate::{Conn, HttpContext, http_context::ConnectionContext};
37use atomic_waker::AtomicWaker;
38use event_listener::Event;
39use futures_lite::io::{AsyncRead, AsyncWrite};
40use hashbrown::HashMap;
41use ping::PendingPing;
42use std::{
43    collections::VecDeque,
44    future::Future,
45    io, mem,
46    sync::{
47        Arc, Mutex, MutexGuard,
48        atomic::{AtomicBool, Ordering},
49    },
50    task::{Context, Poll},
51};
52use swansong::{ShutdownCompletion, Swansong};
53#[cfg(feature = "unstable")]
54#[allow(unused_imports)]
55// re-exports for h2.rs's `pub use connection::{ResponseHeaders, SubmitSend}`
56pub use {response::ResponseHeaders, submit::SubmitSend};
57
58/// Shared per-connection state for HTTP/2.
59///
60/// Wrapped in an [`Arc`] and held by both the [`H2Driver`] driver and every conn task
61/// that holds an open stream's [`Conn`]. Per-stream `StreamState`, HPACK encoder state, and
62/// connection-level send flow control lives here.
63#[derive(Debug)]
64pub struct H2Connection {
65    pub(super) context: ConnectionContext,
66    pub(super) swansong: Swansong,
67    /// Driver-side waker that conn tasks fire whenever they produce work the driver should
68    /// act on — the is-reading signal on first `H2Transport::poll_read`, and the
69    /// `submit_send` arrival. Single-consumer (the driver); N producers (conn tasks). The
70    /// driver registers its current `drive` waker here each iteration it parks.
71    pub(super) outbound_waker: AtomicWaker,
72    /// Per-stream shared state, keyed by stream id. The driver inserts on stream open and
73    /// removes on close. Conn-task code looks up via private accessors on `H2Connection`
74    /// rather than touching the map directly — `StreamState` stays module-private.
75    pub(super) streams: Mutex<HashMap<u32, Arc<StreamState>>>,
76    /// Ids of streams with work for the driver, queued by [`Self::signal_driver`]. The driver
77    /// takes the whole queue each tick, so its per-tick work follows the streams that signaled
78    /// rather than every open stream.
79    pub(super) signaled_streams: Mutex<Vec<u32>>,
80    /// The peer's most recently announced SETTINGS values. The driver writes on every
81    /// inbound SETTINGS frame and is the only reader, so a plain `Mutex` suffices.
82    /// `H2Settings` is `Copy`, so readers take the guard, copy out, and release.
83    ///
84    /// Default-constructed (all fields `None`) means "peer has not yet sent SETTINGS";
85    /// readers should use [`H2Settings::effective_*`][H2Settings::effective_max_frame_size]
86    /// helpers that apply the RFC defaults to absent fields.
87    pub(super) peer_settings: Mutex<H2Settings>,
88    /// Latch flipped to `true` the first (and every subsequent) time the driver applies
89    /// a peer SETTINGS frame. Distinct from `peer_settings` because an absent field is
90    /// ambiguous between "peer hasn't sent SETTINGS yet" and "peer sent SETTINGS without
91    /// that field" — the latch disambiguates, gating operations that require having seen
92    /// the peer's first SETTINGS (e.g. extended CONNECT).
93    pub(super) peer_settings_received: AtomicBool,
94    /// Multi-listener wake source for [`PeerSettings`]. The driver fires `notify(usize::MAX)`
95    /// after applying peer SETTINGS and again on connection close, so any number of
96    /// concurrently-parked `PeerSettings` futures all unblock together. [`Event`] (rather
97    /// than a single [`AtomicWaker`]) is required because multiple application tasks can
98    /// park on `peer_settings` concurrently — e.g. a fan-out of WebSocket-over-h2 upgrades
99    /// on one pooled connection — and `AtomicWaker`'s last-writer-wins semantics would
100    /// strand all but one.
101    pub(super) peer_settings_event: Event,
102    /// Next stream id to allocate for client-role outbound streams. Starts at 1 and
103    /// `+= 2` per allocation. Capped at `2^31` — once exhausted, `fetch_update`'s closure
104    /// refuses to advance, and `open_stream` returns `None` (the caller is expected to
105    /// fail over to a fresh connection).
106    ///
107    /// Allocation happens **while holding `streams_lock`** (see `open_stream`), so id order
108    /// matches shared-map insertion order — the invariant the send pump, which frames
109    /// equal-priority streams in id order, relies on to frame opening HEADERS in monotonic
110    /// order (RFC 9113 §5.1.1). The lock,
111    /// not the atomic, is what orders allocations; the `AtomicU32` is just the interior
112    /// mutability this `Arc`-shared field needs, so `Relaxed` suffices. The one out-of-band
113    /// reader (`can_open_stream`'s exhaustion check) takes `streams_lock` immediately after,
114    /// so its `load` is an advisory fast-path, not a separately-synchronized access.
115    #[cfg(feature = "unstable")]
116    pub(super) next_client_stream_id: std::sync::atomic::AtomicU32,
117    /// Outstanding active PINGs awaiting ACKs, keyed by opaque payload. Completed by the
118    /// driver when a `PING { ack: true }` arrives whose payload matches an entry. Drained
119    /// on connection close so awaiting `send_ping` futures don't leak.
120    pub(super) pending_pings: Mutex<HashMap<[u8; 8], PendingPing>>,
121    /// Opaque payloads queued for outbound `PING { ack: false }` emission. Decoupled from
122    /// `pending_pings` so registration and queuing can happen without holding two locks.
123    pub(super) pending_ping_outbound: Mutex<VecDeque<[u8; 8]>>,
124}
125
126impl H2Connection {
127    /// Construct a new `H2Connection` to manage HTTP/2 for a single peer.
128    pub fn new(context: Arc<HttpContext>) -> Arc<Self> {
129        let swansong = context.swansong().child();
130        Arc::new(Self {
131            context: Arc::new(context),
132            swansong,
133            outbound_waker: AtomicWaker::new(),
134            streams: Mutex::new(HashMap::new()),
135            signaled_streams: Mutex::new(Vec::new()),
136            peer_settings: Mutex::new(H2Settings::default()),
137            peer_settings_received: AtomicBool::new(false),
138            peer_settings_event: Event::new(),
139            #[cfg(feature = "unstable")]
140            next_client_stream_id: std::sync::atomic::AtomicU32::new(1),
141            pending_pings: Mutex::new(HashMap::new()),
142            pending_ping_outbound: Mutex::new(VecDeque::new()),
143        })
144    }
145
146    /// The [`HttpContext`] this connection was constructed with.
147    pub fn context(&self) -> Arc<HttpContext> {
148        Arc::clone(&*self.context)
149    }
150
151    /// This connection's handle on its [`HttpContext`], for each of its streams' conns to share.
152    pub(crate) fn connection_context(&self) -> ConnectionContext {
153        Arc::clone(&self.context)
154    }
155
156    /// The connection-scoped [`Swansong`]. Shuts down on peer GOAWAY, when the server-
157    /// level swansong shuts down, or when the connection ends for any other reason
158    /// (peer disconnect, transport error) — once it leaves the running state, the
159    /// connection will never carry another stream.
160    pub fn swansong(&self) -> &Swansong {
161        &self.swansong
162    }
163
164    /// Attempt graceful shutdown of this HTTP/2 connection.
165    pub fn shut_down(&self) -> ShutdownCompletion {
166        self.swansong.shut_down()
167    }
168
169    /// Whether a fresh stream could be opened on this connection right now.
170    ///
171    /// `true` requires: the connection is running (no GOAWAY received, swansong not asked
172    /// to shut down), inflight streams are below the peer's advertised
173    /// `MAX_CONCURRENT_STREAMS`, and the client stream-id space is not exhausted (capped
174    /// at `2^31 - 1`).
175    ///
176    /// `false` doesn't mean the connection is dead — it might just be saturated and free
177    /// up momentarily. Callers should keep saturated connections in their pool rather than
178    /// evicting; pair this with a separate aliveness check to decide eviction.
179    ///
180    /// Stream-id exhaustion is the one "false" case that *is* permanent: the connection
181    /// will never accept another `open_stream` call, though in-flight streams will still
182    /// complete.
183    ///
184    /// # Panics
185    ///
186    /// Panics if any per-connection mutex is poisoned.
187    #[cfg(feature = "unstable")]
188    pub fn can_open_stream(&self) -> bool {
189        if !self.swansong.state().is_running() {
190            return false;
191        }
192        // Stream-id exhaustion check guards against an exhausted connection passing the
193        // inflight-vs-MAX_CONCURRENT_STREAMS check (no streams in flight → counts as 0)
194        // and the pool selecting it as Available, only for `open_stream` to fail at the
195        // call site with a misleading "shutting down" error.
196        if self.next_client_stream_id.load(Ordering::Relaxed) >= (1u32 << 31) {
197            return false;
198        }
199        // Count wire-active streams only — entries the application is still holding after
200        // a clean wire-close stay in the map but don't count against the peer's
201        // MAX_CONCURRENT_STREAMS.
202        let inflight: u32 = self
203            .streams_lock()
204            .values()
205            .filter(|s| !s.lifecycle_lock().is_closed())
206            .count()
207            .try_into()
208            .unwrap_or(u32::MAX);
209        let cap = self
210            .current_peer_settings()
211            .effective_max_concurrent_streams();
212        inflight < cap
213    }
214
215    /// Driver-side wake primitive. Fire after producing work the driver should service.
216    pub(super) fn outbound_waker(&self) -> &AtomicWaker {
217        &self.outbound_waker
218    }
219
220    /// Tell the driver `stream` has work for it: queue the stream for the driver's next tick,
221    /// unless it is already queued, and wake the driver. Call after writing the work itself (a
222    /// staged part, consumed bytes, a release), which the driver may pick up as soon as the
223    /// stream is queued.
224    pub(super) fn signal_driver(&self, stream_id: u32, stream: &StreamState) {
225        if !stream.needs_servicing.swap(true, Ordering::AcqRel) {
226            self.signaled_streams
227                .lock()
228                .expect("signaled streams mutex poisoned")
229                .push(stream_id);
230        }
231        self.outbound_waker.wake();
232    }
233
234    /// Driver-side: take every queued signal, leaving `empty` (whose capacity is retained) as the
235    /// new queue so neither side reallocates in steady state.
236    pub(super) fn take_signaled_streams(&self, empty: &mut Vec<u32>) {
237        mem::swap(
238            &mut *self
239                .signaled_streams
240                .lock()
241                .expect("signaled streams mutex poisoned"),
242            empty,
243        );
244    }
245
246    /// Driver-side: whether any stream has signaled since the driver last took the queue.
247    pub(super) fn has_signaled_streams(&self) -> bool {
248        !self
249            .signaled_streams
250            .lock()
251            .expect("signaled streams mutex poisoned")
252            .is_empty()
253    }
254
255    /// Lock the per-stream `StreamState` map.
256    pub(super) fn streams_lock(&self) -> MutexGuard<'_, HashMap<u32, Arc<StreamState>>> {
257        self.streams
258            .lock()
259            .expect("connection streams mutex poisoned")
260    }
261
262    /// Lock the peer's SETTINGS. Cheap; held only as long as the returned guard lives.
263    /// Use the `effective_*` helpers on [`H2Settings`] to get a value with RFC defaults
264    /// applied for fields the peer hasn't set; typical callers copy out via `*guard` and
265    /// release immediately.
266    pub(super) fn current_peer_settings(&self) -> MutexGuard<'_, H2Settings> {
267        self.peer_settings
268            .lock()
269            .expect("peer_settings mutex poisoned")
270    }
271
272    /// Request that the driver emit `RST_STREAM` on this stream with the given error code and clean
273    /// up. Clears any queued outbound parts and enqueues an [`OutboundPart::Reset`][reset] —
274    /// nothing else is valid to send after a reset — then wakes the driver, which frames the
275    /// `RST_STREAM` and tears the stream down.
276    ///
277    /// First-wins idempotent: a stream already reset-requested keeps its original code. No-op if
278    /// the stream is already gone from the shared map.
279    ///
280    /// [reset]: super::transport::OutboundPart::Reset
281    pub(crate) fn stream_error(&self, stream_id: u32, code: super::H2ErrorCode) {
282        let Some(stream) = self.streams_lock().get(&stream_id).cloned() else {
283            return;
284        };
285        stream.request_reset(code);
286        self.signal_driver(stream_id, &stream);
287    }
288
289    /// Resolves once the stream is fully closed — reset by either side, both halves
290    /// complete, or already gone from the stream map (including connection teardown).
291    /// Registers on the stream's recv-side waker, which the driver fires on every close
292    /// event.
293    pub(crate) fn poll_stream_closed(&self, stream_id: u32, cx: &mut Context<'_>) -> Poll<()> {
294        let Some(stream) = self.streams_lock().get(&stream_id).cloned() else {
295            return Poll::Ready(());
296        };
297        // Register before checking, so a close that lands between the check and the
298        // driver's wake still finds this waker.
299        stream.recv.waker.register(cx.waker());
300        if stream.lifecycle_lock().is_closed() {
301            Poll::Ready(())
302        } else {
303            Poll::Pending
304        }
305    }
306
307    /// Bind this `H2Connection` to a TCP transport and return an [`H2Driver`] that drives
308    /// the connection.
309    ///
310    /// The driver must be polled to completion via repeated calls to
311    /// [`H2Driver::next`] (or its [`Stream`][futures_lite::stream::Stream] impl); each returned
312    /// [`Conn`] should be spawned on its own task.
313    pub fn run<T>(self: Arc<Self>, transport: T) -> H2Driver<T>
314    where
315        T: AsyncRead + AsyncWrite + Unpin + Send,
316    {
317        H2Driver::new(self, transport, Role::Server)
318    }
319
320    /// Bind this `H2Connection` to an outbound transport and return an [`H2Initiator`] —
321    /// the background-task future a client spawns to drive the connection.
322    ///
323    /// On first poll the driver writes the 24-byte client preface and its initial
324    /// SETTINGS; thereafter it demuxes inbound frames (peer SETTINGS, response HEADERS /
325    /// DATA on our streams, etc.) and pumps outbound bytes (new stream opens, DATA,
326    /// `WINDOW_UPDATEs`) until the connection closes or errors out.
327    ///
328    /// Awaiting the returned future resolves with `Ok(())` on graceful close or
329    /// `Err(H2Error)` on protocol / I/O failure. Streams are not opened via the future
330    /// itself — client code calls stream-open primitives on `H2Connection`; this future
331    /// just runs the framing loop.
332    #[cfg(feature = "unstable")]
333    pub fn run_client<T>(self: Arc<Self>, transport: T) -> H2Initiator<T>
334    where
335        T: AsyncRead + AsyncWrite + Unpin + Send,
336    {
337        H2Initiator::new(H2Driver::new(self, transport, Role::Client))
338    }
339
340    /// Per-stream entry point — call from the runtime adapter's spawned task for each
341    /// [`Conn`] returned by [`H2Driver::next`]. Runs `handler` to produce the response,
342    /// then `send_h2` to hand the framed response to the driver.
343    ///
344    /// # Errors
345    ///
346    /// Returns the [`io::Error`] from `send_h2` if the body's `poll_read` errors or the
347    /// underlying transport fails partway through the response.
348    pub async fn process_inbound<Transport, Handler, Fut>(
349        conn: Conn<Transport>,
350        handler: Handler,
351    ) -> io::Result<Conn<Transport>>
352    where
353        Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
354        Handler: FnOnce(Conn<Transport>) -> Fut,
355        Fut: Future<Output = Conn<Transport>>,
356    {
357        let _guard = conn.context().swansong().guard();
358        handler(conn).await.send_h2().await
359    }
360}