Repository navigation
fix(net): accept a clear FIRST_OBJECT that starts at object 0 (backport #5027) - #5069
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Backport of #5027 to release. A draft-18+ subgroup stream with FIRST_OBJECT clear whose first Object ID is 0 is the whole group, so it is delivered instead of dropped; any other first ID is still dropped. Adapted to release: there is no resumed-subscription path, and the draft-18 test helper uses release's encoding API. (cherry picked from commit fdfdf9a) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Quest outcome: (Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit 23d91184d86a04c94460bf30cb3a6ecccd4da28d against release.
One P2 finding below: the new JS first-object peek outlives local unsubscribe when the publisher stalls. The object-zero compatibility fix and release-specific adaptations otherwise fit the intended scope; retain the narrow approach and add cancellation around the peek, as Rust already does.
Verification: GitHub-only static diff and surrounding-code review, including the upstream change. I did not run tests. GitHub's Check and Test jobs were still running when inspected.
| return; | ||
| let id: bigint | undefined; | ||
| try { | ||
| id = await stream.peekU62(); |
There was a problem hiding this comment.
[P2] Cancel the first-object peek when the subscription closes
If a publisher sends a FIRST_OBJECT-clear subgroup header and stalls before completing its first Object ID, this new await stays pending after the last local subscriber closes. Subscription teardown closes the track/request stream and retires the alias, but does not stop this already-dispatched uni Reader; connection.ts only stops it after handleGroup settles. No group producer exists yet, so the later producer.closed race cannot help. The Reader, handler and active Tail entry therefore survive until the peer advances/ends the stream or the session closes, whereas this branch previously stopped immediately. Race the peek against track.closed and stop/return when cancellation wins. Add a test with a stalled first-ID read, local unsubscribe, and the session kept open.
There was a problem hiding this comment.
Not fixing this in the backport. The gap is real but it is not new to this change, and it is not specific to release:
main(fix(net): accept a clear FIRST_OBJECT that starts at object 0 #5027) has the same unracedpeekU62().- With FIRST_OBJECT set, the first
stream.decodeMaybe(decode)also runs unraced, becauseproduceris still undefined until the first object arrives. So a publisher that stalls before its first object already holds the Reader, the handler, and the Tail entry after a local unsubscribe, until the peer moves or the session closes. The peek has the same lifetime as that existing path.
The fix (race both pre-producer awaits against the subscription going away, as the Rust recv_group already does) belongs on main first and then gets backported, so release does not diverge. I'll record it as a follow-up.
(Written by Claude Opus 5.5)
|
Merge summary for
(Written by Claude Opus 5.5) |
Conflicts in js/net/src/ietf/subscriber.ts, js/net/src/ietf/subscriber.test.ts, and rs/moq-net/src/ietf/subscriber.rs resolve to main: release's side is the #5069 backport of #5027, which main already carries in full, including the resume.is_none() guard the backport dropped and the newer Encoder and moq_net_sim test harness. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
On
release,moq-netand@moq/netdrop every draft-18+ subgroup stream whose FIRST_OBJECT bit is clear, even when its first Object ID is 0. A publisher that leaves the bit clear on a whole group (out of spec on the bit, not missing a head) loses every group. #5027 fixed this onmain.Approach
Cherry-pick of fdfdf9a (#5027). A clear bit now peeks the first Object ID: 0 is delivered as the whole group; any other ID, or a stream with no object, is still dropped and the track resumes at the next group.
Adapted where
releasediverged:releasehas no resumed-subscription path, so theresume.is_none()clause and its comment are gone.release's encoding API (BytesMut+Encode), and the tests run under#[tokio::test](nomoq_net_simonrelease).mainthat is not part of fix(net): accept a clear FIRST_OBJECT that starts at object 0 #5027 was left out.Kept #5027's tests: clear bit at object 0 is delivered, clear bit at object 3 is dropped and the next group arrives. Both fail without the fix (checked in Rust and JS).
Impact
Alternatives
None; this is a straight backport.
Follow-ups
quest/m0/release-22/first-object.mdlives onmainand must be deleted there once this lands (it is not onrelease, so this PR cannot remove it).just check origin/releasepassed apart from fourmoq-uringworker tests that fail locally on a shared RLIMIT_MEMLOCK limit, unrelated to this change.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)