diff --git a/quest/m1/README.md b/quest/m1/README.md index 3e580b1521..d3678a09df 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -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 @@ -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 diff --git a/quest/m1/lite-stream-count.md b/quest/m1/lite-stream-count.md new file mode 100644 index 0000000000..30f8a05363 --- /dev/null +++ b/quest/m1/lite-stream-count.md @@ -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. +- 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 diff --git a/quest/m1/nvenc-teardown.md b/quest/m1/nvenc-teardown.md new file mode 100644 index 0000000000..cde5459bee --- /dev/null +++ b/quest/m1/nvenc-teardown.md @@ -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. + +## Related + +- [NVENC recovery](/quest/m1/nvenc-recovery.md) - the other NVENC failure path, rate changes and partial init diff --git a/quest/m1/rust-track-tail.md b/quest/m1/rust-track-tail.md index 09a528d793..5af1ff663d 100644 --- a/quest/m1/rust-track-tail.md +++ b/quest/m1/rust-track-tail.md @@ -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` diff --git a/quest/m1/signal-race.md b/quest/m1/signal-race.md new file mode 100644 index 0000000000..90695f5847 --- /dev/null +++ b/quest/m1/signal-race.md @@ -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 diff --git a/quest/m1/tokio-reconnect-ports.md b/quest/m1/tokio-reconnect-ports.md new file mode 100644 index 0000000000..df925e6a82 --- /dev/null +++ b/quest/m1/tokio-reconnect-ports.md @@ -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` diff --git a/quest/m2/README.md b/quest/m2/README.md index 33404a8e59..36963249d9 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -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 @@ -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 diff --git a/quest/m2/aac-encode-refusal.md b/quest/m2/aac-encode-refusal.md new file mode 100644 index 0000000000..3008bf0702 --- /dev/null +++ b/quest/m2/aac-encode-refusal.md @@ -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 diff --git a/quest/m2/noq-socket-close.md b/quest/m2/noq-socket-close.md new file mode 100644 index 0000000000..3fa7a9621b --- /dev/null +++ b/quest/m2/noq-socket-close.md @@ -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 diff --git a/quest/m2/nvenc-pool.md b/quest/m2/nvenc-pool.md new file mode 100644 index 0000000000..c1723df728 --- /dev/null +++ b/quest/m2/nvenc-pool.md @@ -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.