Skip to content

feat: one UDP port for QUIC and the WebRTC media path - #7095

Merged
moq-bot[bot] merged 16 commits into
mainfrom
quest/m2/p2p/udp-demux
Nov 2, 2026
Merged

moq-bot[bot] merged 16 commits into
mainfrom
quest/m2/p2p/udp-demux

Conversation

@kixelated

@kixelated kixelated commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

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): classify reads 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. Flows pins a remote to WebRTC whatever it sends and releases it when the Pin guard drops. Queue buffers one stack's datagrams with a byte bound and a per-source share. Counters counts drops by Discard reason (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-owned AsyncUdpSocket in a Split that runs Divert::split on each poll_recv and 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 a WebRtc feed or dropped and counted. Demux lives in server::Config::demux and 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 no Demux clone is kept, and ends once every listening socket is released and every clone is gone. WebRtc has recv, send (a Transmit from the shared port, IPv4 mapped for an AF_INET6 socket), and pin. 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 is unclaimed.
  • QUIC-bit greasing is off on every tokio QUIC server now, not only sharded ones, since the demux tells QUIC apart by its fixed bit.
  • Endpoint driver: a receive that returns an empty batch, because every datagram was diverted, now counts as work, so a non-QUIC flood still yields.
  • moq-relay: Relay::webrtc() returns the feed. It fails on a stream-only relay and on runtime.io_uring, which doesn't split the port yet (io_uring demux). /metrics gains moq_relay_demux_dropped_total{reason}.
  • Docs: doc/bin/relay/config.md describes the port once, and doc/bin/relay/index.md adds the embedding bullet.
  • Quests: deletes the finished quest, and the link to it from the runtime-free QUIC driver, which main added since. Updates rtc-feed (now unblocked), SRT demux, io_uring demux, native pairs, and the one-port README to describe what landed.

Impact

  • Public API, all additive:
    • moq_sock::demux::{classify, Class, Route, Discard, Counters, Flows, Pin, Queue}. Class, Route, and Discard are #[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::webrtc
  • Wire: no tokio QUIC server advertises grease_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-sock bench, demux_route, sweeps pins against shards and is added to Nightly's rust-extra leg. Throughput is datagrams per second, summed over shards:

pins 1 shard 4 shards
0 1.08 G/s 4.12 G/s
1 52 M/s 174 M/s
1024 49 M/s 172 M/s

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-swap snapshot behind an AtomicBool, 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 for Divert::split covering 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. Also one_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 no Demux clone, and the feed ends after the server shuts down.
  • moq-relay: shared_tokio_webrtc_feed and worker_tokio_webrtc_feed take Relay::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

  • No flow-table deferral. WebRTC pins have no routing effect a WebRTC peer can trigger until SRT lands, because WebRTC never sends QUIC-shaped bytes. They ship anyway because rtc-feed pins into them, and a pin does win over the class, which the tests cover.
  • Pins are keyed by the canonical remote address, not a full 4-tuple. The local port is the socket's own. A remote that reaches two of a wildcard socket's local IPs counts as one flow, and rtc-feed's pinned only knows the remote.
  • Queue bounds are private constants: 4 MiB per listener, with no source holding more than 1/8 of it. Each datagram's cost includes its struct overhead, so tiny datagrams can't pin unbounded memory. A fixed share is O(1). Longest-queue-drop would let a lone burst use the whole queue, but scanning for the longest holder costs O(sources) per drop, which a spoofed flood would exploit.
  • Binding responses go to WebRTC. moq-ice runs str0m's full ICE (str0m's default; nothing sets ice_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.
  • A feed answers through the first live socket of the group. Every member shares the port, so the reply's source address is the same whichever member sends it.

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.

  1. Inline split versus a demux task. The quest's Plan described a task that owns the socket and fans datagrams out to per-stack virtual sockets.
    • ✅ Keep the inline split in the QUIC socket's 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.
    • A socket-owning demux task.
  2. API shape and names.
    • ✅ As shipped: moq_tokio::quic::demux::Demux in server::Config::demux, Demux::webrtc() -> WebRtc, Relay::webrtc() -> anyhow::Result<WebRtc>.
    • A demux::Producer / demux::Consumer pair.
    • 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 Socket wrapper this PR split in. The split moved to the Split wrapper above, and the feed's sends moved to the endpoint's slot (Slot::is_released is 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 the bench-harness Criterion, as every crate does since moq-dev/moq#7130. The bench is multi-threaded iter_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::webrtc and Divert::push still called kio::Producer::is_used, which moq-dev/moq#7036 renamed to wanted, so moq-tokio didn't compile.
  • Two tests called tokio::time::timeout, which main's clippy config now disallows in favor of kio::clock::timeout.
  • rs/moq-sock/Cargo.toml had its sections out of cargo sort order.

main renders /metrics through prometheus-client now (moq-dev/moq#7363). The demux counter is an encode_demux family on Scrape, with the same moq_relay_demux_dropped_total{reason} series, and metrics_series_are_pinned covers it.

Checks

On head ec4fc0ec90: CHECK_MODULES=rs just ci origin/main passed: lint-changed, clippy, and nextest over the changed crates and their dependents, with 10958 tests passed. Also cargo clippy --all-targets --all-features -D warnings on moq-tokio, moq-sock, and moq-relay, cargo fmt --check, and quest 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), against origin/main at 88e6990626. This was a new session (round 1), because the base's agent instructions changed. Round 1 on ec4fc0ec90 found nothing.

Online-reviewer threads on the previous head, all fixed in this push:

  • Pullfrog, add #[non_exhaustive] to Class, Route, and Discard: fixed in ec4fc0ec90.
  • Pullfrog (two threads), is_used was removed: fixed in 269e175a62.
  • CodeRabbit, document src_ip for multihomed wildcard binds: fixed in ec4fc0ec90, in the guide and in WebRtc::poll_send.
  • CodeRabbit, document the stream-only failure of relay.webrtc(): fixed in ec4fc0ec90.

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), against origin/main.

  • Round 1 on 0aa6085893, two findings, both fixed in 13334904bb:
    1. Tie feed liveness to the listening socket (medium). Fixed: a listening socket's Divert holds the feed's liveness producer. A feed taken before Server::new moves its config in now stays open while the listener runs. A Demux clone still holds it too, since a feed must be obtainable before the listener binds, which is the shipped API shape the maintainer picked.
    2. Deliver ICE responses before a remote is pinned (medium). Fixed: Class::Check became Class::Ice, which also takes Binding success and error responses, because str0m runs full ICE here.
  • Round 2 on 13334904bb: no findings.

