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