Skip to main content

ConnectionH2

Struct ConnectionH2 

Source
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: Ulid

Connection/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: Duration

Configured 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: H2ByteAccounting

Byte 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: H2ConnectionConfig

Per-listener H2 connection tuning (window size, max streams, shrink ratio).

§stream_idle_timeout: Duration

Per-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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn handle_write<L>( &mut self, context: &mut Context<L>, stream_id: H2StreamId, written: H2Written, status: SocketResult, pass: &mut H2WritePass, )

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.

Source

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.

Source

pub fn prune_inactive_streams_while_closing<L>( &mut self, context: &mut Context<L>, )

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.

Source

pub fn finalize_write<L>( &mut self, tls_wants_write: bool, socket_write: bool, bytes_written_this_pass: usize, context: &mut Context<L>, ) -> H2FinalizeTarget

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.blocks because the converter yielded (e.g. RFC 9218 incremental rotation). LIFECYCLE §9 invariant 16: keep Ready::WRITABLE armed 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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn try_resume_reading<L>(&mut self, context: &Context<L>) -> bool

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.

Source

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.

Source

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

Source

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).

Source

pub fn peer_header_block_in_progress(&self) -> bool

True while a peer HEADERS block is still being reassembled (H2State::ContinuationHeader / H2State::ContinuationFrame).

Source

pub fn goaway_before_forced_close<L>( &mut self, context: &Context<L>, ) -> MuxResult

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.

Source

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.

Source

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_at is None),
  • the knob is 0 / None (indefinite wait explicitly opted in),
  • or the elapsed time is still within the configured budget.
Source

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).

Source

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.

Source

pub fn read_space<'a, L>( &'a mut self, context: &'a mut Context<L>, stream_id: H2StreamId, amount: usize, ) -> &'a mut [u8] ⓘ

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.

Source

pub unsafe fn gather_transmit<L>( &self, context: &Context<L>, stream_id: H2StreamId, io_slices: &mut Vec<IoSlice<'static>>, ) -> (usize, usize)

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.

Source

pub fn write_buffer<'a, L>( &'a mut self, context: &'a mut Context<L>, stream_id: H2StreamId, ) -> &'a mut Kawa<Checkout>

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.

Source

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.

Source

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.

Source

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.

Source

pub fn has_pending_control_write(&self) -> bool

Source

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).

Source

pub fn create_stream<L>( &mut self, stream_id: u32, context: &mut Context<L>, ) -> Option<usize>

Source

pub fn new_stream_id(&mut self) -> Option<u32>

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn end_stream<L>(&mut self, stream_gid: usize, context: &mut Context<L>)

Source

pub fn start_stream<L>( &mut self, stream: usize, context: &mut Context<L>, ) -> bool

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.

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.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.