Follow-ups

  • Datagram carries no ECN or kernel receive timestamp. If rtc-feed wants either for RTP congestion control, add them there.
  • A pinned lookup hashes with SipHash, about 20 ns. If SRT's pins make every datagram take that path, measure a faster keyed hasher with the demux_route bench.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 5 commits October 28, 2026 05:55
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>
@kixelated kixelated changed the title UDP demux feat: one UDP port for QUIC and the WebRTC media path Oct 28, 2026
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>
@kixelated
kixelated marked this pull request as ready for review October 31, 2026 05:38
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: db82f663-25f9-44cc-98b7-858f370c9fcf
Commits

Reviewing files that changed from the base of the PR and between cb17d89 and 3c52ab6.

Files ignored due to path filters: 1
  • Cargo.lock is excluded by !**/*.lock
Files selected for processing: 10
  • doc/bin/relay/index.md
  • rs/moq-relay/src/internal.rs
  • rs/moq-relay/tests/integration/embed.rs
  • rs/moq-sock/Cargo.toml
  • rs/moq-sock/src/demux.rs
  • rs/moq-tokio/src/quic/backend.rs
  • rs/moq-tokio/src/quic/demux.rs
  • rs/moq-tokio/src/quic/driver/endpoint.rs
  • rs/moq-tokio/src/quic/driver/mod.rs
  • sh/gh/nightly.sh

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

Walkthrough

The change adds a shared-port UDP demultiplexer that classifies QUIC, WebRTC, and STUN datagrams, routes traffic through a bounded feed, and tracks discarded datagrams. Tokio QUIC servers use the demultiplexer, and the relay exposes the WebRTC feed when Tokio serves QUIC. Relay metrics report discard counts by reason. Tests cover routing, feed behavior, and lifecycle. Documentation and plans describe the implementation and its io_uring limitation.

Merge Risk: Minimal · up to 3c52a

The guide now explains when the WebRTC feed is unavailable. No issue identified here prevents merging after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely identifies the main change: sharing one UDP port between QUIC and the WebRTC media path.
Description check Passed The description directly explains the UDP demultiplexer, WebRTC feed, relay API, metrics, tests, documentation, and implementation decisions described in the changeset.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 32 functions across 14 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

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.99.0)

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

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

@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing head 13334904bbbebed96e1fe940f82cadecceb1f415 through quest land.

  • Changes since the last review: none. The ## Review record (Codex --deep, round 2 on 13334904bb, no findings) names this head.
  • Online review: the automated OpenAI review on this head reported no actionable findings. There are no unresolved threads.
  • Pre-land check: quest check --base origin/main passes (690 documents) against main at 1ea4b1a2c3, and the head merges into it without conflicts.
  • Decisions: the maintainer settled both open decisions on 2026-10-31 (keep the inline split, ship the API shape as is). No new decisions were taken while merging.
  • Follow-ups, to be planned after merge: ECN and receive timestamps on Datagram if rtc-feed needs them, and a faster keyed hasher for pinned lookups if SRT's pins put every datagram on that path.

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge (squash) October 31, 2026 05:42

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ Minor suggestion inline.

Reviewed changes

  • moq-sock::demux — new runtime-agnostic classifier (classify/is_ice), an arc-swap pin table (Flows/Pin), a bounded per-source Queue, and drop Counters, with unit tests plus the demux_route bench.
  • moq-tokio::quic::demux — Demux wraps the endpoint-owned socket in a Split that runs the split inline in poll_recv, diverting ICE/DTLS/RTP to a WebRtc feed (queue, send through a group member's slot, pin); feed liveness tied to the listening sockets.
  • QUIC server — grease_quic_bit is 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), /metrics gains moq_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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread rs/moq-sock/src/demux.rs Resolved
@moq-bot
moq-bot Bot disabled auto-merge October 31, 2026 13:05
@moq-bot moq-bot Bot removed the quest:merge label Oct 31, 2026
@moq-bot

moq-bot Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

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

.

@moq-bot

moq-bot Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Codex land fixer: fixer crashed (exit 101); run /quest-iterate. Recommendation: run /quest-iterate before picking it again.

@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)

@moq-bot

moq-bot Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

quest land removed this pull request from the queue: Conflicts with the base: sh/gh/nightly.sh
.

@moq-bot moq-bot Bot removed the quest:merge label Nov 1, 2026
@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

quest land removed this pull request from the queue: Conflicts with the base: rs/moq-tokio/src/quic/driver/endpoint.rs
.

@moq-bot
moq-bot Bot enabled auto-merge (squash) November 1, 2026 15:57
@moq-bot moq-bot Bot added the quest:merge label Nov 1, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 main into the branch, resolving the endpoint receive conflict by keeping the branch's recv_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-datagram arrival), 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: Instant in this file is kio::clock::Instant, matching WallMap::sample, and arrival falls back to received when no kernel stamp maps.
  • No other net change: rs/moq-tokio/src/quic/driver/mod.rs and the demux source are identical to the prior head.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

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

.

@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

codex / Codex land fixer: This maintainer pick already used its mechanical fi·x attempt. Run /quest-iterate to diagnose the repeated failure, then pick it again.

(written by codex / Codex)

kixelated and others added 3 commits November 1, 2026 15:38
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 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: 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.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ 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-tokio build break — both kio::Producer::is_used() call sites (now demux.rs:79 and :311) use wanted(), so the crate trivially compiles again.
  • Marked the demux enums #[non_exhaustive] — Class, Route, and Discard carry it, and Divert::split_batch gained the wildcard arm the attribute forces, counting a route this listener does not serve as Discard::Unknown.
  • Ported the demux counter to prometheus-client — merged from main: render_demux is now encode_demux on Scrape, and metrics_series_are_pinned pins moq_relay_demux_dropped_total{reason}.
  • Switched the touched tests to kio::clock::timeout — the two tokio::time::timeout sites in embed.rs and backend.rs, matching main's clippy config.
  • Documented Transmit.src_ip — the wildcard-bind source-address guidance on WebRtc::poll_send and in doc/bin/relay/index.md, plus the stream-only failure of relay.webrtc().
  • Sorted rs/moq-sock/Cargo.toml — section order restored and [lints] workspace = true added.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@kixelated
kixelated enabled auto-merge (squash) November 2, 2026 00:12
@moq-bot
moq-bot Bot disabled auto-merge November 2, 2026 02:03
@moq-bot
moq-bot Bot enabled auto-merge (squash) November 2, 2026 02:03

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ 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 main and kept both benchmark additions in the rust-extra leg — the branch's -p moq-sock --bench demux alongside main's criterion auth … --bench verify. bash -n sh/gh/nightly.sh passes and sh/gh/nightly.test.py (14 tests) is green; the [[bench]] name = "demux" harness = false declaration is present in rs/moq-sock/Cargo.toml, so the new --bench demux target resolves.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@moq-bot

moq-bot Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

codex / Codex land fixer note:

Validation observed an intermittent failure in moq-tokio::integration backend::quic_client_close_drains_finished_track at backend.rs:902: subscribe failed: Unroutable. The full suite then passed all 1042 tests with four workers, including this test. The moq-sock benchmark compile check also passed. No changes were made to this branch; the intermittent test should be followed up.

(written by codex / Codex)

@moq-bot
moq-bot Bot merged commit ce59772 into main Nov 2, 2026
1 check passed
@moq-bot
moq-bot Bot deleted the quest/m2/p2p/udp-demux branch November 2, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant