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
4 changes: 4 additions & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Missing fetch group](/quest/m1/fetch-missing-group.md) - HTTP /fetch answers 404 and `moq fetch` fails cleanly for a group the track lacks
- [libmoq hidden opt-in](/quest/m1/libmoq-hidden.md) - `moq_origin_announced` takes a `hidden` flag so C callers can list `.`-named broadcasts
- [JS track tail](/quest/m1/js-track-tail.md) - a `@moq/net` subscriber delivers every group up to the declared end over lite and IETF, and JS publishers drain their groups before ending a subscription
- [lite-07 stream count](/quest/m1/lite-stream-count.md) - moq-lite-07 replaces SUBSCRIBE_DROP with a group-stream count in SUBSCRIBE_END, like moq-transport
- [Rust track tail](/quest/m1/rust-track-tail.md) - a moq-net subscriber accepts groups that arrive after the subscription's end, and PublishDone carries the real stream count
- [Session death error](/quest/m1/session-death-error.md) - a dying session ends its tracks with its own error in Rust and JS, never a clean end, `Dropped`, or `Cancel`
- [Signal.race cleanup](/quest/m1/signal-race.md) - `Signal.race` releases its signal listeners when its result loses a race
- [Origin narrowing](/quest/m1/origin-narrowing.md) - a live origin grant narrows in place and ends the subscriptions it no longer covers, the deafen boundary #2714 asked for
- [Auth embedder](/quest/m1/auth-embedder.md) - the lease owns its re-check clock, a gateway session holds a lease, and `Cluster::admit` scopes and tags origins in one call
- [Auth expiry clock](/quest/m1/auth-expiry-clock.md) - moq-auth and the relay hold one fixed expiry deadline and honour the same skew allowance
Expand Down Expand Up @@ -51,12 +53,14 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [In-band auth](/quest/m1/auth/README.md) - a session tells its peer what it may publish and subscribe to, unions tokens presented in band, and fails loud on an out-of-scope publish
- [Listener close](/quest/m1/listener-close.md) - closing a listener releases its UDP socket before returning, so a restart can rebind the port
- [Test ports](/quest/m1/tokio-test-ports.md) - moq-tokio tests bind QUIC and WebSocket on independent ephemeral ports, so a parallel run cannot collide
- [Reconnect test ports](/quest/m1/tokio-reconnect-ports.md) - moq-tokio reconnect and worker tests bind their own ports, with a `tcp_local_addr()` accessor
- [Decoded frame ownership](/quest/m1/decoded-frames.md) - retain moq-video Frames across bindings, with native views or CPU conversion as needed
- [C++ through moq-ffi](/quest/m1/cpp/README.md) - generated C++ over moq-ffi with futures and expected-style errors, shipped as a tarball, vcpkg, and Conan, and adopted by the OBS plugin
- [OBS native codecs](/quest/m1/obs-moq-video/README.md) - remove FFmpeg decoding dependencies, deliver GPU frames, and use native audio/video encoders
- [Audio codecs](/quest/m1/audio-codecs/README.md) - platform audio codecs, explicit unsupported cases, and channel layouts up to 7.1
- [Opus descriptions](/quest/m1/audio-opus-input.md) - validate headers and honor codec clock, pre-skip, and gain
- [Capture formats](/quest/m1/audio-capture-format.md) - unsupported overrides refuse before device open and channel counts cannot wrap
- [NVENC teardown](/quest/m1/nvenc-teardown.md) - a rejected NVENC encode no longer hangs process shutdown
- [NVENC recovery](/quest/m1/nvenc-recovery.md) - partial initialization and rejected rate changes preserve valid state
- [GPU pool reservation](/quest/m1/gpu-pool-reservation.md) - a full GPU frame pool is a `None` reservation the caller drops on, not an error to match
- [Transcode source](/quest/m1/transcode-source.md) - select a rendition the chosen backend can actually decode
Expand Down
43 changes: 43 additions & 0 deletions quest/m1/lite-stream-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# [M] moq-lite-07 counts group streams instead of dropping groups

## Goal

On moq-lite-07, a subscriber knows a subscription has delivered everything
once it has seen as many group streams as the publisher opened, the way
moq-transport's PUBLISH_DONE Stream Count works. SUBSCRIBE_DROP is gone from
lite-07: a group the publisher skipped or never opened is simply not counted,
so nothing has to name it. Published versions (lite-01 to -06) keep decoding
SUBSCRIBE_DROP unchanged.

Where reliable reset is negotiated, the count is exact and the subscriber
waits for nothing else. Where it is not (browsers today), a stream reset
before its header arrived is still invisible, so the track-tail grace stays.

## Plan

- Wire: SUBSCRIBE_END gains `Stream Count`, the number of group streams the
publisher opened for this subscription. The publisher sends it once every
group stream below the end has been opened (not finished), like
PUBLISH_DONE, so the boundary arrives slightly later than today. Remove
SUBSCRIBE_DROP and its type from lite-07 and reword the Subscribe Stream
section: the FIN follows once every counted stream has finished or been
reset. lite-07 is unpublished, so this changes it in place; update
`drafts/draft-lcurley-moq-lite.md` and its changelog.
Comment on lines +24 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required concept-doc sync

This quest changes the rs/moq-net wire format but only schedules the draft update, so it can be completed while leaving the user-facing doc/concept description out of sync. Include the applicable concept documentation in the quest scope alongside the Rust, JavaScript, and draft changes.

AGENTS.md reference: AGENTS.md:L94-L98

Useful? React with 馃憤聽/ 馃憥.

- Rust and JS publishers count the streams they open per subscription and
send the count; a relay counts its own downstream streams, never forwarding
the upstream count.
- Subscribers on lite-07 stop waiting once the count is reached, accepting a
late stream below the end within the grace. On lite-05 and -06, the
DROP accounting from JS track tail (#4086) stays as it is.
- Tests in both languages: a late stream after SUBSCRIBE_END, a skipped group
that is never counted, a reset stream, and a count of zero. Add a Rust-JS
interop case.

This lands before lite-07 is published. Rust has never sent or acted on
SUBSCRIBE_DROP, so [Rust track tail](/quest/m1/rust-track-tail.md) builds its
lite accounting on the count rather than on drops.

## Related

- [Rust track tail](/quest/m1/rust-track-tail.md) - builds on this count for moq-lite
- [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - makes the count exact by keeping a reset stream's header
26 changes: 26 additions & 0 deletions quest/m1/nvenc-teardown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# [S] A failed NVENC encode does not hang shutdown

## Goal

After NVENC rejects an encode (P7 with high-quality tuning returns
`InvalidParam`), the process shuts down promptly. Today it hangs on exit.

## Plan

Reproduce it first with the `encode-presets` example from #4099. Suspects,
from reading the code: `encode::Sink` runs NVENC on the `moq-video-encode`
thread, and `Worker::drop` joins it. That thread then drops the encoder, and
`Session::drop` calls a synchronous end-of-stream `encode_picture`. A
`Pending` that did not finish runs a blocking `lock_bitstream`. Either one can
wedge on a session the driver already refused, and the join then waits
forever.

Fix the cause, for example by skipping end-of-stream on a session whose
encode failed. A timeout on the join doesn't count as a fix. Add a regression
test that forces the failing configuration on hardware where NVENC exists and
asserts teardown returns. Wire it into the nightly GPU lane if there is one;
otherwise say where it runs.
Comment on lines +21 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require the NVENC regression test to run in CI

When no nightly GPU lane exists, the otherwise clause permits completing this quest by merely documenting where the hardware test runs, which can leave the regression test manual and unable to catch future teardown hangs. Require creating or using an automated CI lane, at least nightly, or define a CI-runnable alternative rather than accepting non-CI coverage.

AGENTS.md reference: AGENTS.md:L18-L23

Useful? React with 馃憤聽/ 馃憥.


## Related

- [NVENC recovery](/quest/m1/nvenc-recovery.md) - the other NVENC failure path, rate changes and partial init
4 changes: 4 additions & 0 deletions quest/m1/rust-track-tail.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ session in `rs/moq-net/tests/support`. Add a Rust-JS interop case to
`just test smoke --all` for a publisher that ends a track with a group still
in flight.

## Required

- [lite-07 stream count](/quest/m1/lite-stream-count.md) - the moq-lite accounting this builds on, instead of SUBSCRIBE_DROP

## Related

- [JS track tail](/quest/m1/js-track-tail.md) - the same rule in `@moq/net`
Expand Down
30 changes: 30 additions & 0 deletions quest/m1/signal-race.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# [S] Signal.race releases its listeners when it loses a race

## Goal

`Signal.race` from `@moq/signals` no longer leaves a `changed` listener on
each signal when its own promise is raced and loses. Today it disposes them
only when one of the signals changes, so `js/net/src/origin.ts`'s
`#changed()`, raced against `closed` in `connection/forward.ts`, keeps
listeners for as long as the table stays quiet. Awaiting it directly still
works, with the same signature.

## Plan

Make it consistent with the free `race()` and `effect.race` from
[JS retention](https://github.com/moq-dev/moq/pull/4085), which already
subscribe to `Once`/`GetPromise` values and dispose them when the race
settles. `Signal.race` returns that same kind of awaitable instead of a
native promise: it attaches its signal listeners when first awaited or
subscribed, and releases them when it settles or when its last subscriber
detaches. Racing it through `race()` or `effect.race` then cleans up both
sides. Settle the exact return type at PR time, keeping `await` source
compatible; if the change is a published type break, stop and bring it back.

Audit the callers in `js/net` (`announced`, `broadcast`, `group`, `origin`,
`track`, both subscribers) for the ones that race the result, and add a
listener-count test for the `origin.ts` case that fails today.

## Required

- JS retention (#4085) merged, which adds the free `race()` this builds on
25 changes: 25 additions & 0 deletions quest/m1/tokio-reconnect-ports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [S] moq-tokio reconnect and worker tests bind their own ports

## Goal

The moq-tokio integration tests stop picking a free port, releasing it, and
binding it again, a race another process can win. `reconnect.rs`'s
`spawn_server` loses its retry loop, and the worker tests that do not need a
known port bind `:0`.

## Plan

- Add `Server::tcp_local_addr()` and `Listener::tcp_local_addr()`, mirroring
`websocket_local_addr()`, reporting the bound address of the plain TCP
(qmux) listener. Today `StreamListeners` keeps only the configured bind and
moves the bound listener into its accept task. `spawn_server` binds `:0` and
reads the address back. This is an additive public API.
- In `worker.rs`, the tests that only need some port bind `:0` through the
group and use `Group::local_addr()`. The tests that rebind the same port
after a drop, or probe it while the group holds it, keep a known port, which
is the behavior under test.
- No retries or sleeps.

## Related

- [Test ports](https://github.com/moq-dev/moq/pull/4084) - the same fix for `websocket_forbidden_does_not_end_a_quic_connect`
3 changes: 3 additions & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ upstream release waits in [m4](/quest/m4/README.md).
- [Media Foundation encode](/quest/m2/audio-encode-mediafoundation.md) - Windows encodes AAC-LC
- [MediaCodec decode](/quest/m2/audio-decode-mediacodec.md) - Android decodes HE-AAC, multichannel AAC, and what else the device offers
- [MediaCodec encode](/quest/m2/audio-encode-mediacodec.md) - Android encodes AAC-LC
- [AAC encode refusal](/quest/m2/aac-encode-refusal.md) - AAC config encode refuses channel counts it cannot name, on dev
- [Video codec coverage](/quest/m2/video-codec-coverage.md) - prioritize remaining native AV1 and portable decoder gaps
- [#2147](/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md) - moq-video: 10-bit HEVC and AV1 support in the NVIDIA codec path
- [NVENC buffer pool](/quest/m2/nvenc-pool.md) - NVENC reuses input and output buffers instead of allocating per frame, if a benchmark shows it wins
- [Direct3D11 render import](/quest/m2/render-d3d11.md) - Windows presents without downloading every frame to system memory
- [Intra-refresh GOPs](/quest/m2/intra-refresh/README.md) - video with periodic intra refresh publishes, imports, and tunes in cleanly with one group per sweep and a catalog `warmup`
- [#2819](/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md) - moq-video: carry PipeWire DMA-BUFs safely into the Vulkan renderer
Expand All @@ -56,6 +58,7 @@ upstream release waits in [m4](/quest/m4/README.md).
- [L4S on the backbone](/quest/m2/quic-ecn.md) - an ECT(1) option in the fork, an `ecn` config knob, and a dualpi2 measurement
- [Careful resume on reconnect](/quest/m2/quic-careful-resume.md) - a redial starts at the previous connection's rate
- [Keep-alive by deadline](/quest/m2/quic-keep-alive.md) - a PING only when the idle deadline nears, no fixed timer
- [noq socket close](/quest/m2/noq-socket-close.md) - noq releases an endpoint's socket on close, so moq-tokio drops its wrapper
- [Bounded announce prefix table](/quest/m2/announce-prefix-table.md) - compress repeated path tuples on each ordered lite-08 announce stream, with bounded state and measured QUIC-byte savings
- [Drop the hidden cluster exemption](/quest/m2/hidden-exemption.md) - relays stop forcing hidden broadcasts on cluster peers once every peer opts in on the wire
- [Routing cost domains](/quest/m2/routing-cost-domains.md) - design operator boundaries and policy without adding incomparable costs
Expand Down
22 changes: 22 additions & 0 deletions quest/m2/aac-encode-refusal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [S] AAC encode refuses a channel count it cannot name

## Goal

Writing an AudioSpecificConfig for a channel count that no AAC
channelConfiguration names is an error, not a stereo config with a warning,
in `moq_mux::codec::aac::Config::encode` and `@moq/hang`'s
`audioSpecificConfig`. This mirrors the parse side, which since #4093 refuses
reserved values instead of guessing stereo.

## Plan

Both functions become fallible, a published API break in each language, so
this targets `dev`. Counts with a PCE-free configuration map as today. For
the others, either write channelConfiguration 0 with a PCE derived from the
layout, or refuse. Pick one at PR time and apply it in both languages. Test
every count from 1 to 8 and one beyond.

## Related

- [AAC PCE](https://github.com/moq-dev/moq/pull/4093) - the parse half
- [Layout](/quest/m1/audio-codecs/layout.md) - the layout a PCE would be derived from
21 changes: 21 additions & 0 deletions quest/m2/noq-socket-close.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# [M] noq releases an endpoint's socket on close

## Goal

A noq endpoint can close its UDP socket, and report when it has, without
waiting for every connection handle to drop. moq-tokio then deletes the
closable socket wrapper that #4087 added to make `Listener::close` release the
port.

## Plan

In moq-dev/noq, add an endpoint operation that closes the endpoint, waits
until each connection has sent its close, and then releases the socket. Later
sends are dropped and receives end. Test it there. Cut a noq release, bump the
pin, and replace moq-tokio's wrapper with the upstream call. The Go
`TestReconnectAcrossRelayRestart` and moq-tokio's
`close_releases_quic_socket` stay green.

## Related

- [Listener close](https://github.com/moq-dev/moq/pull/4087) - the local wrapper this replaces
18 changes: 18 additions & 0 deletions quest/m2/nvenc-pool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# [M] NVENC reuses its input and output buffers

## Goal

The NVENC backend stops allocating per frame. At 1080p, creating the output
bitstream and input buffer (or registering the CUDA resource) is about half
of frame-to-packet time. A benchmark shows the pooled path is faster at
720p and 1080p, with the numbers in the PR. If it doesn't win, abandon the
quest and report the numbers.

## Plan

`rs/moq-video/src/encode/backend/nvenc.rs` calls `create_output_bitstream`,
`create_input_buffer` (CPU frames) or `register_generic_resource` (CUDA
frames) on every `encode`, and frees them all at the end of the call. Keep a
small pool sized by the frames in flight, which is one today since B-frames
are off. Re-register a CUDA resource only when its pointer changes. Measure
with the `encode-presets` example from #4099.
Loading