Skip to content

Restore relay work for draft-18 and port missing session layer - #211

Merged
englishm merged 3 commits into
cloudflare:draft-18-devfrom
nnazo:nnazo/draft-18-base
Aug 18, 2026
Merged

Restore relay work for draft-18 and port missing session layer#211
englishm merged 3 commits into
cloudflare:draft-18-devfrom
nnazo:nnazo/draft-18-base

Conversation

@nnazo

@nnazo nnazo commented Aug 17, 2026

Copy link
Copy Markdown

The draft-18-dev branch got mangled and only contained wire format changes, so this restores the other relay changes from main ~identically, apart from reapplying draft-18's ALPN negotiation. This also ports the session APIs they call, which landed on main after the fork: SUBSCRIBE_NAMESPACE, direct PUBLISH and SessionConfig, adapted to one bidi stream per request. The last change also stops State::lock unwrapping a poisoned mutex, so one panic no longer spreads to every later access of that state.

nnazo added 3 commits August 17, 2026 15:44
The draft-18 integration resolved moq-relay-ietf and its siblings in favour of
the draft-18 side, which dropped the relay work main had accumulated: track
lookup through the coordinator, the PUBLISH_NAMESPACE fan-out to peer relays,
the namespace and track change broadcasts, the pull-through cache and the
upstream namespace manager. Without those a relay cannot route between hosts at
track granularity, which the draft-16 line has done for a while. That merge
recorded the port as deferred rather than unwanted.

The crates come back from main unchanged, and the commit that follows adapts the
session layer they call to draft-18 rather than reinventing the relay on top of
it.

Restoring wholesale would have reverted draft-18's ALPN version negotiation, so
that is reapplied: the server offers every supported version and rejects a client
that offers none, instead of accepting with no protocol and failing later at
SETUP.

This commit does not build alone.
…raft-18

The restored relay calls session APIs that landed on main after the draft-18
fork, so draft-18 never had them: SUBSCRIBE_NAMESPACE and direct PUBLISH in both
directions, and SessionConfig with its connect and accept constructors.

Adapted rather than copied. Each request owns a bidirectional stream and its
responses omit the Request ID, so responses go through the bidi response paths.
SUBSCRIBE_NAMESPACE sits outside the generic response map because NAMESPACE
carries no Request ID to route on. The object-sending loop is shared between
SUBSCRIBE and PUBLISH as ObjectForwarder.

PUBLISH is accepted with REQUEST_OK, since draft-18 removed the dedicated
PUBLISH_OK type; the old type is still accepted on receive. A protocol violation
closes the session rather than being logged and discarded, and a track alias
reused while live closes with DUPLICATE_TRACK_ALIAS. PUBLISH_DONE carries the
real number of streams opened, which is what tells a subscriber whether it can
discard state.

pending_requests.rs is not ported. Its response routing is structural once each
request owns a stream, and its outbound timeout would have covered PUBLISH alone
while subscribe_open has never had one, so Published::publish documents that ok()
is unbounded and leaves the limit to the caller.

Interop cases in moq-test-client cover both new directions against a local relay.
State::lock and lock_mut unwrapped the inner mutex, so one panic while holding a
state made every later access to it panic too. One State backs every
subscription, reader and waiter built on it, so a single failed request could
take the rest down with it.

Both accessors now recover the guard, log once and clear the poison flag. The
state can hold a half-finished update, which is what the log is for. try_lock and
try_lock_mut still report poisoning, for callers that would rather decide.

The drop path was worse: on a poisoned lock it returned before notify(), leaving
every task awaiting that state waiting for a change that could never arrive.

The unwraps are the same on main, so this applies there unchanged.

Tests poison a state from a thread that panics under the lock, then assert access
still works and that a later drop wakes a waiter. The waiter case panics holding
a shared ref rather than a mutable one, because StateMut notifies as it unwinds
and would make the test pass either way.
@englishm
englishm merged commit fa2c407 into cloudflare:draft-18-dev Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants