You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
π Bug Report β Runtime APIs: client disconnect never calls a response stream's cancel() with typescript_implemented_streamsΒ #8038
With typescript_implemented_streams, when the client disconnects while a Worker is streaming a response body from a JavaScript ReadableStream, the source's cancel() is never called. Pulling stops, but the cancel hook never runs, even several seconds later. With the C++ streams implementation, cancel() runs as soon as the client goes away.
Streaming handlers rely on cancel() for cleanup when the client leaves, for example to clear an interval feeding server-sent events, abort an upstream fetch(), or close a database cursor. Under the flag that cleanup silently never runs. We found it while testing vinext (Next.js on Workers). It also reproduced in a deployed Worker with the flag, where requests may land on different isolates, so that check is less conclusive.
The same bug reproduces without an HTTP client, through a service binding, so it fits in a workerd test. The caller cancels the response body, and the upstream worker's source never sees cancel(). It depends only on the responding worker: with the flag on the upstream alone it fails, and with it on the caller alone it passes.
worker.js:
exportdefault{asynctest(ctrl,env){constresponse=awaitenv.UPSTREAM.fetch("http://upstream/stream");awaitresponse.body.cancel("done reading");awaitnewPromise((resolve)=>setTimeout(resolve,100));conststatus=await(awaitenv.UPSTREAM.fetch("http://upstream/status")).text();console.log(`upstream source cancelled: ${status}`);if(status!=="true")thrownewError("cancel() never reached the upstream source");},};
With the flag: upstream source cancelled: false / [ FAIL ]. With compatibilityFlags = []: upstream source cancelled: true / [ PASS ] (workerd 1.20261027.1). The same happens when the caller breaks out of for await (const chunk of response.body) or aborts a pipeTo().
Activity
james-elicx commentedon Oct 27, 2026
The same bug reproduces without an HTTP client, through a service binding, so it fits in a
workerd test. The caller cancels the response body, and the upstream worker's source never seescancel(). It depends only on the responding worker: with the flag on the upstream alone it fails, and with it on the caller alone it passes.worker.js:upstream.js:config.capnp:With the flag:
upstream source cancelled: false/[ FAIL ]. WithcompatibilityFlags = []:upstream source cancelled: true/[ PASS ](workerd 1.20261027.1). The same happens when the caller breaks out offor await (const chunk of response.body)or aborts apipeTo().vicb commentedon Oct 28, 2026
@james-elicx was faster than me :)
I have a repro at https://github.com/vicb/workerd-TSStream-disconnect if it helps (see the README for info and steps to reproduce)
Cancel a queued TS stream when its pump is dropped