Skip to content

feat(tokio)!: upgrade a WebSocket fallback session to WebTransport - #4189

Merged
kixelated merged 6 commits into
quest/m1/transport-upgrade/READMEfrom
quest/m1/transport-upgrade/rust
Sep 25, 2026
Merged

kixelated merged 6 commits into
quest/m1/transport-upgrade/READMEfrom
quest/m1/transport-upgrade/rust

Conversation

@kixelated

@kixelated kixelated commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Completes /quest/m1/transport-upgrade/rust.md on the transport-upgrade line (#4180).

Problem

https:// races QUIC against the WebSocket fallback, and the loser is dropped. When WebSocket wins (a lost Initial, a slow first dial), the session stays on TCP for its whole life, and the "WebSocket won" memo takes QUIC's head start away from every later dial to that host.

Approach

  • race_transport_connect hands back the still-pending QUIC dial when WebSocket wins. Client::dial returns it as an Upgrade beside the session. The attempt's existing connect timeout still bounds it.
  • Connection polls the upgrade next to the live session. When the QUIC dial completes, status goes to Migrating while the MoQ handshake runs on QUIC. Then the QUIC session becomes live (Connected, epoch + 1). The WebSocket session gets Goaway::new().with_timeout(handover) and moves into the existing Draining path, which closes it at the handover cap. Live tracks splice onto QUIC at a group boundary through the origin, as in a peer GOAWAY.
  • A successful upgrade removes the URL from WEBSOCKET_WON. A failed upgrade logs at debug, restores Connected, and the session stays on WebSocket. If the WebSocket session ends first, the pending dial is dropped and the redial races again.
  • Shared::connected now counts the session it replaces as ended, so sessions_started - sessions_ended stays at 1 across a migration. This also fixes the same drift that already existed on the peer-GOAWAY path.
  • Tests (inline in moq-tokio, since they read the memo). The QUIC path goes through an in-process UDP forwarder that holds every datagram until the test opens it, so WebSocket wins without depending on timing.
    • websocket_upgrades_to_quic: groups 0, 1, 2 arrive in order, with group 1 written while QUIC comes up. The transport flips to WebTransport, epoch and presence advance, the memo is cleared, and the server's WebSocket session receives an empty-URI GOAWAY and closes. Stress-run 30x clean.
    • quic_winning_opens_one_session: QUIC wins, and only one session is ever accepted.
    • a_failed_upgrade_stays_connected pins Migrating and its rollback. The end-to-end test can't observe Migrating, because a moq-lite client finishes its handshake without waiting on the server.

Impact

Breaking on moq-tokio, so this line (#4180) now targets dev.

  • moq-tokio (breaking): server::Transport moved to the crate root as moq_tokio::Transport, with no re-export at the old path. It is shared by the accept and dial sides.
  • moq-tokio (breaking): new Transport::WebTransport variant, distinct from raw Quic (as_str is "webtransport"). The server now reports WebTransport sessions as WebTransport rather than Quic, and the client reports https:// dials as WebTransport and moqt:///moql:// dials as Quic.
  • moq-tokio: new Connection::transport() -> Option<Transport>.
  • moq-tokio: behavior change. A Connection that lands on WebSocket migrates to WebTransport when that dial completes, and briefly holds two sessions during the handover. Status::Migrating now also covers this case (doc updated).
  • moq-tokio: presence counters now count a migrated-away session as ended.
  • moq-ffi: new MoqTransport::WebTransport (appended). MoqRequest::transport() now reports it for WebTransport sessions. The Dart bindings are regenerated.
  • moq (Go): new TransportWebTransport constant, so callers can name the value Request.Transport() now returns for WebTransport sessions.
  • moq-relay: WebTransport maps to moq_auth::Transport::Quic, so the auth contract and wire are unchanged.
  • moq-cli: callers updated to the new path.
  • Wire: none. The client GOAWAY carries an empty URI, which is legal on every version.

Alternatives

  • Report Migrating and Connected back to back at the swap. Rejected: nobody could ever observe it. Migrating instead spans the MoQ handshake on QUIC.
  • An upgrade-specific deadline. Rejected: the connect timeout that already bounds the race keeps bounding its QUIC arm.

Follow-ups

  • Checks: moq-ffi compiles again. The line merged origin/main for fix(ffi): name the binary config conversion so moq-ffi compiles #4157 after merging origin/dev. Workspace clippy and every changed-package nextest pass, and just dart check passes. moq-uring tests fail with ENOMEM in io_uring setup on this host. This PR does not touch moq-uring.

  • Expose Connection::transport() through moq-ffi and the bindings, if a consumer wants it.

  • The qmux (WebSocket) lite session logs WARN failed to send goaway: transport: connection closed even though the peer received it. This is existing noise in the qmux adapter, now hit on every upgrade.

  • Adding a WebTransport value to the auth contract (moq_auth::Transport, js/auth) is left for a follow-up if an auth server needs to tell the two apart.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits September 25, 2026 12:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When WebSocket wins the race, the QUIC dial keeps going. If it lands, the
Connection runs the MoQ handshake on it, swaps it in as the live session,
sends an empty-URI GOAWAY on the WebSocket session, and drains it within the
handover cap. The "WebSocket won" memo forgets the URL so the next dial gives
QUIC its head start again.

Adds Connection::transport(), reusing server::Transport as moq_tokio::Transport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 3 commits September 25, 2026 13:50
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ariant

Moves server::Transport to moq_tokio::Transport with no re-export, and splits
WebTransport from raw QUIC on both the accept and dial sides. moq-ffi gains
MoqTransport::WebTransport; the relay maps it to moq_auth::Transport::Quic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(tokio): upgrade a WebSocket fallback session to QUIC feat(tokio)!: upgrade a WebSocket fallback session to WebTransport Sep 25, 2026
@kixelated
kixelated marked this pull request as ready for review September 25, 2026 21:37
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T21:41:08.154937Z a2476af Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2476af001

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-ffi/src/server.rs
Comment on lines +215 to +216
/// WebTransport over HTTP/3 on QUIC.
WebTransport,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Export WebTransport from the Go wrapper

When a WebTransport request reaches the FFI server, MoqRequest::transport() can now return this sixth value, but go/wrapper/server.go still exports constants only through TransportUnix. The ergonomic Go package therefore has no moq.TransportWebTransport symbol, forcing callers to import the raw FFI package or leaving transport handling unable to name the returned value. Add the documented wrapper constant alongside this enum addition. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L94-L96

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. The other bindings alias the generated enum, but the Go wrapper re-exports each transport by hand and stopped at TransportUnix. Added moq.TransportWebTransport in 6bd6ee8.

(Written by Grok 4.7)

The FFI enum gained WebTransport, but the Go wrapper only re-exported
constants through TransportUnix.

Co-Authored-By: Grok 4.7 <noreply@x.ai>
@kixelated
kixelated merged commit cf3bb19 into quest/m1/transport-upgrade/README Sep 25, 2026
17 checks passed
@kixelated
kixelated deleted the quest/m1/transport-upgrade/rust branch September 25, 2026 23:10

Copy link
Copy Markdown
Collaborator Author

Landing the WebSocket-to-WebTransport upgrade on quest/m1/transport-upgrade/README.

A WebSocket fallback session upgrades to WebTransport when that dial completes. Breaking on this unpublished quest branch: server::Transport moved to moq_tokio::Transport with no re-export, and WebTransport is now distinct from raw Quic.

Also exported moq.TransportWebTransport from the Go wrapper so callers can name the new FFI value.

(Written by Grok 4.7)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant