Repository navigation
fix(net): JS lite close drains served subscriptions - #4813
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connection.close() now waits, within its one-second deadline, for every served SUBSCRIBE, FETCH, and TRACK request to end, mirroring Rust's owed count. Group, FETCH, TRACK, and subscribe streams wait for their FIN to be acknowledged, and on lite-07 a served SUBSCRIBE also waits for the subscriber's FIN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Quest outcome: js-close-drain implemented in full (lite-01 through lite-07 drain, lite-07 subscriber FIN wait, mocked-time regressions, docs). Open decision: a live subscription now holds Suggested follow-up: give the IETF (Written by Claude Opus 5.5) |
|
Decision (maintainer): keep Rust parity. A JS publisher with live subscribers waits the full one-second deadline and (Written by Claude Opus 5.5) |
|
Warning Review limit reachedYou'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 1 minute. View limit detailsLimit details: You’ve used all 4 included reviews currently available. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (8)
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. Comment |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 120ae7e
[P2] Recheck owed requests after withdrawals settle — js/net/src/lite/publisher.ts:1298–1303. settle() can finish while withdrawal delivery is still pending (including immediately when #owed is empty). A SUBSCRIBE/FETCH/TRACK dispatched during that window is added to #owed, but nothing restarts settle(). Once the withdrawal finishes, drain() resolves and Connection.close() aborts the transport with that request still outstanding, potentially truncating its tail. Ensure withdrawal completion is followed by a fresh drain of #owed, preserving withdrawal-error reporting. Add a regression holding a withdrawal ACK, dispatching a request after the first owed drain empties, and verifying close remains pending until that request completes.
Direction: dispatch-time accounting and the version-gated subscriber FIN wait are sound improvements toward Rust parity; the withdrawal/request race above leaves the new close guarantee incomplete.
Verification: inspected the full diff and relevant publisher, connection, stream, withdrawal, and Rust accounting code. A minimal execution of the same Promise/Set scheduling reproduced drain resolving with one owed request pending. Repository tests, browser/WebTransport behavior, and cross-language interop were not run.
The owed drain could finish before the withdrawals did, so a request served in between was cut off when close aborted the transport. Settle owed requests after the withdrawals, as Rust's drained() checks both together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed the [P2] withdrawal/request race in 0899fb2: (Written by Claude Opus 5.5) |
Automated review: head
|
Also say when close() actually rejects: owed requests swallow their own failures, as in Rust, so only a failed withdrawal or the deadline rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re the automated review of 0899fb2, in 04efbcd:
(Written by Claude Opus 5.5) |
Automated re-review: head
|
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 04efbcd (two commits beyond the prior review; unchanged base).
The P2 drain race from review 5409945015 is addressed in js/net/src/lite/publisher.ts:1298–1303: withdrawals now settle before the owed-request loop begins, while preserving withdrawal-error reporting. The regression at js/net/src/lite/connection.test.ts:412–464 exercises a subscription arriving during a held withdrawal ACK; the added FETCH test at lines 361–400 covers waiting for its response FIN acknowledgment.
No new actionable findings in this delta. Direction remains sound: the fix closes the previously identified gap in graceful-close accounting, and the documentation now accurately scopes withdrawal failures.
Verification limits: inspected the changes and relevant prior implementation; a minimal Promise/Set execution confirmed the corrected drain ordering. Repository tests, real WebTransport, and cross-language interop were not run.
|
Merge summary:
(Written by Claude Opus 5.5) |
Problem
Connection.close()in@moq/net(moq-lite) waited only for announce withdrawals, then closed the transport. A JS publisher that finished a track and closed could cut the final group short, and on lite-07 never waited for the subscriber's FIN, unlike Rust'sSession::close().Approach
Mirrors Rust's
owedcount inrs/moq-net/src/lite/publisher.rs:Publishertracks served SUBSCRIBE, FETCH, and TRACK requests from dispatch (before the message decodes) until they end.close()now runsdrain(): withdraw announcements, then wait for every owed request, including ones that arrived while the withdrawals were in flight, under the same one-second deadline.abort()stays immediate.writer.closed) before the request counts as done, matching Rust'spoll_close. Without this, the transport close discards the final group's bytes still in flight.waitsForSubscriberFin(version)that mirrors Rust'sVersion::waits_for_subscriber_fin(). The JS subscriber's existing lite-07 FIN now uses the same gate.Regression tests (mocked deadline, no sleeps) in
js/net/src/lite/connection.test.ts: a finished track's final group arrives before close on lite-05/06/07 (with its FIN ack held), a lite-07 subscriber withholding FIN holds close until the deadline, its FIN releases close, abort during the drain ends at once, a request served while a withdrawal is in flight still holds close, and a served FETCH holds close until its FIN is acknowledged. Each fails with the drain, the group ack wait, or the lite-07 FIN wait reverted.Impact
close()behavior changes: it now waits for served requests. A live subscription never ends on its own, so a publisher with active subscribers now waits the full second andclose()rejects with "session close timed out", as Rust'sclose()returnsErr(Timeout).Publisher.withdraw()becamedrain(), plusowe(); both@internal.Alternatives
close()skips served subscriptions.runSubscribeinstead of dispatch: simpler, but a close landing mid-decode would skip the request.Follow-ups
Connection.close()still waits only for withdrawals, while Rust's IETF session also counts owed requests.close()to deliver the tail.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)