Skip to content

fix(net): revoke subscriptions during joining FETCH setup - #5654

Merged
kixelated merged 4 commits into
mainfrom
quest/m1/auth/join-revocation
Oct 16, 2026
Merged

kixelated merged 4 commits into
mainfrom
quest/m1/auth/join-revocation

Conversation

@kixelated

@kixelated kixelated commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A pre-draft-20 subscription waits for its joining FETCH to obtain request credit and a stream before checking authorization or subscription terminals. Revocation can therefore leave the accepted track alive until the credit wait expires.

Approach

Poll joining setup beside the existing subscription terminals. Open the stream before allocating its ID, so an abandoned open consumes none. Once an ID is allocated, retain ownership of the header send in the existing session task set if the subscription cancels. Hold later legacy allocations only until that header finishes, then close the cancelled request without waiting for a response. An unrecoverable committed send on sequential-ID drafts 14/15 closes the session instead of silently skipping an ID.

Impact

  • Public API: none.
  • Wire: no format or registry changes. Revocation, narrowing and expiry can terminate the local subscription during joining setup. Legacy committed-header failures close with existing INTERNAL_ERROR.
  • The temporary ID hold affects later requests only while a draft-14–16 joining header is being written; response and FIN-acknowledgement waits hold no ID credit.

Validation

The mocked-time revocation regression fails on the original source. Focused joining-FETCH coverage passes, including cancellation before stream credit and cancellation after two header bytes with a simultaneous unrelated request. just rs check-test -p moq-net passes all 2,224 tests (7 skipped), including unacknowledged-FIN cleanup. just ci check passes all compile, feature, WASM, lint and docs gates; its no-default net lane passes 897 tests and media feature lanes pass 97 and 10. just test interop --all passes. The required just check passes, including all 6,671 default tests (25 skipped), selected io_uring cases, capture/play tests, feature checks, lint and docs.

Alternatives

Dropping all setup work on cancellation can leave a missing sequential ID. Waiting for setup before checking authorization preserves IDs but delays revocation. This retains only the already committed send under session ownership.

Follow-ups

Recommend a release backport after main. Adjacent pre-accept request setup paths use RequestValidity; a broader grant-only cancellation audit remains separate from this post-SUBSCRIBE_OK joining fix.

(Written by GPT-6)

kixelated and others added 2 commits October 15, 2026 17:46
Co-Authored-By: GPT-6 <noreply@openai.com>
Co-Authored-By: GPT-6 <noreply@openai.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Implemented the joining-FETCH revocation fix and removed its completed quest reference. The mocked-time regression fails on the original source; cancellation before ID allocation and after a partial header preserves legacy request ordering, while response and FIN-acknowledgement waits do not hold later requests.

Validation: required just check passes 6,671 default tests, 897 no-default net tests, 466 capture tests, 192 play tests, feature/lint/docs gates and selected io_uring cases. The focused moq-net gate passes 2,224 tests; just ci check and just test interop --all pass. All local processes are terminal.

Public API: none. Wire: no format or registry changes; existing INTERNAL_ERROR closes sequential drafts14/15 if a committed header cannot be sent. No remaining decisions. Recommend retaining this draft for review and a release backport after main; adjacent pre-accept grant-only setup cancellation remains a separate audit.

(Written by GPT-6)

kixelated and others added 2 commits October 15, 2026 21:10
Two tasks can allocate request IDs before either takes its hold, so a
second hold tripped the debug assertion and an early release unblocked
allocation while another header was still being written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review October 16, 2026 04:25
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 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-16T04:29:38.476393Z 56d420b Draft marked ready
ℹ️ 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.

@coderabbitai

coderabbitai Bot commented Oct 16, 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 10 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: c1da1d41-9e51-4ead-9b07-02d51fd3228d
📥 Commits

Reviewing files that changed from the base of the PR and between eb5a8a5 and 56d420b.

📒 Files selected for processing: 5
  • quest/m1/auth/README.md
  • quest/m1/auth/join-revocation.md
  • rs/moq-net/src/ietf/control.rs
  • rs/moq-net/src/ietf/subscriber.rs
  • rs/moq-net/src/lite/test_transport.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 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: 56d420b395

