Surfaced by an adversarial review of #370. Pre-existing on main, not introduced by that PR.
Problem
Session::open_uni / Session::open_bi in rs/web-transport-wasm/src/session.rs wrap the browser's createUnidirectionalStream() / createBidirectionalStream() promise in a JsFuture.
Dropping that Rust future — a select! arm losing, a timeout, a cancelled task — does not cancel the underlying JS promise. The browser goes on to create the stream, but no SendStream owns it, so nothing runs the Drop impl that would send a FIN or reset. The stream sits open until the session closes, holding stream-count credit the peer granted.
Repeated cancelled opens can therefore consume credit permanently and wedge further stream creation on the session.
Why it matters more later
Today the window is one round-trip, so this is hard to hit. #370 sets waitUntilAvailable: true, which is currently inert because no engine implements it. Once Chromium ships it (pending https://crbug.com/487117768), a credit-blocked open stays pending indefinitely — so any cancellation while waiting for credit lands squarely in this window. The hazard goes from narrow to routine.
Sketch of a fix
Wrap the promise so cancellation is observable: if the Rust future has been dropped by the time the promise fulfills, immediately close or abort the send side, and cancel the readable for the bidirectional case.
A regression test would cancel a credit-blocked open and assert that later opens on the same session still succeed. Note that web-transport-wasm has no wasm-bindgen-test harness yet, so that needs standing up first.
(written by Opus 5)
Surfaced by an adversarial review of #370. Pre-existing on
main, not introduced by that PR.Problem
Session::open_uni/Session::open_biin rs/web-transport-wasm/src/session.rs wrap the browser'screateUnidirectionalStream()/createBidirectionalStream()promise in aJsFuture.Dropping that Rust future — a
select!arm losing, a timeout, a cancelled task — does not cancel the underlying JS promise. The browser goes on to create the stream, but noSendStreamowns it, so nothing runs theDropimpl that would send a FIN or reset. The stream sits open until the session closes, holding stream-count credit the peer granted.Repeated cancelled opens can therefore consume credit permanently and wedge further stream creation on the session.
Why it matters more later
Today the window is one round-trip, so this is hard to hit. #370 sets
waitUntilAvailable: true, which is currently inert because no engine implements it. Once Chromium ships it (pending https://crbug.com/487117768), a credit-blocked open stays pending indefinitely — so any cancellation while waiting for credit lands squarely in this window. The hazard goes from narrow to routine.Sketch of a fix
Wrap the promise so cancellation is observable: if the Rust future has been dropped by the time the promise fulfills, immediately close or abort the send side, and cancel the readable for the bidirectional case.
A regression test would cancel a credit-blocked open and assert that later opens on the same session still succeed. Note that
web-transport-wasmhas nowasm-bindgen-testharness yet, so that needs standing up first.(written by Opus 5)