Skip to content

fix(net): join pre-draft-20 subscriptions at the live group - #6348

Merged
kixelated merged 2 commits into
mainfrom
fix/ietf-resume-joining-fetch
Oct 22, 2026
Merged

kixelated merged 2 commits into
mainfrom
fix/ietf-resume-joining-fetch

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Bug

moq-dev/moq#6209 made a resumed subscription carry each reader's resume floor as its start. Before draft 20, subscribe_join turned that start into an AbsoluteJoining FETCH from the floor (say group 1) through the live edge. Our publisher serves one group per FETCH and refuses it ("FETCH spanning several groups not supported"). The floor group's leftover frames still arrive through the resume refetch, but the live group never does. The refusal was also swallowed: the subscriber "continued live" with no head for the live group, so a reader hung instead of failing. moq-dev/moq#6232's route_change::ietf_17_resumes_after_a_lagging_route_dies passes on its own head and hangs once main is merged in.

Fix

  • Every pre-draft-20 join is now a live join: Largest Object plus a RelativeJoining FETCH at offset 0, so the FETCH carries only the Largest group's head. Earlier groups go to their own group FETCHes, as they did before fix(net): deduplicate resumed datagrams by identity #6209. The track's start is that Largest group, which is what the feed actually delivers.
  • A refused or failed joining FETCH now aborts the subscription with the publisher's reason and cancels it upstream. This also covers a refusal that lands during the settle after a clean PUBLISH_DONE. A parked mid-group tail is still released.
  • Deleted the multi-group stitching that only an absolute join needed: JoiningFetch, TrackState::{joining, largest}, advance_fill_group, end_fill_group, Live/settle_join_live, and the datagram-group skipping. A fill that spans groups is now refused. Two FETCH-response readers are merged into one.

Why this differs from the suggested Absolute { group_id: max(start, largest) }: when start equals or is below Largest, RelativeJoining{0} means exactly the same thing on the wire, and the live join already uses it. When start is past Largest, AbsoluteJoining{start} is an invalid range that publishers refuse, and with fail-loud that refusal would kill the subscription. One spelling handles both cases. It still fetches the Largest group's head, which a merged live reader may want (subscribe_ranges covers this), and a reader starting later skips that head locally.

Tests

  • a_join_from_any_start_fetches_only_the_largest_group: a start below, at, or past Largest sends RelativeJoining{0} on every draft from 14 to 19. Fails on main, which sends AbsoluteJoining{7}.
  • a_refused_fetch_fails_the_subscription: a FETCH_ERROR/REQUEST_ERROR ends the track with Unsupported and drops the subscription. Fails on main, which continued live. It replaces a_refused_fetch_continues_live.
  • a_joining_fetch_spanning_several_groups_is_refused replaces the absolute-join stitching tests.
  • older_drafts_always_join_live replaces the per-start subscribe_join cases.
  • Applying this commit to a local merge of test(net): cover lagging replicas on epoch wires #6232 and main makes ietf_17_resumes_after_a_lagging_route_dies pass. All 3374 moq-net and moq-relay tests pass there and on this branch, and clippy and fmt are clean.

js/net sends no joining FETCH, so it needs no change.

API and wire impact

  • No public API change.
  • Wire: before draft 20, a subscription with an explicit start now sends RelativeJoining{0} where it used to send AbsoluteJoining{start}. This is valid on every draft, with no change to the format.
  • Behavior: a peer that refuses the joining FETCH now fails the subscription instead of silently losing the live group's head.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

