Skip to main content

ForwardCtx

Struct ForwardCtx 

Source
struct ForwardCtx {
Show 24 fields session_id: SessionId, draft: Arc<SessionDraft>, draft_is_fixed: bool, observer: Arc<dyn ProxyObserver>, hook: Arc<dyn ProxyHook>, cancel: CancellationToken, counters: Arc<Recorder>, shape_stats: Arc<ShapeRecorder>, closer: SessionCloser, egress: EgressConfig, observer_enabled: bool, objects_enabled: bool, object_hook: bool, shaping_enabled: bool, shape: Option<Arc<SessionShaper>>, control_mutation: bool, control_parse: bool, fetch_orders_wanted: bool, fetch_orders: Arc<FetchGroupOrders>, streams_enabled: bool, datagram_hook: bool, next_stream_id: Arc<AtomicU64>, streams: Arc<StreamRegistry>, gauge: Arc<EgressGauge>,
}
Expand description

Shared context for forwarding helpers, avoiding repeated parameter lists.

Fields§

§session_id: SessionId§draft: Arc<SessionDraft>

The draft this session frames with, shared by every task rather than copied into each — see SessionDraft for why that matters and for what settles it. Read through ForwardCtx::draft, or through ForwardCtx::resolved_draft where the answer has to be right rather than current.

§draft_is_fixed: bool

Whether draft is fixed (from ALPN) and should not be refined by peeking at SETUP messages.

§observer: Arc<dyn ProxyObserver>§hook: Arc<dyn ProxyHook>§cancel: CancellationToken§counters: Arc<Recorder>

This session’s slow-path counters.

§shape_stats: Arc<ShapeRecorder>

This session’s shaping counters. Cloned per task exactly as counters is, and carried unconditionally: an unshaped session’s recorder has no class rows and no writer, so the cost of carrying it is one Arc clone per forwarding task and the cost of not carrying it would be an Option branch on the data path.

§closer: SessionCloser

Where an Action::CloseSession lands, and what run_with_transport reads its close code and reason back out of.

§egress: EgressConfig

Engine knobs for the per-stream deferred write queues.

§observer_enabled: bool

Cached observer.wants_events() — gates event construction and emission in the hot forwarding loop. When false, the proxy can skip parsing for observation purposes and run as a byte pump.

§objects_enabled: bool

Whether data streams are framed into objects — the framing gate, which decides whether pipe_data calls pipe_data_framed or pipe_data_passthrough. Keeps its observer_enabled || term because ProxyEvent::Object is an observer-only guarantee. This is not the gate on calling on_object; see object_hook.

§object_hook: bool

Whether ProxyHook::on_object is consulted. Interest::OBJECTS alone, with no observer_enabled || term: an event observer must not hand a hook that declared no object interest the power to drop, delay and rewrite traffic.

§shaping_enabled: bool

Whether this session was configured with a ShapeProfile.

config.shape.is_some() alone, with no observer_enabled || term — the same asymmetry as object_hook and for the same reason: shaping is configuration, so attaching an observer must not arm it. It is a term of objects_enabled, because a profile has to arm framing on its own.

The read below is what makes that implication checkable rather than merely written down.

Exactly shape.is_some(), and the two are kept as separate fields on purpose: this one is a bool a debug_assert! and a hot-path branch can read without touching an Arc, and shape is the engine. The equivalence is checked in pipe_data, where the framing decision is taken.

§shape: Option<Arc<SessionShaper>>

This session’s shaper, or None when no ShapeProfile was configured.

Unlike shape_stats and streams, which are always constructed, this is genuinely optional — there is nothing for an unshaped session to share, and an Option here is what makes “a session with shape: None adds nothing to the shaping path” a fact the type system carries rather than a claim a reviewer checks.

Some or None is fixed for the session’s life. A profile installed on the proxy afterwards can replace what is inside this, and cannot put something here: framing is armed at session start and a session that began as a byte pump produces no ObjectMeta to classify.

§control_mutation: bool

Whether ProxyHook::on_control_message is consulted, which also routes the control stream through the parse-then-forward pipe: the pass-through pipe writes before it parses, so a hook return there would be unexecutable by construction.

§control_parse: bool

Whether a ControlStreamParser is built for somebody to read. Interest::NONE with no observer builds none, which is what makes control_parsers_created == 0 unconditional on that path.

Not the whole answer to “is there a parser”: fetch_orders_wanted is the other, and it builds one for the session’s own use. Ask ForwardCtx::control_frames_are_decoded rather than either alone.

