struct SessionDraft {
initial: DraftVersion,
settled: Sender<Option<(DraftVersion, DraftSource)>>,
deadline: Instant,
control_stream_open: AtomicBool,
}Expand description
The draft this session frames with, and the one place every task reads it from.
§Why a shared cell rather than a field
Drafts 07 to 14 all negotiate the same ALPN, so a session in that cohort starts on the draft its configuration named and learns the wire’s answer from the first SETUP on the control stream. Everything that has to agree with that answer — the object framer on every data stream, the datagram header decoder, the control-frame walker that places an injection, and the capability table each hook site is shown — lives in a task that was spawned before the control stream was even accepted. A draft copied into each of those tasks is a copy of the guess, and no later correction can reach it.
§Reading it
Self::now is the non-blocking read: the best answer so far, or the
starting draft while there is none. Self::resolved is the ordering
edge — it waits for an answer, and is what a task calls when running on
the wrong draft would produce a wrong result rather than a stale label.
§Writing it
Self::settle takes the first write of each rank and keeps the highest
(see DraftSource). Both control directions write: the client’s
direction from CLIENT_SETUP and the relay’s from SERVER_SETUP, so the
pair converges on the version the peers agreed rather than on whichever
direction was read first.
Fields§
§initial: DraftVersionThe draft chosen before the relay was dialled — the ALPN’s answer
where there is one, and the configured draft otherwise. What
Self::now answers while nothing has settled, and what the
deadline settles on.
settled: Sender<Option<(DraftVersion, DraftSource)>>The best answer so far, or None while the session is still running
on initial. A watch rather than an atomic because the waiters are
the point: this is what Self::resolved parks on.
deadline: InstantThe instant Self::resolved stops waiting. Absolute, and shared by
every waiter, so a session pays this window once rather than once per
stream: the first waiter to reach it settles the cell, and every
waiter after that returns immediately.
control_stream_open: AtomicBoolWhether this session has a control stream at all yet.
The only thing that can name a draft is a SETUP, and the only place a
SETUP arrives is a control stream. Until one exists there is nothing
to wait for, so Self::resolved does not wait — which is what
keeps the window off the timing of a session that never opens one.
It is a latch and not a promise. A peer that opened a data stream before its control stream gets the starting draft on that one stream, which is the same answer it would have got with no cell at all; every draft in the cohort puts the setup exchange first, so a session in which that happens is not one they describe.
Implementations§
Source§impl SessionDraft
impl SessionDraft
Sourcefn new(initial: DraftVersion, fixed: bool) -> Self
fn new(initial: DraftVersion, fixed: bool) -> Self
The cell for a session starting on initial.
fixed is whether that draft came from the ALPN. A fixed session is
born settled, so it never waits and no SETUP peek can move it — which
is the right reading of drafts 15 and later, where the SETUP message
carries no version at all.
Sourcefn note_control_stream(&self)
fn note_control_stream(&self)
Record that this session now has a control stream.
Called where one starts being forwarded, in both topologies. What it
buys is the absence of a wait everywhere else: see
Self::control_stream_open.
Sourcefn now(&self) -> DraftVersion
fn now(&self) -> DraftVersion
The best answer so far, without waiting for a better one.
Sourcefn settle(&self, draft: DraftVersion, source: DraftSource) -> bool
fn settle(&self, draft: DraftVersion, source: DraftSource) -> bool
Record draft as this session’s, if source outranks what is held.
Answers whether it landed, so a caller that has work to do only when the session’s draft actually moved can ask rather than compare.
Sourceasync fn resolved(&self, cancel: &CancellationToken) -> DraftVersion
async fn resolved(&self, cancel: &CancellationToken) -> DraftVersion
The draft, waited for.
Returns at once when the session already has an answer, which is
every session whose ALPN named a draft and every session whose
control stream has already been read. It also returns at once when
the session has no control stream yet, because nothing else can
answer and waiting would put DRAFT_SETTLE_WINDOW on the front of
every stream of a session that never opens one.
Otherwise it waits for one of three things: a SETUP naming the draft,
DRAFT_SETTLE_WINDOW expiring, or the session being cancelled —
the last of which is why a teardown is not held up by a window that
has barely started.