diff --git a/drafts/draft-lcurley-moq-lite.md b/drafts/draft-lcurley-moq-lite.md index 029f98648a6..544b7c04030 100644 --- a/drafts/draft-lcurley-moq-lite.md +++ b/drafts/draft-lcurley-moq-lite.md @@ -226,6 +226,9 @@ See [Transports](#transports) for the bindings. How the underlying connection is authenticated is out-of-scope for this draft. +A subscriber that holds an Auth Stream and an Announce Stream needs credit for at least 4 concurrent bidirectional streams to establish a subscription. +AUTH and ANNOUNCE occupy two streams for the session lifetime, and TRACK is briefly held alongside SUBSCRIBE, requiring two more. + ## Transports {#transports} moq-lite defines five transport bindings. All five carry the same control and data streams defined elsewhere in this document; they differ only in how QUIC streams are multiplexed onto the underlying connection. diff --git a/quest/m1/stream-credit/README.md b/quest/m1/stream-credit/README.md index 90e86b12904..8b4eb210783 100644 --- a/quest/m1/stream-credit/README.md +++ b/quest/m1/stream-credit/README.md @@ -42,7 +42,7 @@ receiving every group, and nothing hanging. ## Required -- [A departed subscription releases its streams](/quest/m1/stream-credit/release-on-leave.md) - on lite-07 a subscription whose reader left frees its upstream streams, so 3 slots serve the next one +- [An abandoned subscription setup releases its streams](/quest/m1/stream-credit/release-on-leave.md) - release TRACK and pending SUBSCRIBE when demand leaves during setup - [Open streams](/quest/m1/stream-credit/open-streams.md) - each session lists its open streams and, where the transport can tell, which are fully acknowledged - [Reset fallback](/quest/m1/stream-credit/reset-fallback.md) - a waiting higher-priority open resets the lowest-priority stream, one reset in flight per waiting open - [Takeover](/quest/m1/stream-credit/takeover.md) - on lite-07 a higher-priority group takes over an acknowledged stream, and a group that lost its stream resumes diff --git a/quest/m1/stream-credit/release-on-leave.md b/quest/m1/stream-credit/release-on-leave.md index 7cd4de438f0..69b95024cb1 100644 --- a/quest/m1/stream-credit/release-on-leave.md +++ b/quest/m1/stream-credit/release-on-leave.md @@ -1,23 +1,28 @@ -# [S] A departed subscription releases its streams +# [S] An abandoned subscription setup releases its streams ## Goal -On lite-07, when a subscription's last reader leaves, every stream it held -upstream is released, so the credit returns for the next subscription. A -relay test with exactly 3 bidirectional stream slots serves a second -subscription after the first leaves, as it already does on moq-transport-22 -and on lite-07 with 4 slots. +On lite-07, a subscription whose last reader leaves while SUBSCRIBE is still +opening or awaiting its answer releases its upstream TRACK and pending +SUBSCRIBE, so their credit can serve later requests. ## Plan -Found 2026-11-04 by the scheduler's relay test -(`a_subscription_past_the_credit_is_served_once_credit_returns`, from -[moq-dev/moq#7894](https://github.com/moq-dev/moq/pull/7894)): on lite-07 with exactly 3 bidirectional slots, a second -subscription is never served after the first leaves, while 4 slots work and -moq-transport-22 works with 3. The likely cause is a stream the departed -subscription keeps open upstream (its request stream, or a companion such as -the Tracks stream) after its reader leaves. Reproduce it with the 3-slot -relay test first, find which stream stays open and on which side, fix it, and -set that relay test's credits back to the tight case. With opens now waiting -for credit instead of failing, a leaked stream stalls later subscriptions -silently, and low-credit peers such as browsers hit it first. +Decided 2026-11-05 (maintainer, /quest-plan): ✅ Keep the TRACK/SUBSCRIBE +handshake and document the minimum of four concurrent bidirectional streams. +AUTH and ANNOUNCE hold two slots for the session lifetime; TRACK is briefly +held alongside SUBSCRIBE. Three slots cannot establish the first subscription. +Rejected: metadata on SUBSCRIBE (overlaps other lite-07 work and saves no round +trip), and releasing TRACK early during a live setup. + +The established-subscription regression +`a_subscription_past_the_credit_is_served_once_credit_returns` already passes +with four slots. Keep that budget. The three-slot reproduction did not show a +departure leak. + +Local review identified remaining pending-setup cleanup: `ServeLoop::poll` in +`ServeMode::Establish` waits on establishment without observing lost demand, +while holding `HeldTrack`. Reproduce abandonment with at least four slots, +keeping the publisher and route live, then ensure both the TRACK hold and the +pending SUBSCRIBE registration are released when demand leaves. Preserve the +hold while demand remains. Cover Rust and its JS counterpart where applicable. diff --git a/rs/moq-net/tests/integration/stream_credit.rs b/rs/moq-net/tests/integration/stream_credit.rs index 88189608ceb..963b555f7d0 100644 --- a/rs/moq-net/tests/integration/stream_credit.rs +++ b/rs/moq-net/tests/integration/stream_credit.rs @@ -81,8 +81,8 @@ async fn one_stream_credit_serves_every_backlogged_group() { /// subscription past the publisher's credit is served once an earlier one leaves. #[kio::test] async fn a_subscription_past_the_credit_is_served_once_credit_returns() { - // Room for the bidirectional streams the session holds before the second subscription: - // the second then waits for one of the first's. + // lite-07 needs 4: session-long AUTH and ANNOUNCE plus TRACK held until SUBSCRIBE answers. + // Three is below the setup minimum; the second subscription waits for the first's credit. for (version, credit) in [("moq-lite-07-wip", 4), ("moq-transport-22", 3)] { let version: Version = version.parse().unwrap(); let publisher = origin(1);