Skip to content

fix(moq-net): rank moq-transport FETCH and fill streams in the session queue - #5470

Merged
kixelated merged 5 commits into
mainfrom
quest/m1/subscribe-ranges/ietf-fetch-queue
Oct 14, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m1/subscribe-ranges/ietf-fetch-queue

Conversation

@kixelated

@kixelated kixelated commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest m1/subscribe-ranges/ietf-fetch-queue.

The Rust moq-transport publisher gave fill, FETCH, and standalone END_OF_TRACK streams the raw subscriber priority as their send order. Group streams use the session PriorityQueue rank (255 - rank), so the two scales were mixed and how these streams compared under congestion was arbitrary.

Changes (rs/moq-net/src/ietf/publisher.rs)

  • Ranking: one request's place in the session queue (queue, request id, subscriber priority consumer, GROUP_ORDER). TrackServe holds one, and Ranking::rank(sequence) replaces TrackServe::rank.
  • Ranked<T>: a SendStream wrapper that holds a Rank and applies any move on every write and close poll. The fill parks on its group by racing stream.closed(), so a move reaches the transport whatever the stream is waiting on. The rank is released when the writer drops, so after the stream settles (FIN acknowledged or reset), not when FIN is written.
  • Start of a group first (rs/moq-net/src/priority.rs): Priority takes the stream's start object as its last tie-break, so of two streams for one group (a joining FETCH or fill beside the live rest of that group), the earlier objects send first. Lite has one stream per group and is unchanged.
  • settle: these streams close by finish then poll_close on a writer that still owns the stream (the GroupServe pattern), so a close cancelled before the FIN is acknowledged resets the stream as it releases its rank. Writer::close is unchanged.
  • Draft-20 fill: ranks as the group it fills in its subscription's ranking, the same way JS #runFill does. A fill that only carries a refusal (multi-group) is unranked, also matching JS.
  • Joining FETCH (pre-draft-20): Joined::Group carries the subscription's Ranking, so the FETCH ranks as the group it fetches within the subscription it joins. TrackServe is now built before the join snapshot is registered so the ranking exists in time.
  • Standalone FETCH: its own queue entry keyed by its request id. It ranks against group streams by subscriber priority alone, with the id breaking ties. Group and GROUP_ORDER do not affect it. It follows queue moves; nothing updates a FETCH's priority today.
  • Standalone END_OF_TRACK stream: ranks as the group it ends. Same defect, same fix (one line plus a test).
  • JS: nothing to change. It already ranks its fill and refuses every standalone FETCH (decided in quest(m1): plan the group order follow-ups #5400).

Public API / wire impact

None. All new items are private: Ranking, Ranked and settle in ietf::publisher, and Priority::with_start on the crate-private queue. The wire is unchanged; only local transport send orders change.

Tests (mock time)

  • a_fill_ranks_as_the_group_it_fills: the fill ranks behind a higher priority, an older subscription at the same priority, and its subscription's newer group. It moves up when a rival settles, and to the top on a subscriber priority raise, while parked on its open group.
  • a_standalone_fetch_ranks_by_its_priority: drafts 16 and 20, both GROUP_ORDERs and two groups, always the same rank.
  • a_joining_fetch_ranks_in_its_subscription: every joining draft; keyed by the subscription, not the FETCH's own id.
  • standalone_end_of_track_ranks_as_its_group.
  • a_fill_outranks_the_rest_of_its_group and test_earlier_start_ranks_first_within_a_group: the start of a group outranks a stream queued earlier for the rest of it, in both orders (Codex P1). Both fail without the tie-break.
  • a_ranked_stream_cancelled_after_its_fin_resets: a close cancelled with the FIN unacknowledged resets the stream and frees its rank (review finding).

Before this change, each of these recorded the raw priority (127).

just check passes. just test interop --all: all checks passed.

Decisions

  • ✅ Keep the standalone END_OF_TRACK fix in this PR (maintainer, 2026-10-13).
  • ✅ A standalone FETCH following priority updates folds into moq-transport ranges: a multi-group FETCH keeps its one queue entry and moves with priority updates (maintainer, 2026-10-13). Recorded in that quest's Plan in this PR.
  • ✅ The earlier part of a group sends first: Priority breaks ties within a group by start object, rather than sharing a rank as JS does (maintainer, 2026-10-14).

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

kixelated and others added 2 commits October 13, 2026 14:39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n queue

A fill and a joining FETCH rank as the group they carry in their subscription's
ranking; a standalone FETCH gets its own queue entry keyed by its request id, so it
competes with group streams by subscriber priority alone. The standalone
END_OF_TRACK stream ranks as the group it ends. Each follows its rank until the
stream settles, through a Ranked send-stream wrapper, instead of taking the raw
subscriber priority as its send order.

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

Copy link
Copy Markdown
Collaborator Author

Quest outcome: implemented as scoped and left as a draft.

  • just check: pass.
  • just test interop --all: all checks passed.
  • Not in the quest's text: the standalone END_OF_TRACK stream had the same raw-priority send order, so it now ranks as the group it ends. It is one line plus a test. Split it out if you want this PR narrower.
  • Follow-up: a standalone FETCH ignores priority updates. This only matters once FETCH spans several groups, which is the moq-transport ranges quest.

(Written by Claude Opus 5.5)

…ranges

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review October 13, 2026 22:56
@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 48 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: 812f7134-72dc-47c2-b2b9-1bc09e2cd3dd
📥 Commits

Reviewing files that changed from the base of the PR and between fb7310b and 65ff24d.

📒 Files selected for processing: 6
  • quest/m1/subscribe-ranges/README.md
  • quest/m1/subscribe-ranges/ietf-fetch-queue.md
  • quest/m1/subscribe-ranges/ietf-update.md
  • quest/m1/subscribe-ranges/ietf.md
  • rs/moq-net/src/ietf/publisher.rs
  • rs/moq-net/src/priority.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-14T02:19:32.081367Z 65ff24d New commits
ℹ️ 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.

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

One P2 cancellation/lifetime issue below. Otherwise the shared Ranking/Ranked abstraction is a sensible, contained way to put fill, joining/standalone FETCH and END_OF_TRACK on the same ordering scale, with appropriate parked-stream wake handling. Deferring standalone FETCH priority updates to the range work is explicitly recorded.

Verification: GitHub-only static review of implementation, transport close/drop semantics and added tests; no tests, interop runs or benchmarks executed.

struct Ranked<T> {
stream: T,
/// Absent for a stream that only carries a refusal.
rank: Option<Rank>,

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.

[P2] Keep the rank until cancellation actually resets the stream

Ranked owns the queue entry, but these async writers finish via Writer::close(), which takes the stream out of Writer before awaiting FIN acknowledgement (coding/writer.rs:170–177). If a fill/subscription or FETCH task is canceled during that wait, dropping Ranked releases its rank without Writer's reset fallback. The noq transport explicitly does not reset a FIN-sent stream on drop (moq-uring/src/quic/noq/stream.rs:175–187), so unacknowledged data continues transmitting with a stale send order while the queue reuses the released position. The underlying close behavior predates this change; releasing the new relative rank before settlement is the new consequence. Use a cancellation-safe close that retains the writer/reset guard until poll_close completes, or otherwise retain the rank through settlement. Cover cancellation after FIN with SinkSession::with_unacked_fin and a competing ranked stream.

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.

Agreed, fixed. Fill, FETCH, and END_OF_TRACK streams now close through a private settle helper. It calls finish, then poll_close, on a writer that still owns the stream, the same pattern GroupServe's Closed state uses. Cancelling while the FIN is unacknowledged now resets the stream at the same moment it gives up its rank. Writer::close is unchanged, so request streams keep their deliver-on-drop behavior.

Regression test: a_ranked_stream_cancelled_after_its_fin_resets uses SinkSession::with_unacked_fin and a competing ranked entry. It fails on the old Writer::close (no reset) and passes now.

(Written by Claude Opus 5.5)

…wledged

Writer::close takes the stream out before waiting for the acknowledgement, so a
cancelled close dropped the rank while the stream kept sending. Ranked fill, FETCH,
and END_OF_TRACK streams now finish and poll_close on a writer that still owns
the stream, so cancelling resets it as a cancelled group stream's is.

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: 89c57c8, compared with 3e0277d (unchanged base).

The previous P2 rank-lifetime finding is fixed: #5470 (comment). Fill, FETCH and END_OF_TRACK now retain the Writer reset guard through FIN acknowledgement. Cancellation resets the stream before releasing its rank; successful settlement removes the guard. Rank changes continue to reach the transport while waiting.

No new actionable bugs found. The private settle helper is appropriately scoped and reuses GroupServe's established pattern without changing request-stream close semantics. The new unacknowledged-FIN regression checks both cancellation reset and the competing rank's release.

Verification: GitHub-only static inspection of the delta, all three call sites, Writer ownership/drop behavior and test transport. No tests, interop runs or real noq transfers executed; the added cancellation regression directly exercises END_OF_TRACK, with fill/FETCH sharing the helper.

@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: 89c57c8fff

ℹ️ 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 rs/moq-net/src/ietf/publisher.rs Outdated
let uni = self.session.open_uni().await.map_err(Error::from_transport)?;
let mut writer = Writer::new(uni, self.version);
writer.set_priority(priority);
let mut writer = Writer::new(Ranked::new(uni, Some(ranking.rank(start.group))), self.version);

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 Share the group rank with a joining FETCH

When a joining FETCH backfills the current group, TrackServe normally already has a stream ranked for start.group, but this call inserts a second queue entry for the same group. PriorityQueue assigns every entry a distinct ordinal send order because PriorityItem.id breaks otherwise identical priorities, so the later FETCH ranks below the live suffix instead of tying it. Under sustained congestion, the live group can therefore continually preempt the prefix and prevent the join from completing; both streams need to share a rank or otherwise receive the same send order.

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.

Agreed, this is real, and it applies to the draft-20 fill as well as the joining FETCH. JS gives the fill and the live group stream of the same sequence the same send order, because Priority#position counts only other sequences. The Rust PriorityQueue gives every entry a unique rank, so the prefix lands above or below the live suffix depending on slab id.

The fix changes how the shared queue orders entries, so I've paused the merge for a maintainer call:

  1. Prefix first: add the stream's start object to Priority as the last tie-break within a group, so earlier objects go first. The prefix holds the keyframe the suffix needs, so this is the better order. JS still ties, and could adopt the same rule later.
  2. Tie, matching JS: identical priorities share one rank in the Rust queue. This changes the queue's unique-rank invariant and its rerank wakes.
  3. Leave as is and follow up separately.

My recommendation is 1.

(Written by Claude Opus 5.5)

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 with option 1, the maintainer's choice. Priority now takes each stream's start object as its last tie-break within a group, so a joining FETCH or fill of the current group sends ahead of the live stream carrying the rest of that group. Group streams rank from their slice's first object, fills from their skip, FETCH from its start object, and END_OF_TRACK from its object. Lite has one stream per group, so its ranks are unchanged.

Regressions: a_fill_outranks_the_rest_of_its_group queues the live rest of group 4 first, then expects the fill of its start to rank on top. test_earlier_start_ranks_first_within_a_group covers both orders and shows the start never overrides the group order. Both fail without the tie-break.

(Written by Claude Opus 5.5)

A joining FETCH or fill of the current group shares its group with the live
stream carrying the rest, and the queue split the two by slab id. Priority now
takes the stream's start object as its last tie-break, so the earlier objects,
which the rest cannot be used without, send first. Lite has one stream per group
and is unchanged.

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

Copy link
Copy Markdown
Collaborator Author

Merge summary for head 65ff24d.

Changes:

  • Rust moq-transport fill, joining FETCH, standalone FETCH, and standalone END_OF_TRACK streams rank in the session PriorityQueue instead of using the raw subscriber priority as their send order. Each stream follows its rank until it settles.
  • settle closes these streams so that a close cancelled before the FIN is acknowledged resets the stream as it releases its rank (OpenAI P2).
  • Priority breaks ties within a group by start object, so the start of a group sends ahead of the live rest of it (Codex P1).

Decisions (maintainer):

  • ✅ Keep the END_OF_TRACK fix in this PR.
  • ✅ A multi-group FETCH keeps its one queue entry and moves with priority updates. Recorded in the moq-transport ranges quest's Plan.
  • ✅ The earlier part of a group sends first (option 1).

Verification:

  • CI green on 65ff24d.
  • Codex reviewed 65ff24d with no findings.
  • just test interop --all passed on 65ff24d. Two earlier runs at load averages of 40-57 failed only lite-06 pairs, and different ones each time; lite never uses the new tie-break.
  • just check passed apart from three load flakes outside the changed code, each passing alone:
    • moq-cli::export the_same_instance_within_the_linger_continues (tracked)
    • moq-relay auth::refresh::an_outage_refuses_transiently_and_never_extends_the_expiry (tracked)
    • moq-uring::teardown a_published_close_has_already_left_the_client (untracked)

Public API and wire impact: none.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit f9c2c9f Oct 14, 2026
8 checks passed
@kixelated
kixelated deleted the quest/m1/subscribe-ranges/ietf-fetch-queue branch October 14, 2026 03:47
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