Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion quest/m0/broadcast-epoch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ maintainer's pre-release merge of `release` into `main` keeps `main`'s

- [JS consume identity](/quest/m0/broadcast-epoch/js-consume-identity.md) - requests carry the serving prefix's epoch and new winners cannot reuse an old cached broadcast
- [JS restart keeps the request](/quest/m0/broadcast-epoch/js-restart-keeps-request.md) - a resolved request remains on its old instance, matching Rust's sticky subscriptions
- [Source pin](/quest/m0/broadcast-epoch/source-pin.md) - an export's later requests stay on the broadcast it resolved, so an epochless replacement never splices into the old program
- [Source pin](/quest/m0/broadcast-epoch/source-pin.md) - `Source` is built from the resolved catalog broadcast and every later request stays on it, so an epochless replacement never splices into the old program
- [Publish catalog restart](/quest/m0/broadcast-epoch/publish-catalog-restart.md) - `@moq/publish` never reuses catalog group numbers under one name and epoch after a re-announce
- [Bindings](/quest/m0/broadcast-epoch/bindings.md) - moq-ffi and every wrapper expose the epoch and let a publisher announce one
- [Stats totals and prefix tracks](/quest/m0/broadcast-epoch/stats-split.md) - the same release retires the per-path stats maps for totals and on-demand prefix tracks (decided 2026-10-05)
45 changes: 31 additions & 14 deletions quest/m0/broadcast-epoch/source-pin.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# [S] An export's later requests stay on the broadcast it resolved
# [M] An export's later requests stay on the broadcast it resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Goal

`moq_mux::Source` keeps the first `broadcast::Consumer` it resolves for each
path, its own and every cross-broadcast reference, and serves every later
request for that path from that handle. A late rendition subscribe or SI
repoint never lands on a replacement, epoch or not. On a replaced instance
it fails loudly instead of splicing the new publisher's data into the old
program.
`moq_mux::Source` is built from the catalog broadcast its caller resolved,
keeps the first `broadcast::Consumer` it resolves for each cross-broadcast
reference, and serves every later request for a path from that handle. A
late rendition subscribe or SI repoint never lands on a replacement, epoch
or not. On a replaced instance it fails loudly instead of splicing the new
publisher's data into the old program.

## Plan

Expand All @@ -24,25 +24,42 @@ replacement won in the meantime, it serves that request even without
happens next (`Replaced`, or a stitch).
- Decided: pin every resolved path, not only the source's own, so a replaced
sibling (`broadcast: ./source`) cannot splice in either.
- Decided (2026-10-10, amending the lazy own-path cache): `Source` is built
from the resolved catalog broadcast, which seeds its own-path handle. Most
callers (e.g. moq-cli, moq-ffi, moq-c, moq-hls, moq-rtc, moq-rtmp, and the
fmp4, flv, and codec exporters) subscribe the catalog outside `Source`, so
a lazy first resolve could still land on a replacement. Construction closes
that for every exporter. moq-hls's own catalog hold (`export/upstream.rs`)
should then be redundant, once `Source` returns the seeded handle for every
self-reference; delete it if so. Callers that let the export resolve today
(the TS export from `moq publish`) resolve first, or the export seeds the
handle from the exact broadcast it resolved, never a second resolve; pick
whichever reads simpler.
- The pin lifecycle follows the export's instance. A stitch builds a new
`Source` whose own-path entry is seeded with the broadcast `follow` was
given (sibling entries start empty), so its first track request can't
resolve a newer replacement. A same-epoch return replaces the own-path
handle with the returned broadcast, since the old one's routes are gone.
- The guarantee starts at a path's first resolution. A sibling the catalog
- A sibling's guarantee starts at its first resolution. A sibling the catalog
first references after a replacement resolves whatever serves it then.
The export never held another instance of that path, so that isn't a
splice.
- Tests, with mocked time, on an epochless route: an export resolves `live`,
a replacement wins `live`, and then a rendition added to the old catalog
(and an SI repoint) fails rather than reading the replacement. The same
for a sibling reference already resolved. A stitch whose replacement is
for a sibling reference already resolved. A `Source` built from `live`
after a replacement already won still reads the broadcast it was given on
its first track request. A stitch whose replacement is
itself replaced before the first track request still reads the broadcast
it followed. A same-epoch return reads the returned handle. Today's
epoch-pin test (`source.rs`) moves to the handle.

Public API: no signature change, but a behavior change. A `Source` reused
across a route replacement now returns its pinned handle for a path it
already resolved (`broadcast`, `catalog`, and track requests), or fails on
the replaced instance, instead of resolving the current route. Document it
on `Source`. Wire: none.
Public API: breaking. `Source::new` takes the resolved catalog broadcast,
and every caller in the repo moves with it (follow the Cross-Package Sync
table for moq-ffi and moq-c). A `Source` reused across a route replacement
Comment thread
coderabbitai[bot] marked this conversation as resolved.
returns its pinned handle for a path it already holds (`broadcast`,
`catalog`, and track requests), or fails on the replaced instance, instead
of resolving the current route. Document it on `Source`, fix stale
comments inline, and add a line for the `Source::new` break to the
Unreleased section of `doc/setup/upgrade.md`; no new doc page (decided
2026-10-10). Wire: none.
Loading