Skip to content
Merged
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 @@ -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) - `<moq-watch>` shows an origin refusal as an error instead of sitting offline
Expand Down
41 changes: 41 additions & 0 deletions quest/m1/wt-close-upstream.md
Original file line number Diff line number Diff line change
@@ -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