Skip to content
Merged
4 changes: 4 additions & 0 deletions quest/m0/broadcast-epoch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ epoch while the old publisher's session stays open. A lite-07 viewer and a lite-
that follow the announce `Restart` (or END then START) both reach the new
epoch within one RTT-scale bound rather than the idle timeout, and killing the
newest epoch falls back to a still-live older one.
The same test drives the real players (decided 2026-10-09, from #5154):
`moq play` and a browser `@moq/watch`, the latter through the `just test
media` lane, both show the new run within that bound, without a manual
republish against a live relay.

When the release cut carries stats epochs, drop
[#4810](https://github.com/moq-dev/moq/pull/4810)'s wall-clock group seed and
Expand Down
3 changes: 3 additions & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ ladders.
- [END_OF_TRACK placement](/quest/m1/ietf-end-of-track-placement.md) - END_OF_TRACK rides the upstream's Location, and Rust's header stops claiming END_OF_GROUP
- [moq-uring tests under load](/quest/m1/uring-tests-under-load.md) - uring tests pass while parallel checks share locked memory
- [FFI runtime](/quest/m1/ffi-runtime.md) - moq-ffi drives moq on a multi-thread runtime instead of one thread
- [Cancelled accept](/quest/m1/ffi-accept-cancel.md) - a cancelled moq-ffi `MoqServer::accept` never takes a session, so the server path drops its spawned task
- [Audio group duration](/quest/m1/audio-group-duration.md) - audio groups span at least 20 ms by default, so small frames don't mint a group each
- [Pipelined requests](/quest/m1/pipeline-requests/README.md) - SUBSCRIBE and the first FETCH go out with the track-info request at every hop, so first data arrives a round trip sooner per hop
- [A spinning loop fails a sim test](/quest/m1/spinning-loops.md) - a sim test names any loop that holds a task poll too long, and each one yields through a budget
Expand Down Expand Up @@ -198,3 +199,5 @@ ladders.
- [Catalog colour](/quest/m1/color-catalog.md) - the catalog describes a rendition's colour and HDR properties, which the WebGPU HDR renderer reads
- [WebGPU HDR](/quest/m1/webgpu-hdr.md) - HDR renditions play as HDR where the browser and display can show it, and tone-map to SDR elsewhere
- [Request ID order](/quest/m1/request-id-order.md) - drafts 14 to 16 refuse a reused or lower Request ID in both languages
- [Watch follows a covering prefix](/quest/m1/watch-follow-prefix.md) - `@moq/watch` moves to a covering prefix when the exact route ends, as `moq play` does
- [Import catalog drop](/quest/m1/import-catalog-drop.md) - moq-cli import never drops a catalog producer without finishing it
30 changes: 30 additions & 0 deletions quest/m1/ffi-accept-cancel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# [S] A cancelled moq-ffi accept never takes a session

## Goal

Cancelling a pending `MoqServer::accept` in any binding never takes an
incoming session: the next `accept` gets it. `MoqServer::cancel` still
releases the bound port, and every server call then resolves `Cancelled`.

## Plan

Found in #5140 (ffi-cancel-read), decided 2026-10-09. Start after #5140
lands: on `main` today `Task::run` still spawns every call and there is no
`Task::spawn`. That PR makes `Task::run` await in place, so a cancelled
future that is never polled again makes no progress. `MoqServer::listen` and `accept` keep the spawned path
(`Task::spawn`) because `MoqServer::cancel` blocks its thread until the
in-flight call finishes, and an `accept` parked on that same thread would
never finish (`server_cancel_releases_the_bound_port` hangs). So an `accept`
that is cancelled but not yet freed (uniffi frees it later, at
`rust_future_free`) can still take a session that then goes nowhere.

- Decided: close the listener through a handle kept outside the `Task` lock,
so `cancel` stops it without waiting on the in-flight call. Then `listen`
and `accept` use `run` like every other call, and `Task::spawn` goes away.
Rejected: accepting the race and documenting it.
- Regression in moq-ffi: poll an `accept` once and stop without freeing it,
connect a client, free the cancelled accept, and require the next `accept`
to return that session. Keep `server_cancel_releases_the_bound_port`
passing.

Public API: none. Wire: none.
1 change: 1 addition & 0 deletions quest/m1/ffi-shape/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,5 @@ runs `just test interop --all`.
- [Named error fields](/quest/m1/ffi-shape/error-fields.md) - `MoqError` variants name their fields, so no binding exposes a positional `v1`
- [Bindings](/quest/m0/broadcast-epoch/bindings.md) - the wrappers expose epochs and rename `session.epoch()`, on the reshaped wrappers
- [Group request demand](/quest/m1/ffi-shape/group-request-demand.md) - `MoqGroupRequest::demand()` in moq-ffi and every wrapper
- [Track request demand](/quest/m1/ffi-shape/track-request-demand.md) - `MoqTrackRequest::demand()` in moq-ffi and every wrapper, after Bindings
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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 Break the circular dependency between demand quests

Adding this child without updating the adjacent Group request demand plan leaves the two quests circular: group-request-demand.md says to mirror the “existing” TrackRequest method, while track-request-demand.md says that method is absent and to follow GroupRequest's shape. If either quest is dispatched now, there is no reference API, and the group quest may expose MoqTrackDemand even though Rust returns a distinct group::Demand; define the group-specific binding shape and make the dependency order explicit. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L28-L28

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.

The cycle is only on paper: #5139 implements the group quest with its own MoqGroupDemand (mirroring Rust's group::Demand, not MoqTrackDemand) and deletes group-request-demand.md, so editing that file here would just add a modify/delete conflict with #5139. 63e3ac4 makes the order explicit instead: the track quest starts after #5139 lands. If #5139 is abandoned, its stale "existing demand()" line needs fixing then.

(Written by Claude Opus 5.5)

- [Layers guide](/quest/m1/ffi-shape/layers-guide.md) - a `doc/lib` page maps each Rust layer to every binding's module, once Codecs adds `audio` and `video`
33 changes: 33 additions & 0 deletions quest/m1/ffi-shape/track-request-demand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# [S] A track request reports its demand

## Goal

`MoqTrackRequest` gains `demand()` in moq-ffi and every wrapper (Python, Go,
Swift, Kotlin, Dart, C++), returning a `MoqTrackDemand` like
Comment on lines +5 to +6

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 Include the required C and documentation surfaces

Expand the quest beyond the listed wrappers: adding MoqTrackRequest.demand() to rs/moq-ffi also requires synchronizing rs/moq-c and the applicable doc/lib/* surfaces. Following the current “every wrapper” scope would otherwise leave the C API and language documentation inconsistent with the new public binding method.

AGENTS.md reference: AGENTS.md:L100-L102

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.

Agreed: rs/moq-c already has moq_track_request_*, and the cross-package table pairs moq-ffi with moq-c and doc/lib. 65bc9ef adds both to the quest, with the C side following moq_publish_media_demand.

(Written by Claude Opus 5.5)

`MoqTrackProducer.demand()`, so a dynamic track server can see when nobody
still wants the track and stop producing it. Mirrors Rust's
`track::Request::demand`.

## Plan

Found in #5139 (group request demand), decided 2026-10-09: that quest
assumed `MoqTrackRequest.demand()` already existed, but moq-ffi only has
`demand()` on `MoqTrackProducer` and the media and JSON producers. Start
after #5139 lands, and follow its `MoqGroupRequest.demand()` binding shape,
but keep Rust's lifetime: `accept` hands the same track state to the
producer, so the handle keeps watching it and returns `Closed` only once the
request is rejected or the track closes. A dynamic server can then stop
producing when the last subscriber leaves.

Per the cross-package table, `rs/moq-c` gains the same on its
`moq_track_request_*` functions (following `moq_publish_media_demand`), and
the `doc/lib` pages list it.

Additive in every binding. It edits the same wrappers as
[Bindings](/quest/m0/broadcast-epoch/bindings.md) (#5146), so land it after
that PR to avoid conflicts, without blocking on it. Run
`just test interop --all`. Wire: none.

## Related

- [Bindings](/quest/m0/broadcast-epoch/bindings.md) - edits the same wrappers; land after it
18 changes: 18 additions & 0 deletions quest/m1/import-catalog-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# [XS] moq-cli import finishes its catalog producer

## Goal

No moq-cli import path drops a catalog `track::Producer` without `finish()`
or `abort()`. The warning seen in about 1 of 7 loaded runs of
`import_delivers_the_catalog_finish_at_eof` is traced to its producer and
fixed, or shown harmless and the warning's cause removed.

## Plan

Found in #5155, decided 2026-10-09: under load the test sometimes logs
`track::Producer dropped without finish() or abort() track=catalog.json`,
while the subscriber still sees a clean catalog finish. So some second
catalog producer, or a clone, is dropped on a path that skips `finish()`.
Find which one and finish or abort it at its source.

Public API: none. Wire: none.
1 change: 1 addition & 0 deletions quest/m1/test-flakes-2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ Public API: none. Wire: none.
- [Relay restart rebind](/quest/m1/test-flakes-2/relay-restart-rebind.md) - the crash drill restarts on its original UDP address under concurrent load
- [Impaired handshake](/quest/m1/test-flakes-2/impaired-handshake.md) - the impaired cluster drills' clients never time out while connecting
- [Cluster burst overrun](/quest/m1/test-flakes-2/cluster-burst-overrun.md) - the impaired cluster burst drill always overruns its bottleneck, so it always exercises it
- [TS passthrough jump](/quest/m1/test-flakes-2/ts-passthrough-jump.md) - moq-cli's flagged-jump TS passthrough test passes under load without wall-clock pacing
- [Media audio tone](/quest/m1/media-audio-tone.md) - the `just test media` audio-tone check passes under load, fixed at its cause
21 changes: 21 additions & 0 deletions quest/m1/test-flakes-2/ts-passthrough-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# [S] TS passthrough jump test holds under load

## Goal

`publish::tests::ts_passthrough_crosses_a_relay_through_a_flagged_jump`
(`rs/moq-cli`) passes under a loaded `just check`, fixed at its cause.

## Plan

Seen 2026-10-09 by three PRs on unmodified `main` (#5146, #5155, #5153): the
assertion "moq-transport-14: both copies crossed" fails, so the recording
held at most one copy of the fixture. It failed 2 of 3 loaded runs and
passes alone. The test came in with #5003. It paces its input with a 15 ms
wall-clock sleep and waits up to 10 s.

Decided 2026-10-09: find why the second copy goes missing under load before
changing anything, then replace the wall-clock pacing with mocked time or
observable events where the fixture allows. Never widen the wait or add a
retry, per this questline's rules.

Public API: none. Wire: none.
29 changes: 29 additions & 0 deletions quest/m1/watch-follow-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# [S] @moq/watch follows the route that serves its path

## Goal

When the exact route for a path ends while a covering prefix (such as
`pool`) still serves it, `@moq/watch` moves to the prefix instead of going
offline, as Rust's `moq play` does through the shared follow helper.

## Plan

Found in #5154 (broadcast-epoch apps), decided 2026-10-09:
`Broadcast.#runBroadcast` in `js/watch` ignores `End`, so the player goes
offline even though a covering prefix still announces the path. Start after
#5154 lands, since it adds the helper this quest uses.

- Decided: use the shared follow helper. #5154 moves it from
`moq_mux::Source::follow` into moq-net (`origin::Consumer::follow`) and
mirrors it in `@moq/net`, per the cross-package table; this quest wires
`@moq/watch`'s `Broadcast` onto the JS one. The serving route is the most
specific one covering the path; another route taking over is a `Restart`,
or an `Update` when both carry the same epoch; routes beneath the path are
ignored. Rejected: a private copy in `@moq/watch`, and keeping the Rust
helper in moq-mux (maintainer, 2026-10-09: a net-layer concern that should
mirror across languages).
- Test: a path served both exactly and by a `pool` prefix; ending the exact
route moves playback to the prefix as a restart, and ending both goes
offline.

Public API: none. Wire: none.
Loading