Repository navigation
quest: resumed groups and SUBSCRIBE_DROP (#4392, #4365) #4412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # [S] A resumed group ends once the new copy is past it | ||
|
|
||
| ## Goal | ||
|
|
||
| A reader that awaits only the in-flight group's `read_frame` never parks forever | ||
| after its source fails mid-group. Once the track fails over to a new copy, the | ||
| half-delivered group either completes from that copy or ends with an error, on | ||
| every lite version and for local and remote sources alike. The one exception | ||
| is a copy that later drops the group it is serving; that waits on | ||
| [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md). | ||
|
|
||
| Two reports, one mechanism: | ||
|
|
||
| - #4392: a publisher aborts a track mid-group over a session. On lite-03/04 the | ||
| far subscriber's in-flight `read_frame` stays pending forever; on lite-05/06 | ||
| it errors. | ||
| - #4365: a publisher aborts a track mid-group and re-creates it under the same | ||
| name in one origin. The origin reader parks in `read_frame` while a direct | ||
| reader gets the abort. | ||
|
|
||
| ## Plan | ||
|
|
||
| Why it hangs: `track_ended` in `rs/moq-net/src/model/front.rs` treats a copy | ||
| that dies after delivering as failover and splices a new copy. The resumed | ||
| `resume::Group` then waits in `track.poll_peek_group` | ||
| (`rs/moq-net/src/model/resume.rs`) on that copy, but never asks it for | ||
| anything. Only `resume::Subscriber::apply`, driven by `recv_group`, subscribes | ||
| to a new copy. On lite-03/04 the re-query accepts the track without asking the | ||
| peer (no TRACK stream), so no SUBSCRIBE goes out and the refusal never comes | ||
| back. An in-process copy declares no start, so its peek answers "not yet" | ||
| forever. The doc comment on `resume::Group` promises that a group reader alone | ||
| picks up the continuation; that promise is what breaks. | ||
|
|
||
| Decided: | ||
|
|
||
| - While a resumed group waits on a copy, it holds a subscription on that copy | ||
| with `start = latest`, the same shape as a fresh subscriber. A refusal ends | ||
| the group with that error. | ||
| - If the copy's latest group is past the resumed group, the group ends with an | ||
| error at once. A same-name re-create is a continuation, so group 2 of the old | ||
| track ends and group 3 of the new one arrives. No special case for local | ||
| broadcasts. | ||
| - If the resumed group is the copy's latest, it reads the rest from that | ||
| subscription. A copy that later drops it says so with SUBSCRIBE_DROP once | ||
| [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) lands; until then that case | ||
| waits as today. | ||
| - No FETCH, not even where the version supports it. Fetching older groups from | ||
| the new copy may come later as its own quest. | ||
| - Don't abort the old group eagerly on failover instead: long-lived groups | ||
| like a catalog must survive a failover. | ||
|
|
||
| `poll_finished` needs the same subscription as `poll_current`. Drop the | ||
| subscription once the wait resolves. | ||
|
|
||
| A prototype of the subscription half (about 20 lines in `resume.rs`) turned | ||
| all eight #4392 cases into errors with every moq-net test passing. | ||
|
|
||
| Regression tests, on a paused clock (`#[tokio::test(start_paused = true)]`): | ||
|
|
||
| - The #4392 repro as `rs/moq-net/tests/track_abort_over_session.rs`: abort | ||
| mid-group over one and two hops on lite-03 through lite-06; the in-flight | ||
| read errors. | ||
|
Comment on lines
+60
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The goal promises this behavior on every lite version, but the regression matrix covers only lite-03 through lite-06. The current version set also supports lite-01, lite-02, and lite-07; lite-01/02 use the same pre-TRACK path implicated in the root cause, while lite-07 is an explicitly supported wire variant affected by the companion quest. Add those versions to the matrix or narrow the stated guarantee so the quest cannot finish with supported variants unverified. (Written by GPT-5.6 Sol) Useful? React with 👍 / 👎. |
||
| - The #4365 repro: abort mid-group 2, re-create at group 3 through the origin; | ||
| group 2 ends with an error and group 3 arrives. | ||
|
|
||
| ## Closes | ||
|
|
||
| - [#4392](https://github.com/moq-dev/moq/issues/4392) - close this issue when the quest finishes | ||
| - [#4365](https://github.com/moq-dev/moq/issues/4365) - close this issue when the quest finishes | ||
|
|
||
| ## Related | ||
|
|
||
| - [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - tells a resumed latest group when the new copy dropped it | ||
| - [Splice edge cases](/quest/m1/splice-edges.md) - the same splice code and test harness | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # [L] SUBSCRIBE_DROP accounts for every group | ||
|
|
||
| ## Goal | ||
|
|
||
| A lite subscriber can tell "not yet" from "never" for every stream group in | ||
| its subscription. Each sequence in range either arrives on a Group Stream, is | ||
| sent as a datagram, or is named by a SUBSCRIBE_DROP, including sequences the | ||
| publisher skipped. Only a lost datagram stays unaccounted. Rust and | ||
| `@moq/net` publishers send it on every lite version that has it, and moq-lite-07 | ||
| brings it back in place of `Stream Count`. | ||
|
|
||
| ## Plan | ||
|
|
||
| Today SUBSCRIBE_DROP is on the wire for lite-03 through lite-06 and the Rust | ||
| subscriber accounts for it, but no publisher sends it. lite-07 (still | ||
| `moq-lite-07-wip`, unpublished) removed it for a `Stream Count` on | ||
| SUBSCRIBE_END (#4224). | ||
|
|
||
| Decided: | ||
|
|
||
| - lite-07 restores SUBSCRIBE_DROP and removes `Stream Count`. With every | ||
| sequence accounted for, the count is redundant; one mechanism instead of two. | ||
| A reset whose header may be lost is covered by a DROP. | ||
| - A reliable reset ([Reliable stream reset](/quest/m1/quic/reliable-reset.md)) | ||
| that keeps the stream header acts as a one-group drop, an optimization over | ||
| sending the DROP. | ||
| - Publishers send SUBSCRIBE_DROP on lite-03 through lite-06 too: for every group | ||
| in range they won't deliver (expired, deprioritized, or reset without its | ||
| header delivered) and for every explicit gap. Publishers that skip sequences | ||
| (`cut` and group discontinuities in the media layers) must mark the gap so | ||
| the net layer can drop it. | ||
| - Datagram groups stay best effort. A publisher counts a datagram as | ||
| delivered, so a lost one leaves an uncovered hole that waits out the tail | ||
| grace, as today. | ||
| - A resumed group ([Resumed groups](/quest/m1/resume-latest.md)) that is the | ||
| new copy's latest ends with the DROP's error when the copy drops it. | ||
|
|
||
| Update `drafts/draft-lcurley-moq-lite.md` (SUBSCRIBE_DROP, SUBSCRIBE_END, the | ||
| lite-07 changelog), `doc/concept/moq-lite.md`, and the Rust and JS lite | ||
| publishers, subscribers, and tail accounting. Run `just drafts check` and | ||
| `just test interop --all`. | ||
|
|
||
| Regression tests: a publisher that expires a group, skips a sequence, and | ||
| resets a stream before its header; on each version the subscriber settles | ||
| without waiting out the grace. | ||
|
|
||
| ## Related | ||
|
|
||
| - [Track tail hardening](/quest/m1/track-tail-hardening.md) - the same tail accounting, in both languages | ||
| - [Track tail interop](/quest/m1/track-tail-interop.md) - the Rust-JS proof of the lite-07 drop case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the handed-out group is the only reader, this temporary subscription is the only demand keeping the replacement's wire SUBSCRIBE alive. Dropping it as soon as
poll_currentorpoll_finishedresolves the lookup sends FIN upstream, andlite::publisher::SubscribeState::RunhandlesPeerFinby dropping its in-flight group tasks, so the continuation can be canceled before its remaining frames arrive. Keep the subscription until the logical group finishes or errors, otherwise the group-only reader can still lose a mid-group continuation. (Written by GPT-5.6 Sol)Useful? React with 👍 / 👎.