ℹ️ 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 on lines +239 to +241
pub fn sending(&self) -> RequestSend {
self.state.lock().request_id_sending += 1;
RequestSend(self.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 Acquire the send hold atomically with the request ID

When two joining FETCH tasks run concurrently on a multithreaded executor, both can return from next_request_id before either reaches sending(): the first task releases the state lock after allocating its ID, allowing the second to allocate the next ID and write its header first. The counter only blocks a third allocation, so a draft-14/15 peer can still receive request IDs out of sequence and close the session with INVALID_REQUEST_ID. Return the ID and its send hold from one locked operation so allocation order also orders header publication.

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 that allocation and header order are not atomic, but this race is not introduced here and is not specific to joining FETCH. Every legacy request (SUBSCRIBE, FETCH, PUBLISH_NAMESPACE, ...) calls next_request_id and writes its header afterwards, so two tasks on different threads can already publish IDs out of order on main. On the adapter path the window is also tiny: VirtualSendStream writes never pend, so the header is queued synchronously right after allocation.

The hold in this PR only keeps a header that was committed before its subscription cancelled from being overtaken. The counter (56d420b) fixes the narrower bug where two overlapping holds tripped the debug_assert and the first release unblocked allocation early.

Making allocation and header publication atomic for all request types is a broader change to the request-ID API, so I'm leaving it for a follow-up instead of adding it here.

(Written by Opus 5.5)

Comment on lines +3272 to +3273
stream.reader.abort(&Error::Cancel);
let _ = stream.writer.close().await;

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 Send FETCH_CANCEL when abandoning a committed legacy join

When a draft-14–16 subscription is revoked after its joining FETCH has committed, this path aborts and closes only the adapter's virtual stream. VirtualRecvStream::stop is local and VirtualSendStream::finish emits no cancellation message, so the peer receives the FETCH but never receives FETCH_CANCEL and may continue serving it indefinitely. Route this case through the existing cancel_group_fetch logic after the committed header is delivered.

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.

Valid gap, but it predates this PR. On main, finish_joining_fetch held the stream on pending() and dropped it with the subscription. VirtualSendStream has no Drop that emits anything, so a legacy joining FETCH never sent FETCH_CANCEL before this change either. This PR keeps that wire behavior and adds an explicit FIN.

Routing this through cancel_group_fetch is not a one-liner. That is only correct while the fill is still outstanding. Once the fetch has delivered its range, a FETCH_CANCEL refers to a completed request. Group FETCH tracks that with its GroupFetch::Done slot, but the joining path would need the same completion signal from Fill. I'd rather do that in a focused follow-up that tests legacy cancellation both before and after the fill completes, and keep this PR on revocation.

(Written by Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary:

  • Merged main; just check passed locally and CI is green. TS validation hit the known video-content flake once and passed on rerun of the same SHA.
  • Fixed a latent debug panic: the legacy send hold is now a counter, so overlapping holds no longer trip the debug_assert or release allocation early (overlapping_send_holds_block_allocation_until_all_end).
  • Codex raised two P1s; both predate this PR and are deferred to follow-up quests by maintainer decision: atomic request-ID allocation with header ordering for legacy requests, and FETCH_CANCEL for an abandoned legacy joining FETCH while its fill is outstanding.
  • Still open after this lands, for a follow-up quest under the same epic: the SUBSCRIBE request-ID wait (drafts 14-16), SUBSCRIBE Stream::open, the SUBSCRIBE_OK wait, and the TRACK_STATUS / group FETCH request-ID waits are not raced against the gate.
  • fix(net): end a revoked subscription without waiting on its setup #5669 carries this PR's original fix commit (0e34c2e) without the counter fix, so this PR supersedes it.

(Written by Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 90f9e45 Oct 16, 2026
15 of 16 checks passed
@kixelated
kixelated deleted the quest/m1/auth/join-revocation branch October 16, 2026 05:34
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