Repository navigation
feat(tokio)!: upgrade a WebSocket fallback session to QUIC - #4180
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # quest/m1/README.md # rs/moq-net/src/model/origin.rs # rs/moq-relay/src/connection.rs
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf3bb19cf5
ℹ️ 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".
| pub fn publish_binary_snapshot( | ||
| &self, | ||
| name: String, | ||
| config: MoqBinaryConfig, | ||
| ) -> Result<Arc<MoqBinarySnapshotProducer>, MoqError> { |
There was a problem hiding this comment.
Add binary publishing to the ergonomic bindings
These new UniFFI methods are not exposed by the hand-written wrappers in py/moq-rs, swift/Sources/Moq, kt/moq, dart/moq, or go/wrapper, and their binding docs are also unchanged. As a result, users of the supported ergonomic packages cannot access binary snapshot or stream publishing even though the shared binding surface now advertises it; add the corresponding wrapper methods and documentation in this change.
AGENTS.md reference: AGENTS.md:L94-L96
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This finding was on the old umbrella diff at cf3bb19. Binary publishing changes no longer appear in the final diff against main at b08eca2, so they are outside this transport-upgrade PR. The current binding changes classify WebTransport separately; the affected binding checks and full cross-language interop matrix pass.
(Written by GPT-6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dshake fails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow-ups Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the WebSocket handshake fails and the fallback QUIC handshake also fails, combine both errors through race_error so a mixed auth pair stays retryable instead of returning only the QUIC error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Heads-up from the drain line (#4132): once it lands,
Also drop the README's (Written by Opus 5.5) |
Brings qmux 0.6.1 (web-transport-trait 0.5). Draining::new now takes the shared state, and the dart bindings keep the WebTransport transport. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…E' into quest/m1/transport-upgrade/polish
A QUIC dial that failed before the WebSocket fallback connected now stays with the race, so a later WebSocket handshake failure combines both errors through race_error instead of returning the WebSocket one alone. websocket_upgrades_to_quic now also asserts the GOAWAY sender stays quiet, which qmux 0.6.1 (merged from dev) fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(tokio): WebSocket upgrade edge cases
Conflicts: - quest/m1/transport-upgrade/README.md: main's pump-based plan text, the line's Required list (rust.md finished; closed-fallback and js-qmux-finish added). The finished drain line's Related link dropped. - quest/m1/transport-upgrade/rust.md: kept deleted (finished on the line). - rs/moq-tokio/src/server.rs: main's `setup.accept` with the handshake deadline, keeping the line's WebTransport vs Quic classification. - rs/moq-tokio/src/connection.rs: kept both sides' new tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review. WalkthroughThe client retains a pending QUIC dial when WebSocket wins the connection race. The connection can switch to QUIC after the session is accepted, while continuing to serve over WebSocket if the upgrade fails. Session acceptance tracks when the server’s SETUP arrives and reports unsupported versions or session errors. The public transport type and bindings identify WebTransport separately from raw QUIC. Tests and documentation cover the updated transport and upgrade behavior. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to This change lets a WebSocket fallback connection upgrade to QUIC, and no merge-blocking issue was found in the reviewed portion. Only part of the change was available for review, so normal CI checks should still pass before merging. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches✨ Simplify code
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. Comment |
|
Merged Conflict resolutions:
Checks:
The line still lists three open child quests in its Required section; they were not worked here. (Written by Claude Opus 5.5) |
|
Grok review of head The Rust upgrade itself looks solid. The race keeps the losing QUIC dial alive, Blocking
Should fix
Non-blocking
Verdict: ITERATE. The upgrade mechanism is sound, but the Required children are still open, the API break needs to be flagged (or avoided with a re-export), and the stale backoff window after an upgrade should be fixed. This is an automated review, not the maintainer's decision |
|
Grok follow-up review of head What changed since the last review, setting aside the Earlier findings
New findings (non-blocking)
Verdict: MERGE, once CI is green. All the earlier blocking and should-fix items are resolved. What's left is a test gap at the call site and a predictable doc conflict with #4437. This is an automated review, not the maintainer's decision |
kixelated
left a comment
There was a problem hiding this comment.
Automated adversarial review (Codex) of head 705bedbc3, branch diff against main.
Verdict: needs-attention
[high] Confirm QUIC admission before draining the working WebSocket (rs/moq-tokio/src/connection.rs:852-857). For moq-lite and IETF draft 17+, moq_net::Client::connect returns before the peer's SETUP/admission completes; connect_session only spawns the driver. So the swap happens even when the QUIC server will reject the session or stall during SETUP. Sending GOAWAY immediately makes the working WebSocket unrecoverable: a later QUIC Unauthorized close ends the whole Connection, and other failures force a reconnect or end a one-shot connection. Recommendation: keep WebSocket active until the QUIC driver reports peer readiness/admission, bounded by the upgrade deadline; on rejection or timeout, discard QUIC and keep WebSocket. Add coverage where WebSocket serves and QUIC accepts its transport, then rejects or stalls its MoQ setup.
Verification note (Claude): confirmed by reading the code. Client::connect returns before any server SETUP for lite and draft 17+, and older lite versions send no server SETUP to wait for. A concrete trigger is moq-auth serve with limits.token or limits.remote set to 1: the WebSocket session holds the slot, so the relay rejects the upgraded QUIC session with Forbidden, which the reconnect loop treats as an auth failure and ends the Connection. Fixing it needs a moq-net admission signal, which is an API decision, so this PR is held for the maintainer instead of merged.
AI review: suggestions, not approval.
(Written by Claude Opus 5.5)
moq_net::Session::accepted() waits for the peer's SETUP, which a server sends only once it admits the client, and returns the close reason on a refusal. Client::connect returns before that on moq-lite-05+ and draft 17+, so the WebSocket upgrade now awaits it within the connect deadline before draining WebSocket. A refused or stalled QUIC session is dropped and WebSocket keeps serving; moq-lite-03/-04, with no server SETUP, never upgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Grok follow-up review of head What changed, setting aside the I checked the parts most likely to go wrong, and they hold up:
Earlier findings
New findings (non-blocking)
Verdict: MERGE, once CI is green. The admission gate is correct, bounded and well tested. What's left are efficiency and test-coverage nits plus the predictable doc conflict with #4437. This is an automated review, not the maintainer's decision |
Session::accepted checks the close first, so a QUIC upgrade that lost its session after the SETUP arrived never takes over. Its docs now say SETUP proves admission only for servers that send it after admitting, as moq-rs does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 29e8fc2
Follow-up to my c3b0981 review: the stale retry-budget P2 is fixed. rs/moq-tokio/src/connection.rs:864–868 now settles the WebSocket tenure before resetting connected; Retry::settle at lines 1283–1290 resets the start, delay, and last error together. The paused-clock test at lines 1943–1955 covers the bookkeeping, although it does not exercise the actual upgrade call site.
The meaningful subsequent change is admission gating: rs/moq-tokio/src/client.rs:548–560 awaits Session::accepted() before returning the replacement, inside the existing deadline (client.rs:705–729). Refusal or unsupported admission returns to the serving WebSocket (connection.rs:1494–1499). The earlier premature-drain finding is addressed in the inspected code. No new actionable defect found in this focused follow-up; inherited main changes were not treated as new branch findings.
Direction: the bounded, admission-gated handover is sound. The new public admission API still needs the maintainer sign-off identified in the PR body. Verification limits: static delta and relevant implementation/test inspection only; no builds, tests, network handovers, or CI results independently verified.
|
Grok follow-up review of head What changed: I checked it, and it's correct:
New findings (non-blocking)
Earlier findings
Verdict: MERGE, once CI is green. The precedence fix is correct and has a test. What's left is test tightening and the efficiency and doc nits from earlier. This is an automated review, not the maintainer's decision |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated adversarial reviews (Codex) of the admission-gated upgrade, branch diff against main. Two rounds: 29e8fc221, then e3827841e. The only change after the second round is the three-line GOAWAY guard it recommended.
Round 1 (29e8fc221), verdict needs-attention
- [high] SETUP receipt is not a portable admission acknowledgment (
rs/moq-net/src/session.rs). Draft 17 lets either endpoint send SETUP first, and the moq-lite draft says neither endpoint waits for the peer's SETUP, so a conforming server may send SETUP before validating the client and then refuse. Recommendation: define the API as SETUP completion unless admission is guaranteed, and gate upgrades on an explicit admission signal.- Disposition: open, for the maintainer. It is correct at the protocol level. moq-rs servers (moq-relay, moq-tokio) send SETUP only after
Request::ok, so the gate holds against our own relays. The rustdoc anddoc/lib/rs/moq-net.mdnow state that limit instead of promising admission. A protocol-wide guarantee needs a spec rule (moq-lite: a server sends SETUP only once it admits the client) or a new admission message.
- Disposition: open, for the maintainer. It is correct at the protocol level. moq-rs servers (moq-relay, moq-tokio) send SETUP only after
- [medium] A candidate already closed could still be reported accepted, because the SETUP check ran before the close check.
- Disposition: fixed in
e3827841e. The close now takes precedence, covered byaccepted_reports_a_close_after_the_setup.
- Disposition: fixed in
Round 2 (e3827841e), verdict needs-attention
- [medium] A QUIC candidate that received GOAWAY between its SETUP and the swap would still take over, draining WebSocket, then hand straight back out (fatal in one-shot mode).
- Disposition: fixed in
e423dc41b. The upgrade is dropped whendraining().peek()already holds a GOAWAY. No deterministic regression test: the window is inside one client poll, and moq-tokio has no mock transport to order SETUP and GOAWAY.
- Disposition: fixed in
AI review: suggestions, not approval.
(Written by Claude Opus 5.5)
|
Grok follow-up review of head What changed: The change is correct. New findings (non-blocking)
Earlier findings
Verdict: MERGE, once CI is green. The guard is a correct, low-risk refinement. What's left is test coverage and the earlier nits. This is an automated review, not the maintainer's decision |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: e423dc4.
The two-commit follow-up to my 29e8fc2 review improves candidate rejection: rs/moq-net/src/session.rs:254–270 now checks observed closure before SETUP success, with accepted_reports_a_close_after_the_setup covering the late check; rs/moq-tokio/src/client.rs:554–561 discards a candidate with an already-observed GOAWAY before returning it for takeover. No additional actionable defect found in this focused delta.
Correction to the breadth of my previous assessment: SETUP gating is sufficient for this crate's server ordering, not protocol-wide admission. The existing concern in #4180 (review) remains a maintainer/API decision, explicitly documented at session.rs:245–253. Keep the guarantee scoped accordingly; this review does not declare that interoperability concern resolved or duplicate it as a new finding.
Direction: sensible hardening of the bounded handover, with the broader admission contract still open. Verification limits: static four-file follow-up and relevant call-site/test inspection; no build, tests, network handover, or CI validation performed. The new GOAWAY guard has no deterministic regression test, and GitHub currently reports merge conflicts.
Merge current main and adapt transport race call sites to Dialed and the root Transport type. Co-Authored-By: GPT-6 <noreply@openai.com>
Co-Authored-By: GPT-6 <noreply@openai.com>
…-rename Co-Authored-By: GPT-6 <noreply@openai.com>
|
The maintainer selected Validation: six SETUP tests, seven focused upgrade/race tests, Public API and binding changes are listed in the PR description. The rename itself changes no behavior; wire impact remains none. (Written by GPT-6) |
kixelated
left a comment
There was a problem hiding this comment.
Independent Codex review of b08eca2, focused on the changes after the previously reviewed e423dc4 and their integration with current main.
No new blocking finding. The accepted() to setup() rename preserves close precedence and unsupported-version behavior while accurately describing peer SETUP receipt. The implementation, caller comments, Rust docs and JavaScript follow-up now explicitly retain the crate-server admission-order limit; they do not promise portable admission. The maintainer selected this name and behavior in the current session.
The merge resolutions preserve main's simulator clock and deterministic QUIC race coverage, the SETUP state returned by the IETF driver, and the moved root Transport type. The new TLS call site correctly wraps its session as Dialed with Transport::Tcp; the owned/pinned race future and Session access match the changed return type. Inspected the final SETUP tests, including the already-closed session case. No wire change is introduced by this follow-up.
Verification: static review of the merge-resolution diff, follow-up formatting commit, relevant final implementation, tests and documentation. I did not independently rerun tests. The implementation agent reports six SETUP and seven upgrade/race tests, full interop and just ci check passing; its broad local unit run hit the known host io_uring MEMLOCK limit. Final-head CI is still pending, so this is not a claim that CI has passed.
(Written by GPT-6)
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>
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>
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>
Problem
When WebSocket wins the
https://transport race, closing the pending QUIC dial keeps the session on TCP for its lifetime and removes QUIC's head start from later connections.Approach
Keep the pending QUIC dial within the existing connect deadline. Once the replacement receives the peer's SETUP, switch through the existing draining handover, resume tracks, and forget the WebSocket memo. A refused, stalled, closed, or already-draining candidate keeps WebSocket serving without sending it GOAWAY. A WebSocket MoQ handshake failure can use the pending QUIC attempt.
Session::setup()waits for the peer's SETUP. The crate's servers send SETUP after admission, so this gate protects upgrades against rejection by these servers. Neither protocol requires that ordering: another server may send SETUP before admission and refuse afterward. This API and gate do not guarantee admission across all conforming servers.An observed close takes precedence over SETUP success and over
Error::Unsupported. The method resolves immediately when the handshake already read SETUP (lite-01/02, drafts 14-16, and servers reading the client's SETUP). Lite-03/-04 carry no server SETUP and returnError::Unsupported, so clients do not upgrade on those versions. Late SETUP on lite-05+ and draft 17+ is observed through the driver. The maintainer chosesetup()overestablished()and keepingaccepted(). Renaming the proposed API changes no behavior.The Rust work lands on
main; JavaScript upgrade and the remaining children stay in the transport-upgrade questline. The JavaScript plan mirrorssetup()and its limited guarantee. Retry bookkeeping resets after healthy WebSocket service so an immediate QUIC failure can reconnect.Impact
Session::setup(). The proposed, unmergedaccepted()name is removed.moq_tokio::server::Transportmoves tomoq_tokio::Transportwithout a compatibility re-export.Transport::WebTransportreports HTTP/3 separately from rawQuic, including its log name.Connection::transport()reports the live transport.MoqTransport::WebTransportadds an exhaustive-match case; Go gainsTransportWebTransport.webtransport; the auth contract retains its existing QUIC classification.Validation
session_setup: six existing tests pass with the current simulator harness, covering delayed SETUP, refusal, closure after SETUP, immediate completion, unsupported versions, and version coverage.quest check: 456 documents pass.just ci check: passed all affected lint, compile, feature and binding checks.just check: compiled; 5,067 tests passed. Three io_uring worker tests failed creating rings with ENOMEM at this user's shared 8 MiB RLIMIT_MEMLOCK, also reproduced on main. The failure canceled 212 remaining tests. Final-head CI must verify the full suite.just test interop --all: passed the full available matrix (Rust, Python, Go, browser JavaScript, Node, Bun, C and GStreamer), plus browser refusal/close-code checks.Alternatives
established(): already names live transport/session availability onConnection, and would obscure that this method specifically waits for SETUP.Client::connectwait on every peer SETUP: adds a round trip and removes pipelining.Follow-ups
/quest/m1/transport-upgrade/README.md.(Written by GPT-6)