Repository navigation
quest: plan the broadcast lifecycle epic from the linger audit #6570
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
404a0c0
quest: plan the broadcast lifecycle epic from the linger audit
kixelated 7f913e5
Merge remote-tracking branch 'origin/main' into claude/pensive-lumier…
kixelated 4ad6c62
quest: migrate names the final finish API, not the linger handle
kixelated 4f0bb0a
Merge origin/main into claude/quest-broadcast-lifecycle
kixelated ea36608
Drop link to the deleted public-model quest
kixelated File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Broadcasts are online or offline, and retention is app policy | ||
|
|
||
| ## Goal | ||
|
|
||
| A moq-net broadcast has no lifecycle state of its own: it is online while | ||
| announced and offline once closed. The app marks the end of its content with | ||
| a track FIN (hang: finish the catalog). Keeping a finished broadcast up for | ||
| late readers is a fixed-duration app policy in moq-mux, not a deadline moq-net | ||
| derives from track max-age. Every other timer called "linger" has one meaning | ||
| per name. | ||
|
|
||
| ## Plan | ||
|
|
||
| Decided with the maintainer on 2026-10-23, from an audit of every linger | ||
| timer on main at 1582fdc07d: | ||
|
|
||
| - `broadcast::Producer::finish` exists only in moq-net. Nothing reaches the | ||
| wire: ANNOUNCE active/ended already says online/offline, and a track FIN | ||
| already says a track's content ended. A BROADCAST wire message was | ||
| considered and rejected; it adds no behavior over those two and would need a | ||
| moq-transport mapping. | ||
| - The finished linger was inconsistent and partly accidental: | ||
| - Its duration is the max of track max-ages, which describe how long a | ||
| superseded group stays fresh and never the newest one. The JS publish | ||
| catalog declares `Number.MAX_SAFE_INTEGER` max-age, so every browser | ||
| publisher lingered the full 30s; Rust catalogs declare none. | ||
| - It counted from different events: Rust from the first poll of `wait`, | ||
| JS from the `wait` call, UniFFI from `finish`, and C from the caller's | ||
| own clock. | ||
| - Only Rust ended it when a newer epoch appeared at the path. | ||
| - Closing a broadcast already leaves tracks handed out playing to their end, | ||
| and session drain delivers their tail to attached subscribers. Retention | ||
| only serves readers that arrive after the end. | ||
| - A retaining publisher ignores a newer instance at its path: if the newer | ||
| one leaves, the old one is announced again, and readers see its finished | ||
| catalog and stop. Simpler than watching for supersession, and acceptable | ||
| under online/offline. | ||
| - The other timers stay. Each is a cache or debounce with its own name and | ||
| owner: the Rust idle track copy (30s), discovery (2s), the JS connection | ||
| pool (2s), and the relay's `[stats] linger` (5m). They are not cross-language | ||
| lifecycle guarantees. | ||
| - After this epic, user-facing "linger" means only publisher retention | ||
| (`moq` publishing subcommands, moqsink) and the relay's stats setting. The | ||
| export-side wait becomes `--follow`. | ||
| - Docs change inline in each quest; no separate lifecycle page. | ||
|
|
||
| ## Required | ||
|
|
||
| - [Publisher retention in moq-mux](/quest/m1/broadcast-lifecycle/retention.md) - moq-cli, the gateways, and moqsink keep a finished broadcast up for a fixed `--linger` (default 30s) through a moq-mux helper instead of moq-net's `Finish` | ||
| - [Stateless broadcasts](/quest/m1/broadcast-lifecycle/stateless.md) - `Finish`, its linger, and finished-only supersession are gone from moq-net, `@moq/net`, moq-ffi, moq-c, and every binding | ||
| - [TS export follows by cause](/quest/m1/broadcast-lifecycle/ts-follow.md) - `export ts|srt --linger` becomes `--follow <duration>`, a replacement's catalog has no separate deadline, and a track failure is classified by its cause instead of a 1s grace | ||
| - [SRT tells a clean end from a failure](/quest/m1/broadcast-lifecycle/srt-end.md) - an SRT ingest whose peer goes silent aborts instead of finishing as if it had shut down | ||
| - [Timer cleanups](/quest/m1/broadcast-lifecycle/timers.md) - `ConnectionProps.linger` is gone and stale linger references are fixed | ||
|
|
||
| ## Related | ||
|
|
||
| - [moq-transport finish interop](/quest/m2/ietf-finish.md) - the interop `finish` case, whose late readers rely on retention | ||
| - [CLI and gst flags](/quest/m1/api-audit/cli-flags.md) - removes the opt-in `linger` booleans, leaving completion to this epic | ||
| - [Publication owner](/quest/m1/api-audit/announce-owner.md) - adds `origin::Publication`; it must not forward a `finish` this epic deletes |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # [M] Publisher retention in moq-mux | ||
|
|
||
| ## Goal | ||
|
|
||
| moq-cli imports (stdin, HLS), the RTMP, SRT, RTC, and HLS gateways, and | ||
| moqsink keep a cleanly ended broadcast announced and serving its finished | ||
| tracks for a fixed duration counted from the end, then close it. They no | ||
| longer use moq-net's `broadcast::Finish`. The duration is `--linger | ||
| <duration>` on moq-cli's publishing subcommands and a `linger` duration | ||
| property on moqsink, defaulting to 30s; 0 closes at once. An interrupt | ||
| (Ctrl-C, moqsink NULL) ends it at once. | ||
|
|
||
| ## Plan | ||
|
|
||
| Decided with the maintainer on 2026-10-23: | ||
|
|
||
| - One small moq-mux helper owns it, shared by moq-cli, `moq-rtmp`, | ||
| `moq-srt`, `moq-rtc`, and moqsink. It finishes the catalog and media | ||
| tracks, holds the broadcast and track producers, and closes the broadcast | ||
| when the deadline passes. moq-net gains nothing. | ||
| - The deadline is fixed when the content ends and does not depend on track | ||
| max-age or on when anything is polled. 30s matches today's effective | ||
| behavior (media max-age) and the relay's idle-copy window. | ||
| - It ignores a newer instance announced at the path; see the epic. | ||
| - `<moq-publish>` and the FFI/C bindings do not use it: they finish their | ||
| catalog and close, and a binding app owns its own process lifetime. | ||
| - moq-cli RTSP closes at once today (`rs/moq-cli/src/rtsp.rs`); give it the | ||
| helper too, or say why not. | ||
| - Keep each gateway's existing split between a clean end and a failure | ||
| (RTMP unpublish vs disconnect, WHIP DELETE vs a vanished peer); only a | ||
| clean end retains. SRT's split is | ||
| [its own quest](/quest/m1/broadcast-lifecycle/srt-end.md). | ||
| - Test the helper with mocked time: a late subscriber gets the whole tail | ||
| before the deadline, and the path is unroutable after it. | ||
| - Docs: `doc/bin/cli.md`, `doc/bin/gstreamer.md`, the gateway pages under | ||
| `doc/bin/`. | ||
|
|
||
| Public API: moq-mux gains the helper. moq-cli gains `--linger` on publishing | ||
| subcommands; `moq export --linger` keeps its meaning until | ||
| [TS export follows by cause](/quest/m1/broadcast-lifecycle/ts-follow.md) | ||
| renames it. moqsink's `linger` returns as a duration (the boolean never | ||
| shipped in a release, so no MIGRATE entry). Wire: none. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # [S] SRT tells a clean end from a failure | ||
|
|
||
| ## Goal | ||
|
|
||
| An SRT ingest whose peer stops sending without a shutdown handshake ends its | ||
| broadcast with an error, while a peer that shuts down cleanly finishes it. | ||
| Today both finish (`rs/moq-srt/src/server.rs`, the `Ok(())` arm after the | ||
| socket stream ends), because srt-tokio ends the stream the same way for both. | ||
|
|
||
| ## Plan | ||
|
|
||
| Decided with the maintainer on 2026-10-23: fix it at the source. | ||
|
|
||
| - Find how srt-tokio reports a shutdown handshake versus a peer timeout. If | ||
| it exposes the difference, use it in both the server and the `Client` | ||
| pull (`serve_publish`). | ||
| - If it does not, an upstream srt-tokio change is needed: it is outside the | ||
| moq-dev and kixelated orgs, so ask before posting there. | ||
| - Test both ends with a mocked clock: a silent peer aborts, a clean shutdown | ||
| finishes. | ||
|
|
||
| Public API: none. Wire: none. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # [L] Stateless broadcasts | ||
|
|
||
| ## Goal | ||
|
|
||
| moq-net and `@moq/net` broadcasts have no finished state. `Producer::finish`, | ||
| `Finish`, `Finish::linger`, `Finish::wait`, and the origin's finished-only | ||
| supersession (`Consumer::supersede`, `routes.superseded`, `Announcer::superseded`, | ||
| `BroadcastState.finished`/`superseded`) are deleted, along with moq-ffi's | ||
| `MoqBroadcastFinish` and moq-c's `moq_publish_finish` and | ||
| `moq_publish_finish_close`, and their wrappers in every binding. A publisher | ||
| ends its content by finishing its tracks (hang: the catalog) and goes offline | ||
| by closing the broadcast. | ||
|
|
||
| ## Plan | ||
|
|
||
| Decided with the maintainer on 2026-10-23; see the epic for why. | ||
|
|
||
| - Keep the epoch routing that lets a restarted publisher win | ||
| (#6452); only supersession of a finished broadcast goes. | ||
| - Prove the property this relies on before deleting: a relay keeps | ||
| delivering a finished track to its existing downstream subscribers after | ||
| the upstream publisher's session closes, on lite and moq-transport. Fix | ||
| it at the source if it does not. | ||
| - The interop `finish` case must keep passing on lite. Each interop | ||
| publisher (Rust through the moq-mux helper; JS, C, C++ and the bindings by | ||
| holding their finished broadcast open) keeps its broadcast up long enough | ||
| for the late readers. | ||
| - `js/publish` drops its `Finish` bookkeeping | ||
| (`js/publish/src/broadcast.ts`, the catalog's `Number.MAX_SAFE_INTEGER` | ||
| max-age only if nothing else needs it). `js/net` drops `hooks.linger` and | ||
| its `CACHE_WINDOW` use there. | ||
| - Update `doc/concept` with one paragraph on the model (online = announced, | ||
| end of content = track FIN, retention = app policy), plus `doc/lib/*` and | ||
| every affected package's `MIGRATE.md`. | ||
| - Coordinate with [publication owner](/quest/m1/api-audit/announce-owner.md), | ||
| which touches the same handles. | ||
|
|
||
| Public API: breaking in moq-net, `@moq/net`, moq-ffi, moq-c, and every | ||
| binding. Wire: none. | ||
|
|
||
| ## Required | ||
|
|
||
| - [Publisher retention in moq-mux](/quest/m1/broadcast-lifecycle/retention.md) - Rust publishers stop using `Finish` first, so none loses its tail in between | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # [XS] Timer cleanups | ||
|
|
||
| ## Goal | ||
|
|
||
| `@moq/net`'s `ConnectionProps.linger` is deleted and the pool always uses its | ||
| fixed 2s idle timer. Comments that cite timers which no longer exist are | ||
| fixed. | ||
|
|
||
| ## Plan | ||
|
|
||
| Decided with the maintainer on 2026-10-23: | ||
|
|
||
| - `ConnectionProps.linger` (`js/net/src/connection/pool.ts`) has no caller | ||
| outside the pool, and the pool keeps the largest value any handle ever | ||
| asked for, even after that handle leaves. Delete the prop instead of | ||
| defining a merge rule. | ||
| - `rs/moq-video/src/decode/consumer.rs` cites `TRACK_IDLE_LINGER`, which is | ||
| now `moq_net::model::track::IDLE_LINGER` (crate-private). | ||
| - The OBS "Give up after" help text (`cpp/obs/src/moq-settings.cpp`) says the | ||
| broadcast "lingers for viewers across the gap", but the setting is a | ||
| reconnect backoff timeout. Say what it does. | ||
|
|
||
| Public API: `@moq/net` breaking (`ConnectionProps.linger` removed). Wire: | ||
| none. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # [M] TS export follows by cause | ||
|
|
||
| ## Goal | ||
|
|
||
| `moq export ts --linger` and `moq export srt --linger` become `--follow | ||
| <duration>`: how long to wait for the publisher, or with `--stitch` a | ||
| replacement, to come back. The default 0 means don't follow. The Rust | ||
| `container::ts::Follower::with_linger` and moq-srt's `Subscribe::with_linger` | ||
| and `Client::with_linger` become `with_follow`. A replacement's first catalog | ||
| is awaited until it arrives or the replacement withdraws, with no deadline of | ||
| its own. A track failure is classified by its cause, not by waiting | ||
| `CLOSE_GRACE` (1s) to see whether the announcement also goes. | ||
|
|
||
| ## Plan | ||
|
|
||
| Decided with the maintainer on 2026-10-23: | ||
|
|
||
| - One knob, one meaning. Today `--linger` also bounds the stitch catalog | ||
| fetch (`rs/moq-mux/src/container/ts/follower.rs`, `Awaiting { deadline }`), | ||
| so `--stitch --linger 0` fails on a cold remote replacement. The | ||
| replacement's own lifetime is the bound instead. | ||
| - `CLOSE_GRACE` exists because the track error and the announcement removal | ||
| arrive on different streams. Find what tells "the publisher left" from "this | ||
| export failed" in the track's own end: the abort cause a relay gives a copy | ||
| whose upstream went away. If no cause distinguishes them today, add one at | ||
| the source. A wire error code change updates the drafts. | ||
| - The hidden `--linger` on the other container exports | ||
| (`rs/moq-cli/src/args.rs`) keeps refusing; point its message at `--follow` | ||
| only where following applies. | ||
| - Coordinate the name with [readers follow restarts](/quest/m1/sink-restart-follow.md), | ||
| which owns `moq_mux::follow::Instances`. | ||
| - Docs: `doc/bin/cli.md`, `rs/moq-cli/MIGRATE.md`, `rs/moq-mux` and | ||
| `rs/moq-srt` MIGRATE entries. | ||
|
|
||
| Public API: moq-mux and moq-srt renames (breaking); moq-cli flag rename. | ||
| Wire: none unless the failure cause needs a new code. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.