pub struct Context<L: ListenerHandler + L7ListenerHandler> {Show 20 fields
pub streams: Vec<Stream>,
pub pending_links: VecDeque<usize>,
pub backend_streams: InlineTokenMap<LinkedStreams>,
pub buffers: Box<dyn BufferSource>,
pub listener: Rc<RefCell<L>>,
pub session_ulid: Ulid,
pub session_address: Option<SocketAddr>,
pub public_address: SocketAddr,
pub debug: DebugHistory,
pub h2_stream_shrink_ratio: usize,
pub tls_server_name: Option<String>,
pub tls_cert_names: Option<Arc<Vec<String>>>,
pub protocol: Protocol,
pub tls_version: Option<&'static str>,
pub tls_cipher: Option<&'static str>,
pub tls_alpn: Option<&'static str>,
pub now: Instant,
pub now_wall_ms: u64,
pub request_id_rng: StdRng,
pub backend_deltas: Vec<BackendDelta>,
/* private fields */
}Fields§
§streams: Vec<Stream>§pending_links: VecDeque<usize>Streams whose state is StreamState::Link and need backend connection.
Replaces the O(n) scan of streams in the ready loop.
backend_streams: InlineTokenMap<LinkedStreams>Reverse index: backend token -> global stream IDs currently in
StreamState::Linked(token). Eliminates O(n) scans of streams
when handling backend connect/disconnect/timeout/close events.
An entry may be empty: remove_backend_stream keeps an emptied Vec
for the next link on the same live connection (#1583), so “has linked
streams” is the entry’s emptiness, not the key’s presence.
buffers: Box<dyn BufferSource>Where every buffer this session’s streams and connections need comes from, and the only thing allowed to refuse one.
Boxed rather than concrete so the core names the contract rather than
the worker’s Pool: a refusal is the same event whatever stands
behind it, and the REFUSED_STREAM answer to one is the same answer.
Context::new installs the PoolBufferSource the proxy runs on.
listener: Rc<RefCell<L>>§session_ulid: UlidConnection/session ULID — mirrors Mux.session_ulid. Stored here so
per-stream HttpContext construction in Self::create_stream can
stamp the session slot of the log-context bracket without reaching
back into the parent Mux.
session_address: Option<SocketAddr>§public_address: SocketAddr§debug: DebugHistory§h2_stream_shrink_ratio: usizeShrink threshold ratio for recycled stream slots. Vec is shrunk when total_slots > active_streams * ratio.
tls_server_name: Option<String>TLS SNI value negotiated at handshake, propagated to every
per-stream HttpContext so the routing layer can enforce
the SNI ↔ :authority binding on every H2 stream (and the
single H1 request). None for plaintext listeners or when
the client omitted the SNI extension. Stored pre-lowercased
and without a port for cheap exact-match comparison.
tls_cert_names: Option<Arc<Vec<String>>>Snapshot of the SAN set of the certificate Sōzu actually served at
the TLS handshake. Captured once in https.rs::upgrade_handshake
from the resolver and frozen for the connection lifetime so H2
stream coalescing (RFC 7540 §9.1.1 / RFC 9113 §9.1.1) accepts any
:authority covered by the certificate, with RFC 6125 §6.4.3
wildcard handling. None for plaintext listeners or when SNI was
absent. Some(empty) when the default cert was served — every
:authority is rejected. Arc so the snapshot is shared across
every per-stream HttpContext without re-allocation.
protocol: ProtocolWhether this session’s listener speaks HTTPS or plaintext HTTP.
The one listener value that is genuinely per-connection rather than
per-request: a listener’s protocol is fixed by its type and no
update_config patch can change it, so capturing it once in
Self::new frees both readers — Self::create_stream and the H2
write pass’s :scheme decision — from borrowing the listener at all.
Contrast the per-request knobs, which Self::create_stream re-reads
on every stream precisely because a reload can change them
(LIFECYCLE.md §2.5).
tls_version: Option<&'static str>Negotiated TLS protocol version short-form (e.g. "TLSv1.3").
Captured once at handshake completion in https.rs and propagated
to every per-stream HttpContext so the access log can record it
without reaching back into the rustls session per request. None
for plaintext listeners.
tls_cipher: Option<&'static str>Negotiated TLS cipher suite short-form (e.g.
"TLS_AES_128_GCM_SHA256"). Captured once at handshake completion
and propagated to every per-stream HttpContext. None for
plaintext listeners.
tls_alpn: Option<&'static str>Negotiated ALPN protocol short-form (e.g. "h2", "http/1.1").
Captured once at handshake completion and propagated to every
per-stream HttpContext. None for plaintext listeners or when
no ALPN was negotiated.
now: InstantClock snapshot for the pass currently executing.
Mux is the only clock sampler in the mux: it refreshes this field
once per outer Mux::ready pass and at the top of Mux::timeout
and Mux::shutting_down. Every time-based decision in the H2 core
reads this snapshot — mirrored into
h2::ConnectionH2::now at each public entry point — instead of
calling Instant::now itself, so one pass sees one consistent
“now”. Every deadline armed or evaluated inside a pass is therefore
accurate to within that pass — in either direction. The error is
not one-sided: an arm site runs at an arbitrary depth into its pass
(a DATA or HEADERS refresh, an outbound-byte refresh) and stamps the
snapshot taken at the START of that pass, so the stored instant is
older than the event it records; an eval site runs near the top of a
pass (cancel_timed_out_streams is the first thing
h2::ConnectionH2::poll_read_target does). The measured age is therefore
inflated by the arm site’s depth, and a deadline can fire up to one
pass EARLY as well as one pass late. At base both ends read the real
clock and the comparison was exact; this is the cost of the snapshot.
The flood and back-pressure windows are the one asymmetric case, and
they fail closed: now is constant for the whole pass, so a window
cannot decay part-way through one. A burst arriving during a pass is
weighed in full against the window that was open when the pass
started, where before the change a long pass could decay the counters
under the burst. The window BOUNDARY still carries the same
one-pass error in either direction as every other deadline.
Note that crate::SessionMetrics and crate::timer::TimeoutContainer
deliberately stay on the real clock: metrics want true elapsed time,
and the timeout containers are the embedder’s timer, held by the Mux
adapter in Mux::timeouts and never touched by a core — which
publishes a deadline stamped from this snapshot instead.
now_wall_ms: u64Wall-clock sibling of Self::now: Unix milliseconds for the pass
currently executing.
Self::now is an Instant — an opaque monotonic point with no
epoch and no defined conversion to civil time — so it cannot supply
the 48-bit Unix-millisecond prefix a ULID carries in its high bits
(see Self::next_request_id). This field is that value, and it is
refreshed by Mux at exactly the three sites that refresh
Self::now, from the same pass. The two are siblings: a site that
sets one and not the other lets a request id carry the timestamp of
an older pass.
It is a snapshot rather than a provider closure for the same reason
Self::now is: the mux’s discipline is one clock sample per pass,
read by the core from a field, and a boxed provider would let the
core reach the host at an arbitrary depth into a pass instead.
request_id_rng: StdRngEntropy source for the 80-bit random suffix of a request ULID.
Ulid::generate() is exactly
Ulid::from_timestamp_with_rng(unix_epoch_ms(), &mut rand::rng()) —
two ambient reaches (the wall clock and a thread-local RNG) hidden
inside a dependency, invisible to any grep over this module for
Instant::now / SystemTime::now / rand. Holding the RNG here
replaces the second with a field the embedder owns, exactly as
Self::now_wall_ms replaces the first.
Seeded once per session in Self::new from rand::rng() — the
same OS-backed reseeding CSPRNG Ulid::generate() draws from, so the
unpredictability of a request id is unchanged. A request id that
repeats across sessions would be a regression, so nothing in the mux
re-seeds it; a deterministic simulator assigns this field directly
after construction instead.
backend_deltas: Vec<BackendDelta>Backend accounting the core decided and the embedder has not performed yet (#1340, Question 12).
The core’s outbound channel for registry writes, in the same place
udp’s Output queue sits: a site that used to reach a
Rc<RefCell<Backend>> through Position::Client and mutate it pushes
a BackendDelta here instead. Mux::apply_backend_deltas is the
only drain, and the only writer of Backend::active_requests /
Backend::active_connections on the mux path.
Entries are applied in the order they were pushed, and the drain runs
before anything reads backend load state — in practice, before each
Router::plan_connect, whose load balancer is that sole reader. FIFO plus
drain-before-read leaves the counters at every read point exactly
where mutating in place left them, saturation included.
Implementations§
Source§impl<L: ListenerHandler + L7ListenerHandler> Context<L>
impl<L: ListenerHandler + L7ListenerHandler> Context<L>
pub fn new( session_ulid: Ulid, pool: Weak<RefCell<Pool>>, listener: Rc<RefCell<L>>, session_address: Option<SocketAddr>, public_address: SocketAddr, ) -> Self
Sourcepub fn next_request_id(&mut self) -> Ulid
pub fn next_request_id(&mut self) -> Ulid
Mint the request ULID for a stream this session is about to open, or for the next request of an H1 keep-alive connection.
The mux’s two production ULID sites call it: ConnectionH2::create_stream
(lib/src/protocol/mux/h2.rs) for each H2 stream, and the keep-alive
branch of ConnectionH1::writable (lib/src/protocol/mux/h1.rs), which
hands it to HttpContext::reset. It replaces Ulid::generate() there.
generate() composes two ambient reaches — the wall clock and a
thread-local RNG — inside rusty_ulid; this composes the same ULID
from Self::now_wall_ms and Self::request_id_rng, the two
sources the embedder owns. Same bytes, same layout, no host reach
from inside the core.
Ulid::from_timestamp_with_rng panics above 0xFFFF_FFFF_FFFF ms
(year 10889); Self::now_wall_ms is a Unix-millisecond count, so
reaching it requires a host clock set eight millennia ahead.
pub fn active_len(&self) -> usize
Sourcepub fn http_context(&self, stream_id: usize) -> &HttpContext
pub fn http_context(&self, stream_id: usize) -> &HttpContext
Shared accessor for the HttpContext owned by a stream.
Prefer this over &self.streams[stream_id].context at call sites
that only need read access — it keeps the Stream/HttpContext
relationship encapsulated and reads the same regardless of whether
the caller is inside Router::plan_connect, the H2 mux, or a free
helper. Panics on an out-of-bounds stream_id, which is the same
behaviour as the raw streams[sid] indexing it replaces.
Sourcepub fn http_context_mut(&mut self, stream_id: usize) -> &mut HttpContext
pub fn http_context_mut(&mut self, stream_id: usize) -> &mut HttpContext
Mutable sibling of Self::http_context. Use when routing
decisions need to stamp cluster_id / backend_id on the stream’s
HttpContext (e.g. Router::plan_connect at the fill-cluster /
fill-backend points).
Sourcepub fn link_stream(&mut self, stream_id: usize, token: Token)
pub fn link_stream(&mut self, stream_id: usize, token: Token)
Register a stream as linked to a backend token in the reverse index.
Sourcepub fn unlink_stream(&mut self, stream_id: usize) -> Option<Token>
pub fn unlink_stream(&mut self, stream_id: usize) -> Option<Token>
Remove a stream from the backend reverse index if it is currently
Linked. Returns the backend token if one was removed.
Sourcepub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option<usize>
pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option<usize>
Open (or recycle) the stream slot for a request that has just arrived, capturing the listener configuration it will run on.
This is the mux’s single listener-snapshot point. ConnectionH2 calls
it from its HEADERS branch, so an H2 stream captures the listener as
the operator had it configured when that request’s HEADERS landed, and
finishes on that capture whatever a concurrent reload does; the next
HEADERS on the same connection takes a fresh one. The H1 sessions in
http.rs / https.rs call it once per connection instead, because one
H1 connection owns one slot that HttpContext::reset deliberately
carries across keep-alive requests. LIFECYCLE.md §2.5 states the
resulting staleness window for both.
Every value read under the borrow below is re-read on each call for
that reason. The two that are NOT read here — Self::protocol and
the TLS fields — are connection-scoped by nature and captured once.
window seeds Stream::front_window, the stream’s send window on
the frontend connection. The backend leg’s window is the backend’s to
size: ConnectionH2::start_stream sets Stream::back_window.
Sourcepub fn shrink_trailing_recycle(&mut self)
pub fn shrink_trailing_recycle(&mut self)
Remove consecutive Recycle entries from the end of the streams Vec.
This prevents unbounded growth when H2 streams are created and recycled over time, reclaiming memory from slots that are no longer needed.