§fetch_orders_wanted: bool

Whether this session has to decode control frames to read its own fetch streams — drafts 18, 19 and 20, framing data.

Unlike control_parse this arms no report and calls no hook. It is the one case where the proxy parses the control plane for itself, and it is why a hook declaring Interest::OBJECTS alone can still see a draft-19 fetch Object.

§fetch_orders: Arc<FetchGroupOrders>

What each FETCH this session carried asked for, waiting for the response stream that answers it.

Written by both control pipes and read by the object framer; see FetchGroupOrders.

§streams_enabled: bool

Whether on_stream_open, on_stream_header and on_stream_end are consulted. Interest::STREAMS contains Interest::OBJECTS structurally, so this implies objects_enabled.

§datagram_hook: bool

Whether ProxyHook::on_datagram is consulted.

§next_stream_id: Arc<AtomicU64>

The session’s StreamKey mint. One counter per session, shared by every forwarding task through the ArcForwardCtx is cloned per task and per stream, so a plain AtomicU64 would give each clone its own sequence and two streams would collide on id 0. The Arc is what makes unique for the session’s lifetime true rather than aspirational.

§streams: Arc<StreamRegistry>

Every forwarded stream that is still live, and the gate each one releases when it ends.

Always constructed, for every session, exactly like next_stream_id and unlike anything a ShapeProfile will later arm: StreamAction::SerializeAfter is gated by Interest::STREAMS and the capability table publishes it as an unconditional Yes at both stream sites, so a registry that only existed when a profile was configured would make that published cell a lie. An empty registry allocates nothing and touches no counter, so interest_none.rs’s whole-struct Counters::default() comparison and its !release_timer_started() companion stay falsifiable.

§gauge: Arc<EgressGauge>

How many bytes this session’s egress queues are holding, summed across every stream. Always constructed, like streams and for a related reason: a gauge that only some queues reported into would answer this session has nothing left to flush while another stream still held a deferred frame, and the one caller that reads it — a requested close deciding whether it may stop waiting — would act on that answer.

Costs one Arc clone per forwarding task and two relaxed atomic updates per queued unit. A session that queues nothing, which is every session with no timing action and no profile, never touches it: the counters only move inside PendingQueue::push and its releases.

Implementations§

Source§

impl ForwardCtx

Source

fn draft(&self) -> DraftVersion

The draft this session frames with, as it stands now.

Source

fn control_frames_are_decoded(&self) -> bool

Whether a control frame gets decoded on this session at all.

Two unrelated reasons, deliberately summed in one place rather than spelled a || b at each of the pipes: control_parse is somebody asking to be told, and fetch_orders_wanted is the session needing the answer itself. A pipe that tested only the first left a draft-19 fetch stream unaddressable on an Interest::OBJECTS session, which is the shape of hook the object site exists for.

Source

fn caps(&self) -> Capabilities

What this session’s draft can be asked for.

Built here, at each site that needs one, rather than cached on this struct. Capabilities is a Copy newtype over a draft, so constructing it costs a move of one enum and answers for the draft the session is framing with at that moment — while a cached copy would have been built beside the guess and would go on answering for it after the peer named something else. One draft in one cell has one consumer to keep correct; a cached table beside it would be a second.

Source

async fn resolved_draft(&self) -> DraftVersion

The draft this session frames with, waited for.

The ordering edge between the control stream, which learns the draft, and the tasks that have to agree with it. Called where the wrong draft produces a wrong result rather than a stale label: the object framer decides where an object ends, and a datagram header decoder decides what a datagram says. See SessionDraft::resolved for what bounds the wait.

Source

fn mint_key(&self, side: ProxySide) -> StreamKey

Mint this stream’s session-local identity.

Called once per forwarded stream, at accept, and handed to every hook site that stream reaches. Monotonic, never reused, and deliberately not the transport stream id: on the WebTransport arm that is the constant 0 for every stream, so a transport-keyed identity collapses a whole side onto one entry.

Source

fn emit(&self, event: impl FnOnce() -> ProxyEvent)

Emit a proxy event only if the observer wants events.

Takes a closure so the ProxyEvent is not constructed when observation is disabled — avoiding clones of message payloads in the hot path.

Source

fn reporter<'a>( &'a self, side: ProxySide, stream_id: Option<u64>, ) -> Reporter<'a>

A reporter for one stream direction, or for a datagram path (stream_id: None).

Trait Implementations§

Source§

impl Clone for ForwardCtx

Source§

fn clone(&self) -> ForwardCtx

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. 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<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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more