Skip to content

moq-net + js/net: pre-merge API hardening for moq-lite-05#2170

Merged
kixelated merged 5 commits into
devfrom
claude/moq-net-api-review-ccc7ab
Jul 12, 2026
Merged

moq-net + js/net: pre-merge API hardening for moq-lite-05#2170
kixelated merged 5 commits into
devfrom
claude/moq-net-api-review-ccc7ab

Conversation

@kixelated

@kixelated kixelated commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Tightens the moq-net public surface before dev lands on main and its shapes become semver-load-bearing. Targets dev: renames, removals, and signature changes to pub items in moq-net, js/net, and moq-ffi break existing contracts.

Came out of an extensive review of the moq-net dev delta ahead of the dev→main merge (the "actually start using moq-lite-05" milestone). The goal was the API shape: catch every change that is a one-line fix now but a breaking change forever once it ships on main.

Future-proofing (avoid an immediate semver bump after merge)

  • #[non_exhaustive] on Subscription, group::Fetch, and the Announced enum (all will gain fields/variants).
  • Move Subscription under track:: (track::Subscription); it only appears in track signatures.
  • Rename the last flat Bandwidth{Producer,Consumer} into a bandwidth::{Producer,Consumer} role module, matching every other Producer/Consumer pair.
  • Rename the kio::Pending payload futures to a consistent gerund scheme: track::{Subscribe→Subscribing, Fetch→Fetching, InfoQuery→Querying}, origin::Requested→Requesting.
  • Turn the announce update tuple (PathOwned, Announced) into a named #[non_exhaustive] struct announce::Update { path, event }.
  • Re-export web_transport_trait (it bounds the Client/Server entry points but was unnameable).

