diff --git a/quest/m1/README.md b/quest/m1/README.md index 63cb0a6250..641d047c02 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -29,6 +29,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Graceful close in bindings](/quest/m1/bindings-graceful-close.md) - on dev, `shutdown` drains a session in moq-ffi and every wrapper like Rust, so the wrappers keep the tail of a publish - [Close codes](/quest/m1/close-codes.md) - a client sees the peer's application close code over WebSocket and raw QUIC, like WebTransport - [Raw stream codes](/quest/m1/raw-stream-codes.md) - raw QUIC stream resets and stops carry the application's code, not an HTTP/3-mapped one +- [WebTransport close upstream](/quest/m1/wt-close-upstream.md) - web-transport-moq delivers the close capsule itself, and moq-tokio's `CLOSE_LINGER` is deleted - [Live in apps](/quest/m1/announce-live-apps.md) - the demo and `@moq/room` show "no broadcasts" from the `live` marker, which waits for the first session on page load - [IETF hidden default](/quest/m1/ietf-hidden-default.md) - a moq-transport peer without the MoQ Hidden option is advertised hidden namespaces; one with it filters per subscription - [Watch refusal](/quest/m1/watch-refusal.md) - `` shows an origin refusal as an error instead of sitting offline diff --git a/quest/m1/wt-close-upstream.md b/quest/m1/wt-close-upstream.md new file mode 100644 index 0000000000..a277c17cb0 --- /dev/null +++ b/quest/m1/wt-close-upstream.md @@ -0,0 +1,41 @@ +# [S] WebTransport close delivers its capsule upstream + +## Goal + +Closing a WebTransport session delivers CLOSE_WEBTRANSPORT_SESSION, so a +browser sees the close code and reason, even when the caller drops its last +session handle right after `close()`. The fix lives in `web-transport-moq`, +and moq-tokio's `CLOSE_LINGER` workaround is deleted. + +## Plan + +[#4429](https://github.com/moq-dev/moq/pull/4429) found the root cause: +`web-transport-moq` 1.3.2's `Session::close` sends the capsule from a spawned +task that holds the connection and the CONNECT stream, but not the H3 +`Settings` (control and QPACK streams). Dropping the last `Session` right +after `close()` finishes the control stream in the same flight as the +capsule, and Chromium reports "Connection lost." with no code. #4429 worked +around it in `rs/moq-tokio/src/transport.rs` by keeping a session clone in a +detached task until `closed()` resolves, capped at a 10s `CLOSE_LINGER`. + +Decided 2026-09-29: fix it at the source and remove the timeout workaround. + +- In moq-dev/noq, the close path keeps whatever the capsule needs alive until + it is delivered, without the caller's help. Its regression test drops the + session right after `close()` and asserts the peer reads the capsule + before the H3 control stream ends; it must fail on 1.3.2. A Rust peer + alone proves nothing: `session_close_surfaces_a_rejection_code` in + `rs/moq-tokio/tests/broadcast.rs` passed on 1.3.2 without the linger, + because only Chromium treats the control stream's end as fatal. Add a + browser check too if the playwright harness makes it cheap. +- Release `web-transport-moq` 1.3.x and bump the pin on `main` (2.x on `dev` + if it has moved). +- Delete `CLOSE_LINGER`, its task, and its mock-session tests from + moq-tokio. + +Public API: none. Wire: none. + +## Related + +- [Close codes on every transport](/quest/m1/close-codes.md) - the same symptom over qmux and raw QUIC +- [UnknownSession log flood](/quest/m1/unknown-session-logs.md) - another `web-transport-moq` release and pin bump