feat(moq-net): enforce the subscriber latency budget - #2890
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Subscription::latency/latencyMaxwas propagated as metadata but the track model never acted on it. This enforces it inrs/moq-netat both ends of a subscription, which is the ceiling that #2784 needs now that #2785 is reverted ondev.The JS mirror is stacked on top as #2919 (originally filed as #2892), so each language reviews on its own.
track::Subscriber'spoll_recv_group/poll_next_group/poll_read_frameskip a group that has drifted past the subscription's budget, so one poll walks a whole backlog off. Both ends go through the same code: a relay serves downstream viapoll_recv_group, and a local consumer reads the same way.fetch_groupis exempt, since a fetch names historical content explicitly.Error::Oldis reserved for a cursor still holding unread content (a half-read payload, a publisher with buffered frames). Without this a relay reset the downstream stream for a group it had delivered in full, since the FIN and the next group's first frame are separate streams and the reader is routinely parked at the group's end when the verdict is taken.moq_mux::container::Consumer::newinherits that already-negotiated budget.stale: { bytes, frames, groups, datagrams }in JSON stats and as matchingmoq_relay_stale_*_totalPrometheus counters. Expiry after handoff counts only the unread tail, and cloned readers share the one attribution.Public API changes
Targets
devfor one breaking cleanup:moq_mux::container::Consumer::with_latency.Consumer::newnow inherits the initialmoq_net::track::Subscriptionlatency;set_latencyremains for mid-stream changes.moq_net::stats::Contentcounter shape, and astats::Traffic::stale: Contentfield on the existing#[non_exhaustive], serde-defaulted struct.kio::ConsumerWeak::poll, mirroringConsumer::poll. Additive: it lets a watcher register for changes without joining the consumer count.Wire behavior changes
The encoding is unchanged; no draft update is needed, and the existing draft already specifies the two age backstops. What a peer observes differently:
Oldwhen the group expires while its transport write is flow-control blocked, or while waiting on stream credit. It was previously held open.Test plan
nix develop --command just check/just check-allnix develop --command just test all: 3258 Rust tests pass, 2 skipped; all JS suites green.real_time_reads_a_live_stream_without_truncating_it(2s GOPs read as they arrive at the default budget, with the FIN landing after the successor's first frame) anda_budget_is_measured_from_the_readers_position(a straggler frame arriving after the next group opened still reaches a reader inside its budget).dev, 9-14ns here.(Written by Opus 5)