Repository navigation
feat: one UDP port for QUIC and the WebRTC media path - #7095
Conversation
Quest-Claim: 1555076f208bbd7d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's endpoint now owns its socket (#7045), so the backend's Socket wrapper is gone. The demux wraps the endpoint's AsyncUdpSocket in a Split that runs Divert::split on every receive, and a WebRtc feed answers through the endpoint's Slot, which the endpoint empties when it releases the port. QUIC-bit greasing is still off on every tokio server. moq-sock's bench takes the bench-harness criterion, as every crate does since #7130. Records the maintainer's 2026-10-31 picks in the one-port Plan and drops a new link to the finished quest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
moq-ice runs str0m's full ICE, whose own connectivity checks are answered by Binding responses without USERNAME. Those were dropped as plain STUN, so ICE could never validate a pair on the shared port. Class::Check becomes Class::Ice, which also takes Binding success and error responses; only the WebRTC agent sends requests from this port. A listening socket's Divert now holds the feed's liveness producer, so a feed taken before Server::new moves its config in stays open while the listener runs, and ends once the endpoint releases its socket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review.
|
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 13334904bbbebed96e1fe940f82cadecceb1f415.
No actionable bugs found in the changed code. The inline split is a reasonable direction: it preserves the endpoint receive path and GRO ordering while bounding the WebRTC queue; the snapshot pin table avoids a per-packet cross-shard mutex. A separate socket-owning task would add coordination without a clear benefit here.
Checked classification/ICE responses, pin lifetime, queue accounting, batch compaction, socket/feed shutdown and sends, relay/worker integration, and the added tests. The two issues described in the PR's earlier local review—listener-owned feed liveness and delivery of Binding responses—are addressed in this head; these are not new findings.
Verification limits: static GitHub inspection, including an independent lifecycle/send-path check. I did not run builds, tests, benchmarks, or real ICE/DTLS interoperability checks.
|
Landing head
(Written by Claude Opus 5.5) |
There was a problem hiding this comment.
ℹ️ Minor suggestion inline.
Reviewed changes
moq-sock::demux— new runtime-agnostic classifier (classify/is_ice), anarc-swappin table (Flows/Pin), a bounded per-sourceQueue, and dropCounters, with unit tests plus thedemux_routebench.moq-tokio::quic::demux—Demuxwraps the endpoint-owned socket in aSplitthat runs the split inline inpoll_recv, diverting ICE/DTLS/RTP to aWebRtcfeed (queue,sendthrough a group member's slot,pin); feed liveness tied to the listening sockets.- QUIC server —
grease_quic_bitis off on every tokio QUIC server now (was sharded-only), and the endpoint driver counts a fully diverted read as work so a non-QUIC flood yields. moq-relay—Relay::webrtc()returns the feed (fails stream-only and io_uring),/metricsgainsmoq_relay_demux_dropped_total{reason}, and integration tests cover the shared and worker tokio feeds.- Docs / quests — relay config/index docs describe the one UDP port; the finished quest is deleted and five others updated.
The inline comment notes a forward-compatibility gap in the new public enums. I traced the demux split (GRO compaction, pin canonicalization, queue accounting) and the feed's liveness/queue races and found no behavioral issues.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
quest land removed this pull request from the queue: the batch check failed (exit 101). js: selecting docs
..js: selecting reader
...
----------------------------------------------------------------------
Ran 24 tests in 0.382s
OK
python3 sh/js/audit.test.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.069s
OK
python3 sh/rs/test-lints.test.py
.
----------------------------------------------------------------------
Ran 1 test in 2.539s
OK
python3 sh/gh/nightly.test.py
..............
----------------------------------------------------------------------
Ran 14 tests in 1.181s
OK
python3 sh/gh/pages-clean.test.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.052s
OK
python3 sh/gh/bench-report.test.py
............
----------------------------------------------------------------------
Ran 12 tests in 0.440s
OK
bench-ffi: 2 measured entries
bench-rust: failure; no history point
bench-ffi: failure; no history point
bench-rust: failure; no history point
no completed benchmark reports; no history point
bench-rust: 1 measured entries
bench-rust: failure; no history point
no completed benchmark reports; no history point
Paired comparison of c2 against c1: 1 confirmed regressions, 1 targets not compared
- `rust/kio/lock/4`: +40.0% (rounds +30.0%, +40.0%, +50.0%)
- `rust/gst/moq-gst/catalog` not compared: base build failed
Paired comparison of c2 against c1: 0 confirmed regressions, 0 targets not compared
python3 sh/gh/ts-paths.test.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s
OK
sh/go/publish-wrapper.test.sh
publish-wrapper test: no rsync on PATH, skipping
bash .claude/hooks/direnv.test.sh
direnv hook: could not load the dev shell, see the direnv output above.
direnv hook: regression tests passed
python3 sh/rs/uring.test.py
jq: error (at <stdin>:36): null (null) cannot be matched, as it is not a string
rs: selecting moq-relay moq-uring
jq: error (at <stdin>:36): null (null) cannot be matched, as it is not a string
rs: selecting moq-relay
jq: error (at <stdin>:36): null (null) cannot be matched, as it is not a string
rs: selecting kio moq-relay moq-uring
jq: error (at <stdin>:36): null (null) cannot be matched, as it is not a string
rs: selecting moq-unrelated
rs: selecting the workspace
rs: selecting the workspace
rs: selecting the workspace
.
----------------------------------------------------------------------
Ran 1 test in 0.280s
OK
ci: check rs failed
ci: check rs_shapes failed
ci: seconds per module
75 check rs (failed)
77 check rs_shapes (failed)
278 check js
52 test js
13 check markdown
2 check gh
46 test gh
68 check flake
0 check drill
3 check quest
0 check ts_paths
14 check shell
0 check large_files
1 check toml
0 check nix
0 check justfile
392 wall time
error: recipe `ci` failed on line 104 with exit code 101
. |
|
Codex land fixer: @moq/p2p:build | ✍️ Rewriting package.json... @moq/p2p:build | 🔗 Converted @moq/qmux: workspace:^ → ^0.3.3 @moq/p2p:build | 📄 Copying README.md... @moq/p2p:build | 🔍 Running publint... @moq/p2p:build | 📦 Package built successfully in dist/ @moq/p2p:build | Done in 238ms @moq/pattern:build | ✍️ Rewriting package.json... @moq/pattern:build | 📄 Copying README.md... @moq/pattern:build | 🔍 Running publint... @moq/pattern:build | 📦 Package built successfully in dist/ @moq/pattern:build | ✍️ Generating jsr.json... @moq/pattern:build | 📦 jsr.json written @moq/pattern:build | Done in 233ms @moq/publish:build | �[36mvite v8.3.4 �[32mbuilding client environment for production...�[36m�[39m @moq/publish:build | transforming... @moq/publish:build | ✓ 139 modules transformed. @moq/publish:build | rendering chunks... @moq/publish:build | computing gzip size... @moq/publish:build | dist/assets/capture-worker.js 0.64 kB @moq/publish:build | dist/assets/capture-worklet.js 1.37 kB @moq/publish:build | dist/capture-worker-BJ0xkcGF.js 0.81 kB │ gzip: 0.50 kB │ map: 1.00 kB @moq/publish:build | dist/index.js 1.06 kB │ gzip: 0.54 kB │ map: 1.57 kB @moq/publish:build | dist/support/index.js 1.16 kB │ gzip: 0.65 kB │ map: 4.26 kB @moq/publish:build | dist/support/element.js 1.36 kB │ gzip: 0.54 kB │ map: 3.20 kB @moq/publish:build | dist/capture-worklet-CRiEEPvQ.js 1.54 kB │ gzip: 0.70 kB │ map: 1.74 kB @moq/publish:build | dist/video-CKv257p3.js 4.83 kB │ gzip: 1.94 kB │ map: 19.44 kB @moq/publish:build | dist/microphone-BIV2nutQ.js 8.09 kB │ gzip: 2.43 kB │ map: 35.77 kB @moq/publish:build | dist/element.js 10.88 kB │ gzip: 3.19 kB │ map: 40.35 kB @moq/publish:build | dist/ui/element.js 33.96 kB │ gzip: 8.52 kB │ map: 54.90 kB @moq/publish:build | dist/encoder-m79Iaxwg.js 58.93 kB │ gzip: 17.31 kB │ map: 277.56 kB @moq/publish:build | dist/src-BlFDRcol.js 483.22 kB │ gzip: 111.10 kB │ map: 1,872.47 kB @moq/publish:build | @moq/publish:build | �[32m✓ built in 230ms�[39m @moq/publish:build | ✍️ Rewriting package.json... @moq/publish:build | 🔗 Converted @moq/clock: workspace:^ → ^0.1.0 @moq/publish:build | 🔗 Converted @moq/hang: workspace:^ → ^0.5.2 @moq/publish:build | 🔗 Converted @moq/json: workspace:^ → ^0.4.2 @moq/publish:build | 🔗 Converted @moq/net: workspace:^ → ^0.4.2 @moq/publish:build | 🔗 Converted @moq/signals: workspace:^ → ^0.2.5 @moq/publish:build | 🔗 Converted @moq/ui: workspace:^ → ^0.1.0 @moq/publish:build | 🔗 Converted @moq/video: workspace:^ → ^0.1.0 @moq/publish:build | 📄 Copying README.md... @moq/publish:build | 🔍 Running publint... @moq/publish:build | 📦 Package built successfully in dist/ @moq/publish:build | Done in 1.28s @moq/qmux:build | ✍️ Rewriting package.json... @moq/qmux:build | 🔗 Converted @moq/clock: workspace:^ → ^0.1.0 @moq/qmux:build | 📄 Copying README.md... @moq/qmux:build | 🔍 Running publint... @moq/qmux:build | 📦 Package built successfully in dist/ @moq/qmux:build | ✍️ Generating jsr.json... @moq/qmux:build | 📦 jsr.json written @moq/qmux:build | Done in 972ms @moq/room:build | ✍️ Rewriting package.json... @moq/room:build | 🔗 Converted @moq/clock: workspace:^ → ^0.1.0 @moq/room:build | 🔗 Converted @moq/hang: workspace:^ → ^0.5.2 @moq/room:build | 🔗 Converted @moq/json: workspace:^ → ^0.4.2 @moq/room:build | 🔗 Converted @moq/net: workspace:^ → ^0.4.2 @moq/room:build | 🔗 Converted @moq/publish: workspace:^ → ^0.5.2 @moq/room:build | 🔗 Converted @moq/signals: workspace:^ → ^0.2.5 @moq/room:build | 🔗 Converted @moq/watch: workspace:^ → ^0.6.2 @moq/room:build | 📄 Copying README.md... @moq/room:build | 🔍 Running publint... @moq/room:build | 📦 Package built successfully in dist/ @moq/room:build | ✍️ Generating jsr.json... @moq/room:build | 📦 jsr.json written @moq/room:build | Done in 1.50s @moq/signals:build | ✍️ Rewriting package.json... @moq/signals:build | 🔗 Converted @moq/clock: workspace:^ → ^0.1.0 @moq/signals:build | 📄 Copying README.md... @moq/signals:build | 🔍 Running publint... @moq/signals:build | 📦 Package built successfully in dist/ @moq/signals:build | ✍️ Generating jsr.json... @moq/signals:build | 📦 jsr.json written @moq/signals:build | Done in 627ms @moq/ui:build | �[36mvite v8.3.4 �[32mbuilding client environment for production...�[36m�[39m @moq/ui:build | transforming... @moq/ui:build | ✓ 6 modules transformed. @moq/ui:build | rendering chunks... @moq/ui:build | computing gzip size... @moq/ui:build | dist/index.js 7.90 kB │ gzip: 3.00 kB │ map: 23.92 kB @moq/ui:build | @moq/ui:build | �[32m✓ built in 24ms�[39m @moq/ui:build | ✍️ Rewriting package.json... @moq/ui:build | 🔗 Converted @moq/signals: workspace:^ → ^0.2.5 @moq/ui:build | 📄 Copying README.md... @moq/ui:build | 🔍 Running publint... @moq/ui:build | 📦 Package built successfully in dist/ @moq/ui:build | Done in 625ms @moq/video:build | ✍️ Rewriting package.json... @moq/video:build | 🔗 Converted @moq/signals: workspace:^ → ^0.2.5 @moq/video:build | 📄 Copying README.md... @moq/video:build | 🔍 Running publint... @moq/video:build | 📦 Package built successfully in dist/ @moq/video:build | ✍️ Generating jsr.json... @moq/video:build | 📦 jsr.json written @moq/video:build | Done in 336ms @moq/watch:build | �[36mvite v8.3.4 �[32mbuilding client environment for production...�[36m�[39m @moq/watch:build | transforming... @moq/watch:build |quest: fixer agent exited 101 (written by Codex) |
|
quest land removed this pull request from the queue: Conflicts with the base: sh/gh/nightly.sh |
|
quest land removed this pull request from the queue: Conflicts with the base: rs/moq-tokio/src/quic/driver/endpoint.rs |
There was a problem hiding this comment.
Caution
The base merge itself is clean, but the branch still does not compile. rs/moq-tokio/src/quic/demux.rs:79 and :305 call kio::Producer::is_used(), which main removed in favor of wanted(), so moq-tokio (and every crate depending on it, including moq-relay) fails with E0599. This is the same blocker flagged at the prior heads and tracked by the open thread at demux.rs:79; this merge did not touch that file, so it is not duplicated inline.
Reviewed changes
- Merged
maininto the branch, resolving the endpoint receive conflict by keeping the branch'srecv_limiter.record_work(msgs.max(1))while absorbing main's kernel receive-timestamp sampling (RecvState.wall,wall.sample(received, kio::clock::Wall::now())per batch, and the per-datagramarrival), so a fully diverted (empty) read still counts as work and the first datagram of each GRO batch reports an arrival. The resolution is correct:Instantin this file iskio::clock::Instant, matchingWallMap::sample, andarrivalfalls back toreceivedwhen no kernel stamp maps. - No other net change:
rs/moq-tokio/src/quic/driver/mod.rsand the demux source are identical to the prior head.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
quest land removed this pull request from the queue: the batch check failed (exit 101). * [new tag] v0.17.0 -> v0.17.0
* [new tag] v0.18.0 -> v0.18.0
* [new tag] v0.19.0 -> v0.19.0
* [new tag] v0.19.1 -> v0.19.1
* [new tag] v0.19.2 -> v0.19.2
* [new tag] v0.19.3 -> v0.19.3
* [new tag] v0.19.4 -> v0.19.4
* [new tag] v0.19.5 -> v0.19.5
* [new tag] v0.19.6 -> v0.19.6
* [new tag] v0.20.0 -> v0.20.0
* [new tag] v0.21.0 -> v0.21.0
* [new tag] v0.21.1 -> v0.21.1
* [new tag] v0.22.0 -> v0.22.0
* [new tag] v0.23.0 -> v0.23.0
* [new tag] v0.24.0 -> v0.24.0
* [new tag] v0.24.1 -> v0.24.1
* [new tag] v0.24.2 -> v0.24.2
* [new tag] v0.24.3 -> v0.24.3
* [new tag] v0.25.0 -> v0.25.0
* [new tag] v0.25.1 -> v0.25.1
* [new tag] v0.25.2 -> v0.25.2
* [new tag] v0.25.3 -> v0.25.3
* [new tag] v0.26.0 -> v0.26.0
* [new tag] v0.26.1 -> v0.26.1
* [new tag] v0.27.0 -> v0.27.0
* [new tag] v0.27.1 -> v0.27.1
* [new tag] v0.27.2 -> v0.27.2
* [new tag] v0.27.3 -> v0.27.3
* [new tag] v0.28.0 -> v0.28.0
* [new tag] v0.28.1 -> v0.28.1
* [new tag] v0.28.2 -> v0.28.2
* [new tag] v0.28.3 -> v0.28.3
* [new tag] v0.29.0 -> v0.29.0
* [new tag] v0.29.1 -> v0.29.1
* [new tag] v0.29.2 -> v0.29.2
* [new tag] v0.29.3 -> v0.29.3
* [new tag] v0.29.4 -> v0.29.4
* [new tag] v0.29.5 -> v0.29.5
* [new tag] v0.30.0 -> v0.30.0
* [new tag] v0.31.0 -> v0.31.0
* [new tag] v0.31.1 -> v0.31.1
* [new tag] v0.31.2 -> v0.31.2
* [new tag] v0.32.0 -> v0.32.0
* [new tag] v0.32.1 -> v0.32.1
* [new tag] v0.32.2 -> v0.32.2
* [new tag] v0.4.0 -> v0.4.0
* [new tag] v0.5.0 -> v0.5.0
* [new tag] v0.6.0 -> v0.6.0
* [new tag] v0.6.1 -> v0.6.1
* [new tag] v0.7.0 -> v0.7.0
* [new tag] v0.7.1 -> v0.7.1
* [new tag] v0.7.2 -> v0.7.2
* [new tag] v0.8.0 -> v0.8.0
* [new tag] v0.9.0 -> v0.9.0
derivation evaluated to /nix/store/srlzfgxqfw21gfhwzas4dvvwdk7il96l-moq-relay-0.17.2.drv
checking derivation packages.x86_64-linux.default...
derivation evaluated to /nix/store/kppir2szqnndivs69mvsppfl8gjgng5g-moq-all.drv
checking derivation packages.x86_64-linux.uniffi-bindgen-dart...
derivation evaluated to /nix/store/1v1hdl7dhiwdis0y2lmh6vjx149is362-uniffi-bindgen-dart-0.3.1-kixelated.6+v0.32.0.drv
checking derivation packages.x86_64-linux.uniffi-bindgen-cpp...
derivation evaluated to /nix/store/im9pz4z9gbnxq8cgg736sqc7d5y07nc3-uniffi-bindgen-cpp-0.11.0-kixelated.7+v0.32.2.drv
checking derivation packages.x86_64-linux.moq...
derivation evaluated to /nix/store/zfcmasikajx13nqs1g0a0my83vvvxhx3-moq-cli-0.14.2.drv
checking derivation packages.x86_64-linux.moq-cli...
derivation evaluated to /nix/store/hv61q4ncyjdmv18plpyi91gajc3k7n0z-moq.drv
checking derivation packages.x86_64-linux.moq-bench...
derivation evaluated to /nix/store/374w9lzc66rwlvccyqaq8hdz54563fhy-moq-bench-0.0.1.drv
checking derivation packages.x86_64-linux.moq-boy...
derivation evaluated to /nix/store/wlkvwmi5qdnsmxqam7gq7b1vnjcvbvfg-moq-boy-0.5.12.drv
checking derivation packages.x86_64-linux.moq-c...
derivation evaluated to /nix/store/42hk8499awmbi7mwjmiwl8k7s8g236m3-moq-c-0.7.0.drv
checking derivation packages.x86_64-linux.moq-gst...
derivation evaluated to /nix/store/pj2vmpmm3pi0802cd3q6mcpxblll54d6-moq-gst-0.4.12.drv
checking derivation packages.x86_64-linux.moq-libav...
derivation evaluated to /nix/store/a89nqi7kxr5j7yplcgk6baz0ga0jg7iy-moq-libav-0.1.0.drv
checking derivation packages.x86_64-linux.packaging...
derivation evaluated to /nix/store/5ah4qskczdqcgzwc40r0jfj1wybpgbyj-moq-packaging-tools.drv
checking flake output 'legacyPackages'...
checking flake output 'devShells'...
checking derivation devShells.x86_64-linux.default...
derivation evaluated to /nix/store/5v04vgms9riqa36qaaw3952l4033a019-nix-shell.drv
checking flake output 'formatter'...
checking derivation formatter.x86_64-linux...
derivation evaluated to /nix/store/ddrvrd57nqxd8i3h6b1ksppn59zb606p-nixfmt-tree-2.6.0.drv
checking flake output 'checks'...
checking derivation checks.x86_64-linux.github-runner-vm...
derivation evaluated to /nix/store/fswl53ml170sqwsjs3h49l9cj17yqw4m-vm-test-run-github-runner-vm.drv
checking derivation checks.x86_64-linux.package-source-assets...
derivation evaluated to /nix/store/xwq4l53nchkacrriphschxv2sqj1qg8x-package-source-assets.drv
checking derivation checks.x86_64-linux.github-runner-vm-image...
derivation evaluated to /nix/store/clblbf3dswnjjx93h3wrg7n4nq058ygg-vm-test-run-github-runner-vm-image.drv
checking derivation checks.x86_64-linux.github-runner-vm-config...
error: interrupted by the user
ci: check rs failed
ci: seconds per module
65 check rs (failed)
65 check js (cancelled)
66 check flake (cancelled)
66 wall time
error: recipe `ci` failed on line 107 with exit code 101
. |
|
codex / Codex land fixer: (written by codex / Codex) |
main renders /metrics through prometheus-client now (#7363), so the demux drop counter becomes an `encode_demux` family on `Scrape`, and the pinned series test covers it. Also rename the two `kio::Producer::is_used` calls to `wanted`, which #7036 renamed; the previous merge left them uncompiled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ply sources `Class`, `Route`, and `Discard` gain variants with the SRT demux, so they are `#[non_exhaustive]`; the tokio split counts a route it does not serve as `unknown`. `WebRtc::poll_send` and the relay guide say to set `src_ip` from `Datagram::local` on a multihomed wildcard bind, and the guide names the stream-only relay as a case `relay.webrtc()` refuses. The tests use the ambient-clock `kio::clock::timeout`, which main's clippy config now requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An earlier merge left the Apple dev-dependencies after the bench table. 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: ec4fc0ec905a1b2cbc02d14fc32bda881539490d, compared with my previous review of 64974fb1ede2b41a759a7a2cce0480f8a2375178, accounting for merged base changes.
Prior finding fixed: both demux liveness checks now use Producer::wanted(), whose consumer-exists semantics match the old calls. No new actionable bugs found in this update.
The metrics merge follows the existing counter encoder rather than retaining a second formatter, preserving the drop-reason series and zero-valued rows. The non-exhaustive enums and unknown-route fallback fit the planned additional protocols without changing current routing. The documented source-IP requirement and stream-only limitation also address the existing reviewers' concerns; these are not new findings. The inline split remains a reasonable direction, with no additional abstraction needed for these fixes.
Verification limits: static GitHub source and base-relative diff inspection, including the updated metrics tests and timeout call sites. I did not run builds, tests, benchmarks, or WebRTC interoperability checks.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the prior pullfrog review (cb17d89c4) is the main merge plus the fixes for both prior threads. All three concerns land cleanly at this head.
- Fixed the
moq-tokiobuild break — bothkio::Producer::is_used()call sites (nowdemux.rs:79and:311) usewanted(), so the crate trivially compiles again. - Marked the demux enums
#[non_exhaustive]—Class,Route, andDiscardcarry it, andDivert::split_batchgained the wildcard arm the attribute forces, counting a route this listener does not serve asDiscard::Unknown. - Ported the demux counter to prometheus-client — merged from
main:render_demuxis nowencode_demuxonScrape, andmetrics_series_are_pinnedpinsmoq_relay_demux_dropped_total{reason}. - Switched the touched tests to
kio::clock::timeout— the twotokio::time::timeoutsites inembed.rsandbackend.rs, matchingmain's clippy config. - Documented
Transmit.src_ip— the wildcard-bind source-address guidance onWebRtc::poll_sendand indoc/bin/relay/index.md, plus the stream-only failure ofrelay.webrtc(). - Sorted
rs/moq-sock/Cargo.toml— section order restored and[lints] workspace = trueadded.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the prior pullfrog review (ec4fc0ec9) is a single base merge (3c52ab601) that resolved the sh/gh/nightly.sh conflict. No source or docs changed. I confirmed the merge keeps both nightly benchmark additions instead of dropping either, and that the file stays valid.
- Merged
mainand kept both benchmark additions in therust-extraleg — the branch's-p moq-sock --bench demuxalongside main'scriterion auth … --bench verify.bash -n sh/gh/nightly.shpasses andsh/gh/nightly.test.py(14 tests) is green; the[[bench]] name = "demux" harness = falsedeclaration is present inrs/moq-sock/Cargo.toml, so the new--bench demuxtarget resolves.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
codex / Codex land fixer note:
(written by codex / Codex) |

Finishes the UDP demux quest: the relay's one UDP port serves QUIC and hands the WebRTC media path (ICE checks, DTLS, RTP) to an embedder.
Summary
moq-sock::demux(runtime-agnostic):classifyreads RFC 7983's first-byte ranges, with QUIC filling the gaps by its fixed bit, and parses STUN just far enough to tell ICE (a Binding request with USERNAME, or a Binding response) from a public query. No STUN codec dependency.Flowspins a remote to WebRTC whatever it sends and releases it when thePinguard drops.Queuebuffers one stack's datagrams with a byte bound and a per-source share.Counterscounts drops byDiscardreason (stun,unknown,unclaimed,overflow).moq-tokio::quic::demux: the QUIC server's socket runs the split inline in its receive. The demux wraps the endpoint-ownedAsyncUdpSocketin aSplitthat runsDivert::spliton eachpoll_recvand passes everything else through. QUIC segments are compacted in place and stay on the endpoint, so a QUIC datagram is never copied or handed across tasks. Everything else is diverted to aWebRtcfeed or dropped and counted.Demuxlives inserver::Config::demuxand is shared by every shard of a worker group, so one feed and one set of counters covers the whole port. The feed stays open while the listener runs, even if noDemuxclone is kept, and ends once every listening socket is released and every clone is gone.WebRtchasrecv,send(aTransmitfrom the shared port, IPv4 mapped for an AF_INET6 socket), andpin. A feed sends through the endpoint's own socket slot, as its connections do: it shares their writable-waker fan-out, and it loses its datagrams once the endpoint releases the port. Diverting runs only while a feed exists; otherwise the traffic isunclaimed.moq-relay:Relay::webrtc()returns the feed. It fails on a stream-only relay and onruntime.io_uring, which doesn't split the port yet (io_uring demux)./metricsgainsmoq_relay_demux_dropped_total{reason}.doc/bin/relay/config.mddescribes the port once, anddoc/bin/relay/index.mdadds the embedding bullet.Impact
moq_sock::demux::{classify, Class, Route, Discard, Counters, Flows, Pin, Queue}.Class,Route, andDiscardare#[non_exhaustive], since SRT adds to each.moq_tokio::quic::demux::{Demux, WebRtc, Datagram, Counters, Discard, Pin, Transmit}moq_tokio::server::Config::demux(the struct is#[non_exhaustive])moq_relay::Relay::webrtcgrease_quic_bit(RFC 9287) now; before, only sharded ones held it back. Clients keep the fixed bit set. No framing change.Performance
A new
moq-sockbench,demux_route, sweeps pins against shards and is added to Nightly'srust-extraleg. Throughput is datagrams per second, summed over shards:The first version guarded the pin table with a mutex. With a single pin held, four shards dropped to 6.8 M/s because they contended on that lock. The table is now an
arc-swapsnapshot behind anAtomicBool, so an unpinned listener pays about 1 ns per datagram and a pinned one about 20 ns. The cost is flat in the number of pins and scales with shards.Tests
moq-sock: one test per first-byte class (including Binding responses and indications), malformed STUN, routing, pin refcounts, the queue's per-source share, and the counters.moq-tokio: unit tests forDivert::splitcovering each class reaching its stack, a mixed GRO batch keeping its stride, a pin overriding the class, unclaimed traffic, a flooding source overflowing alone, and the feed ending with the listener. Alsoone_port_serves_quic_and_webrtc: on a dual-stack port, a QUIC session connects while an ICE check, DTLS, and RTP reach the feed, the reply leaves from the QUIC port, and a public STUN query is counted. The test keeps noDemuxclone, and the feed ends after the server shuts down.moq-relay:shared_tokio_webrtc_feedandworker_tokio_webrtc_feedtakeRelay::webrtc(), send DTLS from 16 sources to the QUIC port, receive all of them, and answer through the feed. There is also a metrics rendering test.Choices made without asking
pinnedonly knows the remote.moq-iceruns str0m's full ICE (str0m's default; nothing setsice_lite), so the relay's agent sends its own checks and needs their responses, which carry no USERNAME. Only the WebRTC agent sends STUN requests from this port, since[stun]binds its own. This goes past the old quest Plan's "USERNAME-carrying STUN" wording, which predates the full-ICE consumer.Decisions
Settled by the maintainer, 2026-10-31. The finished quest's file is deleted here, so they are recorded in the one-port Plan too.
poll_recv. QUIC keeps its zero-copy, same-task path and its GRO batches, as the io_uring demux plan also wants ("a QUIC datagram never leaves its worker"). Cost: WebRTC delivery advances only while the endpoint reads its socket, which it does for the listener's whole life, and the endpoint driver counts an empty batch as work.moq_tokio::quic::demux::Demuxinserver::Config::demux,Demux::webrtc() -> WebRtc,Relay::webrtc() -> anyhow::Result<WebRtc>.demux::Producer/demux::Consumerpair.Relay::demux() -> &Demux, so an embedder also reads the counters directly.Merging main
main's endpoint now owns its socket (moq-dev/moq#7045), which deleted the backend's
Socketwrapper this PR split in. The split moved to theSplitwrapper above, and the feed's sends moved to the endpoint's slot (Slot::is_releasedis new, crate-private). The PR's own unreachable-send test is dropped, since main moved the same test to the driver's tokio socket.moq-sock's bench takes thebench-harnessCriterion, as every crate does since moq-dev/moq#7130. The bench is multi-threadediter_custom, so it stays out of the CodSpeed list and runs in Nightly.The land queue ejected the PR on
check rs(2026-11-01). Earlier mechanical merges had left three breaks, all fixed here:Demux::webrtcandDivert::pushstill calledkio::Producer::is_used, which moq-dev/moq#7036 renamed towanted, somoq-tokiodidn't compile.tokio::time::timeout, which main's clippy config now disallows in favor ofkio::clock::timeout.rs/moq-sock/Cargo.tomlhad its sections out ofcargo sortorder.main renders
/metricsthrough prometheus-client now (moq-dev/moq#7363). The demux counter is anencode_demuxfamily onScrape, with the samemoq_relay_demux_dropped_total{reason}series, andmetrics_series_are_pinnedcovers it.Checks
On head
ec4fc0ec90:CHECK_MODULES=rs just ci origin/mainpassed: lint-changed, clippy, and nextest over the changed crates and their dependents, with 10958 tests passed. Alsocargo clippy --all-targets --all-features -D warningson moq-tokio, moq-sock, and moq-relay,cargo fmt --check, andquest check --base origin/main.Review
mechanical fix by codex / Codex:
Merged base and re·solved sh/gh/nightly.sh conflict retaining both demux and auth benchmarks; passed bash -n sh/gh/nightly.sh; python3 sh/gh/nightly.test.py (14 tests); cargo build --locked -p moq-sock -p moq-tokio -p moq-relay --all-targets; cargo test --locked -p moq-sock -p moq-tokio -p moq-relay.(head 3c52ab6)Codex
gpt-6-astra, medium effort (quest review --deep), againstorigin/mainat88e6990626. This was a new session (round 1), because the base's agent instructions changed. Round 1 onec4fc0ec90found nothing.Online-reviewer threads on the previous head, all fixed in this push:
#[non_exhaustive]toClass,Route, andDiscard: fixed inec4fc0ec90.is_usedwas removed: fixed in269e175a62.src_ipfor multihomed wildcard binds: fixed inec4fc0ec90, in the guide and inWebRtc::poll_send.relay.webrtc(): fixed inec4fc0ec90.Earlier rounds:
mechanical fix by codex / Codex:
Merged base and re·solved endpoint receive conflict by retaining demux work accounting and base receive timestamps.(head cb17d89)mechanical fix by codex / Codex:
Merged base, retaining both WebRTC and RTMP relay hooks and documentation and base deletion of the completed TCP quest.(head 219487b)mechanical fix by codex / Codex:
Merged base and preserved both the demux benchmark and Apple-only test dependency in Cargo.toml.(head 85c76cc)mechanical fix by codex / Codex:
Merged base and re·solved nightly benchmark conflict by preserving both sign and demux coverage.(head 64974fb)Codex
gpt-6-astra, medium effort (quest review --deep), againstorigin/main.0aa6085893, two findings, both fixed in13334904bb:Divertholds the feed's liveness producer. A feed taken beforeServer::newmoves its config in now stays open while the listener runs. ADemuxclone still holds it too, since a feed must be obtainable before the listener binds, which is the shipped API shape the maintainer picked.Class::CheckbecameClass::Ice, which also takes Binding success and error responses, because str0m runs full ICE here.13334904bb: no findings.Follow-ups
Datagramcarries no ECN or kernel receive timestamp. If rtc-feed wants either for RTP congestion control, add them there.demux_routebench.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code