Skip to content
Open
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
1 change: 1 addition & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ bench all require its crate.
- [Binding audio delay](/quest/m1/binding-surface.md) - moq-ffi and every wrapper configure and observe audio playout delay
- [Per-frame arrivals in @moq/watch](/quest/m1/watch-arrivals.md) - the audio and video decoders expose a window of recent frame arrivals, late and skipped marks included, as a signal
- [Bump web-transport-iroh for the capsule close](/quest/m1/iroh-capsule-bump.md) - condition: moq-dev/web-transport#419 ships in a release, then moq's iroh HTTP/3 client reports a peer's close capsule
- [Early drops](/quest/m1/subscribe-drop-early.md) - Rust and JS publishers drop a sequence the moment they give it up, and forward an upstream drop
- [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - every stream group in a lite subscription arrives or is dropped by name, and lite-07 drops its stream count for it
- [lite-07 Live flag](/quest/m1/lite-live.md) - a separate `Live` field on lite-07 SUBSCRIBE, so merged floors never starve a subscriber; late lower groups build on it
- [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - a lite-07 SUBSCRIBE asks for past and live ranges in either order and replaces FETCH; relays fill misses by range, including over moq-transport
Expand Down
59 changes: 59 additions & 0 deletions quest/m1/subscribe-drop-early.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# [L] Publishers drop a group the moment they give it up

## Goal

On lite-05 and lite-06, the Rust and `@moq/net` publishers send a
SUBSCRIBE_DROP for a sequence as soon as they give it up, and still name every
sequence the subscription never got just before the FIN. A subscriber to
either publisher, directly or through a relay, settles without waiting out its
grace. Both implementations behave identically.

Non-goals, kept in [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md): resets
before the group header, lite-07, and surfacing a drop to readers.

## Plan

Today only the Rust publisher sends SUBSCRIBE_DROP, and only at the end
(`drop_unserved` in `rs/moq-net/src/lite/publisher.rs`). The JS publisher
never sends one, so every sequence it skips leaves its subscriber waiting out
the grace (raised by Codex on moq-dev/moq#5301).

Decided (maintainer, 2026-10-11):

- Rust and JS together, identical behavior, rather than JS catching up to
Rust's end-of-track sweep alone.
- An early DROP covers:
- cursor skips: sequences below a SUBSCRIBE_START pin, below
`position_cursor`/`positionCursor`, or below the pre-06 SUBSCRIBE_UPDATE
re-pin (moq-dev/moq#5301);
- max-delay expiry: a group skipped for aging past the subscriber's max
delay. This also closes the parent's note that end-of-track aging misses
a gap the subscriber still waits on;
- explicit gaps, only when the upstream publisher named them: an incoming
SUBSCRIBE_DROP, or a producer cut. A sequence that simply has not arrived
is never inferred dropped, since a reordered group may still show up.
- Forwarding an upstream drop needs the track model to record a sequence as
dropped. Add that per-sequence mark to both models (`rs/moq-net/src/model/track.rs`,
`js/net/src/track.ts`), set by an incoming SUBSCRIBE_DROP and by a producer
cut, and have both publishers forward it as an early DROP. The parent
later reads the same mark to surface drops to readers. The mark must survive
the cache reclaiming the slot (see the parent's notes from #4998).
- The end-of-track sweep stays as the backstop; a sequence already dropped
early is not named twice.

Update the lite draft's SUBSCRIBE_DROP text to say a publisher SHOULD send it
as soon as it gives a sequence up. Run `just drafts check`.
Comment on lines +44 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the draft update with SUBSCRIBE_DROP restoration

This child cannot make the requested normative draft change while keeping lite-07 as a non-goal. The current draft has no SUBSCRIBE_DROP message section and explicitly says lite-07 removed it (drafts/draft-lcurley-moq-lite.md:1542-1547), while the parent that depends on this child owns restoring it. Updating only the historical changelog would not establish the new SHOULD, but restoring the message here would prematurely change lite-07. Move this behavior wording to the parent, or move the restoration into this child so the implementation and draft can land consistently.

Useful? React with 👍 / 👎.


Tests, with mock time:

- Mirrored Rust and JS publisher unit tests for each trigger: the subscriber's
owed range settles at the skip, before the track ends and without the grace.
Comment on lines +49 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Defer the pre-FIN settlement test to the parent

The requested pre-track-end assertion cannot pass within this quest's stated scope. JS invokes #settleTail only after #runResponses resolves on subscribe-stream FIN (js/net/src/lite/subscriber.ts:721-727), and Rust similarly enters ServeMode::Tail only after receiving FIN (rs/moq-net/src/lite/subscriber.rs:5179-5208); receiving a DROP merely accounts its range. Because lines 11-12 explicitly defer surfacing drops to readers, nothing externally settles when the early DROP arrives. Test the emitted/accounted DROP and immediate post-FIN settlement here, or pull reader disposition into this child.

Useful? React with 👍 / 👎.

- A relay forwards an upstream DROP downstream on both stacks.
- A lite-05 and a lite-06 case in `just test interop --tail`: a JS publisher
skips a group and a Rust subscriber settles without waiting out its grace,
and the reverse.

## Related

- [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - the parent: lite-07, resets before the header, readers
- [Tail arrivals](/quest/m1/tail-arrivals.md) - per-track live dispositions survive cache eviction
10 changes: 7 additions & 3 deletions quest/m1/subscribe-drop.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ live readers or recovery; the JS lite-03/04 path does not run that response
loop. Once a track ends, the Rust
publisher on lite-05 and lite-06 names every sequence from its
SUBSCRIBE_START to the end that the subscription never got (skipped,
stale, or missing its head), just before its FIN. Still missing: JS
publishers, drops sent as soon as a group is given up rather than at the end,
and groups reset before their header. The end-of-track drops also miss a gap
stale, or missing its head), just before its FIN. Still missing: groups
reset before their header. JS publishers and drops sent as soon as a group is
given up belong to [Early drops](/quest/m1/subscribe-drop-early.md). The end-of-track drops also miss a gap
the publisher aged out of its grace while the subscriber still waits on it:
the publisher ages a gap from when it queued the group above it, which can be
well before stream credit lets that group's header out. lite-07 (still `moq-lite-07-wip`,
Expand Down Expand Up @@ -95,6 +95,10 @@ publisher skipped or never opened ends the track without waiting out the
grace. Decided in the 2026-09-30 audit: the case moved here so the basic
tail interop could land first.

## Required

- [Early drops](/quest/m1/subscribe-drop-early.md) - Rust and JS publishers drop a sequence the moment they give it up, and forward an upstream drop

## Related

- [Tail arrivals](/quest/m1/tail-arrivals.md) - per-track live dispositions survive cache eviction
Expand Down
Loading