Misuse resistance / correctness

  • read_frame / poll_read_frame (group + track) now return the full frame::Frame { timestamp, payload } instead of bare Bytes, so the mandatory per-frame timestamp is no longer silently dropped.
  • Remove the inherent Timestamp::max that panicked on mixed scales and shadowed the total Ord::max.
  • De-panic track::Producer::subscribe after abort(); Subscriber::update / Subscribing::update return Result instead of panicking on a closed track.
  • Session close state is shared across clones: first close wins, transport closes on last-clone drop (was: any clone's drop killed the shared connection).
  • server::Request rejects the handshake on drop with a warning (was: silent client hang until idle timeout).
  • Wire the full SUBSCRIBE_UPDATE into the model subscriber so the producer aggregate reflects it and relays re-forward it upstream (was priority + end only).
  • Counters fields are crate-private (load-bearing atomic orderings must not leak).

Ordered delivery is now a DVR-only opt-in

Subscription::default is ordered: false, and the aggregate is ordered only when every live subscriber asks for it (AND, was OR). Mirrored in the js/net wire defaults and moq-ffi's MoqSubscription default.

Datagrams

Drop the misleading public MAX_DATAGRAM_PAYLOAD (advertised 1200, but the encoded body overshot it). Enforce a 65535 buffering ceiling as FrameTooLarge, and drop oversize datagrams per hop against max_datagram_size() with a debug log, so the limit is enforced where it actually applies (including a future hop with a smaller MTU).

js/net (mirrors the above)

subscribe() takes a Subscription options object (not a positional priority), Subscriber.update() replaces updatePriority(), Datagram/Bandwidth are namespaced (Track.Datagram, Bandwidth.*), detached public consumer constructors are hidden, dead AnnouncedOptions.ignoreSelf is removed, datagram age uses performance.now(), plus doc fixes.

Test plan

Follow-ups (filed, out of scope here)

🤖 Generated with Claude Code

(Written by Claude Fable 5)


Update — rebased on dev + fixes (Written by Claude Opus 4.8)

Tighten the moq-net public surface before dev lands on main and becomes
semver-load-bearing. All changes are on dev (breaking allowed).

Future-proofing (avoid an immediate semver bump after merge):
- #[non_exhaustive] on Subscription, group::Fetch, and the Announced enum.
- Move Subscription under track:: (track::Subscription); it only appears in
  track signatures.
- Rename the flat Bandwidth{Producer,Consumer} into a bandwidth role module
  (bandwidth::{Producer,Consumer}), matching every other Producer/Consumer.
- Rename the kio::Pending payload futures to gerunds for a consistent scheme:
  track::{Subscribe->Subscribing, Fetch->Fetching, InfoQuery->Querying},
  origin::Requested->Requesting.
- Turn the announce update tuple (PathOwned, Announced) into a named
  #[non_exhaustive] struct announce::Update { path, event }.
- Re-export web_transport_trait (it bounds Client/Server entry points).

Misuse resistance / correctness:
- read_frame / poll_read_frame (group + track) now return the full
  frame::Frame { timestamp, payload } instead of bare Bytes, so the mandatory
  per-frame timestamp is no longer silently dropped.
- Remove the inherent Timestamp::max that panicked on mixed scales and shadowed
  the total Ord::max.
- De-panic track::Producer::subscribe after abort(), and make Subscriber::update
  / Subscribing::update return Result instead of panicking on a closed track.
- Session close state is shared across clones: first close wins, and the
  transport closes on last-clone drop (was: any clone's drop killed the shared
  connection).
- server::Request rejects the handshake on drop (was: silent client hang until
  idle timeout).
- Wire the full SUBSCRIBE_UPDATE into the model subscriber so the producer
  aggregate reflects it and relays re-forward it upstream (was priority + end
  only).
- Counters fields are crate-private (load-bearing atomic orderings must not
  leak).

Datagrams:
- Drop the misleading public MAX_DATAGRAM_PAYLOAD cap (1200, but the encoded
  body overshot it); enforce a 65535 buffering ceiling as FrameTooLarge and
  drop oversize datagrams per hop against max_datagram_size() with a debug log.

Ordered delivery is now a DVR-only opt-in: Subscription::default is
ordered:false, and the aggregate is ordered only when every subscriber asks for
it (AND, was OR). Mirrored in js/net wire defaults.

js/net mirrors the above: subscribe() takes a Subscription options object
(not positional priority), Subscriber.update() replaces updatePriority(),
Datagram/Bandwidth are namespaced (Track.Datagram, Bandwidth.*), detached
public consumer constructors are hidden, dead AnnouncedOptions.ignoreSelf is
removed, datagram age uses performance.now(), plus doc fixes.

moq-ffi: MoqSubscription.ordered defaults to false to match.

Follow-ups filed: #2148-#2155 (FFI/binding parity gaps), #2164
(subscribe_media options record), #2165 (libmoq macOS video build), #2166
(moq-net missing docs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @kixelated, your pull request is larger than the review limit of 150000 diff characters

kixelated and others added 3 commits July 11, 2026 16:14
…iew-ccc7ab

# Conflicts:
#	rs/moq-ffi/src/consumer.rs
#	rs/moq-net/src/model/origin.rs
The session close-once refactor deferred the single transport close to the
last `Session` clone's drop. In `MoqSession`, that clone lives in the `closed`
task and is released after `Drop`'s `RUNTIME.enter()` guard, so the transport
backend's lingering-CLOSE `tokio::spawn` ran with no reactor in context and
panicked ("there is no reactor running"), aborting the process under
panic=abort. This surfaced as a SIGABRT in the Python `test_server` roundtrip.

Close the transport explicitly while the runtime is entered; close-once dedup
makes the trailing clone drop a no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reconcile the API-hardening PR with overlapping work that landed on dev.
Where dev and this branch reshaped the same surface, resolved as follows:

- read_frame / poll_read_frame (group + track): keep this branch's
  `frame::Frame` return; drop dev #2174's additive `read_frame_full` /
  `poll_read_frame_full` split. Redirected the moq-ffi / libmoq / internal
  callers to the collapsed `read_frame`; kept dev's MoqFrame + timestamp_us
  FFI plumbing (sourced from the Frame).
- Subscriber::update / SubscriberControl::update: keep this branch's
  `Result` de-panic (dev's #2177 control() still panicked on a closed track);
  ignore the Result at the FFI/libmoq call sites where a closed-track update
  is meaningless.
- moq_net::Subscription -> moq_net::track::Subscription in dev's newer libmoq.
- OriginAnnounce tuple -> announce::Update struct in dev's moq-native
  lite-06 announce test.
- js subscribe options: keep this branch's `Subscription` type name (mirrors
  Rust track::Subscription) over dev #2167's SubscribeOptions; keep the hooks
  indirection that hides the detached Request constructor.
- lite/subscriber.ts: take dev's announce-id machinery (#2160, incl.
  AnnouncedOptions/ignoreSelf) wholesale; port its priority poll onto this
  branch's subscriptionSignal.

Validated: cargo check/clippy --workspace clean; moq-net/moq-ffi/libmoq/
moq-native/moq-mux tests pass; js/net tsc+biome+236 tests; py 45 tests pass.
@kixelated kixelated enabled auto-merge (squash) July 12, 2026 15:31
The `moq_net::Subscription` -> `moq_net::track::Subscription` rename in the
dev merge pushed two signatures past the width limit; rustfmt wants them
wrapped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated merged commit 368f366 into dev Jul 12, 2026
4 checks passed
@kixelated kixelated deleted the claude/moq-net-api-review-ccc7ab branch July 12, 2026 16:28
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