Skip to content

moq-net: reshape Track into TrackInfo + an async TrackConsumer handle#1631

Merged
kixelated merged 4 commits into
devfrom
track-revamp
Jun 5, 2026
Merged

moq-net: reshape Track into TrackInfo + an async TrackConsumer handle#1631
kixelated merged 4 commits into
devfrom
track-revamp

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

Reworks the track model so we don't have to know TrackInfo until a SUBSCRIBE_OK / FETCH_OK / TRACK_STATUS arrives, which makes room for FETCH alongside SUBSCRIBE. TrackConsumer is now a cheap async handle you subscribe/info (later fetch); behind the scenes that resolves into a SUBSCRIBE, and TrackRequest::accept(info) locks the info in and returns a static TrackProducer. Completes the prototype and updates every consumer/test across the workspace.

Model (rs/moq-net)

  • Split Track (name + properties) into TrackInfo (properties only); the name now lives on the handles. BroadcastBroadcastInfo.
  • BroadcastConsumer::consume_track → fallible track(name). Subscribing is track(name)?.subscribe(sub)?.await, resolving on SUBSCRIBE_OK. TrackRequest::accept(info) hands back a TrackProducer that can't change the info.
  • Added TrackProducer::{consume, subscribe} (synchronous in-process subscribe, since info is known at creation) and sync subscription() snapshots; the async change-notifier is now subscription_changed().
  • ⚠️ As noted in the prototype, moq-lite has no TRACK_STATUS yet, so a standalone info() would block — older versions still go through a real subscribe. Left as-is for now.

Deadlock fixes found wiring it up end-to-end

Both surfaced via stack-sampling the hung moq-ffi::server_client_roundtrip:

  • Lost wakeup on accept()TrackSubscriberPending/TrackInfoPending passed a temporary kio::Waiter; kio stores a weak waker, so it died on poll return and accept()'s wake hit nobody. Now the waiter is held across polls.
  • Spurious modifiedpoll_subscription_changed used retain_mut, tripping Mut's DerefMut and flagging TrackState modified on every no-op poll (draining unrelated waiters). Now a shared helper iterates immutably; added kio::Consumer::is_closed.

Catalog (rs/hang)

  • The catalog embedded moq_net::Track, which no longer carries a name and shouldn't carry compress/timescale/cache anyway. Replaced with a { name: String } type matching the JS TrackSchema exactly (byte-identical wire, no js/hang change). default_track()default_track_info() -> TrackInfo (it only ever configured the catalog.json producer).

Allocation

  • Track names are Arc<str> (handle clones — weak/consume/subscribe/accept — are refcount bumps; impl Into<Arc<str>> lets an existing Arc pass through free). The broadcast lookup maps key by the same shared Arc<str>. TrackInfo stays a plain Clone value (small, inline, no heap).

Consumers updated

hang, moq-mux, moq-relay, moq-audio, moq-cli, moq-gst, moq-boy, moq-rtc, libmoq, moq-ffi, moq-native (+ their tests/examples).

Test plan

  • cargo test --workspace — 941 passed, 0 failed (2 ignored)
  • cargo fmt --all --check clean (Nix-pinned toolchain)
  • Previously-hung moq-ffi::server_client_roundtrip passes
  • js/ not touched — this is an API-shape change, not a wire change (catalog JSON is identical); JS @moq/net handle parity is a possible follow-up

🤖 Generated with Claude Code

(Written by Claude)

kixelated and others added 4 commits June 4, 2026 10:43
Complete the track-revamp prototype and update every consumer/test.

Model (rs/moq-net):
- Split `Track` (name + props) into `TrackInfo` (props only) and a name
  carried separately on the handles. `Broadcast` -> `BroadcastInfo`.
- `BroadcastConsumer::consume_track` -> fallible `track(name)`; subscribing
  is now `track(name)?.subscribe(sub)?.await` and resolves on SUBSCRIBE_OK
  (or info), making room for FETCH. A `TrackRequest::accept(info)` locks in
  the info and hands back a static `TrackProducer`.
- Add `TrackProducer::{consume, subscribe}` (sync in-process subscribe) and
  sync `subscription()` snapshots; the async change-notifier is now
  `subscription_changed()`.

Fixes found wiring it up end-to-end:
- TrackSubscriberPending/TrackInfoPending kept a temporary kio::Waiter, whose
  weak waker died on poll return, so accept()'s wakeup was lost. Hold the
  waiter across polls.
- poll_subscription_changed used retain_mut, tripping Mut's DerefMut and
  flagging TrackState modified on no-op polls (draining unrelated waiters).
  Iterate immutably via a shared helper; add kio::Consumer::is_closed.

hang catalog: replace the embedded `moq_net::Track` with a `{ name: String }`
type matching the JS `TrackSchema`; `default_track()` -> `default_track_info()`.

Allocation: track names are `Arc<str>` (handle clones are refcount bumps;
`impl Into<Arc<str>>` lets an existing Arc pass through free), and the
broadcast lookup maps key by the same shared `Arc<str>`.

Consumers/tests updated across hang, moq-mux, moq-relay, moq-audio, moq-cli,
moq-gst, moq-boy, moq-rtc, libmoq, moq-ffi, moq-native.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	rs/hang/src/catalog/chat.rs
#	rs/hang/src/catalog/mod.rs
#	rs/hang/src/catalog/root.rs
…dcast rename

cargo doc -D warnings flagged links to the removed Track/Broadcast items.
Point them at the new types (TrackInfo/BroadcastConsumer/TrackConsumer) and
use crate:: paths where the type is no longer in scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated merged commit c38abd5 into dev Jun 5, 2026
2 checks passed
@kixelated kixelated deleted the track-revamp branch June 5, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant