Skip to content

feat(lite): keep group headers across resets and drop the tail grace for declaring publishers - #5804

Closed
kixelated wants to merge 4 commits into
mainfrom
quest/m1/quic/group-reset
Closed

kixelated wants to merge 4 commits into
mainfrom
quest/m1/quic/group-reset

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Problem

Quest: quest/m1/quic/group-reset.md.

A lite subscriber waits out a tail grace (the max delay, or 1s) when a publisher ends a subscription with a counted Group Stream still missing, because a stream reset before its header leaves no trace. moq-quic can already reset with RESET_STREAM_AT (#5690), but the lite publisher never used it for group headers, and nothing told the subscriber it could stop guessing.

Approach

  • Transport. transport::poll::SendStream::checkpoint() marks the data written so far for a later reset to still deliver. transport::poll::Session::reliable_reset() says whether that holds (the peer accepted reset_stream_at). Both are defaulted no-ops/false, so browser, qmux, and iroh report "no" without changes. moq-quic gains Connection::reliable_reset(). The moq-tokio and moq-uring adapters forward both. The in-memory transport models it (memory::Config::reliable_reset, truncating a reset stream to the checkpoint).
  • Publisher. A lite Group Stream now counts toward Stream Count only once its GROUP message is flushed, and is checkpointed right there. Counting is applied on every session, not just declaring ones: a stream reset before its header was never visible anyway.
  • Signal. A new lite-07 SETUP parameter Reliable Reset (0x6, flag). An endpoint sends it when its version has a Stream Count and its transport reports reliable_reset(). A relay never forwards it; each hop declares its own transport.
  • Subscriber. On lite-07, when the publisher declared it, the tail settles with no grace (Settle::exact in Rust, settle(..., { exact: true }) in @moq/net). Datagram holes are not counted, so they cannot hang it. lite-05/06 keep the grace, since their accounting cannot tell a datagram hole from a stream in flight. @moq/net reads the declaration but never makes it: the browser cannot reset that way.
  • Draft. draft-lcurley-moq-lite.md: the parameter, the counting rule, RESET_STREAM_AT on Group Streams, the subscriber's unbounded wait, and the lite-07 changelog.

Decision for the maintainer: the signal's wire shape

  1. SETUP parameter (implemented). Per session and per direction, gated on the transport actually negotiating it, so a browser publisher on a native-capable QUIC stack never claims it. Costs one parameter and one rule.
  2. A broader "accounts every group" SETUP parameter. Same mechanism, but it names the guarantee instead of the transport trick, so a future publisher with another way to account for a lost header (e.g. a DROP) could declare it too. Wider promise and harder to state precisely.
  3. No signal. Pair it with the SUBSCRIBE_DROP quest instead: a publisher names every group it resets before the header, and a reset that keeps its header is just a cheaper way to do that. This removes the grace for everyone (browser included) but needs DROP back on lite-07, and the draft currently removes it in favor of Stream Count.

Recommendation: ship 1 now. It is the smallest change that removes the grace where it is safe. Revisit 3 when the subscribe-drop quest settles, since option 3 makes this parameter redundant. The two conflict today: that quest wants to restore DROP and drop Stream Count, and this design relies on the count to exclude datagrams.

Impact

Public API (Rust):

  • moq_net::transport::poll::Session::reliable_reset(&self) -> bool (defaulted).
  • moq_net::transport::poll::SendStream::checkpoint(&mut self) (defaulted).
  • moq_net::transport::memory::Config::reliable_reset (the struct is non_exhaustive).
  • moq_net::lite::Setup::reliable_reset field and Setup::detect_reliable_reset.
  • moq_quic::Connection::reliable_reset(), plus reliable_reset() on the moq-tokio and moq-uring QUIC connections.

Public API (JS): Setup.reliableReset / SetupProps.reliableReset in @moq/net lite.

Wire: lite-07 (moq-lite-07-wip) only. New SETUP parameter 0x6. A declaring publisher resets counted Group Streams with RESET_STREAM_AT. lite-05/06 and IETF are unchanged on the wire. On every lite version, a Group Stream reset before its GROUP message is written is no longer counted.

Stopgap

web-transport-trait 0.5 has no checkpoint or reliable-reset method, so the moq-tokio and moq-uring adapters recognize moq-quic's types by Any downcast. The clean fix is defaulted checkpoint() and reliable_reset() methods upstream in web-transport-trait, at which point the downcasts go away.

Verification

  • cargo clippy -p moq-quic -p moq-tokio -p moq-uring -p moq-net --all-features --tests -D warnings: clean. moq-uring is also clean with --no-default-features, apart from pre-existing dead-code warnings.
  • cargo test -p moq-net --all-features: passes.
  • cargo test -p moq-tokio --lib: passes.
  • moq-quic reset_stream_at tests and moq-uring web:: integration tests: pass.
  • New regression tests fail without the fix:
    • moq-net a_reset_group_that_keeps_its_header_ends_without_the_grace: hangs without the checkpoint.
    • moq-tokio reset_delivers_the_checkpoint and moq-uring a_reset_stream_keeps_its_checkpoint: real moq-quic, through the moq-net adapters.
    • @moq/net lite-07 waits past the grace ... declared.
  • Also new: a_declared_publisher_is_waited_on_past_the_grace and a_declared_publisher_is_waited_on_through_a_relay (relay hop), plus setup round-trip tests in both languages.
  • bun test in js/net (1939 pass) and tsc --noEmit: pass.
  • just drafts check: passes.
  • test/interop/interop.sh --tail --publishers rust --subscribers rust,js-native-bun: all Rust and JS tail lanes pass. These lanes negotiate lite-06, so they cover the counting change but not the signal.
  • Full just check was not run, to keep the build scoped on a shared machine.

Not done (follow-ups)

  • Chrome wasm harness and native interop coverage of the signal: the tail lanes would need a lite-07 variant. INTEROP_VERSION currently turns on compat mode, which skips them.
  • Check whether Chrome hands a group header to JS when a plain RESET_STREAM follows the ACK. Until then, a peer without reset_stream_at is treated as unable to keep it.
  • Upstream checkpoint / reliable_reset in web-transport-trait and drop the downcasts.

🤖 Generated with Claude Code

kixelated and others added 4 commits October 16, 2026 17:00
A publisher on a transport with reliable resets declares it in SETUP, and
a lite-07 subscriber then waits for every counted stream without a grace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
moq-quic reports whether the peer accepted RESET_STREAM_AT, and the
moq-tokio and moq-uring adapters forward that and a stream checkpoint
to moq-net. The backend traits have no word for either, so moq-quic's
types are recognized by downcast until web-transport-trait grows one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or it

A lite-07 endpoint declares the SETUP Reliable Reset parameter (0x6)
when its transport keeps a reset stream's checkpoint. A subscriber whose
publisher declared it waits for every counted Group Stream without the
grace; datagram holes and older drafts keep it. @moq/net reads the
declaration but never makes it. Adds a relay-hop test and the draft text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Quest outcome for quest/m1/quic/group-reset.md: the work is implemented, but the quest is not complete, so this stays a draft.

Done:

  • Checkpointed group resets in moq-net, moq-tokio, and moq-uring.
  • The lite-07 SETUP Reliable Reset parameter (0x6).
  • Exact tail settling in Rust and @moq/net.
  • The draft text.
  • Tests covering the signal on and off, a relay hop, and real moq-quic through both adapters.

Open:

  • The maintainer's call on the signal's wire shape. The PR body recommends the SETUP parameter for now and revisiting it alongside the subscribe-drop quest.
  • How this fits with that quest restoring SUBSCRIBE_DROP and removing Stream Count.
  • Chrome wasm and lite-07 interop coverage.
  • The Chrome plain-RESET_STREAM check.
  • Upstreaming the web-transport-trait methods.

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Superseded by moq-dev/moq#5983, which merged in fdbdcce. That's the maintainer's decision from 2026-10-19, relayed from an interactive session.

#5983 restores SUBSCRIBE_DROP on lite-07 and removes Stream Count. The draft now requires every sequence in range to arrive as a Group Stream or a datagram, or be named in SUBSCRIBE_DROP. The publisher also drops every group it resets ("A reset can lose its header, so the control stream also accounts for the group"). So a subscriber never waits out the tail grace for a reset that lost its header, for any publisher, browsers included, without a transport feature or a SETUP parameter.

This PR's signal (option 1) relied on Stream Count to tell a lost stream from a datagram gap, and that field is gone. Option 3 ("no signal, pair it with SUBSCRIBE_DROP") is what landed. RESET_STREAM_AT would only save one DROP per reset, which doesn't justify the new transport-trait methods or the Any downcasts. reset_at stays in moq-quic for the WebTransport adapters.

The quest (quest/m1/quic/group-reset.md) is deleted in moq-dev/moq#6041. This PR stays open until the maintainer confirms closing it.

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #5983 (reasoning in #5804 (comment)). The quest deletion lands in #6041.

(Written by Claude Opus 5.5)

@kixelated kixelated closed this Oct 20, 2026
@kixelated
kixelated deleted the quest/m1/quic/group-reset branch October 22, 2026 06:01
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