Skip to content

fix(tokio)!: keep the transport that won the connect race - #5484

Merged
kixelated merged 4 commits into
mainfrom
quest/m0/drop-ws-upgrade
Oct 14, 2026
Merged

kixelated merged 4 commits into
mainfrom
quest/m0/drop-ws-upgrade

Conversation

@kixelated

@kixelated kixelated commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Completes the m0 quest "Keep the winning transport" (deleted here).

A moq-tokio connection whose https:// race is won by WebSocket now stays on WebSocket for that session's lifetime. The pending QUIC dial is dropped the moment WebSocket wins, the same way the WebSocket dial is dropped when QUIC wins. An epochless viewer no longer sees a Restart right after connecting, because no second session replaces the first.

Changes

  • rs/moq-tokio: removes the WebSocket-to-QUIC upgrade from feat(tokio)!: upgrade a WebSocket fallback session to QUIC #4180. That covers Upgrade and its stages, the Next::Upgraded handover, and State::upgrade along with its abort/close handling. Also removes fix(tokio): retain QUIC fallback after an early WebSocket close #5284's closed-fallback path (finish_upgrade) and the fallback where a failed MoQ handshake over WebSocket waited on the pending QUIC dial. A WebSocket session that fails or closes early now goes through the ordinary reconnect, which races again.
  • race_transport_connect returns only the winner. The losing arm drops before it returns.
  • The WEBSOCKET_WON memo stays. Its only eviction belonged to the upgrade, so a later race that QUIC wins now clears the entry (websocket::forget in race_transport_connect), and the next dial gives QUIC its head start again. Documented on the static.
  • rs/moq-net: removes Session::setup() and its plumbing (session::Setup, the poll_seen helpers, the extra ietf::start return value) along with tests/session_setup.rs. Only the upgrade used it; GOAWAY drain migration does not gate on it.
  • Unchanged: Connection::transport() and Transport::WebTransport in the bindings, fix(net): retire epochless handles only once their Restart is deliverable #5443's Release origin fix, and js/net.
  • rs/moq-net/tests/upgrade.rs is now replacement.rs and describes a same-peer replacement session (GOAWAY drain or publisher replacement). The Release behavior it covers is unchanged.
  • Docs: doc/concept/transport.md, the websocket module doc, and the Connection::transport() and Status::Migrating docs no longer describe an upgrade.

