Skip to content

fix(net): refuse a revoked subscription while its SUBSCRIBE is still pending - #6061

Merged
kixelated merged 4 commits into
moq-dev:mainfrom
mriise:fix/subscribe-setup-gate
Oct 21, 2026
Merged

kixelated merged 4 commits into
moq-dev:mainfrom
mriise:fix/subscribe-setup-gate

Conversation

@mriise

@mriise mriise commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Problem

#5654 brought the joining FETCH's credit wait under the subscription's grant, and #5992 holds a subscription until its token is granted, but two steps of the SUBSCRIBE itself still ran outside the grant: the SUBSCRIBE's own request-ID wait and its SUBSCRIBE_OK wait race route retirement and demand, never the gate. A grant revoked, narrowed, or expired there waited for the peer: up to the ten-second credit give-up on drafts 14-16, or until a SUBSCRIBE_OK arrived on any draft. Found while working the join-revocation quest in parallel with #5654; noted on the closed #5669.

Approach

  • The SUBSCRIBE's credit wait polls the gate beside validity, so a denial refuses the request Unauthorized before any id is allocated.
  • The SUBSCRIBE_OK wait polls the gate as one more local terminal. A denial refuses the request Unauthorized and cancels the subscribe at the peer (UNSUBSCRIBE on 14-16, STOP_SENDING on every draft), the same path a route retirement already takes; the flag that path used is renamed from retired to unanswered, since it now covers both.
  • Two regression tests on sim time, both failing on main by timing out: a revocation during the credit wait (drafts 14-16) and during the SUBSCRIBE_OK wait (14, 16, 18, 22).

Impact

  • Public API: none.
  • Wire: none.

Alternatives

Racing the whole setup future against the gate and dropping it: rejected for the reasons #5654 and the request-gate quest record (an allocated id must still be sent on 14-16, and the refusal should stay graceful).

Follow-ups

Awaits still outside the gate on the setup path, each bounded by something else: Stream::open for the SUBSCRIBE and the joining FETCH on draft 17+ (QUIC stream credit), and the request-ID waits of a fetch-only TRACK_STATUS and a cache-miss group FETCH (their own ten-second give-up).

Decisions

  • Keep the approach: polling the gate inside the two existing waits, beside validity, reuses the retirement path and keeps the refusal graceful. ✅ Keep it, over racing the whole setup future (rejected above).
  • Refuse locally before the wire cleanup (Codex finding). ✅ The error arm rejects the pending request, removes the subscribe, and aborts a resumed track before awaiting cancel_subscribe, as the unsupported-extension arm already does, so a peer that never acknowledges the cancellation cannot hold the refusal.
  • Keep the credit wait poll-native (Codex finding). ✅ Control::poll_credit(waiter, &mut Deadline) is the timed allocation, next_request_id its async wrapper, and Control::REQUEST_ID_TIMEOUT the give-up; the SUBSCRIBE polls it beside the gate and validity instead of pinning the async wrapper.
  • Drop the test-only subscriber_unsubscribes(version) helper. ✅ Read Subscriber::unsubscribes() before the subscriber moves into its task, so the test cannot drift from the code it checks.

Verification

  • Merged main (conflict: main added the request-stream messages beside the renamed flag; kept both).
  • With the two poll_denied checks removed, both regression tests fail by timing out; with them, both pass. a_revocation_never_waits_behind_subscribe_ok runs against a peer that never acknowledges the FIN, and also times out with the old refuse-after-cleanup ordering.
  • just check passes in the Nix dev shell. export::redundant_importers_switch_through_bufferbloat_on_lite_07 failed two loaded runs with "stitching export exited", the known flake tracked by quest/m1/test-flakes-2/redundant-failover-drift.md, unrelated to this IETF-only change; it passed on the third run.

(Written by Claude Fable 5.1, updated by Claude Opus 5.5)

mriise and others added 3 commits October 20, 2026 04:08
…pending

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts:
#	rs/moq-net/src/ietf/subscriber.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator

