Skip to main content

sozu_lib/protocol/mux/
mod.rs

1//! HTTP/1.1 and HTTP/2 multiplexing layer.
2//!
3//! This module unifies HTTP/1.1 and HTTP/2 behind a single [`Mux`] session
4//! state machine that integrates with sozu's mio event loop. The key types:
5//!
6//! - [`Mux`]: The top-level session state, generic over socket (`TcpStream` or
7//!   `FrontRustls`) and listener. Implements `SessionState`.
8//! - [`Connection`]: Enum dispatching to [`ConnectionH1`] or [`ConnectionH2`]
9//!   for protocol-specific readable/writable logic.
10//! - [`Stream`]: Per-request state with front/back kawa buffers, metrics, and
11//!   lifecycle tracking. Shared between H1 and H2 paths.
12//! - [`Context`]: Per-session context (cluster, backends, routing, timeouts).
13//!
14//! The H2 implementation handles RFC 9113 framing, HPACK (RFC 7541), flow
15//! control, flood detection (CVE-2023-44487, CVE-2019-9512/9514/9515/9518,
16//! CVE-2024-27316), and graceful shutdown (double-GOAWAY per RFC 9113 §6.8).
17
18use std::{
19    cell::RefCell,
20    collections::{HashMap, VecDeque},
21    fmt::Debug,
22    io::ErrorKind,
23    net::{Shutdown, SocketAddr},
24    rc::{Rc, Weak},
25    sync::Arc,
26    time::{Duration, Instant},
27};
28
29use mio::{Token, net::TcpStream};
30use rusty_ulid::Ulid;
31use sozu_command::{
32    logging::ansi_palette,
33    proto::command::{Event, EventKind},
34    ready::Ready,
35};
36
37/// Protocol label + session descriptor used as a prefix on every [`Mux`] log
38/// line. Matches the RUSTLS log-context convention:
39/// `[<ulid> - - -]\tMUX\tSession(...)\t >>>`. When colored output is enabled
40/// (via [`ansi_palette`]) the label is wrapped in bold bright-white ANSI
41/// (uniform across every protocol) and the session detail block is rendered
42/// in light grey.
43///
44/// Fields included in the session block:
45/// - `frontend` — mio token of the frontend socket
46/// - `peer` — peer address (or `None` if the socket is gone)
47/// - `streams` — number of streams currently held by the [`Context`]
48/// - `backends` — number of backend connections in the [`Router`]
49/// - `pending_links` — streams waiting to be linked to a backend
50/// - `readiness` — frontend mio readiness snapshot
51macro_rules! log_context {
52    ($self:expr) => {{
53        let (open, reset, grey, gray, white) = ansi_palette();
54        format!(
55            "[{ulid} - - -]\t{open}MUX{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}peer{reset}={white}{peer:?}{reset}, {gray}streams{reset}={white}{streams}{reset}, {gray}backends{reset}={white}{backends}{reset}, {gray}pending_links{reset}={white}{pending_links}{reset}, {gray}readiness{reset}={white}{readiness}{reset})\t >>>",
56            open = open,
57            reset = reset,
58            grey = grey,
59            gray = gray,
60            white = white,
61            ulid = $self.session_ulid,
62            frontend = $self.frontend_token.0,
63            peer = $self.frontend.socket().peer_addr().ok(),
64            streams = $self.context.streams.len(),
65            backends = $self.router.backends.len(),
66            pending_links = $self.context.pending_links.len(),
67            readiness = $self.frontend.readiness(),
68        )
69    }};
70}
71
72/// Lighter variant of [`log_context!`] that omits the
73/// `streams`/`backends`/`pending_links` counts. Used at sites where the
74/// borrow checker forbids reading `self.router.backends` or
75/// `self.context.streams` (e.g. inside a method that already holds a mutable
76/// borrow on one of them). The ULID and frontend snapshot still carry enough
77/// context to correlate the line back to the rest of the session.
78macro_rules! log_context_lite {
79    ($self:expr) => {{
80        let (open, reset, grey, gray, white) = ansi_palette();
81        format!(
82            "[{ulid} - - -]\t{open}MUX{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}peer{reset}={white}{peer:?}{reset}, {gray}readiness{reset}={white}{readiness}{reset})\t >>>",
83            open = open,
84            reset = reset,
85            grey = grey,
86            gray = gray,
87            white = white,
88            ulid = $self.session_ulid,
89            frontend = $self.frontend_token.0,
90            peer = $self.frontend.socket().peer_addr().ok(),
91            readiness = $self.frontend.readiness(),
92        )
93    }};
94}
95
96/// Module-level prefix for logs emitted from free functions or routing
97/// blocks where no [`Mux`] is in scope. Honours the colored flag.
98///
99/// Two arms:
100/// * `log_module_context!()` — zero-arg, legacy `MUX\t >>>` output. Kept
101///   for sites without an `HttpContext` in scope (e.g. the generic
102///   `trace!` that fires before the variant-specific match).
103/// * `log_module_context!($http_context)` — rich form. `$http_context`
104///   must be `&HttpContext`. Produces the same
105///   `[session req cluster backend]` bracket as RUSTLS/PIPE/TCP followed
106///   by a `Session(...)` block, so MUX lines emitted from variant match
107///   arms stay filterable by session ULID or request ULID. Mirrors
108///   `router.rs:log_module_context!($http_context)` (see there).
109macro_rules! log_module_context {
110    () => {{
111        let (open, reset, _, _, _) = ansi_palette();
112        format!("{open}MUX{reset}\t >>>", open = open, reset = reset)
113    }};
114    ($http_context:expr) => {{
115        let (open, reset, grey, gray, white) = ansi_palette();
116        let http_ctx: &HttpContext = &$http_context;
117        let ctx = http_ctx.log_context();
118        format!(
119            "{gray}{ctx}{reset}\t{open}MUX{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend:?}{reset}, {gray}method{reset}={white}{method:?}{reset}, {gray}authority{reset}={white}{authority:?}{reset})\t >>>",
120            open = open,
121            reset = reset,
122            grey = grey,
123            gray = gray,
124            white = white,
125            ctx = ctx,
126            frontend = http_ctx.session_address,
127            method = http_ctx.method,
128            authority = http_ctx.authority,
129        )
130    }};
131}
132
133pub mod answers;
134pub mod auth;
135pub mod connection;
136mod converter;
137pub mod debug;
138mod h1;
139mod h2;
140pub mod parser;
141mod pkawa;
142pub mod router;
143pub(crate) mod serializer;
144mod shared;
145pub mod stream;
146
147use crate::metrics::names;
148use crate::{
149    BackendConnectionError, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerHandler,
150    ProxySession, Readiness, RetrieveClusterError, SessionIsToBeClosed, SessionMetrics,
151    SessionResult, StateResult,
152    backends::{Backend, BackendError},
153    http::HttpListener,
154    https::HttpsListener,
155    pool::{Checkout, Pool},
156    protocol::{SessionState, http::editor::HttpContext},
157    retry::RetryPolicy,
158    server::push_event,
159    socket::{FrontRustls, SessionTcpStream, SocketHandler, SocketResult, stats::socket_rtt},
160};
161
162pub(crate) use crate::protocol::mux::answers::{
163    forcefully_terminate_answer, set_default_answer, set_default_answer_with_retry_after,
164};
165use crate::protocol::mux::connection::{EndpointClient, EndpointServer};
166pub use crate::protocol::mux::{
167    answers::terminate_default_answer,
168    connection::Connection,
169    debug::{DebugEvent, DebugHistory},
170    h1::ConnectionH1,
171    h2::ConnectionH2,
172    h2::H2ByteAccounting,
173    h2::H2ConnectionConfig,
174    h2::H2DrainState,
175    h2::H2FloodConfig,
176    h2::H2FlowControl,
177    parser::H2Error,
178    router::Router,
179    stream::{Stream, StreamParts, StreamState},
180};
181
182// ── Tuning Constants ─────────────────────────────────────────────────────────
183
184/// Maximum event loop iterations before forcefully closing a session.
185/// Prevents infinite loops from consuming the single-threaded worker.
186const MAX_LOOP_ITERATIONS: i32 = 10_000;
187// ─────────────────────────────────────────────────────────────────────────────
188
189/// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer
190type GenericHttpStream = kawa::Kawa<Checkout>;
191type StreamId = u32;
192type GlobalStreamId = usize;
193pub type MuxClear = Mux<SessionTcpStream, HttpListener>;
194pub type MuxTls = Mux<FrontRustls, HttpsListener>;
195
196pub enum Position {
197    Client(String, Rc<RefCell<Backend>>, BackendStatus),
198    Server,
199}
200
201impl Debug for Position {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        match self {
204            Self::Client(cluster_id, _, status) => f
205                .debug_tuple("Client")
206                .field(cluster_id)
207                .field(status)
208                .finish(),
209            Self::Server => write!(f, "Server"),
210        }
211    }
212}
213
214impl Position {
215    fn is_server(&self) -> bool {
216        match self {
217            Position::Client(..) => false,
218            Position::Server => true,
219        }
220    }
221    fn is_client(&self) -> bool {
222        !self.is_server()
223    }
224
225    /// Increment the global `count!()` counter for bytes read on this side.
226    pub fn count_bytes_in_counter(&self, size: usize) {
227        match self {
228            Position::Client(..) => count!(names::backend::BACK_BYTES_IN, size as i64),
229            Position::Server => count!(names::backend::BYTES_IN, size as i64),
230        }
231    }
232
233    /// Increment the global `count!()` counter for bytes written on this side.
234    pub fn count_bytes_out_counter(&self, size: usize) {
235        match self {
236            Position::Client(..) => count!(names::backend::BACK_BYTES_OUT, size as i64),
237            Position::Server => count!(names::backend::BYTES_OUT, size as i64),
238        }
239    }
240
241    /// Attribute `size` bytes read to the appropriate `SessionMetrics` field.
242    pub fn count_bytes_in(&self, metrics: &mut SessionMetrics, size: usize) {
243        match self {
244            Position::Client(..) => metrics.backend_bin += size,
245            Position::Server => metrics.bin += size,
246        }
247    }
248
249    /// Attribute `size` bytes written to the appropriate `SessionMetrics` field.
250    pub fn count_bytes_out(&self, metrics: &mut SessionMetrics, size: usize) {
251        match self {
252            Position::Client(..) => metrics.backend_bout += size,
253            Position::Server => metrics.bout += size,
254        }
255    }
256}
257
258#[derive(Debug)]
259pub enum BackendStatus {
260    Connecting(Instant),
261    Connected,
262    KeepAlive,
263    Disconnecting,
264}
265
266#[derive(Debug, Clone, Copy)]
267pub enum MuxResult {
268    Continue,
269    Upgrade,
270    CloseSession,
271}
272
273pub trait Endpoint: Debug {
274    fn readiness(&self, token: Token) -> &Readiness;
275    fn readiness_mut(&mut self, token: Token) -> &mut Readiness;
276    /// Returns the underlying TCP socket for the peer side of a stream.
277    ///
278    /// Used by access-log emission to capture TCP_INFO RTT for the side the
279    /// caller does NOT own directly: a frontend connection (Position::Server)
280    /// reads the backend socket through this method, and a backend connection
281    /// (Position::Client) reads the frontend socket the same way. `token` is
282    /// ignored by [`super::connection::EndpointServer`] (which has a single
283    /// frontend connection) and used as a key by
284    /// [`super::connection::EndpointClient`] (which keys backends by token).
285    /// Returns `None` when the token doesn't resolve, mirroring the existing
286    /// fallback paths in `readiness`/`readiness_mut`.
287    fn socket(&self, token: Token) -> Option<&TcpStream>;
288    /// If end_stream is called on a client it means the stream has PROPERLY finished,
289    /// the server has completed serving the response and informs the endpoint that this stream won't be used anymore.
290    /// If end_stream is called on a server it means the stream was BROKEN, the client was most likely disconnected or encountered an error
291    /// it is for the server to decide if the stream can be retried or an error should be sent. It should be GUARANTEED that all bytes from
292    /// the backend were read. However it is almost certain that all bytes were not already sent to the client.
293    fn end_stream<L: ListenerHandler + L7ListenerHandler>(
294        &mut self,
295        token: Token,
296        stream: GlobalStreamId,
297        context: &mut Context<L>,
298    );
299    /// If start_stream is called on a client it means the stream should be attached to this endpoint,
300    /// the stream might be recovering from a disconnection, in any case at this point its response MUST be empty.
301    /// If the start_stream is called on a H2 server it means the stream is a server push and its request MUST be empty.
302    /// Returns false if the stream could not be started (e.g. max concurrent streams reached).
303    fn start_stream<L: ListenerHandler + L7ListenerHandler>(
304        &mut self,
305        token: Token,
306        stream: GlobalStreamId,
307        context: &mut Context<L>,
308    ) -> bool;
309}
310
311/// Shared logic for half-close accounting: clear `bit` from `readiness.event` on
312/// socket errors/would-block, and return `true` (yield) when no bytes were
313/// transferred so the caller can park the half. Rationale for clearing only
314/// `bit` (not both halves) on `Closed`: the opposite half may still need one
315/// last pass to flush queued frames or the TLS close_notify.
316fn update_readiness(
317    size: usize,
318    status: SocketResult,
319    readiness: &mut Readiness,
320    bit: Ready,
321) -> bool {
322    trace!(
323        "{}   size={}, status={:?}",
324        log_module_context!(),
325        size,
326        status
327    );
328    match status {
329        SocketResult::Continue => {}
330        SocketResult::Closed | SocketResult::Error | SocketResult::WouldBlock => {
331            readiness.event.remove(bit);
332        }
333    }
334    if size > 0 {
335        false
336    } else {
337        readiness.event.remove(bit);
338        true
339    }
340}
341
342fn update_readiness_after_read(
343    size: usize,
344    status: SocketResult,
345    readiness: &mut Readiness,
346) -> bool {
347    update_readiness(size, status, readiness, Ready::READABLE)
348}
349
350fn update_readiness_after_write(
351    size: usize,
352    status: SocketResult,
353    readiness: &mut Readiness,
354) -> bool {
355    update_readiness(size, status, readiness, Ready::WRITABLE)
356}
357pub struct Context<L: ListenerHandler + L7ListenerHandler> {
358    pub streams: Vec<Stream>,
359    /// Streams whose state is `StreamState::Link` and need backend connection.
360    /// Replaces the O(n) scan of `streams` in the ready loop.
361    pub pending_links: VecDeque<GlobalStreamId>,
362    /// Reverse index: backend token -> global stream IDs currently in
363    /// `StreamState::Linked(token)`. Eliminates O(n) scans of `streams`
364    /// when handling backend connect/disconnect/timeout/close events.
365    pub backend_streams: HashMap<Token, Vec<GlobalStreamId>>,
366    pub pool: Weak<RefCell<Pool>>,
367    pub listener: Rc<RefCell<L>>,
368    /// Connection/session ULID — mirrors `Mux.session_ulid`. Stored here so
369    /// per-stream `HttpContext` construction in [`Self::create_stream`] can
370    /// stamp the session slot of the log-context bracket without reaching
371    /// back into the parent [`Mux`].
372    pub session_ulid: Ulid,
373    pub session_address: Option<SocketAddr>,
374    pub public_address: SocketAddr,
375    pub debug: DebugHistory,
376    /// Shrink threshold ratio for recycled stream slots.
377    /// Vec is shrunk when total_slots > active_streams * ratio.
378    pub h2_stream_shrink_ratio: usize,
379    /// TLS SNI value negotiated at handshake, propagated to every
380    /// per-stream [`HttpContext`] so the routing layer can enforce
381    /// the SNI ↔ `:authority` binding on every H2 stream (and the
382    /// single H1 request). `None` for plaintext listeners or when
383    /// the client omitted the SNI extension. Stored pre-lowercased
384    /// and without a port for cheap exact-match comparison.
385    pub tls_server_name: Option<String>,
386    /// Snapshot of the SAN set of the certificate Sōzu actually served at
387    /// the TLS handshake. Captured once in `https.rs::upgrade_handshake`
388    /// from the resolver and frozen for the connection lifetime so H2
389    /// stream coalescing (RFC 7540 §9.1.1 / RFC 9113 §9.1.1) accepts any
390    /// `:authority` covered by the certificate, with RFC 6125 §6.4.3
391    /// wildcard handling. `None` for plaintext listeners or when SNI was
392    /// absent. `Some(empty)` when the default cert was served — every
393    /// `:authority` is rejected. `Arc` so the snapshot is shared across
394    /// every per-stream `HttpContext` without re-allocation.
395    pub tls_cert_names: Option<Arc<Vec<String>>>,
396    /// Whether the routing layer must reject any request whose authority
397    /// host does not exact-match `tls_server_name` (CWE-346 / CWE-444).
398    /// Mirrors `HttpsListenerConfig::strict_sni_binding`; captured once
399    /// at `Context::new` so routing decisions on each stream avoid a
400    /// per-stream `listener.borrow()`.
401    pub strict_sni_binding: bool,
402    /// Whether the request-side block walk must strip any client-supplied
403    /// `X-Real-IP` header before forwarding (anti-spoofing). Mirrors
404    /// `HttpListenerConfig::elide_x_real_ip` /
405    /// `HttpsListenerConfig::elide_x_real_ip`; captured once at
406    /// `Context::new` so per-stream `HttpContext`s do not need to call
407    /// `listener.borrow()` again. Independent of `send_x_real_ip`.
408    pub elide_x_real_ip: bool,
409    /// Whether `on_request_headers` injects a proxy-generated `X-Real-IP`
410    /// header carrying the connection peer IP (post-PROXY-v2 unwrap).
411    /// Mirrors `HttpListenerConfig::send_x_real_ip` /
412    /// `HttpsListenerConfig::send_x_real_ip`; captured once at
413    /// `Context::new`. Independent of `elide_x_real_ip`.
414    pub send_x_real_ip: bool,
415    /// Negotiated TLS protocol version short-form (e.g. `"TLSv1.3"`).
416    /// Captured once at handshake completion in `https.rs` and propagated
417    /// to every per-stream [`HttpContext`] so the access log can record it
418    /// without reaching back into the rustls session per request. `None`
419    /// for plaintext listeners.
420    pub tls_version: Option<&'static str>,
421    /// Negotiated TLS cipher suite short-form (e.g.
422    /// `"TLS_AES_128_GCM_SHA256"`). Captured once at handshake completion
423    /// and propagated to every per-stream [`HttpContext`]. `None` for
424    /// plaintext listeners.
425    pub tls_cipher: Option<&'static str>,
426    /// Negotiated ALPN protocol short-form (e.g. `"h2"`, `"http/1.1"`).
427    /// Captured once at handshake completion and propagated to every
428    /// per-stream [`HttpContext`]. `None` for plaintext listeners or when
429    /// no ALPN was negotiated.
430    pub tls_alpn: Option<&'static str>,
431}
432
433impl<L: ListenerHandler + L7ListenerHandler> Context<L> {
434    pub fn new(
435        session_ulid: Ulid,
436        pool: Weak<RefCell<Pool>>,
437        listener: Rc<RefCell<L>>,
438        session_address: Option<SocketAddr>,
439        public_address: SocketAddr,
440    ) -> Self {
441        let h2_stream_shrink_ratio = listener
442            .borrow()
443            .get_h2_connection_config()
444            .stream_shrink_ratio as usize;
445        let strict_sni_binding = listener.borrow().get_strict_sni_binding();
446        let elide_x_real_ip = listener.borrow().get_elide_x_real_ip();
447        let send_x_real_ip = listener.borrow().get_send_x_real_ip();
448        Self {
449            streams: Vec::new(),
450            pending_links: VecDeque::new(),
451            backend_streams: HashMap::new(),
452            pool,
453            listener,
454            session_ulid,
455            session_address,
456            public_address,
457            debug: DebugHistory::new(),
458            h2_stream_shrink_ratio,
459            tls_server_name: None,
460            tls_cert_names: None,
461            strict_sni_binding,
462            elide_x_real_ip,
463            send_x_real_ip,
464            tls_version: None,
465            tls_cipher: None,
466            tls_alpn: None,
467        }
468    }
469
470    pub fn active_len(&self) -> usize {
471        self.streams
472            .iter()
473            .filter(|s| !matches!(s.state, StreamState::Recycle))
474            .count()
475    }
476
477    /// Shared accessor for the [`HttpContext`] owned by a stream.
478    ///
479    /// Prefer this over `&self.streams[stream_id].context` at call sites
480    /// that only need read access — it keeps the `Stream`/`HttpContext`
481    /// relationship encapsulated and reads the same regardless of whether
482    /// the caller is inside `Router::connect`, the H2 mux, or a free
483    /// helper. Panics on an out-of-bounds `stream_id`, which is the same
484    /// behaviour as the raw `streams[sid]` indexing it replaces.
485    pub fn http_context(&self, stream_id: GlobalStreamId) -> &HttpContext {
486        &self.streams[stream_id].context
487    }
488
489    /// Mutable sibling of [`Self::http_context`]. Use when routing
490    /// decisions need to stamp `cluster_id` / `backend_id` on the stream's
491    /// [`HttpContext`] (e.g. `Router::connect` at the fill-cluster /
492    /// fill-backend points).
493    pub fn http_context_mut(&mut self, stream_id: GlobalStreamId) -> &mut HttpContext {
494        &mut self.streams[stream_id].context
495    }
496
497    /// Register a stream as linked to a backend token in the reverse index.
498    pub fn link_stream(&mut self, stream_id: GlobalStreamId, token: Token) {
499        self.streams[stream_id].state = StreamState::Linked(token);
500        self.backend_streams
501            .entry(token)
502            .or_default()
503            .push(stream_id);
504    }
505
506    /// Remove a stream from the backend reverse index if it is currently
507    /// `Linked`. Returns the backend token if one was removed.
508    pub fn unlink_stream(&mut self, stream_id: GlobalStreamId) -> Option<Token> {
509        if let StreamState::Linked(token) = self.streams[stream_id].state {
510            remove_backend_stream(&mut self.backend_streams, token, stream_id);
511            Some(token)
512        } else {
513            None
514        }
515    }
516
517    pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option<GlobalStreamId> {
518        let http_context = {
519            let listener = self.listener.borrow();
520            let mut http_context = HttpContext::new(
521                self.session_ulid,
522                request_id,
523                listener.protocol(),
524                self.public_address,
525                self.session_address,
526                listener.get_sticky_name().to_string(),
527                listener.get_sozu_id_header().to_string(),
528                self.elide_x_real_ip,
529                self.send_x_real_ip,
530            );
531            // Propagate the connection-scoped TLS SNI onto every per-stream
532            // HttpContext so `route_from_request` can enforce the SNI ↔
533            // `:authority` binding for each H2 stream independently.
534            http_context.tls_server_name = self.tls_server_name.clone();
535            // Mirror the frozen-at-handshake SAN snapshot. `Arc` clone is a
536            // refcount bump, not a deep copy — every per-stream
537            // `HttpContext` shares the same `Vec<String>`.
538            http_context.tls_cert_names = self.tls_cert_names.clone();
539            // Mirror the listener's strict_sni_binding flag onto each
540            // HttpContext so the routing layer can honor operator opt-outs
541            // without reaching back into the listener on every request.
542            http_context.strict_sni_binding = self.strict_sni_binding;
543            // Propagate the connection-scoped TLS metadata onto every
544            // per-stream HttpContext so the access log can record it without
545            // touching the rustls session on every request. These are
546            // `&'static str` borrows from the rustls label tables — copy is
547            // a pointer move.
548            http_context.tls_version = self.tls_version;
549            http_context.tls_cipher = self.tls_cipher;
550            http_context.tls_alpn = self.tls_alpn;
551            http_context
552        };
553        let recycle_slot = self
554            .streams
555            .iter()
556            .position(|s| s.state == StreamState::Recycle);
557        if let Some(stream_id) = recycle_slot {
558            let stream = &mut self.streams[stream_id];
559            trace!("{} Reuse stream: {}", log_module_context!(), stream_id);
560            stream.state = StreamState::Idle;
561            stream.attempts = 0;
562            stream.front_received_end_of_stream = false;
563            stream.back_received_end_of_stream = false;
564            stream.front_data_received = 0;
565            stream.back_data_received = 0;
566            stream.request_counted = false;
567            stream.window = i32::try_from(window).unwrap_or(i32::MAX);
568            stream.context = http_context;
569            stream.back.clear();
570            stream.back.storage.clear();
571            stream.front.clear();
572            stream.front.storage.clear();
573            stream.metrics.reset();
574            stream.metrics.mark_request_start();
575            // After recycling a slot, check if the Vec has excessive trailing
576            // Recycle entries (more than 2x active streams of total capacity).
577            let active = self.active_len();
578            let total = self.streams.len();
579            if total > 1 && active > 0 && total > active * self.h2_stream_shrink_ratio {
580                self.shrink_trailing_recycle();
581            }
582            return Some(stream_id);
583        }
584        self.streams
585            .push(Stream::new(self.pool.clone(), http_context, window)?);
586        Some(self.streams.len() - 1)
587    }
588
589    /// Remove consecutive `Recycle` entries from the end of the streams Vec.
590    ///
591    /// This prevents unbounded growth when H2 streams are created and recycled
592    /// over time, reclaiming memory from slots that are no longer needed.
593    pub fn shrink_trailing_recycle(&mut self) {
594        while self
595            .streams
596            .last()
597            .is_some_and(|s| s.state == StreamState::Recycle)
598        {
599            self.streams.pop();
600        }
601    }
602}
603
604/// Remove `stream_id` from the backend-token reverse index for `token`.
605/// Free function to allow split borrows when `context.streams` is already
606/// mutably borrowed (preventing a `Context::unlink_stream` call).
607pub(super) fn remove_backend_stream(
608    index: &mut HashMap<Token, Vec<GlobalStreamId>>,
609    token: Token,
610    stream_id: GlobalStreamId,
611) {
612    if let Some(ids) = index.get_mut(&token) {
613        ids.retain(|&id| id != stream_id);
614        if ids.is_empty() {
615            index.remove(&token);
616        }
617    }
618}
619
620pub struct Mux<Front: SocketHandler, L: ListenerHandler + L7ListenerHandler> {
621    pub configured_frontend_timeout: Duration,
622    pub frontend_token: Token,
623    pub frontend: Connection<Front>,
624    pub router: Router,
625    pub context: Context<L>,
626    /// Per-session correlation ID generated at construction time. Included in
627    /// every log line emitted from this module so all events for a single
628    /// frontend connection can be reassembled (independent of the ephemeral
629    /// per-stream request id used by access logs).
630    pub session_ulid: Ulid,
631}
632
633impl<Front: SocketHandler, L: ListenerHandler + L7ListenerHandler> Mux<Front, L> {
634    pub fn front_socket(&self) -> &TcpStream {
635        self.frontend.socket()
636    }
637}
638
639impl<Front: SocketHandler + std::fmt::Debug, L: ListenerHandler + L7ListenerHandler> Mux<Front, L> {
640    fn sync_upgrade_buffers(&mut self) {
641        for stream in &mut self.context.streams {
642            stream
643                .front
644                .storage
645                .buffer
646                .sync(stream.front.storage.end, stream.front.storage.head);
647            stream
648                .back
649                .storage
650                .buffer
651                .sync(stream.back.storage.end, stream.back.storage.head);
652        }
653    }
654
655    fn delay_close_for_frontend_flush(&mut self, reason: &'static str) -> bool {
656        let _ = self.frontend.initiate_close_notify();
657        // LIFECYCLE §9 invariant 16: consult per-stream back-buffers in
658        // addition to the connection-level pending-write predicate so
659        // shutdown does not close while any open H2 stream still has
660        // kawa bytes queued after a voluntary scheduler yield.
661        if self
662            .frontend
663            .has_pending_write_including_streams(&self.context)
664        {
665            let readiness = self.frontend.readiness_mut();
666            readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR;
667            readiness.signal_pending_write();
668            debug!(
669                "{} Mux delaying close on {}: {:?}",
670                log_context!(self),
671                reason,
672                self.frontend
673            );
674            true
675        } else {
676            false
677        }
678    }
679
680    /// Drive the frontend I/O path during shutdown, when the server is polling
681    /// `shutting_down()` outside the normal epoll readiness loop.
682    ///
683    /// This is required for H2 graceful shutdown because a stream may still
684    /// need one last readable pass to observe the peer's END_STREAM or one last
685    /// writable pass to retire the stream, emit GOAWAY, or flush TLS records.
686    fn drive_frontend_shutdown_io(&mut self) -> SessionIsToBeClosed {
687        let force_h2_read = matches!(self.frontend, Connection::H2(_));
688        let force_h2_write = matches!(self.frontend, Connection::H2(_));
689        let readiness = self.frontend.readiness().clone();
690        if !force_h2_read
691            && !force_h2_write
692            && readiness.event.is_empty()
693            && !self.frontend.has_pending_write()
694        {
695            return false;
696        }
697
698        if force_h2_read || self.frontend.readiness().event.is_readable() {
699            self.frontend
700                .readiness_mut()
701                .interest
702                .insert(Ready::READABLE);
703            match self
704                .frontend
705                .readable(&mut self.context, EndpointClient(&mut self.router))
706            {
707                MuxResult::Continue => {}
708                MuxResult::CloseSession | MuxResult::Upgrade => return true,
709            }
710        }
711
712        if !force_h2_write
713            && !self.frontend.has_pending_write()
714            && !self.frontend.readiness().event.is_writable()
715        {
716            return false;
717        }
718
719        let mut iterations = 0;
720        loop {
721            self.frontend
722                .readiness_mut()
723                .interest
724                .insert(Ready::WRITABLE);
725            if force_h2_write {
726                self.frontend.readiness_mut().signal_pending_write();
727            }
728            match self
729                .frontend
730                .writable(&mut self.context, EndpointClient(&mut self.router))
731            {
732                MuxResult::Continue => {}
733                MuxResult::CloseSession | MuxResult::Upgrade => return true,
734            }
735
736            iterations += 1;
737            if iterations >= MAX_LOOP_ITERATIONS
738                || (!self.frontend.has_pending_write()
739                    && !self.frontend.readiness().event.is_writable())
740            {
741                break;
742            }
743        }
744        false
745    }
746}
747
748impl<Front: SocketHandler + std::fmt::Debug, L: ListenerHandler + L7ListenerHandler> SessionState
749    for Mux<Front, L>
750{
751    fn ready(
752        &mut self,
753        session: Rc<RefCell<dyn ProxySession>>,
754        proxy: Rc<RefCell<dyn L7Proxy>>,
755        _metrics: &mut SessionMetrics,
756    ) -> SessionResult {
757        let mut counter = 0;
758
759        if self.frontend.readiness().event.is_hup()
760            && !self.delay_close_for_frontend_flush("frontend HUP")
761        {
762            debug!(
763                "{} Mux closing on frontend HUP: {:?}",
764                log_context!(self),
765                self.frontend
766            );
767            return SessionResult::Close;
768        }
769
770        // Start service timers on all active streams after the HUP check.
771        // This mirrors session-level service_start/service_stop in Http(s)Session::ready()
772        // to measure only CPU processing time, excluding epoll wait between cycles.
773        for stream in &mut self.context.streams {
774            if stream.state.is_open() {
775                stream.metrics.service_start();
776            }
777        }
778
779        let start = Instant::now();
780        self.context.debug.push(DebugEvent::ReadyTimestamp(
781            std::time::SystemTime::now()
782                .duration_since(std::time::UNIX_EPOCH)
783                .unwrap_or_default()
784                .as_millis() as usize,
785        ));
786        trace!("{} {:?}", log_context!(self), start);
787        loop {
788            self.context.debug.push(DebugEvent::LoopStart);
789            loop {
790                self.context.debug.push(DebugEvent::LoopIteration(counter));
791                if self.frontend.readiness().filter_interest().is_readable() {
792                    let res = {
793                        let context = &mut self.context;
794                        let res = self
795                            .frontend
796                            .readable(context, EndpointClient(&mut self.router));
797                        context.debug.push(DebugEvent::SR(
798                            self.frontend_token,
799                            res,
800                            self.frontend.readiness().clone(),
801                        ));
802                        res
803                    };
804                    match res {
805                        MuxResult::Continue => {}
806                        MuxResult::CloseSession => {
807                            if !self.delay_close_for_frontend_flush("frontend readable") {
808                                debug!(
809                                    "{} Mux close from frontend readable: {:?}",
810                                    log_context!(self),
811                                    self.frontend
812                                );
813                                return SessionResult::Close;
814                            }
815                        }
816                        MuxResult::Upgrade => {
817                            self.sync_upgrade_buffers();
818                            return SessionResult::Upgrade;
819                        }
820                    }
821                }
822
823                let mut all_backends_readiness_are_empty = true;
824                let mut dead_backends = Vec::new();
825                let mut backend_close: Option<(&'static str, Token)> = None;
826                for (token, client) in self.router.backends.iter_mut() {
827                    let readiness = client.readiness_mut();
828                    // Check the raw event for HUP/ERROR — not filter_interest(),
829                    // because interest only contains READABLE|WRITABLE and would
830                    // always mask out HUP (0b01000) and ERROR (0b00100).
831                    let dead = readiness.event.is_hup() || readiness.event.is_error();
832                    if dead {
833                        trace!(
834                            "{} Backend({:?}) -> {:?}",
835                            log_context_lite!(self),
836                            token,
837                            readiness
838                        );
839                        readiness.event.remove(Ready::WRITABLE);
840                    }
841
842                    if client.readiness().filter_interest().is_writable() {
843                        let position = client.position_mut();
844                        match position {
845                            Position::Client(
846                                cluster_id,
847                                backend,
848                                BackendStatus::Connecting(start),
849                            ) => {
850                                #[cfg(debug_assertions)]
851                                self.context
852                                    .debug
853                                    .push(DebugEvent::CCS(*token, cluster_id.clone()));
854
855                                let mut backend_borrow = backend.borrow_mut();
856                                if backend_borrow.retry_policy.is_down() {
857                                    info!(
858                                        "{} backend server {} at {} is up",
859                                        log_context_lite!(self),
860                                        backend_borrow.backend_id,
861                                        backend_borrow.address
862                                    );
863                                    incr!(
864                                        "backend.up",
865                                        Some(cluster_id),
866                                        Some(&backend_borrow.backend_id)
867                                    );
868                                    gauge!(
869                                        names::backend::AVAILABLE,
870                                        1,
871                                        Some(cluster_id),
872                                        Some(&backend_borrow.backend_id)
873                                    );
874                                    push_event(Event {
875                                        kind: EventKind::BackendUp as i32,
876                                        backend_id: Some(backend_borrow.backend_id.to_owned()),
877                                        address: Some(backend_borrow.address.into()),
878                                        cluster_id: Some(cluster_id.to_owned()),
879                                        metric_detail: None,
880                                    });
881                                }
882
883                                //successful connection, reset failure counter
884                                backend_borrow.failures = 0;
885                                backend_borrow.set_connection_time(start.elapsed());
886                                backend_borrow.retry_policy.succeed();
887
888                                if let Some(ids) = self.context.backend_streams.get(token) {
889                                    for &stream_id in ids {
890                                        self.context.streams[stream_id].metrics.backend_connected();
891                                        backend_borrow.active_requests += 1;
892                                    }
893                                }
894                                trace!(
895                                    "{} connection success: {:#?}",
896                                    log_context_lite!(self),
897                                    backend_borrow
898                                );
899                                drop(backend_borrow);
900                                *position = Position::Client(
901                                    std::mem::take(cluster_id),
902                                    backend.clone(),
903                                    BackendStatus::Connected,
904                                );
905                                client
906                                    .timeout_container()
907                                    .set_duration(self.router.configured_backend_timeout);
908                            }
909                            Position::Client(..) => {}
910                            Position::Server => {
911                                error!(
912                                    "{} backend connection cannot be in Server position",
913                                    log_context_lite!(self)
914                                );
915                            }
916                        }
917                        let res = {
918                            let context = &mut self.context;
919                            let res = client.writable(context, EndpointServer(&mut self.frontend));
920                            context.debug.push(DebugEvent::CW(
921                                *token,
922                                res,
923                                client.readiness().clone(),
924                            ));
925                            res
926                        };
927                        match res {
928                            MuxResult::Continue => {}
929                            MuxResult::Upgrade => {
930                                error!(
931                                    "{} only frontend connections can trigger Upgrade",
932                                    log_context_lite!(self)
933                                );
934                            }
935                            MuxResult::CloseSession => {
936                                backend_close = Some(("backend writable", *token));
937                                break;
938                            }
939                        }
940                        // Cross-readiness: backend wrote → wake frontend reader
941                        let context = &mut self.context;
942                        self.frontend.try_resume_reading(context);
943                    }
944
945                    if client.readiness().filter_interest().is_readable() {
946                        let res = {
947                            let context = &mut self.context;
948                            let res = client.readable(context, EndpointServer(&mut self.frontend));
949                            context.debug.push(DebugEvent::CR(
950                                *token,
951                                res,
952                                client.readiness().clone(),
953                            ));
954                            res
955                        };
956                        match res {
957                            MuxResult::Continue => {}
958                            MuxResult::Upgrade => {
959                                error!(
960                                    "{} only frontend connections can trigger Upgrade (readable)",
961                                    log_context_lite!(self)
962                                );
963                            }
964                            MuxResult::CloseSession => {
965                                backend_close = Some(("backend readable", *token));
966                                break;
967                            }
968                        }
969                    }
970
971                    if dead
972                        && !client.readiness().filter_interest().is_readable()
973                        && !client.has_buffer_pressure(&self.context)
974                    {
975                        self.context
976                            .debug
977                            .push(DebugEvent::CH(*token, client.readiness().clone()));
978                        trace!("{} Closing {:#?}", log_context_lite!(self), client);
979                        match client.position() {
980                            Position::Client(cluster_id, backend, BackendStatus::Connecting(_)) => {
981                                let mut backend_borrow = backend.borrow_mut();
982                                backend_borrow.failures += 1;
983
984                                let already_unavailable = backend_borrow.retry_policy.is_down();
985                                backend_borrow.retry_policy.fail();
986                                incr!(
987                                    "backend.connections.error",
988                                    Some(cluster_id),
989                                    Some(&backend_borrow.backend_id)
990                                );
991                                if !already_unavailable && backend_borrow.retry_policy.is_down() {
992                                    error!(
993                                        "{} backend server {} at {} is down",
994                                        log_context_lite!(self),
995                                        backend_borrow.backend_id,
996                                        backend_borrow.address
997                                    );
998                                    incr!(
999                                        "backend.down",
1000                                        Some(cluster_id),
1001                                        Some(&backend_borrow.backend_id)
1002                                    );
1003                                    gauge!(
1004                                        names::backend::AVAILABLE,
1005                                        0,
1006                                        Some(cluster_id),
1007                                        Some(&backend_borrow.backend_id)
1008                                    );
1009                                    push_event(Event {
1010                                        kind: EventKind::BackendDown as i32,
1011                                        backend_id: Some(backend_borrow.backend_id.to_owned()),
1012                                        address: Some(backend_borrow.address.into()),
1013                                        cluster_id: Some(cluster_id.to_owned()),
1014                                        metric_detail: None,
1015                                    });
1016                                }
1017                                trace!(
1018                                    "{} connection fail: {:#?}",
1019                                    log_context_lite!(self),
1020                                    backend_borrow
1021                                );
1022                            }
1023                            Position::Client(_, backend, _) => {
1024                                let mut backend_borrow = backend.borrow_mut();
1025                                let count = self
1026                                    .context
1027                                    .backend_streams
1028                                    .get(token)
1029                                    .map_or(0, |ids| ids.len());
1030                                backend_borrow.active_requests =
1031                                    backend_borrow.active_requests.saturating_sub(count);
1032                            }
1033                            Position::Server => {
1034                                error!(
1035                                    "{} dead backend cannot be in Server position",
1036                                    log_context_lite!(self)
1037                                );
1038                            }
1039                        }
1040                        client.close(&mut self.context, EndpointServer(&mut self.frontend));
1041                        dead_backends.push(*token);
1042                    }
1043
1044                    if !client.readiness().filter_interest().is_empty() {
1045                        all_backends_readiness_are_empty = false;
1046                    }
1047                }
1048                // Remove dead backends from the map BEFORE handling
1049                // backend_close. client.close() already decremented
1050                // connections_per_backend / backend.connections gauges in
1051                // the loop above; if we return SessionResult::Close before
1052                // removing them, Mux::close() would decrement again
1053                // (double-decrement → gauge underflow).
1054                if !dead_backends.is_empty() {
1055                    for token in &dead_backends {
1056                        let proxy_borrow = proxy.borrow();
1057                        if let Some(mut client) = self.router.backends.remove(token) {
1058                            client.timeout_container().cancel();
1059                            let socket = client.socket_mut();
1060                            if let Err(e) = proxy_borrow.deregister_socket(socket) {
1061                                error!(
1062                                    "{} error deregistering back socket({:?}): {:?}",
1063                                    log_context!(self),
1064                                    socket,
1065                                    e
1066                                );
1067                            }
1068                            // invariant: write-only shutdown — Shutdown::Both on a TLS frontend
1069                            // discards the receive buffer and elicits TCP RST, truncating the
1070                            // already-queued response. Canonical write-up: `lib/src/https.rs:650-655`.
1071                            // Backend sockets follow the same discipline for symmetry.
1072                            if let Err(e) = socket.shutdown(Shutdown::Write)
1073                                && e.kind() != ErrorKind::NotConnected
1074                            {
1075                                error!(
1076                                    "{} error shutting down back socket({:?}): {:?}",
1077                                    log_context!(self),
1078                                    socket,
1079                                    e
1080                                );
1081                            }
1082                        } else {
1083                            error!("{} session {:?} has no backend!", log_context!(self), token);
1084                        }
1085                        if !proxy_borrow.remove_session(*token) {
1086                            error!(
1087                                "{} session {:?} was already removed!",
1088                                log_context!(self),
1089                                token
1090                            );
1091                        }
1092                    }
1093                    trace!("{} FRONTEND: {:#?}", log_context!(self), self.frontend);
1094                    trace!(
1095                        "{} BACKENDS: {:#?}",
1096                        log_context!(self),
1097                        self.router.backends
1098                    );
1099                }
1100                if let Some((reason, token)) = backend_close {
1101                    if !self.delay_close_for_frontend_flush(reason) {
1102                        debug!(
1103                            "{} Mux close from {} token={:?}: frontend={:?}",
1104                            log_context!(self),
1105                            reason,
1106                            token,
1107                            self.frontend
1108                        );
1109                        return SessionResult::Close;
1110                    }
1111                    all_backends_readiness_are_empty = false;
1112                }
1113
1114                if self.frontend.readiness().filter_interest().is_writable() {
1115                    let res = {
1116                        let context = &mut self.context;
1117                        let res = self
1118                            .frontend
1119                            .writable(context, EndpointClient(&mut self.router));
1120                        context.debug.push(DebugEvent::SW(
1121                            self.frontend_token,
1122                            res,
1123                            self.frontend.readiness().clone(),
1124                        ));
1125                        res
1126                    };
1127                    match res {
1128                        MuxResult::Continue => {}
1129                        MuxResult::CloseSession => {
1130                            if !self.delay_close_for_frontend_flush("frontend writable") {
1131                                debug!(
1132                                    "{} Mux close from frontend writable: {:?}",
1133                                    log_context!(self),
1134                                    self.frontend
1135                                );
1136                                return SessionResult::Close;
1137                            }
1138                        }
1139                        MuxResult::Upgrade => {
1140                            self.sync_upgrade_buffers();
1141                            return SessionResult::Upgrade;
1142                        }
1143                    }
1144                    // Cross-readiness: frontend wrote → wake parked backends.
1145                    // If any backend resumes, invalidate the stale readiness
1146                    // flag so the inner loop continues instead of breaking.
1147                    let context = &mut self.context;
1148                    for (_token, backend) in self.router.backends.iter_mut() {
1149                        if backend.try_resume_reading(context) {
1150                            all_backends_readiness_are_empty = false;
1151                        }
1152                    }
1153                }
1154
1155                if self.frontend.readiness().filter_interest().is_empty()
1156                    && all_backends_readiness_are_empty
1157                {
1158                    break;
1159                }
1160
1161                counter += 1;
1162                if counter >= MAX_LOOP_ITERATIONS {
1163                    incr!(names::http::INFINITE_LOOP_ERROR);
1164                    if self.frontend.has_pending_write() {
1165                        debug!(
1166                            "{} Mux loop budget exhausted while frontend flush pending: {:?}",
1167                            log_context!(self),
1168                            self.frontend
1169                        );
1170                        self.frontend.readiness_mut().event.remove(Ready::WRITABLE);
1171                        self.frontend.timeout_container().set(self.frontend_token);
1172                        break;
1173                    }
1174                    return SessionResult::Close;
1175                }
1176            }
1177
1178            let context = &mut self.context;
1179            let answers_rc = context.listener.borrow().get_answers().clone();
1180            let mut dirty = false;
1181            while let Some(stream_id) = context.pending_links.pop_front() {
1182                let Some(stream) = context.streams.get(stream_id) else {
1183                    continue;
1184                };
1185                if stream.state != StreamState::Link {
1186                    continue;
1187                }
1188                // Before the first request triggers a stream Link, the frontend timeout is set
1189                // to a shorter request_timeout, here we switch to the longer nominal timeout
1190                self.frontend
1191                    .timeout_container()
1192                    .set_duration(self.configured_frontend_timeout);
1193                let front_readiness = self.frontend.readiness_mut();
1194                dirty = true;
1195                match self.router.connect(
1196                    stream_id,
1197                    context,
1198                    session.clone(),
1199                    proxy.clone(),
1200                    self.frontend_token,
1201                ) {
1202                    Ok(_) => {
1203                        let state = context.streams[stream_id].state;
1204                        context.debug.push(DebugEvent::CC(stream_id, state));
1205                    }
1206                    Err(error) => {
1207                        trace!("{} Connection error: {}", log_module_context!(), error);
1208                        let stream = &mut context.streams[stream_id];
1209                        let answers = answers_rc.borrow();
1210                        use BackendConnectionError as BE;
1211                        match error {
1212                            BE::MaxConnectionRetries(_)
1213                            | BE::MaxSessionsMemory
1214                            | BE::MaxBuffers => {
1215                                warn!(
1216                                    "{} backend retry budget exhausted: {}",
1217                                    log_module_context!(stream.context),
1218                                    error
1219                                );
1220                                set_default_answer(stream, front_readiness, 503, &answers);
1221                            }
1222                            BE::Backend(BackendError::NoBackendForCluster(_)) => {
1223                                set_default_answer(stream, front_readiness, 503, &answers);
1224                            }
1225                            BE::RetrieveClusterError(RetrieveClusterError::RetrieveFrontend(
1226                                ref err,
1227                            )) => {
1228                                // RFC 9110 §15.5.1: a malformed authority is a
1229                                // 400. A syntactically valid authority that
1230                                // simply has no matching frontend stays on the
1231                                // historical 404 path.
1232                                let code = match err {
1233                                    FrontendFromRequestError::HostParse { .. }
1234                                    | FrontendFromRequestError::InvalidCharsAfterHost(_) => 400,
1235                                    FrontendFromRequestError::NoClusterFound(_) => 404,
1236                                };
1237                                set_default_answer(stream, front_readiness, code, &answers);
1238                            }
1239                            BE::RetrieveClusterError(RetrieveClusterError::UnauthorizedRoute) => {
1240                                set_default_answer(stream, front_readiness, 401, &answers);
1241                            }
1242                            BE::RetrieveClusterError(
1243                                RetrieveClusterError::SniAuthorityMismatch { .. },
1244                            ) => {
1245                                // RFC 9110 §15.5.20: 421 Misdirected Request is the
1246                                // semantically correct status for an authority that
1247                                // does not belong to this TLS connection. The
1248                                // http.sni_authority_mismatch metric emitted in
1249                                // `route_from_request` remains the durable signal;
1250                                // the 421 body here is what a client sees and may
1251                                // retry on a fresh TLS connection with a matching SNI.
1252                                set_default_answer(stream, front_readiness, 421, &answers);
1253                            }
1254                            BE::RetrieveClusterError(RetrieveClusterError::HttpsRedirect) => {
1255                                // Use the redirect status stashed by `Router::route_from_request`
1256                                // (#1009). Falls back to 301 for the legacy
1257                                // `cluster.https_redirect = true` path that does
1258                                // not set the field.
1259                                let code = stream.context.redirect_status.unwrap_or(301);
1260                                set_default_answer(stream, front_readiness, code, &answers);
1261                            }
1262
1263                            BE::Backend(ref e) => {
1264                                error!("{} backend connection error: {}", log_module_context!(), e);
1265                                set_default_answer(stream, front_readiness, 503, &answers);
1266                            }
1267                            BE::RetrieveClusterError(ref other) => {
1268                                error!(
1269                                    "{} unexpected RetrieveClusterError variant: {:?}",
1270                                    log_module_context!(),
1271                                    other
1272                                );
1273                                set_default_answer(stream, front_readiness, 503, &answers);
1274                            }
1275                            // TCP specific error
1276                            BE::NotFound(ref msg) => {
1277                                error!(
1278                                    "{} NotFound is TCP-specific, not reachable in mux: {:?}",
1279                                    log_module_context!(),
1280                                    msg
1281                                );
1282                                set_default_answer(stream, front_readiness, 503, &answers);
1283                            }
1284                            // Per-(cluster, source-IP) connection limit reached.
1285                            // Emit HTTP 429 with the resolved `Retry-After`. The
1286                            // value is computed in `Router::connect` (where the
1287                            // SessionManager + cluster override are reachable)
1288                            // and stashed on the stream context just before the
1289                            // error is returned, so the answer engine can render
1290                            // (or elide) the header without re-deriving the
1291                            // resolution chain here.
1292                            BE::TooManyConnectionsPerIp { ref cluster_id } => {
1293                                debug!(
1294                                    "{} per-(cluster, source-IP) limit hit for cluster {:?}",
1295                                    log_module_context!(),
1296                                    cluster_id
1297                                );
1298                                let retry_after = stream.context.retry_after_seconds;
1299                                set_default_answer_with_retry_after(
1300                                    stream,
1301                                    front_readiness,
1302                                    429,
1303                                    &answers,
1304                                    retry_after,
1305                                );
1306                            }
1307                        }
1308                        context.debug.push(DebugEvent::CCF(stream_id, error));
1309                    }
1310                }
1311                // All routing error arms now set a default answer, transitioning
1312                // the stream out of Link state. No re-enqueue needed.
1313            }
1314            if !dirty {
1315                break;
1316            }
1317        }
1318
1319        // Stop service timers before yielding to epoll, so idle wait time is excluded
1320        // from the service_time metric. For Close/Upgrade returns, close() handles cleanup.
1321        for stream in &mut self.context.streams {
1322            if stream.state.is_open() {
1323                stream.metrics.service_stop();
1324            }
1325        }
1326
1327        #[cfg(debug_assertions)]
1328        {
1329            // Verify backend_streams index matches actual stream states.
1330            let mut expected: HashMap<Token, Vec<GlobalStreamId>> = HashMap::new();
1331            for (id, stream) in self.context.streams.iter().enumerate() {
1332                if let StreamState::Linked(token) = stream.state {
1333                    expected.entry(token).or_default().push(id);
1334                }
1335            }
1336            assert_eq!(
1337                expected.len(),
1338                self.context.backend_streams.len(),
1339                "backend_streams index key count mismatch: expected={:?}, actual={:?}",
1340                expected,
1341                self.context.backend_streams
1342            );
1343            for (token, mut expected_ids) in expected {
1344                let mut actual_ids = self
1345                    .context
1346                    .backend_streams
1347                    .get(&token)
1348                    .cloned()
1349                    .unwrap_or_default();
1350                expected_ids.sort();
1351                actual_ids.sort();
1352                assert_eq!(
1353                    expected_ids, actual_ids,
1354                    "backend_streams index mismatch for token {token:?}",
1355                );
1356            }
1357        }
1358
1359        SessionResult::Continue
1360    }
1361
1362    fn update_readiness(&mut self, token: Token, events: Ready) {
1363        trace!("{} EVENTS: {:?} on {:?}", log_context!(self), events, token);
1364        self.context.debug.push(DebugEvent::EV(token, events));
1365        if token == self.frontend_token {
1366            self.frontend.readiness_mut().event |= events;
1367        } else if let Some(c) = self.router.backends.get_mut(&token) {
1368            c.readiness_mut().event |= events;
1369        }
1370    }
1371
1372    fn timeout(&mut self, token: Token, _metrics: &mut SessionMetrics) -> StateResult {
1373        trace!("{} MuxState::timeout({:?})", log_context!(self), token);
1374        let front_is_h2 = match self.frontend {
1375            Connection::H1(_) => false,
1376            Connection::H2(_) => true,
1377        };
1378        let answers_rc = self.context.listener.borrow().get_answers().clone();
1379        let mut should_close = true;
1380        let mut should_write = false;
1381        if self.frontend_token == token {
1382            trace!(
1383                "{} MuxState::timeout_frontend({:#?})",
1384                log_context!(self),
1385                self.frontend
1386            );
1387            self.frontend.timeout_container().triggered();
1388            // The per-stream reaper (bidirectional-idle + outbound
1389            // flow-control-stall guards) normally runs only from `readable()`,
1390            // but a fully-silent peer never triggers a read event. Run it on the
1391            // connection-timeout path too so a window-stalled stream — a buffered
1392            // response the peer refuses to drain by holding its receive window
1393            // shut — is reaped and its MAX_CONCURRENT_STREAMS slot freed, instead
1394            // of lingering until the 30-minute zombie checker. The reaper queues
1395            // an `RST_STREAM(CANCEL)`; because `has_pending_write()` does NOT
1396            // observe `pending_rst_streams` (it gates connection close, so a
1397            // queued RST must not read as "keep open"), set `should_write` via
1398            // the dedicated `has_pending_control_write()` probe so the reset is
1399            // actually flushed to the peer before the connection closes — without
1400            // it, a fully-silent peer's stalled stream is freed but the peer sees
1401            // only EOF, never the RST(CANCEL).
1402            if let Connection::H2(h2) = &mut self.frontend {
1403                h2.cancel_timed_out_streams(
1404                    &mut self.context,
1405                    &mut EndpointClient(&mut self.router),
1406                );
1407                if h2.has_pending_control_write() {
1408                    should_write = true;
1409                }
1410            }
1411            if self.frontend.has_pending_write() {
1412                should_write = true;
1413            }
1414            let front_readiness = self.frontend.readiness_mut();
1415            for stream_id in 0..self.context.streams.len() {
1416                match self.context.streams[stream_id].state {
1417                    StreamState::Idle => {
1418                        // In h1 an Idle stream is always the first request, so we can send a 408
1419                        // In h2 an Idle stream doesn't necessarily hold a request yet,
1420                        // in most cases it was just reserved, so we can just ignore them.
1421                        if !front_is_h2 {
1422                            let answers = answers_rc.borrow();
1423                            let stream = &mut self.context.streams[stream_id];
1424                            stream.context.access_log_message = Some("client_timeout");
1425                            set_default_answer(stream, front_readiness, 408, &answers);
1426                            should_write = true;
1427                        }
1428                    }
1429                    StreamState::Link => {
1430                        // This is an unusual case, as we have both a complete request and no
1431                        // available backend yet. For now, we answer with 503.
1432                        // Not a timeout-driven outcome from the operator's
1433                        // perspective — leave access_log_message as None.
1434                        let answers = answers_rc.borrow();
1435                        let stream = &mut self.context.streams[stream_id];
1436                        set_default_answer(stream, front_readiness, 503, &answers);
1437                        should_write = true;
1438                    }
1439                    StreamState::Linked(_) => {
1440                        // The frontend timed out while a stream is linked to a backend.
1441                        // The backend timeout should handle this, but in case the backend
1442                        // is also stalled, send a 504 and terminate the stream.
1443                        if !self.context.streams[stream_id].back.consumed {
1444                            self.context.unlink_stream(stream_id);
1445                            let answers = answers_rc.borrow();
1446                            let stream = &mut self.context.streams[stream_id];
1447                            stream.context.access_log_message =
1448                                Some("client_timeout_during_response");
1449                            set_default_answer(stream, front_readiness, 504, &answers);
1450                            should_write = true;
1451                        } else if self.context.streams[stream_id].back.is_completed() {
1452                            // Response fully proxied, stream can be closed
1453                        } else if self.context.streams[stream_id].back.is_terminated()
1454                            || self.context.streams[stream_id].back.is_error()
1455                        {
1456                            // Response is terminated/error but not fully written to frontend.
1457                            // Keep the session alive briefly to flush remaining data.
1458                            should_close = false;
1459                        } else {
1460                            // Partial response in progress — forcefully terminate
1461                            self.context.unlink_stream(stream_id);
1462                            let stream = &mut self.context.streams[stream_id];
1463                            stream.context.access_log_message =
1464                                Some("client_timeout_during_response");
1465                            forcefully_terminate_answer(
1466                                stream,
1467                                front_readiness,
1468                                H2Error::InternalError,
1469                            );
1470                            should_write = true;
1471                        }
1472                        // end_stream is called in a second pass below to avoid
1473                        // borrow conflicts on context.streams.
1474                    }
1475                    StreamState::Unlinked => {
1476                        // A stream Unlinked already has a response and its backend closed.
1477                        // In case it hasn't finished proxying we wait. Otherwise it is a stream
1478                        // kept alive for a new request, which can be killed.
1479                        if !self.context.streams[stream_id].back.is_completed() {
1480                            should_close = false;
1481                        }
1482                    }
1483                    StreamState::Recycle => {
1484                        // A recycled stream is an h2 stream which doesn't hold a request anymore.
1485                        // We can ignore it.
1486                    }
1487                }
1488            }
1489            // Second pass: end streams that were linked to backends.
1490            // This is done separately to avoid borrow conflicts on context.streams.
1491            let linked_streams: Vec<(GlobalStreamId, Token)> = self
1492                .context
1493                .streams
1494                .iter()
1495                .enumerate()
1496                .filter_map(|(id, stream)| {
1497                    if let StreamState::Linked(back_token) = stream.state {
1498                        Some((id, back_token))
1499                    } else {
1500                        None
1501                    }
1502                })
1503                .collect();
1504            for (stream_id, back_token) in linked_streams {
1505                if let Some(backend) = self.router.backends.get_mut(&back_token) {
1506                    backend.end_stream(stream_id, &mut self.context);
1507                }
1508            }
1509        } else if let Some(backend) = self.router.backends.get_mut(&token) {
1510            trace!(
1511                "{} MuxState::timeout_backend({:#?})",
1512                log_context_lite!(self),
1513                backend
1514            );
1515            backend.timeout_container().triggered();
1516            let front_readiness = self.frontend.readiness_mut();
1517            let linked_ids: Vec<GlobalStreamId> = self
1518                .context
1519                .backend_streams
1520                .get(&token)
1521                .map_or_else(Vec::new, |ids| ids.to_owned());
1522            for stream_id in linked_ids {
1523                // This stream is linked to the backend that timedout
1524                if self.context.streams[stream_id].back.is_terminated()
1525                    || self.context.streams[stream_id].back.is_error()
1526                {
1527                    trace!(
1528                        "{} Stream terminated or in error, do nothing, just wait a bit more",
1529                        log_module_context!()
1530                    );
1531                    // Nothing to do, simply wait for the remaining bytes to be proxied
1532                    if !self.context.streams[stream_id].back.is_completed() {
1533                        should_close = false;
1534                    }
1535                } else if !self.context.streams[stream_id].back.consumed {
1536                    // The response has not started yet
1537                    trace!(
1538                        "{} Stream still waiting for response, send 504",
1539                        log_module_context!()
1540                    );
1541                    self.context.unlink_stream(stream_id);
1542                    let answers = answers_rc.borrow();
1543                    let stream = &mut self.context.streams[stream_id];
1544                    stream.context.access_log_message = Some("backend_timeout");
1545                    set_default_answer(stream, front_readiness, 504, &answers);
1546                    should_write = true;
1547                } else {
1548                    trace!(
1549                        "{} Stream waiting for end of response, forcefully terminate it",
1550                        log_module_context!()
1551                    );
1552                    self.context.unlink_stream(stream_id);
1553                    let stream = &mut self.context.streams[stream_id];
1554                    stream.context.access_log_message = Some("backend_response_timeout");
1555                    forcefully_terminate_answer(stream, front_readiness, H2Error::InternalError);
1556                    should_write = true;
1557                }
1558                backend.end_stream(stream_id, &mut self.context);
1559            }
1560            // Re-arm the backend timeout if the session stays alive (draining streams).
1561            // Without this, the timeout is consumed and the session becomes immortal
1562            // until the zombie checker runs.
1563            if !should_close {
1564                backend.timeout_container().set(token);
1565            }
1566        } else {
1567            // Session received a timeout for an unknown token, ignore it
1568            return StateResult::Continue;
1569        }
1570        if should_write {
1571            // Drain as much pending data as possible before closing.
1572            // A single writable() call is insufficient for large responses —
1573            // the TLS buffer may need multiple flushes. Without this loop,
1574            // the session is killed with unflushed TLS data, causing the
1575            // client to receive a truncated TLS record ("decode error").
1576            //
1577            // The constant 16 is empirical: it papers over a missing
1578            // invariant-15 hop in the H2 mux state machine where the
1579            // writable readiness signal is not always re-armed after a
1580            // partial flush. Long-term plan: reach invariant-15 closure
1581            // and remove this loop. See `lib/src/protocol/mux/LIFECYCLE.md`.
1582            let mut result = StateResult::Continue;
1583            for _ in 0..16 {
1584                result = match self
1585                    .frontend
1586                    .writable(&mut self.context, EndpointClient(&mut self.router))
1587                {
1588                    MuxResult::Continue => StateResult::Continue,
1589                    MuxResult::Upgrade => StateResult::Upgrade,
1590                    MuxResult::CloseSession => StateResult::CloseSession,
1591                };
1592                if result != StateResult::Continue
1593                    || !self.frontend.readiness_mut().interest.is_writable()
1594                {
1595                    break;
1596                }
1597            }
1598            // Re-arm the frontend timeout so the session doesn't become immortal.
1599            // The writable call may have partially flushed the response — we need
1600            // the timeout to fire again if the flush stalls.
1601            if result == StateResult::Continue {
1602                self.frontend.timeout_container().set(self.frontend_token);
1603            }
1604            return result;
1605        }
1606        if should_close {
1607            if self.delay_close_for_frontend_flush("timeout") {
1608                debug!(
1609                    "{} Mux timeout delaying close for frontend flush: token={:?}, frontend={:?}",
1610                    log_context!(self),
1611                    token,
1612                    self.frontend
1613                );
1614                self.frontend.timeout_container().set(self.frontend_token);
1615                return StateResult::Continue;
1616            }
1617            if front_is_h2 {
1618                debug!(
1619                    "{} Mux timeout returning CloseSession: token={:?}, frontend={:?}",
1620                    log_context!(self),
1621                    token,
1622                    self.frontend
1623                );
1624                for (idx, stream) in self.context.streams.iter().enumerate() {
1625                    if stream.state != StreamState::Recycle {
1626                        debug!(
1627                            "{}   timeout stream[{}]: state={:?}, front_phase={:?}, back_phase={:?}, front_completed={}, back_completed={}",
1628                            log_context!(self),
1629                            idx,
1630                            stream.state,
1631                            stream.front.parsing_phase,
1632                            stream.back.parsing_phase,
1633                            stream.front.is_completed(),
1634                            stream.back.is_completed()
1635                        );
1636                    }
1637                }
1638            }
1639            StateResult::CloseSession
1640        } else {
1641            // Re-arm the frontend timeout. Without this, the timeout is consumed
1642            // by triggered() and the session stays alive indefinitely until the
1643            // zombie checker runs (default: 30 minutes).
1644            self.frontend.timeout_container().set(self.frontend_token);
1645            StateResult::Continue
1646        }
1647    }
1648
1649    fn cancel_timeouts(&mut self) {
1650        trace!("{} MuxState::cancel_timeouts", log_context!(self));
1651        self.frontend.timeout_container().cancel();
1652        for backend in self.router.backends.values_mut() {
1653            backend.timeout_container().cancel();
1654        }
1655    }
1656
1657    fn print_state(&self, _context: &str) {
1658        // The trait-required `context: &str` parameter (protocol tag like
1659        // "HTTPS"/"HTTP" passed by callers) predates the unified
1660        // `log_context!(self)` envelope. The canonical `MUX` tag lives
1661        // inside `log_context!`, so we ignore the parameter here and emit
1662        // the bracketed Session(...) block instead, mirroring the second
1663        // `error!` in this function.
1664        error!(
1665            "\
1666{} Session(Mux)
1667\tFrontend:
1668\t\ttoken: {:?}\treadiness: {:?}
1669\tBackend(s):",
1670            log_context!(self),
1671            self.frontend_token,
1672            self.frontend.readiness()
1673        );
1674        for (backend_token, backend) in &self.router.backends {
1675            error!(
1676                "{} \t\ttoken: {:?}\treadiness: {:?}",
1677                log_context!(self),
1678                backend_token,
1679                backend.readiness()
1680            )
1681        }
1682    }
1683
1684    fn close(&mut self, proxy: Rc<RefCell<dyn L7Proxy>>, _metrics: &mut SessionMetrics) {
1685        if self.context.debug.is_interesting() {
1686            warn!("{} {:?}", log_context!(self), self.context.debug.events);
1687        }
1688        debug!("{} MUX CLOSE", log_context!(self));
1689        trace!("{} FRONTEND: {:#?}", log_context!(self), self.frontend);
1690        trace!(
1691            "{} BACKENDS: {:#?}",
1692            log_context!(self),
1693            self.router.backends
1694        );
1695
1696        // Log active streams at session teardown for timeout diagnosis
1697        let active_count = self
1698            .context
1699            .streams
1700            .iter()
1701            .filter(|s| s.state.is_open() && s.metrics.start.is_some())
1702            .count();
1703        if active_count > 0 {
1704            debug!(
1705                "{} Session close with {} active stream(s)",
1706                log_context!(self),
1707                active_count
1708            );
1709            for (idx, stream) in self
1710                .context
1711                .streams
1712                .iter()
1713                .enumerate()
1714                .filter(|(_, s)| s.state.is_open() && s.metrics.start.is_some())
1715            {
1716                let elapsed = stream.metrics.service_time();
1717                debug!(
1718                    "{}   active stream[{}]: state={:?} service_time={:?} method={:?} path={:?} status={:?}",
1719                    log_context!(self),
1720                    idx,
1721                    stream.state,
1722                    elapsed,
1723                    stream.context.method,
1724                    stream.context.path,
1725                    stream.context.status,
1726                );
1727            }
1728            incr!(names::h2::CLOSE_WITH_ACTIVE_STREAMS);
1729        }
1730
1731        // Distribute H2 connection-level overhead (control frames) across in-flight
1732        // streams so that access log bytes_in/bytes_out reflect actual wire cost.
1733        // Integer division may lose up to (active_count - 1) bytes, which is acceptable.
1734        let active_count = active_count.max(1);
1735        let (total_overhead_in, total_overhead_out) = self.frontend.overhead_bytes();
1736        let share_in = total_overhead_in / active_count;
1737        let share_out = total_overhead_out / active_count;
1738
1739        // Generate access logs for in-flight streams on session teardown.
1740        // Skip streams that already had their access log emitted (metrics.start is
1741        // set to None by metrics.reset() after generate_access_log in the happy path).
1742        // Frontend RTT is the same for every stream on this session — snapshot
1743        // it once outside the loop instead of paying one TCP_INFO syscall per
1744        // open stream.
1745        let client_rtt = socket_rtt(self.frontend.socket());
1746        for stream in &mut self.context.streams {
1747            if stream.state.is_open() && stream.metrics.start.is_some() {
1748                stream.metrics.bin += share_in;
1749                stream.metrics.bout += share_out;
1750                stream.metrics.service_stop();
1751                if stream.metrics.backend_stop.is_none() {
1752                    stream.metrics.backend_stop();
1753                }
1754                // Only mark as error if the stream had an actual protocol/processing failure
1755                // (kawa parse error, backend error). Normal timeouts, client disconnects,
1756                // and graceful connection closures are not errors.
1757                let is_error = stream.front.is_error() || stream.back.is_error();
1758                let server_rtt = stream.linked_token().and_then(|token| {
1759                    self.router
1760                        .backends
1761                        .get(&token)
1762                        .and_then(|c| socket_rtt(c.socket()))
1763                });
1764                stream.generate_access_log(
1765                    is_error,
1766                    Some("session close"),
1767                    self.context.listener.clone(),
1768                    client_rtt,
1769                    server_rtt,
1770                );
1771                stream.state = StreamState::Recycle;
1772            }
1773        }
1774
1775        self.frontend
1776            .close(&mut self.context, EndpointClient(&mut self.router));
1777
1778        for (token, client) in &mut self.router.backends {
1779            let proxy_borrow = proxy.borrow();
1780            client.timeout_container().cancel();
1781            let socket = client.socket_mut();
1782            if let Err(e) = proxy_borrow.deregister_socket(socket) {
1783                error!(
1784                    "{} error deregistering back socket({:?}): {:?}",
1785                    log_context_lite!(self),
1786                    socket,
1787                    e
1788                );
1789            }
1790            // invariant: write-only shutdown — Shutdown::Both on a TLS frontend
1791            // discards the receive buffer and elicits TCP RST, truncating the
1792            // already-queued response. Canonical write-up: `lib/src/https.rs:650-655`.
1793            // Backend sockets follow the same discipline for symmetry.
1794            if let Err(e) = socket.shutdown(Shutdown::Write)
1795                && e.kind() != ErrorKind::NotConnected
1796            {
1797                error!(
1798                    "{} error shutting down back socket({:?}): {:?}",
1799                    log_context_lite!(self),
1800                    socket,
1801                    e
1802                );
1803            }
1804            if !proxy_borrow.remove_session(*token) {
1805                error!(
1806                    "{} session {:?} was already removed!",
1807                    log_context_lite!(self),
1808                    token
1809                );
1810            }
1811
1812            match client.position() {
1813                Position::Client(cluster_id, backend, _) => {
1814                    let mut backend_borrow = backend.borrow_mut();
1815                    backend_borrow.dec_connections();
1816                    gauge_add!(names::backend::CONNECTIONS, -1);
1817                    // Second `-1` site for `backend.pool.size` (the first is
1818                    // in `connection.rs::pre_close_client_bookkeeping`). This
1819                    // path runs during session teardown when the frontend
1820                    // session iterates the backends map directly without
1821                    // routing through `Connection::close`. Both `-1` sites
1822                    // mirror the single `+1` in router.rs::connect and the
1823                    // matching `backend.connections, -1` calls already
1824                    // present here, so symmetry follows from
1825                    // `backend.connections` correctness.
1826                    gauge_add!(names::backend::POOL_SIZE, -1);
1827                    gauge_add!(
1828                        names::backend::CONNECTIONS_PER_BACKEND,
1829                        -1,
1830                        Some(cluster_id),
1831                        Some(&backend_borrow.backend_id)
1832                    );
1833                    let count = self
1834                        .context
1835                        .backend_streams
1836                        .get(token)
1837                        .map_or(0, |ids| ids.len());
1838                    backend_borrow.active_requests =
1839                        backend_borrow.active_requests.saturating_sub(count);
1840                    trace!(
1841                        "{} connection (session) closed: {:#?}",
1842                        log_context_lite!(self),
1843                        backend_borrow
1844                    );
1845                }
1846                Position::Server => {
1847                    error!(
1848                        "{} close_backend called on Server position",
1849                        log_context_lite!(self)
1850                    );
1851                }
1852            }
1853        }
1854        // Clear the reverse index after all backends have decremented their
1855        // active_requests counters (which depend on the index for stream counts).
1856        self.context.backend_streams.clear();
1857    }
1858
1859    fn shutting_down(&mut self) -> SessionIsToBeClosed {
1860        // RFC 9113 §6.8: initiate graceful shutdown with double-GOAWAY pattern.
1861        // Only send the initial GOAWAY once. The final GOAWAY (with the real
1862        // last_stream_id) is handled by finalize_write() when all streams drain.
1863        // Calling graceful_goaway() again would send the final GOAWAY
1864        // prematurely and force-disconnect before in-flight streams complete.
1865        if !self.frontend.is_draining() {
1866            match self.frontend.graceful_goaway() {
1867                MuxResult::CloseSession => return true,
1868                MuxResult::Continue => {
1869                    // graceful_goaway() queued a GOAWAY frame. Flush it directly
1870                    // since the event loop uses edge-triggered epoll and won't
1871                    // deliver a new WRITABLE event for an already-writable socket.
1872                    self.frontend.flush_zero_buffer();
1873                }
1874                _ => {}
1875            }
1876        } else {
1877            trace!(
1878                "{} shutting_down: already draining, skipping duplicate GOAWAY",
1879                log_context!(self)
1880            );
1881            // shut_down_sessions() runs outside ready(), so retry flushing any
1882            // previously-buffered GOAWAY/TLS records on each pass.
1883            self.frontend.flush_zero_buffer();
1884        }
1885        if self.drive_frontend_shutdown_io() {
1886            return true;
1887        }
1888        // Forced-close deadline: once the H2 listener's
1889        // `h2_graceful_shutdown_deadline_seconds` budget has elapsed from
1890        // the moment `graceful_goaway` armed `drain.started_at`, stop
1891        // waiting for streams and tear the session down. `drive_frontend_
1892        // shutdown_io` above already had a chance to flush any pending
1893        // TLS/GOAWAY records; this branch accepts that some bytes may be
1894        // lost in exchange for honoring the operator-configured SLA.
1895        // Listeners that disable the knob (`= 0` → `None`) short-circuit
1896        // the check inside `graceful_shutdown_deadline_elapsed`.
1897        if self.frontend.graceful_shutdown_deadline_elapsed() {
1898            debug!(
1899                "{} Mux shutting_down: graceful-shutdown deadline elapsed, forcing close",
1900                log_context!(self)
1901            );
1902            return true;
1903        }
1904        if matches!(self.frontend, Connection::H2(_)) && self.frontend.is_draining() {
1905            for stream in &mut self.context.streams {
1906                if stream.front_received_end_of_stream {
1907                    continue;
1908                }
1909                if !matches!(stream.state, StreamState::Linked(_) | StreamState::Unlinked) {
1910                    continue;
1911                }
1912                if stream.front.consumed
1913                    && stream.front.storage.is_empty()
1914                    && stream.front.is_completed()
1915                {
1916                    stream.front_received_end_of_stream = true;
1917                    self.frontend
1918                        .readiness_mut()
1919                        .interest
1920                        .insert(Ready::WRITABLE);
1921                    self.frontend.readiness_mut().signal_pending_write();
1922                }
1923            }
1924        }
1925        let mut can_stop = true;
1926        for stream in &mut self.context.streams {
1927            match stream.state {
1928                StreamState::Linked(_) => {
1929                    can_stop = false;
1930                }
1931                StreamState::Unlinked => {
1932                    kawa::debug_kawa(&stream.front);
1933                    kawa::debug_kawa(&stream.back);
1934                    if stream.is_quiesced() {
1935                        continue;
1936                    }
1937                    stream.context.closing = true;
1938                    can_stop = false;
1939                }
1940                _ => {}
1941            }
1942        }
1943        if self.frontend.has_pending_write() {
1944            return false;
1945        }
1946        if can_stop {
1947            let active_h2_streams = self
1948                .context
1949                .streams
1950                .iter()
1951                .enumerate()
1952                .filter(|(_, s)| {
1953                    if s.state == StreamState::Recycle {
1954                        return false;
1955                    }
1956                    if s.state == StreamState::Unlinked && s.is_quiesced() {
1957                        return false;
1958                    }
1959                    true
1960                })
1961                .collect::<Vec<_>>();
1962            if matches!(self.frontend, Connection::H2(_)) && !active_h2_streams.is_empty() {
1963                debug!(
1964                    "{} Mux shutting_down returning true with active H2 streams: {:?}",
1965                    log_context!(self),
1966                    self.frontend
1967                );
1968                for (idx, stream) in active_h2_streams {
1969                    debug!(
1970                        "{}   shutdown stream[{}]: state={:?}, front_phase={:?}, back_phase={:?}, front_completed={}, back_completed={}",
1971                        log_context!(self),
1972                        idx,
1973                        stream.state,
1974                        stream.front.parsing_phase,
1975                        stream.back.parsing_phase,
1976                        stream.front.is_completed(),
1977                        stream.back.is_completed()
1978                    );
1979                }
1980            }
1981        }
1982        if can_stop {
1983            return true;
1984        }
1985
1986        false
1987    }
1988}
1989
1990#[cfg(test)]
1991mod tests {
1992    use super::*;
1993
1994    #[test]
1995    fn update_readiness_after_read_closed_keeps_writable() {
1996        let mut readiness = Readiness {
1997            event: Ready::READABLE | Ready::WRITABLE | Ready::HUP,
1998            interest: Ready::READABLE | Ready::WRITABLE | Ready::HUP,
1999        };
2000
2001        let should_yield = update_readiness_after_read(17, SocketResult::Closed, &mut readiness);
2002
2003        assert!(!should_yield);
2004        assert!(!readiness.event.is_readable());
2005        assert!(readiness.event.is_writable());
2006        assert!(readiness.event.is_hup());
2007    }
2008}