pub struct ConnectionH2 {Show 14 fields
pub session_ulid: Ulid,
pub last_stream_id: u32,
pub local_settings: H2Settings,
pub peer_settings: H2Settings,
pub position: Position,
pub readiness: Readiness,
pub state: H2State,
pub timeout_duration: Duration,
pub pending_table_size_update: Option<u32>,
pub zero: Kawa<Checkout>,
pub bytes: H2ByteAccounting,
pub settings_sent_at: Option<Instant>,
pub connection_config: H2ConnectionConfig,
pub stream_idle_timeout: Duration,
/* private fields */
}Fields§
§session_ulid: UlidConnection/session ULID propagated from the parent super::Mux. Used to
stamp the session slot of the [session req cluster backend] log
prefix emitted by this module’s log_context! / log_context_stream!
macros.
last_stream_id: u32§local_settings: H2Settings§peer_settings: H2Settings§position: Position§readiness: Readiness§state: H2State§timeout_duration: DurationConfigured idle timeout for this connection. The core never arms a
wheel entry itself: it publishes the next instant it wants to be called
back at through ConnectionH2::poll_timeout, and the embedder — the
Mux adapter — owns the TimeoutContainer that reflects it onto the
real timer. See LIFECYCLE.md §7.7.
pending_table_size_update: Option<u32>RFC 7541 §4.2 / §6.3 pending dynamic-table-size-update signal.
Some(new_size) when a peer SETTINGS frame adjusted
SETTINGS_HEADER_TABLE_SIZE and we have not yet prepended the
matching 001xxxxx HPACK directive to a header block. Consumed and
cleared by H2BlockConverter::emit_pending_size_update_if_new_block
on the next Block::StatusLine or Block::Header encoded for the
connection. Until then the peer’s decoder still has its previous
(possibly larger) table cap, so emitting is a correctness
requirement, not a nicety — see the RFC 9113 encoder-decoder
synchronisation contract (§6.5.2).
zero: Kawa<Checkout>The read landing zone for every frame header and every stream-0
frame’s own payload bytes, for the duration of ONE frame’s read. It is
input only: control frames are serialised into Self::output
(#1604), and a HEADERS+CONTINUATION block is reassembled in
Self::header_reassembly; see that field’s doc and
h2_header_reassembly.rs for why, and LIFECYCLE.md invariant 24 for
the bugs the two splits close.
bytes: H2ByteAccountingByte accounting for connection overhead attribution.
settings_sent_at: Option<Instant>RFC 9113 §6.5: timestamp when we sent SETTINGS and are awaiting ACK. If the peer does not ACK within SETTINGS_ACK_TIMEOUT, we send GOAWAY with SettingsTimeout error.
connection_config: H2ConnectionConfigPer-listener H2 connection tuning (window size, max streams, shrink ratio).
stream_idle_timeout: DurationPer-stream idle cap. Streams with no activity for longer than this are
RST_STREAM(CANCEL)’d by Self::cancel_timed_out_streams. Compared
against stream_table’s per-stream activity/flow-control-stall
caches — see h2_stream_table::H2StreamTable::collect_timed_out.
Implementations§
Source§impl ConnectionH2
impl ConnectionH2
Sourcepub fn poll_timeout(&self) -> Option<Instant>
pub fn poll_timeout(&self) -> Option<Instant>
The next instant this connection wants its embedder to call timeout()
at, or None for “no timer”. The adapter reflects this onto the real
wheel; nothing in this module touches crate::timer.
Sourcepub fn arm_timeout(&mut self)
pub fn arm_timeout(&mut self)
Push the deadline one full Self::timeout_duration out from the
connection’s clock snapshot. Replaces the old
TimeoutContainer::reset() at the same three call sites, and reads
self.now rather than the real clock (invariant 20).
A no-op while lingering: the linger deadline bounds the whole drain.
Sourcepub fn set_timeout_duration(&mut self, duration: Duration, now: Instant)
pub fn set_timeout_duration(&mut self, duration: Duration, now: Instant)
Adopt a new configured duration and re-arm from now.
Mirrors TimeoutContainer::set_duration, which likewise cancelled and
re-armed rather than keeping the old deadline. now is the adapter’s
snapshot: this is called from Mux::ready, outside the entry points
that mirror it into self.now.
Sourcepub fn new(
session_ulid: Ulid,
peer_address: Option<SocketAddr>,
position: Position,
buffers: &mut dyn BufferSource,
flood_config: H2FloodConfig,
connection_config: H2ConnectionConfig,
stream_idle_timeout: Duration,
graceful_shutdown_deadline: Option<Duration>,
timeout_duration: Duration,
expect_read: Option<(H2StreamId, usize)>,
readiness_interest: Ready,
) -> Option<Self>
pub fn new( session_ulid: Ulid, peer_address: Option<SocketAddr>, position: Position, buffers: &mut dyn BufferSource, flood_config: H2FloodConfig, connection_config: H2ConnectionConfig, stream_idle_timeout: Duration, graceful_shutdown_deadline: Option<Duration>, timeout_duration: Duration, expect_read: Option<(H2StreamId, usize)>, readiness_interest: Ready, ) -> Option<Self>
Shared constructor for both server and client H2 connections.
Differences between server and client are captured by the caller-provided
position, expect_read, and readiness_interest parameters.
Sourcepub fn poll_read_target<E, L>(
&mut self,
context: &mut Context<L>,
endpoint: &mut E,
) -> H2ReadTarget
pub fn poll_read_target<E, L>( &mut self, context: &mut Context<L>, endpoint: &mut E, ) -> H2ReadTarget
Ask the core what it wants read from the socket, so the caller can
perform that read and report it back through Self::handle_read.
This is the read half of the same two-call protocol the write half
already uses: h2_transmit::gather borrows the bytes to send, the
caller writes them, h2_transmit::confirm is told how many landed.
Here the core names the buffer it wants filled, the caller
performs the read, and Self::handle_read is told how many bytes
arrived and with what status.
This is not AsyncRead::poll_read. Nothing here is a future,
context is this module’s Context and not a task::Context, and
lib/ holds no asynchronous function. The name follows
UdpManager::poll_output (protocol/udp/manager.rs), this
repository’s existing spelling for “ask the core what it has for its
caller”, the way Self::handle_read follows that module’s
UdpManager::handle_input.
Everything up to and including the decision of which buffer needs
how many bytes lives on this side of the split, because two of the
three answers are produced by that prelude: a SETTINGS-ACK timeout and
an idle expect_read both end the pass with no read at all. A poll
that could not say “nothing” would not be the core’s answer.
Sourcepub fn handle_read<E, L>(
&mut self,
context: &mut Context<L>,
endpoint: E,
stream_id: H2StreamId,
outcome: H2ReadOutcome,
) -> MuxResult
pub fn handle_read<E, L>( &mut self, context: &mut Context<L>, endpoint: E, stream_id: H2StreamId, outcome: H2ReadOutcome, ) -> MuxResult
Tell the core what the caller’s read actually did, then let it consume the bytes and dispatch the frame they completed.
The second half of Self::poll_read_target’s protocol. stream_id
is the one the matching H2ReadTarget named, and outcome answers
that same variant: H2ReadOutcome::Skipped for
H2ReadTarget::Skip, H2ReadOutcome::Filled for
H2ReadTarget::Fill.
read_buffer is called a second time here rather than carried across
the split: StreamParts borrows context, so a buffer held across the
caller’s read would pin context.debug and Self::handle_frame’s whole
&mut Context with it. Re-deriving it from a Copy H2StreamId is
a match and a field projection.
Sourcepub fn poll_write_target<E, L>(
&mut self,
context: &mut Context<L>,
endpoint: &mut E,
pass: &mut H2WritePass,
) -> H2WriteTarget
pub fn poll_write_target<E, L>( &mut self, context: &mut Context<L>, endpoint: &mut E, pass: &mut H2WritePass, ) -> H2WriteTarget
Ask the core what it wants written to the socket, so the caller can
perform that write and report it back through Self::handle_write.
The write half of Self::poll_read_target’s protocol, with ONE forced
divergence: a read pass ENDS with handle_read, while a write pass
performs an unbounded number of writes, so this is re-entered until it
answers H2WriteTarget::Done or H2WriteTarget::Finalize. The
pass parameter is what makes that re-entry meaningful: 9.6a needed no
stash because expect_read already lived on stream_table, whereas one
write pass builds a dozen values that have to survive the round trip.
It is a &mut local of Self::write_streams and not an
Option<H2WritePass> field on self: every exit ends the pass, so it
never outlives one call, and resumption ACROSS calls is already
H2StreamTable::expect_write.
A caller that stops driving after an H2WriteTarget::Transmit
strands the pass’s converter, and with it the three HPACK scratch
buffers it took out of hpack_state::HpackState — silently, and for
the life of the connection. Between the transition into
H2WritePhase::Prepare and the first statement of
H2WritePhase::End this function has exactly one return, the
Transmit yield, so the drive loop is the whole guarantee;
H2WritePass’s Drop carries the matching debug_assert!. A
re-entry AFTER the pass answered is the other half of that argument and
is handled separately: End moves the pass to
H2WritePhase::Ended, whose arm answers Done rather than
re-entering an arm whose first statement takes three Options it has
already emptied.
Unlike Self::handle_write this never sees a SocketResult. The
pass terminator is pass.stalled, written in exactly one statement over
there, and size > 0 with SocketResult::WouldBlock is NOT a stall —
update_readiness decides on the size alone. A terminator keyed on the
status would truncate every response a TLS frontend accepts in two
rounds, which is why adding that parameter here has to be a visible
signature change rather than a one-token edit.
Sourcepub fn handle_write<L>(
&mut self,
context: &mut Context<L>,
stream_id: H2StreamId,
written: H2Written,
status: SocketResult,
pass: &mut H2WritePass,
)where
L: ListenerHandler + L7ListenerHandler,
pub fn handle_write<L>(
&mut self,
context: &mut Context<L>,
stream_id: H2StreamId,
written: H2Written,
status: SocketResult,
pass: &mut H2WritePass,
)where
L: ListenerHandler + L7ListenerHandler,
Tell the core what the caller’s write actually did.
The second half of Self::poll_write_target’s protocol, and the
pre-image flush loop’s BODY minus the three statements the shell now
owns: h2_transmit::gather has already run, the write has happened,
and h2_transmit::confirm has already discharged the 'static
descriptors, so nothing here holds a borrow of kawa.storage.
It answers nothing, because nothing in this body can end a pass: the
pass continues after a transmit, and the decision to stop is taken by
Self::poll_write_target reading H2WritePass::stalled.
This is the only place on the write core that sees a
SocketResult, and the only place that writes pass.stalled — in
one statement, the verbatim move of the pre-image’s flush test.
update_readiness_after_write returns stalled iff size == 0;
status only clears the event bit. FrontRustls answers
(buffered_size > 0, WouldBlock) structurally whenever rustls accepted
plaintext while the kernel was full, and that is NOT a stall.
Which counter the bytes land in, and which debug_site they are logged
under, are decided by the PASS PHASE and not by the stream: the resume
path’s bytes are socket catch-up rather than the voluntary scheduler
yield LIFECYCLE §9 invariant 16 retains Ready::WRITABLE for, so
H2WritePass::resume_bytes must never reach finalize_write.
Sourcepub fn ensure_tls_flushed(&mut self, tls_wants_write: bool)
pub fn ensure_tls_flushed(&mut self, tls_wants_write: bool)
Re-arm edge-triggered WRITABLE event if rustls still has buffered TLS data.
Takes the answer rather than asking for it: tls_wants_write is
H2Shell::tls_wants_write’s result, read by the caller. That query was
this composite’s whole remaining socket reach, and handing it in is
what leaves a body a byte-in / byte-out core can keep.
When the caller reads it is not uniform, and cannot be. Five of the six
call sites already hold the answer — four from the h2_close decision
just taken on it, H2Shell::initiate_close_notify from its own branch
condition — and pass that binding: nothing mutates the socket in
between, so a second query would answer the same. The sixth, the
H2ControlFlushTarget::Stalled arm of H2Shell::writable, has no
such binding and must read it AFTER the H2Shell::flush_output_to_socket
whose stall brought it there: that write is what changes the answer.
The stalled-drain tails of Self::flush_pending_control_frames used
to read it and re-arm individually; its one output flush now answers
H2ControlFlushTarget::Stalled and that arm re-arms.
No re-arm follows a write the kernel refused
(kernel_refused_write): the socket is full, the kernel raises
the next WRITABLE edge once the peer reads, and that edge flushes the
records. A synthetic edge would only repeat the refused write, and
Mux::ready_inner did so on every inner iteration until
MAX_LOOP_ITERATIONS counted http.infinite_loop.error whenever a
TLS client stopped reading (sozu-proxy/sozu#1788). The close decisions
still read tls_wants_write unchanged: a connection with records
pending keeps WRITABLE interest and stays open until they are flushed.
Sourcepub fn prune_inactive_streams_while_closing<L>(
&mut self,
context: &mut Context<L>,
)where
L: ListenerHandler + L7ListenerHandler,
pub fn prune_inactive_streams_while_closing<L>(
&mut self,
context: &mut Context<L>,
)where
L: ListenerHandler + L7ListenerHandler,
Drop stream-id mappings for streams that never became active before a connection-level close. This happens on incomplete/oversized header blocks: the stream slot is created on the initial HEADERS frame, then a GOAWAY closes the connection before the request is fully materialized.
Sourcepub fn finalize_write<L>(
&mut self,
tls_wants_write: bool,
socket_write: bool,
bytes_written_this_pass: usize,
context: &mut Context<L>,
) -> H2FinalizeTargetwhere
L: ListenerHandler + L7ListenerHandler,
pub fn finalize_write<L>(
&mut self,
tls_wants_write: bool,
socket_write: bool,
bytes_written_this_pass: usize,
context: &mut Context<L>,
) -> H2FinalizeTargetwhere
L: ListenerHandler + L7ListenerHandler,
Post-write phase: check drain completion, flush TLS, and update readiness.
bytes_written_this_pass reports the total outbound bytes write_streams
pushed to the socket (across every stream), and is used to distinguish
two very different “no expect_write” states:
- Voluntary yield with progress: at least one DATA/HEADERS frame
emitted, but a stream left non-empty
back.out/back.blocksbecause the converter yielded (e.g. RFC 9218 incremental rotation). LIFECYCLE §9 invariant 16: keepReady::WRITABLEarmed so the session loop can resume flushing on the next tick without waiting for an external wake-up that edge-triggered epoll will not deliver. - No progress at all: converter pushed every block back (e.g. flow
window exhausted, no HEADERS ready yet). Strip
Ready::WRITABLE— forward progress must come from an external trigger (WINDOW_UPDATE, new request), not from looping writable().
Answers H2FinalizeTarget: H2FinalizeTarget::Done in the normal
case — MuxResult::Continue, or the graceful GOAWAY taken when
draining with every stream gone — and otherwise the TLS flush step its
caller must perform before Self::finalize_write_after_flush settles
the pass.
tls_wants_write is H2Shell::tls_wants_write’s answer, read by the
caller. It is the pre-flush half of the query / flush / query triple
this function used to run inline; the flush and the post-flush query
are the caller’s too, which is what leaves a body a byte-in /
byte-out core can keep. The caller reads it before the RFC 9113 §6.8
graceful-GOAWAY check below rather than after, which the query’s
freedom from side effects makes unobservable: on that path the value
is discarded.
Sourcepub fn finalize_write_after_flush(&mut self, tls_wants_write: bool) -> MuxResult
pub fn finalize_write_after_flush(&mut self, tls_wants_write: bool) -> MuxResult
Settle a write pass whose H2FinalizeTarget asked for the TLS flush
step, given the answer H2Shell::tls_wants_write gave AFTER it.
The second half of Self::finalize_write’s protocol, and the reason
the split is not a rename: tls_wants_write here is a DIFFERENT
question from the one that produced H2FinalizeTarget::Flush or
H2FinalizeTarget::SkipFlush. The first asks whether rustls holds
records; this one asks whether the step between them landed —
socket_write(&[])’s (size, status) is discarded at the call site,
so nothing else on this path can tell. A caller that reused the first
answer here would re-arm forever or never, and either one strands the
connection under edge-triggered epoll.
The readiness policy of Self::finalize_write is deliberately NOT
reopened: once records were found pending, this pass leaves the bits to
the next one, so the degenerate arguments below are the shape
h2_close::goaway_close_action’s own post-flush call already uses —
the inputs the flush could not change are not re-read.
Sourcepub fn flush_pending_control_frames(&mut self) -> H2ControlFlushTarget
pub fn flush_pending_control_frames(&mut self) -> H2ControlFlushTarget
Queue pending control frames (WINDOW_UPDATEs, RST_STREAMs) behind the ordered output and flush it, before entering the main writable state machine.
Answers H2ControlFlushTarget: H2ControlFlushTarget::Proceed
when writable() should carry on into its state dispatch,
H2ControlFlushTarget::Done when a GOAWAY already ended the pass,
H2ControlFlushTarget::FlushOutput when Self::output_pending
holds bytes for the caller to move, and
H2ControlFlushTarget::Stalled when that move stalled — in which
case the caller reads tls_wants_write and hands it to
Self::ensure_tls_flushed.
The socket write is the caller’s, as everywhere on this write path:
the core answers a step, the caller performs the socket work, and the
core is told what the socket said, through
Self::handle_control_flush.
There is one flush, at the end. Every stage appends whole frames to the one ordered output queue (#1604), so a stage no longer has to wait for an earlier one’s bytes to leave a shared buffer before it can serialise, and whatever was already queued — an ACK, the rest of a stream frame a partial write cut — stays ahead of what this walk adds.
Sourcepub fn handle_control_flush(&mut self, stalled: bool) -> H2ControlFlushTarget
pub fn handle_control_flush(&mut self, stalled: bool) -> H2ControlFlushTarget
Answer the H2ControlFlushTarget::FlushOutput the caller just
performed: stalled is what the caller’s write reported.
Sourcepub fn dispatch_writable_state(
&mut self,
tls_wants_write: bool,
) -> H2WritableStateTarget
pub fn dispatch_writable_state( &mut self, tls_wants_write: bool, ) -> H2WritableStateTarget
Take writable()’s (H2State, Position) decision, given the answer
H2Shell::tls_wants_write gave AFTER the pass preamble’s flush.
tls_wants_write is that answer, read by the caller. It is the third
step of the preamble’s own query / flush / query triple and the
pre-flush question both close arms decide on: the
(H2State::Error, Position::Server) arm has no flush of its own and
reads nothing else, while the H2State::GoAway arm asks for a second
flush through H2WritableStateTarget::Flush. ONE read serves
whichever of the two runs — only one of them can — and nothing mutates
the socket between the caller’s read and this call. The arms that never
consult it discard it, exactly as H2Shell::write_streams discards the
answer it reads before Self::finalize_write’s graceful-GOAWAY
check; H2Shell::tls_wants_write is free of side effects, which is what
makes both unobservable.
Answers H2WritableStateTarget. Every arm but two ends the pass with
H2WritableStateTarget::Done; the H2State::GoAway arm may ask for
the TLS flush step, and the four proxying states name
H2WritableStateTarget::WriteStreams. That last one is the caller’s
to run for the same reason the flush is — H2Shell::write_streams owns
the socket and this function does not — and it is why this function
takes neither Context nor Endpoint: no other arm reads either.
Sourcepub fn dispatch_writable_state_after_flush(
&mut self,
tls_wants_write: bool,
) -> MuxResult
pub fn dispatch_writable_state_after_flush( &mut self, tls_wants_write: bool, ) -> MuxResult
Settle the H2State::GoAway arm whose H2WritableStateTarget::Flush
asked for the TLS flush step, given the answer
H2Shell::tls_wants_write gave AFTER it.
The second half of Self::dispatch_writable_state’s protocol, built
the way Self::finalize_write_after_flush is built and for the same
reason: tls_wants_write here is a DIFFERENT question from the one
that arm decided on. The first asks whether rustls holds records; this
one asks whether the flush between them landed —
socket_write(&[])’s (size, status) is discarded at the call site,
so nothing else on this path can tell. A caller that reused the first
answer here would close a TLS connection with records still pending,
and the peer would read a truncated response; that is the truncation
vector h2_close’s module doc names, not a style point.
peer_gone is deliberately not re-read — the degenerate false below
is h2_close::goaway_close_action’s own post-flush convention — for
the same reason Self::finalize_write_after_flush does not reopen
the readiness policy: the flush cannot resurrect a departed peer, and
the decision answers from the one input it could have changed.
Sourcepub fn compute_stream_byte_totals<L: ListenerHandler + L7ListenerHandler>(
&self,
context: &Context<L>,
) -> (usize, usize)
pub fn compute_stream_byte_totals<L: ListenerHandler + L7ListenerHandler>( &self, context: &Context<L>, ) -> (usize, usize)
Compute the total bytes transferred across all active streams.
Returns (total_bytes_in, total_bytes_out) where bytes_in = bin + backend_bin
and bytes_out = bout + backend_bout for each stream.
Sourcepub fn try_resume_reading<L>(&mut self, context: &Context<L>) -> boolwhere
L: ListenerHandler + L7ListenerHandler,
pub fn try_resume_reading<L>(&mut self, context: &Context<L>) -> boolwhere
L: ListenerHandler + L7ListenerHandler,
Re-enable READABLE if this connection is parked waiting for buffer space and the target stream’s buffer now has enough room.
This is the cross-readiness counterpart to the same-connection check in
writable(). When the other side of a stream (frontend or backend)
drains data via its own writable(), it frees buffer space that this
connection was waiting for. Without this explicit wake-up the connection
stays parked and the session deadlocks until a timeout fires.
Returns true if READABLE was re-enabled.
Sourcepub fn cancel_timed_out_streams<E, L>(
&mut self,
context: &mut Context<L>,
endpoint: &mut E,
)
pub fn cancel_timed_out_streams<E, L>( &mut self, context: &mut Context<L>, endpoint: &mut E, )
Cancel streams that have been idle longer than Self::stream_idle_timeout.
A stream is considered idle when no meaningful application data (non-empty
DATA frames or HEADERS) has been received since the last activity timestamp
in H2StreamTable::stream_last_activity_at.
Mitigates slow-multiplex Slowloris (Pass 4 Medium #3): the connection-level
idle timer resets on every frame, so a peer sending periodic control frames
can pin max_concurrent_streams slots for the full nominal connection timeout.
Per-stream idle deadlines guarantee each stream terminates if it stops making
forward progress, regardless of connection-level liveness.
Timed-out streams receive RST_STREAM(CANCEL) and are immediately removed from the streams map so they no longer count against MAX_CONCURRENT_STREAMS. Backend endpoints are notified and metrics are finalized.
Sourcepub fn handle_flood_violation(
&mut self,
violation: H2FloodViolation,
) -> MuxResult
pub fn handle_flood_violation( &mut self, violation: H2FloodViolation, ) -> MuxResult
Log a flood violation with full session context and emit the GOAWAY.
Centralises the “flood detected” reporting so every site that observes a
H2FloodViolation gets the same session-scoped log line, matching the
RUSTLS log-context convention. Also emits the per-kind statsd counter
(h2.flood.violation.<kind>) so SOC dashboards can window the trip
rate without parsing logs — every CVE-mitigation in the H2 family
(Rapid Reset, MadeYouReset, CONTINUATION/PING/SETTINGS floods, header
overflow, glitch) funnels through this site.
Source§impl ConnectionH2
impl ConnectionH2
Sourcepub fn goaway(&mut self, error: H2Error) -> MuxResult
pub fn goaway(&mut self, error: H2Error) -> MuxResult
Queue the final GOAWAY with last_stream_id set to the highest
peer-initiated stream observed (H2StreamTable::highest_peer_stream_id).
Sourcepub fn peer_header_block_in_progress(&self) -> bool
pub fn peer_header_block_in_progress(&self) -> bool
True while a peer HEADERS block is still being reassembled
(H2State::ContinuationHeader / H2State::ContinuationFrame).
Sourcepub fn goaway_before_forced_close<L>(
&mut self,
context: &Context<L>,
) -> MuxResultwhere
L: ListenerHandler + L7ListenerHandler,
pub fn goaway_before_forced_close<L>(
&mut self,
context: &Context<L>,
) -> MuxResultwhere
L: ListenerHandler + L7ListenerHandler,
RFC 9113 §6.8: the final GOAWAY a draining connection sends when its
graceful-shutdown budget elapses, just before Mux::shutting_down_inner
(lib/src/protocol/mux/mod.rs) closes it. Without it a forced close
leaves the peer holding only the advisory 2^31-1 GOAWAY, which says
nothing about which streams were processed (sozu-proxy/sozu#1647).
A stream whose opening field block is still being reassembled is
excluded: it is StreamState::Idle, it never reached a backend, and
the peer may retry it. create_stream already counted it in
H2StreamTable::highest_peer_stream_id when its HEADERS frame header
arrived. Every stream create_stream accepted before it has a lower
id (RFC 9113 §5.1.1, enforced by handle_header_state against
Self::last_stream_id), and none can be accepted after it while the
block is open (§6.10), so every stream sozu may have acted on is below
it: the reported id is the one two below it (the previous odd id, or
0). A higher id counted in highest_peer_stream_id can only be a
refused stream, which sozu did not act on either.
A connection already in H2State::GoAway or H2State::Error has
sent (or failed to send) its final GOAWAY and gets no second one.
Sourcepub fn graceful_goaway(&mut self, now: Instant) -> MuxResult
pub fn graceful_goaway(&mut self, now: Instant) -> MuxResult
RFC 9113 §6.8: Initiate graceful shutdown using the double-GOAWAY pattern.
First call sends GOAWAY with last_stream_id = 0x7FFFFFFF (MAX) to signal
the intent to stop accepting new streams while allowing in-flight streams
to complete. The connection enters draining mode.
When draining is already true (second invocation), sends the final GOAWAY
with the actual highest_peer_stream_id so the peer knows which streams
were processed.
now is the caller’s clock snapshot and is what arms the forced-close
budget. It is a parameter rather than a read of Self::now because
the caller that matters — Mux::shutting_down — runs outside
ready() and therefore outside the pass that last refreshed the
mirror. In-module callers pass self.now.
Sourcepub fn graceful_shutdown_deadline_elapsed(&self) -> bool
pub fn graceful_shutdown_deadline_elapsed(&self) -> bool
Returns true when the graceful-shutdown budget armed by
Self::graceful_goaway has elapsed. A return of true signals
the enclosing session loop that the proxy-initiated drain must
transition to a forced close: remaining streams will not complete
in time and keeping the connection open past the deadline defeats
the soft-stop SLA.
Returns false when:
- drain has not started yet (
started_atisNone), - the knob is
0/None(indefinite wait explicitly opted in), - or the elapsed time is still within the configured budget.
Sourcepub fn arm_graceful_shutdown_deadline(&mut self, now: Instant)
pub fn arm_graceful_shutdown_deadline(&mut self, now: Instant)
Arm the graceful-shutdown budget of a connection a proxy soft-stop
finds already draining, which graceful_goaway is not called for: a
peer GOAWAY drains without arming it. See
H2DrainState::arm_deadline_if_unarmed (h2_drain.rs).
Sourcepub fn adopt_now(&mut self, now: Instant)
pub fn adopt_now(&mut self, now: Instant)
True when the reaper has queued control frames (RST_STREAM) into
h2_control_tx::H2ControlTx that have not yet been serialized. Kept SEPARATE
from H2Shell::has_pending_write because that probe gates connection close
(the mod.rs close-gating sites) and must NOT treat a queued RST as a
reason to keep the connection open; this probe is consulted ONLY by the
MuxState::timeout flush gate to push a silent-peer RST_STREAM(CANCEL)
onto the wire before the connection closes.
Adopt the embedder’s clock snapshot for this pass.
Self::now is mirrored from Context::now at each public entry
point rather than read from the host — invariant 20 — and a driver
standing where H2Shell::writable stands has to perform that mirror
itself. The other entry points that need it do it inside their own
bodies; the write pass is assembled by its caller, so its mirror is
exposed.
Sourcepub fn read_space<'a, L>(
&'a mut self,
context: &'a mut Context<L>,
stream_id: H2StreamId,
amount: usize,
) -> &'a mut [u8] ⓘwhere
L: ListenerHandler + L7ListenerHandler,
pub fn read_space<'a, L>(
&'a mut self,
context: &'a mut Context<L>,
stream_id: H2StreamId,
amount: usize,
) -> &'a mut [u8] ⓘwhere
L: ListenerHandler + L7ListenerHandler,
The landing zone an H2ReadTarget::Fill names.
The read half of the byte-in / byte-out surface: a driver answers
Fill by filling this slice and reporting what it managed, and never
needs to know which buffer it belongs to. H2Shell::readable fills it
from a socket; sim/tests/h2_simulation.rs fills it from a
VecDeque<u8>, and that is the whole difference between them.
Takes context apart from self rather than borrowing all of *self,
for the reason read_space gives: the caller must keep the rest of
the connection borrowable while the slice is live.
Sourcepub unsafe fn gather_transmit<L>(
&self,
context: &Context<L>,
stream_id: H2StreamId,
io_slices: &mut Vec<IoSlice<'static>>,
) -> (usize, usize)where
L: ListenerHandler + L7ListenerHandler,
pub unsafe fn gather_transmit<L>(
&self,
context: &Context<L>,
stream_id: H2StreamId,
io_slices: &mut Vec<IoSlice<'static>>,
) -> (usize, usize)where
L: ListenerHandler + L7ListenerHandler,
Gather one H2WriteTarget::Transmit: the ordered output queue
first, then stream_id’s queued frames, through
h2_transmit::gather_after. Answers (prefix_len, bytes_offered).
§Safety
h2_transmit::gather_after’s contract: neither this connection’s
output queue nor the stream’s kawa may be mutated or dropped until
io_slices is emptied, which h2_transmit::confirm does first.
Sourcepub fn write_buffer<'a, L>(
&'a mut self,
context: &'a mut Context<L>,
stream_id: H2StreamId,
) -> &'a mut Kawa<Checkout>where
L: ListenerHandler + L7ListenerHandler,
pub fn write_buffer<'a, L>(
&'a mut self,
context: &'a mut Context<L>,
stream_id: H2StreamId,
) -> &'a mut Kawa<Checkout>where
L: ListenerHandler + L7ListenerHandler,
The buffer an H2WriteTarget::Transmit names.
The write half of the same surface, and the one a driver brackets with
Self::gather_transmit and h2_transmit::confirm around its own
vectored write. See write_buffer for why it takes context apart
from self.
Sourcepub fn output_pending(&self) -> &[u8] ⓘ
pub fn output_pending(&self) -> &[u8] ⓘ
The queued output bytes an H2ControlFlushTarget::FlushOutput is
asking the caller to move, or empty when there are none left.
Sourcepub fn consume_output_flush(
&mut self,
size: usize,
status: SocketResult,
) -> bool
pub fn consume_output_flush( &mut self, size: usize, status: SocketResult, ) -> bool
Account for one write of Self::output_pending, and answer whether
the caller should stop.
true means the write stalled (WouldBlock or a zero-length write) and
the caller must wait for its next writable event; false means carry
on until Self::output_pending is empty.
Sourcepub fn has_pending_write_with(&self, tls_wants_write: bool) -> bool
pub fn has_pending_write_with(&self, tls_wants_write: bool) -> bool
H2Shell::has_pending_write with the TLS answer supplied instead of
queried — the byte-in / byte-out spelling of the same probe.
A driver with no TLS layer passes false and gets the connection’s own
two conditions back.
pub fn has_pending_control_write(&self) -> bool
Sourcepub fn stream_count(&self) -> usize
pub fn stream_count(&self) -> usize
Number of streams currently tracked in the wire map. stream_table is
private to this module, so this is the sole accessor for router.rs’s
H2-connection-reuse heuristic (picks the non-draining H2 backend
connection with the fewest active streams).
pub fn create_stream<L>(
&mut self,
stream_id: u32,
context: &mut Context<L>,
) -> Option<usize>where
L: ListenerHandler + L7ListenerHandler,
pub fn new_stream_id(&mut self) -> Option<u32>
Sourcepub fn force_disconnect(&mut self) -> MuxResult
pub fn force_disconnect(&mut self) -> MuxResult
Take the force-disconnect decision as far as it goes without a socket, and park the rest.
Everything this function can settle by itself, it settles here:
H2State::Error, and on a Position::Client connection the backend
status and the HUP readiness event. What it cannot settle is the
tls_wants_write answer h2_close::force_disconnect_action decides
on, because the socket belongs to the shell — so it raises an
H2ForceDisconnectTarget and answers provisionally.
The provisional answer is exact everywhere but one case.
Position::Client always continued. A Position::Server whose peer is
gone always closes, because h2_close::force_disconnect_action
answers CloseSession on peer_gone whatever the socket says. Only a
server connection with a live peer genuinely turns on the query, and it
answers MuxResult::Continue until the shell settles it — the
conservative direction of the two, since a session held one pass too
long is recoverable and a session closed with records still buffered is
the truncation h2_close::force_disconnect_action exists to prevent.
Seventeen sites reach this function and every one returns its answer
immediately, so on the ordinary paths no core code runs between the
raise and the return of the entry point the shell called.
Self::goaway and Self::send_initial_goaway are the exception on
their GOAWAY-serialisation failure arms, which Self::enqueue_rst’s
flood escalation can reach from deep in a frame handler; what those
paths can observe in the gap is documented on
Self::force_disconnect_after_query.
Sourcepub fn poll_force_disconnect(&mut self) -> Option<H2ForceDisconnectTarget>
pub fn poll_force_disconnect(&mut self) -> Option<H2ForceDisconnectTarget>
Take the H2ForceDisconnectTarget this connection parked, if it
parked one.
Taking rather than reading is what makes a second settlement
impossible: the next call sees None and the shell’s helper answers
with the result it was already holding.
Sourcepub fn force_disconnect_after_query(
&mut self,
target: H2ForceDisconnectTarget,
tls_wants_write: bool,
) -> MuxResult
pub fn force_disconnect_after_query( &mut self, target: H2ForceDisconnectTarget, tls_wants_write: bool, ) -> MuxResult
Settle a Self::force_disconnect with the tls_wants_write answer
the shell read on its behalf.
tls_wants_write arrives as this function’s own parameter and the
target as the other, which is the same discipline
H2WritableStateTarget::Flush keeps by carrying no bool: the
pre-raise state and the post-raise socket answer are parameters of one
function that never read each other’s source, and no binding survives
in the raising scope for a later edit to point at the wrong question.
The query is read fresh here, exactly as Self::force_disconnect
used to read it fresh at all seventeen sites — including the four whose
caller already held an answer, which is why none of them passes its own.
Between the raise and this call the core cannot change the answer: it
holds no socket, so nothing it runs can flush a record. What the gap
can change is what the log lines render. On the ordinary paths there is
no gap. On Self::goaway’s serialisation-failure arm, reachable from
Self::enqueue_rst, ConnectionH2::remove_dead_stream and the rest
of a frame handler may run first, so streams= and expect_write=
report the connection as the settlement finds it rather than as the
raise left it. peer_gone is not among them — it is carried.
Sourcepub fn reset_stream<E, L>(
&mut self,
wire_stream_id: u32,
stream_id: usize,
context: &mut Context<L>,
endpoint: E,
error: H2Error,
) -> MuxResult
pub fn reset_stream<E, L>( &mut self, wire_stream_id: u32, stream_id: usize, context: &mut Context<L>, endpoint: E, error: H2Error, ) -> MuxResult
Reset a stream: tear down kawa state, emit RST_STREAM on the wire,
and record MadeYouReset accounting.
wire_stream_id is the on-wire StreamId; stream_id is the internal
GlobalStreamId slot. Callers already carry both so we pass them
explicitly rather than scanning self.streams. The wire id is threaded
into Self::enqueue_rst which queues the frame for serialisation in
Self::flush_pending_control_frames on the next writable tick —
independent of whether the caller immediately evicts the slot via
remove_dead_stream (which they usually do). This is what guarantees
the RST reaches the peer for malformed HEADERS / flow-control /
content-length violations flagged by h2spec 2.0.
pub fn end_stream<L>(&mut self, stream_gid: usize, context: &mut Context<L>)where
L: ListenerHandler + L7ListenerHandler,
pub fn start_stream<L>(
&mut self,
stream: usize,
context: &mut Context<L>,
) -> boolwhere
L: ListenerHandler + L7ListenerHandler,
Trait Implementations§
Source§impl Debug for ConnectionH2
Renders the peer address this connection snapshotted at construction,
where it used to render the socket behind it.
impl Debug for ConnectionH2
Renders the peer address this connection snapshotted at construction, where it used to render the socket behind it.
socket_ref() handed mio::net::TcpStream’s own Debug a concrete OS
handle — local address, peer address and file descriptor — which is more
than a reader of this struct needs and is a value a byte-in / byte-out core
cannot produce. peer_address is what every log_context! line already
renders, it survives the peer’s reset where a live getpeername(2) answers
ENOTCONN, and it carries no descriptor. In the H2 simulator it is a fixed
address by construction, so no ephemeral port reaches a trace through here
any more.
No production body in this file reaches SocketHandler::socket_ref any
more, and socket_mut never had one. ConnectionH2::snapshot_rtts held
the last of them until issue #1339 Q11 moved the frontend RTT read out to
the shell (ShellEndpoint::local_rtt since #1590); the remaining spellings
are all in mod tests.