Public API and wire impact

  • moq-net, breaking: Session::setup() is removed. It was never released (feat(tokio)!: upgrade a WebSocket fallback session to QUIC #4180 is not on release), so there is no upgrade note.
  • moq-tokio: no signature changes. Behavior change: no QUIC upgrade after a WebSocket win, and a failed WebSocket handshake no longer falls back to the pending QUIC dial in the same attempt; the next attempt races again. Status::Migrating now only means a GOAWAY handover.
  • Bindings (moq-ffi and the languages built on it): no change. MoqTransport::WebTransport stays.
  • Wire: none.

Tests

  • Regression, mocked time: client::tests::websocket_winning_cancels_the_quic_dial fails on main ("the QUIC dial outlived WebSocket's win") and passes here. It watches a drop signal on the losing arm. quic_winning_cancels_the_websocket_dial covers the other direction.
  • connection::tests::quic_winning_cancels_the_websocket_dial replaces the flaky quic_winning_opens_one_session. The fixture accepts the fallback's TCP socket and only then releases QUIC, so QUIC always wins with both dials in flight. The test then waits for that socket to close, which shows the cancellation directly; the old try_recv only showed an empty queue at one moment.
  • Deleted the tests for removed behavior: websocket_upgrades_to_quic, a_refused_upgrade_keeps_websocket, abort_ends_an_upgrade_in_flight, close_ends_an_upgrade_in_flight, failed_websocket_handshake_falls_back_to_quic, closed_websocket_session_falls_back_to_pending_quic, the closed_fallback_* and a_failed_upgrade_stays_connected tests, the upgrade Retry tests, a_stalled_upgrade_gives_up_at_the_deadline, and race_transport_connect_keeps_a_pending_quic_dial.
  • Regression, mocked time: client::tests::quic_winning_restores_its_head_start fails with the eviction line removed ("QUIC won, yet the next dial still skips its head start") and passes with it.
  • just check: passes on the final head (6262 Rust tests plus lint, docs, and JS). Earlier runs hit moq-relay wall-clock flakes already tracked by quest/m1/test-flakes-2/auth-refresh-mock-time.md (a_fixed_lease_still_expires, an_outage_refuses_transiently_and_never_extends_the_expiry, renewal_outlives_the_initial_credential_and_withdrawal_ends_only_its_grant); none touch the race, and each passed in isolation.
  • just test interop --all: all checks passed on the final head.

Decisions

  • ✅ The in-attempt handshake fallback stays removed: a WebSocket win drops the QUIC dial at once, and a failed MoQ handshake over WebSocket goes through the ordinary reconnect, which races again (maintainer).
  • ✅ A later QUIC win clears the WEBSOCKET_WON entry, restoring QUIC's head start (maintainer).

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Quest start outcome: the implementation is done and the PR stays a draft until the maintainer decides.

Two calls made while implementing, to check in review:

Open follow-up: should a later QUIC win clear the WEBSOCKET_WON entry? That would give QUIC its head start back once UDP works. I recommend yes, as a one-line follow-up; the quest kept it out of scope.

(Written by Claude Opus 5.5)

@kixelated
kixelated force-pushed the quest/m0/drop-ws-upgrade branch from 621655a to a84eeb6 Compare October 14, 2026 03:52
@kixelated
kixelated marked this pull request as ready for review October 14, 2026 03:52
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 068f4759-0011-4c58-8fb2-43c5ac8d80b5
📥 Commits

Reviewing files that changed from the base of the PR and between 071c6f8 and 22fa4f2.

📒 Files selected for processing: 22
  • doc/concept/transport.md
  • quest/m0/README.md
  • quest/m0/drop-ws-upgrade.md
  • quest/m1/restart-followers.md
  • quest/m1/slow-source.md
  • quest/m2/quic-sim.md
  • quest/m3/quic-careful-resume.md
  • quest/m3/uring-tcp/README.md
  • rs/moq-net/src/client.rs
  • rs/moq-net/src/ietf/peer.rs
  • rs/moq-net/src/ietf/session.rs
  • rs/moq-net/src/lite/session.rs
  • rs/moq-net/src/lite/setup.rs
  • rs/moq-net/src/server.rs
  • rs/moq-net/src/session.rs
  • rs/moq-net/tests/replacement.rs
  • rs/moq-net/tests/session_setup.rs
  • rs/moq-tokio/src/client.rs
  • rs/moq-tokio/src/connection.rs
  • rs/moq-tokio/src/websocket.rs
  • test/portrait/relay.toml
  • test/ts/README.md
💤 Files with no reviewable changes: 6
  • rs/moq-net/src/ietf/peer.rs
  • rs/moq-net/tests/session_setup.rs
  • rs/moq-net/src/lite/setup.rs
  • quest/m3/quic-careful-resume.md
  • rs/moq-net/src/lite/session.rs
  • quest/m0/drop-ws-upgrade.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

Walkthrough

The transport race now returns the first connected transport and cancels the losing dial. A WebSocket session keeps its transport until closure or GOAWAY, after which the connection follows the normal reconnect flow. Session SETUP tracking and its public wait API were removed. Replacement tests and documentation were updated to describe session replacement and transport racing.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 22fa4

This change keeps whichever transport connects first and cancels the losing dial. WebSocket sessions now stay on WebSocket until they end and the next connection races again. No concrete merge-blocking issue was identified.

Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: the connection keeps the transport that wins the connection race. It is concise and specific.
Description check Passed The description accurately explains the transport-race change, removal of WebSocket-to-QUIC upgrades, related API changes, tests, and validation results.
Docstring Coverage Passed Docstring coverage is 84.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 75 functions across 8 files. (8 skipped: 8 …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 Clippy (1.98.1)

Clippy execution timed out

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 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-10-14T03:55:32.236475Z a84eeb6 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.

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed commit: a84eeb6

No actionable bugs found. Keeping the winning transport removes substantial handover complexity and the associated epochless restart path. The race owns and drops its losing future before returning, and a later QUIC win restores its head start. GOAWAY migration, predecessor deadlines, retry accounting and close/abort handling remain coherent; removing Session::setup removes observation plumbing without removing protocol SETUP/auth processing. The renamed replacement tests retain the Release/same-epoch assertions.

The documented removal of same-attempt handshake fallback matches the maintainer's explicit choice. Regression coverage directly observes loser cancellation rather than an instantaneously empty queue.

Limits: static GitHub review of the changes and relevant connection, auth and transport ownership paths. No tests independently run; exact-head CI was still in progress. External dependency cancellation internals were outside this repository-only review.

@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: a84eeb693c

ℹ️ 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 doc/concept/transport.md
Comment on lines +45 to +46
client; the relay enables it with `[web.https]`. A session that lands on
WebSocket stays there until it ends; the next connect races again.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update every remaining WebSocket-upgrade reference

After this paragraph establishes that sessions no longer upgrade, a repo-wide search still finds test/portrait/relay.toml:8-9 and test/ts/README.md:596-603 justifying version selection and CI coverage in terms of WebSocket-to-QUIC upgrades; the README even points readers to transport-upgrade tests that this commit deletes. Update those references, along with the stale statements in quest/m1/slow-source.md:8-9 and quest/m3/quic-careful-resume.md:13-15, so the harness rationale and documented coverage describe the behavior that now ships.

AGENTS.md reference: AGENTS.md:L28-L29

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.

Agreed, fixed in 22fa4f2. test/portrait/relay.toml and test/ts/README.md now justify lite-07 by a replacement session instead of the upgrade, and the README no longer points to the deleted transport-upgrade tests. quest/m1/slow-source.md, quest/m3/quic-careful-resume.md and quest/m2/quic-sim.md no longer describe the upgrade as current behavior. The two remaining mentions (restart-followers.md, uring-tcp/README.md) record the removal itself.

(Written by Claude Opus 5.5)

kixelated and others added 4 commits October 13, 2026 21:13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A connection whose race WebSocket wins now stays on WebSocket for the
session's lifetime. The QUIC dial is cancelled the moment WebSocket wins,
as WebSocket is when QUIC wins, so an epochless viewer no longer sees a
Restart right after connecting.

Removes the WebSocket-to-QUIC upgrade from #4180, the closed-fallback
handover from #5284, and moq_net::Session::setup(), which only the upgrade
used. The WEBSOCKET_WON memo stays and now lasts for the process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WEBSOCKET_WON memo lost its only eviction with the upgrade. A later
race that QUIC wins now clears the entry, since UDP gets through after
all, so the next dial gives QUIC its head start again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TS round-trip rig and the portrait relay still justified lite-07 by
the upgrade; their epochs now matter only for a replacement session.
Three quests described the upgrade as current behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary for head 22fa4f2.

Changes: a connection keeps the transport that won the race. The WebSocket-to-QUIC upgrade (#4180) and the closed-fallback handover (#5284) are removed, the losing dial drops the moment the other arm wins, and moq_net::Session::setup() is removed (it never shipped). A later QUIC win clears the WEBSOCKET_WON entry, restoring QUIC's head start. Stale upgrade references in the TS rig, the portrait relay, and three quests are fixed per the Codex finding.

Decisions (maintainer):

  • ✅ The in-attempt handshake fallback stays removed.
  • ✅ A QUIC win clears the WEBSOCKET_WON entry, with a mocked-time regression that fails without it.

Verification: just check and just test interop --all pass locally. CI is green on this head. The first Interop attempt failed just test media on a browser audio stall ("audio tone: cold start"), which I reproduced on plain origin/main (3 of 3 local runs), so it predates this PR. It passed on rerun. Reviews: OpenAI and Codex on a84eeb6; the later push is the docs fix plus a rebase.

Public API: moq-net Session::setup() removed (breaking, unreleased). Wire: none.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit f78c24a Oct 14, 2026
10 of 11 checks passed
@kixelated
kixelated deleted the quest/m0/drop-ws-upgrade branch October 14, 2026 05:43
kixelated added a commit that referenced this pull request Oct 14, 2026
Main dropped the WebSocket-to-QUIC upgrade (#5484), so the request token no
longer re-attaches after a failed upgrade; both race arms seed the session
they keep.

Co-authored-by: Kyle Sletmoe <sletmoe@amazon.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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