A resumed subscription now carries each reader's resume floor as its start
(#6209). Before draft 20 that start became an AbsoluteJoining FETCH from the
floor through the live edge, which a publisher serving one group per FETCH
refuses, and the refusal quietly continued live without the live group's head.

Every pre-draft-20 join is now a live join: Largest Object plus a
RelativeJoining FETCH for the Largest group alone, leaving earlier groups to
group FETCHes as before #6209. A refused or failed joining FETCH now fails
the subscription with its reason instead of leaving readers waiting on a head
that never comes. The multi-group stitching only an absolute join needed is
gone.

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

coderabbitai Bot commented Oct 22, 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 9 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
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c869a6fa-4e59-4495-b445-8c2f2b685e87
📥 Commits

Reviewing files that changed from the base of the PR and between 6e64133 and 6f81650.

📒 Files selected for processing: 1
  • 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 22, 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-22T19:09:12.986827Z 6f81650 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 added this pull request to the merge queue Oct 22, 2026
@kixelated
kixelated removed this pull request from the merge queue due to a manual request Oct 22, 2026
# Conflicts:
#	rs/moq-net/src/ietf/subscriber.rs
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merged origin/main (no rebase) after moq-dev/moq#6111 landed. The only conflict was in rs/moq-net/src/ietf/subscriber.rs, at the end of the PUBLISH_DONE settle: #6111 removed track.set_tail_pending(false) (the received end now waits out the grace in the model), while this PR wraps the finish in the failed-joining-FETCH branch. Kept this PR's unjoined abort and took #6111's plain track.finish() in the success branch. No behavior change beyond adapting to the removed call.

Checks on 6f81650: just fix clean; just check passed its Rust lint, clippy and drill stages, then stopped at nix flake check (the github-runner VM build needs user namespaces this host lacks); the Rust tests for moq-net and its dependents ran by hand (8903 passed); just test interop passed; quest check passed.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 22, 2026

@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: 6f81650730

ℹ️ 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 3775 to +3776
let _ = stream.writer.close().await;
}

/// `true` when the publisher answered FETCH_OK. A FETCH_ERROR / REQUEST_ERROR is a
/// refusal, not a session error: the live subscription continues.
async fn read_fetch_response(
&self,
stream: &mut Stream<S, Version>,
messages: &mut super::request_stream::Messages<S>,
) -> Result<bool, Error> {
let super::request_stream::Framed(type_id, body) = messages.message(&mut stream.reader).await?;
let mut data = body.decoder(self.version);

match type_id {
ietf::FetchOk::ID => {
let _msg = ietf::FetchOk::decode_msg(&mut data, self.version)?;
Ok(true)
}
ietf::FetchError::ID if self.version == Version::Draft14 => {
let _msg = ietf::FetchError::decode_msg(&mut data, self.version)?;
Ok(false)
}
ietf::RequestError::ID => {
let _msg = ietf::RequestError::decode_msg(&mut data, self.version)?;
Ok(false)
}
_ => Err(Error::UnexpectedMessage),
}
res

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 Propagate the refusal before awaiting FIN acknowledgement

When the publisher returns FETCH_ERROR or REQUEST_ERROR but does not acknowledge our FIN, writer.close().await remains pending, so the newly captured res is never returned to JoiningTask and the subscription continues hanging instead of failing with the publisher's reason. Report the error before waiting for request-stream cleanup, or drive that cleanup independently; the repository's with_unacked_fin transport explicitly models this peer behavior.

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

Useful? React with 👍 / 👎.

Merged via the queue into main with commit b605299 Oct 22, 2026
9 checks passed
@kixelated
kixelated deleted the fix/ietf-resume-joining-fetch branch October 22, 2026 20:29
kixelated added a commit that referenced this pull request Oct 22, 2026
Keeps main's ietf subscriber (#6348 joins pre-draft-20 subscriptions at the
live group and refuses a multi-group joining fill) and serves track fills on
top of it; signed priorities (#6142) reach Fill; the ledger moved to the crate
root (#5878, #6111). The miss judge reads the live arrival grace rather than
the ledger's stored one, which stays at its 1s default until a gap folds.

Also answers review: a fetch of u64::MAX fails at once rather than parking on
a handler no fill can ask, Subscription::start builds without `async`, and the
MIGRATE guides no longer call GroupDemand or group::Request unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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