Skip to content

feat(net): let a subscriber choose group order - #5385

Merged
kixelated merged 10 commits into
mainfrom
quest/m1/subscribe-ranges/order
Oct 13, 2026
Merged

kixelated merged 10 commits into
mainfrom
quest/m1/subscribe-ranges/order

Conversation

@kixelated

@kixelated kixelated commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A subscriber could not choose which pending group a publisher sends first. Group order was fixed newest first, the moq-transport GROUP_ORDER was hard-coded to Descending, and the JS codec field from #5370 (by @kkroo) was decoded but never set or honored.

Builds on #5370: its commit is cherry-picked first with its authorship, and the later commits carry Co-authored-by: Omar Ramadan. Completes quest/m1/subscribe-ranges/order.md, planned in #5379.

Approach

  • Model. track::Order { Ascending, Descending } (JS "ascending" | "descending") on Subscription.order, defaulting to Descending (the behavior before this PR). It only orders transmission. max_delay still decides what gets delivered, so an ascending subscriber that wants a backlog needs a budget covering it.
  • Aggregation. Descending wins: the aggregate is Ascending only when every subscriber is. Each downstream publisher (a relay's per-session subscription) still ranks its own groups in its own subscriber's order.
  • Publishers. The lite priority queue moves to the crate root (rs/moq-net/src/priority.rs, js/net/src/priority.ts). It ranks a subscription's groups in that subscription's order, and a SUBSCRIBE_UPDATE that changes the order re-ranks groups already in flight. The moq-transport publisher now uses the same session queue. Before this, every group stream in a subscription shared one send order.
  • Wire.
    • lite-07 (unpublished) adds Group Order (8) after Subscriber Priority in SUBSCRIBE and SUBSCRIBE_UPDATE: 0x0 descending, 0x1 ascending, anything else is a protocol violation.
    • lite-06 and older cannot carry it, so an ascending subscription silently falls back to descending (maintainer decision). lite-03 to 05 keep their retired byte, still written as 0.
    • moq-transport GROUP_ORDER maps in both directions, and draft-14's 0 decodes as Descending.
  • Tests (mock time).
    • rs/moq-net/tests/group_order.rs runs lite-05/06/07 and moq-transport 14/17/22, both directly and through a relay:
      • a backlog arrives ascending or descending as requested;
      • over lite-05/06 an ascending request is served newest first;
      • one relay request serves an ascending and a descending subscriber, each in its own order, and the upstream request moves to descending once the descending subscriber joins.
    • The mock transport now records each stream's send order, so a held set of streams can be released the way a congested sender transmits them.
    • JS adds js/net/src/order.test.ts plus codec, priority and aggregation tests.

Impact

  • Rust public API: new moq_net::track::Order, new Subscription::order field and Subscription::with_order. Subscription is #[non_exhaustive], so this is additive.
  • JS public API: new Track.Order type and Track.Subscription.order?. An unknown value throws a TypeError.
  • Wire, lite-07 (unpublished): Group Order byte in SUBSCRIBE and SUBSCRIBE_UPDATE. drafts/draft-lcurley-moq-lite.md is updated (Group Order section and changelog), and Group Order is dropped from the "Deleted Fields" list.
  • Wire, moq-transport:
    • The subscriber now sends the aggregate order as GROUP_ORDER, where it used to always send Descending.
    • The publisher honors it, and through draft-15 its SUBSCRIBE_OK restates the order it serves (it used to always say Descending). Draft-17+ keep the publisher-default track property, Descending.
    • Behavior change: group streams within a subscription are now ranked newest first by default (they used to share one send order). FETCH streams keep sending the raw subscriber priority, so under congestion they now fall below the ranked group streams, the same as lite FETCH.
  • Wire, lite-06 and older: unchanged.
  • moq-ffi and the bindings are untouched. The FFI Subscription does not expose order yet, so it stays private until a consumer needs it.

Alternatives

  • Use lite-03 to 05's retired Ordered byte to carry the order. Not taken, per the decision that older wires fall back.
  • Give moq-transport a composite priority << 8 | group send order instead of the shared queue. Writer::set_priority takes a u8, and sharing the queue keeps both publishers consistent.

Follow-ups

  • The moq-transport subscriber never updates an upstream request, for priority or order. If an ascending subscriber reaches the relay first, the upstream GROUP_ORDER stays ascending after a descending subscriber joins. lite-07 updates it. The test records this.
  • The Rust moq-transport fill/FETCH streams still use the raw subscriber priority. JS ranks the draft-20 fill in the queue as the group it fills, and Rust could match.
  • Expose order through moq-ffi and the bindings once a consumer asks for it.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

HangPlayerEngineer and others added 7 commits October 12, 2026 19:05
GROUP_ORDER was a module constant written unconditionally on both wire
shapes, and `Subscribe` had no field for it, so no caller -- internal or
public -- could ask for Ascending. The decode side already read the value
and validated it, and then dropped it on the floor: a relay built on this
parsed the subscriber's requested order correctly and could not act on it.

Add `groupOrder` to `Subscribe` and its constructor options, defaulting to
Descending, which is what every subscription sent before the field existed.
Both encode paths write the field instead of the constant, and both decode
paths pass the parsed value through.

`GroupOrder.Default` (0x0) is the wire's "no preference". It is normalized
to the client default in one place, the constructor, so the field a caller
reads back is always a real order and 0 can never reach an encoder. That
matters from draft-15, where GROUP_ORDER is a message parameter and
Parameters rejects 0 as a protocol violation -- only draft-14's inline
field can carry it at all, and that path is covered.

Tests run on draft-14 and draft-20. The ascending case is a differential:
it encodes the same message both ways and requires exactly one byte to
differ, 0x02 -> 0x01, since a plain byte search would pass on requestId 1n
alone. Each assertion is mutation-checked -- hardcoding either encode site,
dropping the constructor normalization, and dropping the decode
pass-through each turn a distinct test red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`track::Order` (Ascending/Descending, default Descending) rides on
`Subscription`, aggregates with Descending winning, and orders a lite
subscription's group streams in the publisher's priority queue. lite-07
SUBSCRIBE and SUBSCRIBE_UPDATE carry it; older wires fall back to descending.

Co-authored-by: Omar Ramadan <1845185+kkroo@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The IETF subscriber sends the aggregate's order as GROUP_ORDER and the
publisher serves it: its group streams now rank in the session-wide priority
queue (moved from lite to the crate root), newest first by default or oldest
first when asked, where they used to share one send order per subscription.

Covered on mock time, directly and through a relay: a backlog arrives in the
subscription's order over lite-07 and moq-transport and newest first over
older lite, and one relay request serves an ascending and a descending
subscriber in their own orders. The mock transport now records send order so
a test can release held streams the way a congested sender transmits them.

Co-authored-by: Omar Ramadan <1845185+kkroo@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Track.Order` ("ascending" | "descending", default "descending") rides on
`Track.Subscription.order` and aggregates with descending winning. lite-07
SUBSCRIBE and SUBSCRIBE_UPDATE carry it; older lite wires fall back to
descending. moq-transport maps it to GROUP_ORDER both ways, with the wire's
0 decoding as descending.

The group stream ranking moves to a shared `priority.ts` and ranks each
subscription's in-flight groups in its own order. The IETF publisher now
uses it too, so its group and fill streams carry a send order.

Co-authored-by: Omar Ramadan <1845185+kkroo@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 72ea546a-e039-4c0f-bc63-85eb6349da82
📥 Commits

Reviewing files that changed from the base of the PR and between 951f8ee and a473a77.

📒 Files selected for processing (40)
  • doc/concept/moq-lite.md
  • drafts/draft-lcurley-moq-lite.md
  • js/net/src/broadcast.test.ts
  • js/net/src/ietf/ietf.test.ts
  • js/net/src/ietf/publisher.test.ts
  • js/net/src/ietf/publisher.ts
  • js/net/src/ietf/subscribe.ts
  • js/net/src/ietf/subscriber.ts
  • js/net/src/integration.test.ts
  • js/net/src/lite/publisher.test.ts
  • js/net/src/lite/publisher.ts
  • js/net/src/lite/subscribe.test.ts
  • js/net/src/lite/subscribe.ts
  • js/net/src/lite/subscriber.ts
  • js/net/src/lite/version.ts
  • js/net/src/order.test.ts
  • js/net/src/priority.test.ts
  • js/net/src/priority.ts
  • js/net/src/track.test.ts
  • js/net/src/track.ts
  • quest/m1/subscribe-ranges/README.md
  • quest/m1/subscribe-ranges/js.md
  • quest/m1/subscribe-ranges/model.md
  • quest/m1/subscribe-ranges/order.md
  • rs/moq-net/benches/priority.rs
  • rs/moq-net/src/fuzz.rs
  • rs/moq-net/src/ietf/group.rs
  • rs/moq-net/src/ietf/publisher.rs
  • rs/moq-net/src/ietf/subscriber.rs
  • rs/moq-net/src/lib.rs
  • rs/moq-net/src/lite/mod.rs
  • rs/moq-net/src/lite/publisher.rs
  • rs/moq-net/src/lite/subscribe.rs
  • rs/moq-net/src/lite/subscriber.rs
  • rs/moq-net/src/lite/version.rs
  • rs/moq-net/src/model/subscription.rs
  • rs/moq-net/src/model/track.rs
  • rs/moq-net/src/priority.rs
  • rs/moq-net/tests/group_order.rs
  • rs/moq-net/tests/support/mock.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T03:37:01.532344Z a473a77 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21dc99c15f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread doc/concept/moq-lite.md Outdated
Comment on lines +198 to +200
first needs a max delay that covers the backlog. When a relay serves subscribers of
both orders from one upstream subscription, it asks upstream for newest first, and
still sends each subscriber its groups in that subscriber's own order.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the moq-transport relay exception

This claims that a relay with mixed-order subscribers always switches its upstream request to newest-first, but the new moq-transport path reads the aggregate only for the initial SUBSCRIBE and explicitly never updates its order (rs/moq-net/src/ietf/subscriber.rs:2101-2106); the added test likewise expects the upstream to remain ascending when an ascending subscriber joined first (rs/moq-net/tests/group_order.rs:214-222). In that scenario a later descending live subscriber can remain behind the upstream backlog, so either implement the switch or qualify this user-facing claim as Lite-07-only.

AGENTS.md reference: doc/AGENTS.md:L9-L9

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in the latest push: the concept doc now says that over moq-transport, the first subscriber to arrive fixes the upstream order. Making the moq-transport subscriber update its upstream request is listed as a follow-up in the PR description.

(Written by Claude Opus 5.5)

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 f615a78 against 4426d52. The latest documentation change addresses the existing relay-order clarification; two correctness issues remain:

  • P2: Keep IETF streams ranked until transport closure. In js/net/src/ietf/publisher.ts:885–886, ranking.remove(stream) follows fire-and-forget stream.close(); fills do the same at lines 1025–1026. Bytes can still be buffered while closure is pending. Removing the stream freezes its send order, so later groups can receive the same rank and priority updates no longer affect the pending stream. Retain registration until stream.closed settles, as the lite publisher does, and cover a completed group whose transport close remains pending. The new order tests keep groups open and miss this case.

  • P2: Return the selected order in draft-14 SUBSCRIBE_OK. Rust publisher:776 and JS publisher:433–438 now serve an ascending request in ascending order, but both SUBSCRIBE_OK paths still supply hardcoded Descending properties (Rust:56,857; JS:523). The draft-14 encoders serialize that property into the response's actual Group Order field, so the reply announces the opposite delivery order. For draft 14, return the selected subscription order; preserve the publisher-default property semantics on newer drafts.

The shared ranking direction and legacy-wire fallback otherwise look consistent. GitHub-only static review; no tests or benchmarks run.

(Written by OpenAI)

Through draft-15, SUBSCRIBE_OK restates the order a subscription is
served in, so announce the subscriber's choice rather than a fixed
Descending. Later drafts keep the publisher's default track property.

The JS moq-transport publisher now keeps a closed group or fill stream
ranked until it settles, so a FIN still queued is not frozen while the
groups behind it are promoted.

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

Copy link
Copy Markdown
Collaborator Author

Addressed both findings from the OpenAI review of f615a78 in 5978f92:

  • Closing streams stay ranked (JS): the moq-transport publisher now removes a group or fill stream from the ranking only once stream.closed settles, matching the lite publisher. New test ietf keeps a closing group stream ranked until it settles holds the FIN and fails without the fix.
  • SUBSCRIBE_OK order: through draft-15 (the drafts where it is a SUBSCRIBE_OK field or parameter) it now restates the subscription's selected order in Rust and JS. Draft-17+ keep the publisher-default track property, Descending. JS now also decodes the draft-14/15 value instead of discarding it. New tests subscribe_ok_announces_the_served_order (Rust) and SUBSCRIBE_OK announces the served order (JS).

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 5978f92e5a

ℹ️ 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".

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@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 a473a77 against 951f8ee. Both P2 findings from my previous review are addressed:

  • JS IETF group/fill streams now remain ranked until transport closure settles (js/net/src/ietf/publisher.ts:890–893,1032–1036).
  • Rust and JS legacy SUBSCRIBE_OK now acknowledge the selected delivery order (rs/moq-net/src/ietf/publisher.rs:858–866; js/net/src/ietf/publisher.ts:523–528).

The follow-up adds regression coverage for pending closure and order acknowledgment. I checked the main-merge overlap against the updated base; the fixes remain intact. No new actionable findings. The shared-ranking approach and versioned fallback look consistent with the documented scope.

GitHub-only static review; no tests or benchmarks run.

(Written by OpenAI)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary

Lets a subscriber choose group order: track::Order { Ascending, Descending } on Subscription.order (JS "ascending" | "descending"), default Descending, aggregated so Descending wins.

  • API: additive. Rust moq_net::track::Order, Subscription::order, Subscription::with_order. JS Track.Order, Subscription.order?. FFI untouched.
  • Wire:
    • lite-07 (unpublished) carries Group Order in SUBSCRIBE and SUBSCRIBE_UPDATE, and lite-06 and older fall back to Descending.
    • moq-transport GROUP_ORDER maps both ways, the publisher honors it, and SUBSCRIBE_OK restates it through draft-15.
    • The moq-transport publisher now ranks group streams through the shared session queue, newest first by default.
  • Settled decisions:
    • order never affects expiry;
    • moq-transport uses the shared queue;
    • the IETF upstream order is fixed at SUBSCRIBE.
  • Review:
    • Codex's finding on 21dc99c and both OpenAI P2s on f615a78 are fixed, with regression tests.
    • Codex was clean on 5978f92, and the OpenAI review of the final head a473a77 found nothing.
  • Checks: just check, just drafts check, the JS tests, and just test interop --all pass. Main is merged in after fix(interop): give the Rust interop clients the repo rustfmt config #5381.
  • Credit: builds on feat(moq-net): let a subscriber choose the SUBSCRIBE group order #5370 by @kkroo. Their commit keeps its authorship, and the later commits carry Co-authored-by.

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge October 13, 2026 03:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a473a77dc5

ℹ️ 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".

let mut track_serve =
TrackServe::new(self.session.clone(), track, request_id, self.version, range, timescale);
TrackServe::new(self.session.clone(), track, request_id, self.version, range, timescale)
.ranked(self.priority.clone());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep equal-priority subscriptions tied

Routing every moq-transport subscription through this session queue regresses equal-priority fairness: Priority::cmp compares the subscription/request ID before the group, so every stream from the older subscription ranks ahead of every stream from a later subscription. With the common default priority and a backlog on the first track, a live second track can receive no bandwidth until that entire backlog drains; previously these streams retained the same transport priority and could be scheduled fairly. The queue needs to interleave each subscription's next group rather than using the subscription ID as a global tier.

Useful? React with 👍 / 👎.

@@ -1,13 +1,18 @@
//! Worst-case insertion benchmarks for the Lite publisher's stream priority queue.
//! Worst-case insertion benchmarks for the publishers' stream priority queue.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Benchmark both fan-out axes

Now that this benchmark covers the session-wide publisher queue, it still varies only one total depth and constructs every entry with subscription ID 0. It therefore cannot reveal whether costs grow differently with the number of subscriptions versus the number of pending groups per subscription. Add an N-by-M sweep for those independent fan-out axes, including order changes, rather than treating their product as one depth.

AGENTS.md reference: AGENTS.md:L34-L37

Useful? React with 👍 / 👎.

@kixelated
kixelated added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit d775256 Oct 13, 2026
10 checks passed
@kixelated
kixelated deleted the quest/m1/subscribe-ranges/order branch October 13, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant