Repository navigation
feat(quic)!: hierarchical send groups, one per subscription - #5711
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…batim Unmodified copies, not yet compiled, so a reviewer can diff the adaptation that follows against upstream: - quinn-rs/quinn 7616e6b2782722f3ce4a1b181ef08817d7f545e4 quinn/src -> rs/moq-tokio/src/quic/driver (lib.rs -> mod.rs), the commit moq-quic forked from. - moq-dev/noq e27c0ce00741d81c8620b98e345d4f2c88e0768c web-transport-moq/src -> rs/moq-tokio/src/quic/web (lib.rs -> mod.rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop every moq-noq dependency. moq-tokio's QUIC backend now drives moq-quic through quinn's async layer (`quic::driver`) with web-transport-moq folded in as `quic::web`; moq-uring's sans-IO driver moves from moq-noq-proto to moq-quic. Upstream noq stays only behind moq-tokio's `iroh` feature. - The `noq` cargo feature is `quic` everywhere it was forwarded. - `moq_tokio::noq::Error` is `moq_tokio::quic::Error`; `Error::Noq` is `Error::Quic`. - moq-quic takes `moq_clock::Instant` and raises a once-per-connection Draining endpoint event, which backs the driver's `wait_all_draining`. - qlog writes one trace per endpoint (per dial on moq-uring's connect side), since moq-quic holds one sink per transport config. - Session stats report the congestion controller's bandwidth estimate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The capsule reader maps a lost stream to UnexpectedEnd, so every ordinary session close warned "failed to read capsule". A read error while the connection is closed is now debug; one on a live connection still warns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relay peak RSS on the fanout workloads matches moq-noq on origin/main, which was the last open part of the Goal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carry #5638's steering changes onto the renamed moq-quic drivers: the shard CID comes from moq_sock::shard::cid, and moq-uring stops greasing the QUIC bit on sharded endpoints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Endpoint::close now refuses attempts still queued for accept, and wait_all_draining waits for accepts partway through Incoming::accept, so a server shutdown cannot release the socket before those peers hear back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace quinn's single pending-stream heap with a hierarchical schedule: - Streams outside every group go first, in quinn's order (priority, then round robin), so control and HTTP/3 streams are never queued behind media. - Groups belong to a scheduling domain (one default). Inside a domain the highest backlogged priority is served strictly, and equal priorities share bytes by deficit round robin, charged in wire bytes. Domains share bytes the same way, so opening more groups buys a domain nothing. - A stream's priority widens to i64 and orders it only within its group. Debt carries across a group going idle; unused credit does not, and a blocked stream holds no turn. Connection::send_groups opens, moves and closes groups and domains as owned tokens. moq-tokio's and moq-uring's drivers wrap a group in a shared SendGroup handle that closes on drop, and their streams gain set_group. Retransmits re-queue through the same schedule, so a lost range keeps its group's share. Tests drive the real send path with differently sized streams: byte fairness within a bounded window, preemption, per-group order, priority updates, sparse sequences, blocked streams, retransmits, domains, resets and leaving a group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The poll transport gains send groups: - Session::send_group(priority) opens one, or returns None on a transport without them. - SendStream::Group names the transport's group (Ungrouped, uninhabited, for flat transports), SendStream::set_group joins one, and SendStream::set_priority widens to i64. - SendGroup::set_priority moves a whole group. The lite and IETF publishers open one group per subscription (and per standalone FETCH; a joining FETCH shares its subscription's) at the subscriber priority. Group streams join it ranked by sequence in the subscription's group order, with a group's first stream ahead of the rest of it. SUBSCRIBE_UPDATE moves the group once instead of reranking every stream, and an order flip re-stamps the open streams. On a transport without groups the session-wide PriorityQueue still ranks every stream, as before. moq-tokio's and moq-uring's adapters reach backend groups through new Grouped and GroupedSend extension traits: native QUIC and WebTransport open real groups; iroh, qmux, ffi and wasm stay flat. The moq-tokio adapter defers a join made while a write or FIN watch holds the stream, like a priority change. A benchmark compares the groups with the PriorityQueue and two scalar packings on congested audio and video workloads, and sweeps subscriptions against in-flight streams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hierarchical send groups landed in moq-quic and every subscription uses one, so drop the quest and the links that waited on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quest/m1/quic/scheduler
The server socket retried every failed recv, so a persistent error spun the endpoint task while tokio kept readiness asserted. Only WouldBlock retries now, matching the driver's tokio runtime. Also cap moq_quic at info like noq was, fix the driver doc example, and point the moq-uring README at the just recipe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the async publishers name it, so a model-only build flagged it as dead code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Status: draft, ready for maintainer review once #5664 lands.
(Written by Opus 5.5) |
…quest/m1/quic/scheduler
Drop GroupedSend::set_order: the adapters use a single set_priority(i64), documented everywhere as the priority within the stream's send group, or session-wide when ungrouped. Grouped and GroupedSend leave the public API. pub(crate) is refused (E0446) because the public adapters' `type Group` projects through GroupedSend, so both are `pub` in a private module and never re-exported: other crates can neither name nor implement them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # rs/moq-quic/README.md # rs/moq-tokio/src/quic/driver/connection.rs # rs/moq-tokio/src/quic/driver/mod.rs # rs/moq-tokio/src/quic/driver/send_stream.rs # rs/moq-tokio/src/quic/web/send.rs # rs/moq-tokio/src/quic/web/session.rs # rs/moq-uring/src/quic/driver/stream.rs # rs/moq-uring/src/quic/mod.rs
|
Iterated on review:
(Written by Opus 5.5) |
# Conflicts: # quest/m1/quic/scheduler.md # quest/m2/quic-deadline.md
|
TS validation failure on 674746b, checked against main:
The last 10 of each ran interleaved, branch then main, so both saw the same load. The single local failure has the #5565 signature exactly: (Written by Opus 5.5) |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Custom transports implement moq-net's transport::poll traits, which already carry send groups. The generic web_transport_trait adapters, whose bound #5711 sealed to the crates' own backends, are deleted along with the sealed Grouped/GroupedSend pair. quic-qmux's port now targets moq-net's traits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in reliable reset (#5690) and per-core sharding (#5688). A reset stream with a Reliable Size now stays in its send group's schedule for just the data below that size, and leaves the schedule as soon as nothing is left to send: on reset, on STOP_SENDING abandoning the prefix, and on removal once the reset is acknowledged. The schedule still holds only streams with something to send, so the write loop and can_send_stream_data need no reset special case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review (Written by Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c16b117fc
ℹ️ 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".
Browser WebTransport from Wasm has no send groups, and Lite datagrams bypass the stream scheduler, so neither gets the byte-fair split. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
Datagram priority will move datagrams into the send-group hierarchy; its quest now owns updating the Priority row when that lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Heads-up: #5781 (moq-quic characterization tests) adds exact-order scheduling goldens in (Written by Claude Opus 5.5) |
|
Ready to land at
Checks:
(Written by Opus 5.5) |
|
moq-quic now has Criterion benchmarks (#5812, landing shortly): stream send/priority, receive/reset/stop, endpoint routing, and wire codec cases, each swept over two axes. Once it merges, please rebase and run (Written by Claude Opus 5.5) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #699. Completes
quest/m1/quic/scheduler.md, which this PR deletes.What
One QUIC connection now carries the MoQ scheduling hierarchy instead of packing it into a scalar:
streams/schedule.rs).i64: the priority within the stream's send group, or session-wide when ungrouped.SendGrouphandle that closes on drop. Their streams gainset_group.PriorityQueueexactly as before.Results
cargo bench -p moq-net --bench schedulecompares the groups against thePriorityQueueand two scalar packings on a congested link. The shares, lag and queue work are deterministic. The timings were taken at load 60 to 150 on a shared host, so read them only as a relative comparison.[p][seq][p][p][seq][p][seq][p][seq]The groups beat the queue on every workload: alice-bob took 8.5 ms against 186 ms, wrap 24 against 181 ms, sparse 61 against 376 ms, and crowd 12.7 ms against 76 ms. The crowd figures come from a re-run at load 23; the first run's crowd timing was taken at the load peak and was noise. The
fanoutsweep times one frame and one join, push and leave across subscriptions in {1, 16, 256, 4096} and in-flight streams per subscription in {1, 16, 256}. Each case stays between 20 and 700 ns with no slope against the table size. The benchmark is picked up bybench/run.shin nightly.Public API
Breaking, in every case:
transport::poll:Session::send_group(&mut self, i64) -> Option<…::Group>is required, not defaulted, likeprotocol.SendStream::GroupandSendStream::set_group(&Self::Group)are added.SendStream::set_prioritytakesi64instead ofi32. It is the only stream ordering setter: the priority within the stream's send group, or session-wide when ungrouped.SendGrouptrait (Clone,set_priority(i64)) andUngrouped, the uninhabited group for flat transports.i64, so neither side converts.Boxablealso requiresGroup: MaybeSend + MaybeSync.Connection::send_groups() -> SendGroups<'_>withopen,close,set_priority,priority,open_domainandclose_domain. Group priorities arei64.SendGroupandSendDomainare owned, non-Clonetokens tagged with their connection, so another connection panics on them.SendStream::set_group(Option<&SendGroup>)is added, andSendStream::{set_priority, priority}usei64.quic::driver::SendGroup(set_priority/priorityini64),Connection::send_group(i64), andquic::web::Session::send_group(i64)are added.SendStream::set_groupare added, and theirset_priorityandpriorityusei64.transport::Session<S>now accepts only this crate's backends: native QUIC/WebTransport (real groups), iroh and qmux (flat). Its bound is a sealed trait pair,GroupedandGroupedSend, which is not public API (see decision 3), so a customweb_transport_trait::Sessionno longer fits. No crate in the repo wraps one. Custom transports implement moq-net'stransport::polltraits directly, which already carry send groups, and Drop web-transport-trait deletes these adapters.quic::SendGroup,Connection::send_group,web::Session::send_group, and streamset_groupandset_priority(i64). Itstransport::Session<S>adapter uses the same sealed pair, so it accepts only the crate's native QUIC session.Wire: none. Priorities stay local scheduling state; no message, field or draft changes. That is why
just test interop --allwas not run.Decisions
set_priority(i64)is the single name everywhere: the moq-net trait, both drivers and the adapters. It means the priority within the stream's send group, or session-wide when ungrouped.set_orderis gone.GroupedandGroupedSendare not public API.pub(crate)was tried and is refused with E0446: the public adapters'poll::SendStreamimpl saystype Group = S::Group, which projects throughGroupedSend, and an associated type may not leak a crate-private trait. So each pair ispubinside a private module (moq_tokio::transport::grouped,moq_uring::transport::adapter) and never re-exported. Other crates can neither name nor implement them, which is stronger than#[doc(hidden)], and nothing outside either crate needs them.2 * seq(negated for ascending), plus 1 for a stream that starts at object 0. This keeps the joining FETCH prefix ahead of the live rest of its group.PriorityQueuestays as the flat fallback for browsers, iroh, qmux, ffi and wasm.Follow-ups
SendDomainper broadcast scope.streams/saturated.rsthrough its record writer once it adopts the scheduler.transport::polltraits, and the genericweb_transport_traitadapters with the sealedGrouped/GroupedSendpair are deleted.schedulebench on an idle host for absolute timings, since the shared host never got below load 23.Checks
just check origin/mainon the merged head is green: clippy, docs, the moq-net no-default-features build, and 7369 workspace tests.cargo clippy --all-targets --all-features -D warningscovers theschedulebench.scheduleandsaturated: byte fairness, preemption, per-group order, priority updates, wrap and sparse sequences, blocked streams, retransmits not starved, domains, reset cleanup, leaving a group.grouped_streams_rank_within_their_subscription.send_defers_a_group_across_an_inflight_write.send_group_from_another_connectionandsend_domain_from_another_connection.reliable_reset_sends_its_prefix_then_frees_its_groupandstop_sending_drops_a_reliable_prefix_from_the_schedule: aRESET_STREAM_ATstream (feat(quic)!: reliable reset from a send-stream checkpoint #5690) stays in its group only for its Reliable Size prefix.🤖 Generated with Claude Code
(Written by Opus 5.5)