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
1 change: 1 addition & 0 deletions quest/m0/broadcast-epoch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +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
- [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)
48 changes: 48 additions & 0 deletions quest/m0/broadcast-epoch/source-pin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# [S] An export's later requests stay on the broadcast it 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.

## Plan

Found by Codex on #5147 (2026-10-10), planned the same day. `Source` pins
its own path by epoch (`epoch` in `rs/moq-mux/src/source.rs`, set through
`pinned` from `ts::Export`). An epochless route (every default lite-06
session) has `None`, so each later request re-resolves the path, and if a
replacement won in the meantime, it serves that request even without
`--stitch`.

- Decided: pin by handle, replacing the epoch field. One mechanism covers
epoch and epochless routes. A replaced instance's sticky front refuses new
tracks, so a late request on it errors, and the Follower decides what
happens next (`Replaced`, or a stitch).
- Decided: pin every resolved path, not only the source's own, so a replaced

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the required two-axis pinning benchmark

Pinning every resolved path introduces per-export state that fans out across N sibling routes and is consulted by M later requests, but the plan only requires correctness tests. Add a benchmark sweeping both path count and request count so an implementation with table-wide lookup or cleanup costs cannot land without exposing the slope.

AGENTS.md reference: AGENTS.md:L36-L37

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree. The fan-out rule targets axes that grow with the table: publishers, subscribers, routes, sessions. This map is per export and keyed by path. Its N is the number of distinct broadcasts one catalog references (the own path plus, typically, zero to two siblings), and each request is one hash lookup. No request touches the whole map, and nothing is cleaned up across exports, so there is no slope to expose.

(Written by Claude Opus 5.5)

sibling (`broadcast: ./source`) cannot splice in either.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- 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
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
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.
1 change: 1 addition & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ bench all require its crate.
- [Catalog warmup](/quest/m1/catalog-warmup.md) - `warmup` on video and audio renditions, in the catalog and the draft
- [Audio warmup](/quest/m1/audio-warmup.md) - a viewer joining an Opus rendition mid-stream never hears the unconverged first 80 ms
- [#3021](/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md) - moq-gst picks the broadcast wall epoch; a restarted source is a new epoch, not a forward re-anchor
- [TS stitch catalog bound](/quest/m1/ts-follow-catalog-bound.md) - `--linger` bounds a mid-stream `--stitch` until the replacement's catalog arrives, so it ends loudly instead of stalling
- [TS passthrough export](/quest/m1/ts-passthrough-export.md) - `export ts --passthrough` writes the `m2ts` track back byte-identical (less late drops) on a fixed delay
- [FLV and MKV export delay](/quest/m1/export-delay.md) - FLV and MKV interleave through the shared jitter buffer on a fixed delay, breaking the CLI once
- [MKV lacing](/quest/m1/mkv-lacing.md) - laced MKV blocks import as one timed frame each, refused without DefaultDuration
Expand Down
34 changes: 34 additions & 0 deletions quest/m1/ts-follow-catalog-bound.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# [S] A TS export stitch is bounded by the linger until its catalog arrives

## Goal

When `moq export ts --stitch` (or moq-srt egress with `stitch`) switches to
a replacement mid-stream, `--linger` bounds the wait for the replacement's
catalog, as it bounds a same-epoch return. A replacement that never delivers
its first catalog snapshot ends the export with an error instead of stalling
it with nothing going out.

## Plan

Found while iterating #5147 (2026-10-10), planned the same day. `ts::Follower`
(`rs/moq-mux/src/container/ts/follower.rs`) carries a linger deadline for a
return, which #5147 extended to the returned catalog's first snapshot. A
stitch that starts after the old broadcast ended inherits that deadline. A
mid-stream stitch, taken from `Running` while the old instance still serves,
carries none (`settling: None` in `State::Following`), so its catalog wait is
unbounded.

- Decided: the linger is the one bound, from the `Restart` to the
replacement's first catalog snapshot. With `--linger 0` a stitch only
succeeds when the catalog is already there. Rejected: bounding by
`--delay`, and a fixed constant.
- On expiry the export ends with an error naming the replacement that never
served its catalog.
- Tests, with mocked time: a stitch onto a replacement whose catalog never
answers ends at the linger. One whose catalog answers within it switches
as today.
- Docs: the `--stitch` and `--linger` text in `doc/bin/cli.md` and
`doc/bin/srt.md`, including that the default `--linger 0s` lets a stitch
follow only a replacement whose catalog is already there.

Public API: none. Behavior: a stitch can now end the export. Wire: none.
Loading