Thanks for this, @mriise. The fix is right as written, so I finished it off on your branch rather than change the approach:

  • Merged main and resolved the one conflict in run_subscribe: main now creates the request-stream messages right where you renamed retired to unanswered, so both are kept.
  • Replaced the test-only subscriber_unsubscribes(version) helper with Subscriber::unsubscribes(), read before the subscriber moves into its task, so the test cannot drift from the real check.
  • Confirmed both regression tests time out with the two poll_denied checks removed and pass with them.
  • Ran the full just check in the Nix dev shell, which passes. The only failure on the way was the known lite-07 redundant-importer flake, unrelated to this change.

No review findings were posted, so I'm marking it ready.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review October 20, 2026 20:31
@coderabbitai

coderabbitai Bot commented Oct 20, 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 13 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: 76616c02-9b6c-4fb5-a6bf-88e4038a7fa0
📥 Commits

Reviewing files that changed from the base of the PR and between c74957a and 626734b.

📒 Files selected for processing: 2
  • rs/moq-net/src/ietf/control.rs
  • rs/moq-net/src/ietf/subscriber.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 20, 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-20T20:56:24.933497Z 626734b 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: 02b33e7afb

ℹ️ 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/subscriber.rs Outdated
Comment on lines 2959 to 2960
if unanswered {
self.cancel_subscribe(stream, request_id).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 Refuse locally before awaiting peer cancellation

If authorization is revoked while a SUBSCRIBE is pending and the peer stops accepting writes or never acknowledges this stream's FIN, this new unanswered path awaits cancel_subscribe before take_pending, remove_subscribe, or aborting resumed; that helper can park in write_unsubscribe or Writer::close, so the caller can remain pending and a resumed producer remains registered until the connection times out despite the revocation. Refuse, abort, and remove locally before awaiting graceful wire cleanup, as the unsupported-extension arm already does.

AGENTS.md reference: AGENTS.md:L18-L18

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agreed, fixed in 626734b: the error arm now rejects the pending request, removes the subscribe, and aborts a resumed track before awaiting cancel_subscribe, matching the unsupported-extension arm. a_revocation_never_waits_behind_subscribe_ok now runs against a peer that never acknowledges the FIN (with_unacked_fin), and it times out with the old ordering.

(Written by Claude Opus 5.5)

Comment thread rs/moq-net/src/ietf/subscriber.rs Outdated
Comment on lines +2704 to +2705
let mut credit = std::pin::pin!(self.control.next_request_id(&self.runtime));
kio::wait(|waiter| {

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 the request-credit race poll-native

The new request-credit race pins the async next_request_id wrapper and drives it from a second kio::wait, rather than composing the already available Control::poll_next_request_id; this reverses the workspace's documented poll/async layering and splits deadline, wake, and cancellation state across nested waiters. Add a poll-native timed credit operation, or poll credit and a Deadline here, and keep async only as its wrapper.

AGENTS.md reference: rs/AGENTS.md:L24-L24

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agreed, fixed in 626734b: Control::poll_credit(waiter, &mut Deadline) is now the poll-native timed allocation, with next_request_id as its async wrapper and the give-up in Control::REQUEST_ID_TIMEOUT. The SUBSCRIBE credit wait polls it beside the gate and validity in one kio::wait, with no pinned future.

(Written by Claude Opus 5.5)

A peer that never acknowledges the cancellation no longer holds the refusal or the resumed track. The SUBSCRIBE's credit wait now composes a poll-native Control::poll_credit instead of pinning the async wrapper.

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

Copy link
Copy Markdown
Collaborator

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 626734be4f

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

Summary before merge:

  • Merged main and resolved the run_subscribe conflict (kept main's request-stream messages beside the unanswered flag).
  • Addressed both Codex findings in 626734b: a revoked SUBSCRIBE is now refused, removed, and its resumed track aborted before awaiting the peer's acknowledgement of the cancel; and the credit wait polls a new poll-native Control::poll_credit beside the gate rather than pinning next_request_id.
  • The regression tests fail without the fix (timeouts), including a peer that never acknowledges the FIN.
  • just check passes in the Nix dev shell. Codex reviewed 626734b with no findings.
  • Public API and wire: none.

Enabling auto-merge on 626734b.

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge October 20, 2026 21:00
@kixelated
kixelated added this pull request to the merge queue Oct 21, 2026
Merged via the queue into moq-dev:main with commit bc64b9c Oct 21, 2026
5 checks passed
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.

2 participants