From 0826108a707d6f9a12b1654861ad1b1d11d199ea Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 23:20:42 -0800 Subject: [PATCH 1/5] chore(quest): claim quest/m1/stream-credit/release-on-leave.md Quest-Claim: 6b7d2ce6d5b0e858 From 83cb9d70ba2c1f23e05c6f1259f40f4a2ceb304c Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 23:43:16 -0800 Subject: [PATCH 2/5] docs(quest): record lite-07 three-slot setup blocker Co-Authored-By: GPT-6.1 Sol --- quest/m1/stream-credit/release-on-leave.md | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/quest/m1/stream-credit/release-on-leave.md b/quest/m1/stream-credit/release-on-leave.md index 7cd4de438f0..2780af4fbe4 100644 --- a/quest/m1/stream-credit/release-on-leave.md +++ b/quest/m1/stream-credit/release-on-leave.md @@ -21,3 +21,48 @@ 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. + + +### Investigation + +2026-11-04: reproduced before any fix with the existing relay regression, changing +only lite-07's credit from 4 to exactly 3. The command +`just rs test -p moq-net --test integration -E 'test(a_subscription_past_the_credit_is_served_once_credit_returns)'` +failed: "moq-lite-07-wip: the waiting subscription never got credit". +The client stream types before departure were `[7, 1, 6]`: AUTH, ANNOUNCE, TRACK. +The first SUBSCRIBE had not reached the publisher. The initial diagnosis of a +leak after an established subscription was therefore incomplete. + +`ServeLoop::poll` waits in `ServeMode::Establish` without checking whether demand +left, keeping the answered `HeldTrack` open. An experimental abandonment check +and explicit cancellation of the opening registration released that TRACK and let +the second TRACK resolve. With the first publisher track kept alive, the same +regression then failed later: "no group after credit returned". The second +SUBSCRIBE was now blocked behind its own TRACK. This confirms that departure +cleanup alone does not achieve the Goal. The incomplete implementation and test +edits were removed pending the contract decision above. + +Before claiming, checked the current patches of +[moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) and +[moq-dev/moq#7737](https://github.com/moq-dev/moq/pull/7737). Their stale-group and +pending-join changes touch the files but do not implement this cleanup or remove +the TRACK/SUBSCRIBE credit dependency. + + +## Waiting + +- Maintainer: choose the stream-budget contract before implementation. Investigation + on main `0834a1eab0` found that three slots cannot establish even the first media + subscription: AUTH and ANNOUNCE hold two, TRACK holds the third until SUBSCRIBE + answers, and SUBSCRIBE needs a fourth. Options: + 1. **(Recommended)** Make lite-07 subscription setup fit three slots by returning + immutable track metadata on SUBSCRIBE, removing the companion TRACK from this + path. This revises the unpublished wire and needs Rust/JS and draft updates; + retain standalone TRACK for info-only queries. + 2. Keep the separate TRACK/SUBSCRIBE handshake and scope this quest to departure + cleanup with four slots, documenting its minimum credit requirement. This + abandons the present three-slot Goal. + 3. Release TRACK before SUBSCRIBE opens when credit is exhausted. This creates a + demand gap for on-demand producers and needs an explicit decision about their + lifetime; do not silently weaken the current hold guarantee. + Check: record the chosen contract in Plan, then resume implementation. From 8bd04d551538837f7e7ce57c0a5b574ad8ef4d7d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 5 Nov 2026 08:49:46 -0800 Subject: [PATCH 3/5] docs(lite): explain the four-stream setup minimum Co-Authored-By: GPT-6.1 Sol --- drafts/draft-lcurley-moq-lite.md | 4 ++ quest/m1/stream-credit/README.md | 1 - quest/m1/stream-credit/release-on-leave.md | 68 ------------------- rs/moq-net/tests/integration/stream_credit.rs | 4 +- 4 files changed, 6 insertions(+), 71 deletions(-) delete mode 100644 quest/m1/stream-credit/release-on-leave.md diff --git a/drafts/draft-lcurley-moq-lite.md b/drafts/draft-lcurley-moq-lite.md index 029f98648a6..d6847c14533 100644 --- a/drafts/draft-lcurley-moq-lite.md +++ b/drafts/draft-lcurley-moq-lite.md @@ -226,6 +226,10 @@ See [Transports](#transports) for the bindings. How the underlying connection is authenticated is out-of-scope for this draft. +A lite-07 session needs credit for at least 4 concurrent bidirectional streams. +AUTH and ANNOUNCE occupy two streams for the session lifetime, and establishing a subscription briefly holds TRACK alongside SUBSCRIBE, requiring two more. +Three streams cannot establish the first subscription; this is a minimum setup budget, not a stream leak. + ## 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 d162b732ed5..2a342ce9a1e 100644 --- a/quest/m1/stream-credit/README.md +++ b/quest/m1/stream-credit/README.md @@ -39,5 +39,4 @@ 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 - [A higher-priority group makes room](/quest/m1/stream-credit/make-room.md) - it takes over a lower-priority stream, or one is reset, without over-cancelling diff --git a/quest/m1/stream-credit/release-on-leave.md b/quest/m1/stream-credit/release-on-leave.md deleted file mode 100644 index 2780af4fbe4..00000000000 --- a/quest/m1/stream-credit/release-on-leave.md +++ /dev/null @@ -1,68 +0,0 @@ -# [S] A departed subscription 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. - -## 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. - - -### Investigation - -2026-11-04: reproduced before any fix with the existing relay regression, changing -only lite-07's credit from 4 to exactly 3. The command -`just rs test -p moq-net --test integration -E 'test(a_subscription_past_the_credit_is_served_once_credit_returns)'` -failed: "moq-lite-07-wip: the waiting subscription never got credit". -The client stream types before departure were `[7, 1, 6]`: AUTH, ANNOUNCE, TRACK. -The first SUBSCRIBE had not reached the publisher. The initial diagnosis of a -leak after an established subscription was therefore incomplete. - -`ServeLoop::poll` waits in `ServeMode::Establish` without checking whether demand -left, keeping the answered `HeldTrack` open. An experimental abandonment check -and explicit cancellation of the opening registration released that TRACK and let -the second TRACK resolve. With the first publisher track kept alive, the same -regression then failed later: "no group after credit returned". The second -SUBSCRIBE was now blocked behind its own TRACK. This confirms that departure -cleanup alone does not achieve the Goal. The incomplete implementation and test -edits were removed pending the contract decision above. - -Before claiming, checked the current patches of -[moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) and -[moq-dev/moq#7737](https://github.com/moq-dev/moq/pull/7737). Their stale-group and -pending-join changes touch the files but do not implement this cleanup or remove -the TRACK/SUBSCRIBE credit dependency. - - -## Waiting - -- Maintainer: choose the stream-budget contract before implementation. Investigation - on main `0834a1eab0` found that three slots cannot establish even the first media - subscription: AUTH and ANNOUNCE hold two, TRACK holds the third until SUBSCRIBE - answers, and SUBSCRIBE needs a fourth. Options: - 1. **(Recommended)** Make lite-07 subscription setup fit three slots by returning - immutable track metadata on SUBSCRIBE, removing the companion TRACK from this - path. This revises the unpublished wire and needs Rust/JS and draft updates; - retain standalone TRACK for info-only queries. - 2. Keep the separate TRACK/SUBSCRIBE handshake and scope this quest to departure - cleanup with four slots, documenting its minimum credit requirement. This - abandons the present three-slot Goal. - 3. Release TRACK before SUBSCRIBE opens when credit is exhausted. This creates a - demand gap for on-demand producers and needs an explicit decision about their - lifetime; do not silently weaken the current hold guarantee. - Check: record the chosen contract in Plan, then resume implementation. 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); From 27e2747155d82e4307dba54c156fc6bc1b5a30d2 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 5 Nov 2026 08:51:03 -0800 Subject: [PATCH 4/5] docs(lite): keep setup guidance focused on stream credit Co-Authored-By: GPT-6.1 Sol --- drafts/draft-lcurley-moq-lite.md | 1 - 1 file changed, 1 deletion(-) diff --git a/drafts/draft-lcurley-moq-lite.md b/drafts/draft-lcurley-moq-lite.md index d6847c14533..e2a7dc43631 100644 --- a/drafts/draft-lcurley-moq-lite.md +++ b/drafts/draft-lcurley-moq-lite.md @@ -228,7 +228,6 @@ How the underlying connection is authenticated is out-of-scope for this draft. A lite-07 session needs credit for at least 4 concurrent bidirectional streams. AUTH and ANNOUNCE occupy two streams for the session lifetime, and establishing a subscription briefly holds TRACK alongside SUBSCRIBE, requiring two more. -Three streams cannot establish the first subscription; this is a minimum setup budget, not a stream leak. ## Transports {#transports} moq-lite defines five transport bindings. From 033f19140cc9c0c8b842fdb15b68a4e271a566af Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 5 Nov 2026 08:55:11 -0800 Subject: [PATCH 5/5] docs(quest): retain pending subscription abandonment cleanup Co-Authored-By: GPT-6.1 Sol --- quest/m1/stream-credit/README.md | 2 ++ quest/m1/stream-credit/release-on-leave.md | 28 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 quest/m1/stream-credit/release-on-leave.md diff --git a/quest/m1/stream-credit/README.md b/quest/m1/stream-credit/README.md index 2a342ce9a1e..5091fe4239a 100644 --- a/quest/m1/stream-credit/README.md +++ b/quest/m1/stream-credit/README.md @@ -39,4 +39,6 @@ receiving every group, and nothing hanging. ## Required +- [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 + - [A higher-priority group makes room](/quest/m1/stream-credit/make-room.md) - it takes over a lower-priority stream, or one is reset, without over-cancelling diff --git a/quest/m1/stream-credit/release-on-leave.md b/quest/m1/stream-credit/release-on-leave.md new file mode 100644 index 00000000000..69b95024cb1 --- /dev/null +++ b/quest/m1/stream-credit/release-on-leave.md @@ -0,0 +1,28 @@ +# [S] An abandoned subscription setup releases its streams + +## Goal + +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 + +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.