fix: make query.live stream teardown safe - #16790
Draft
Nic-Polumeyv wants to merge 5 commits into
Draft
Conversation
|
Install the latest version of pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/e3b23e4a5546951b85bbd306b8345f13518646daOpen in |
🦋 Changeset detectedLatest commit: e3b23e4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Nic-Polumeyv
marked this pull request as draft
August 13, 2026 21:37
Nic-Polumeyv
force-pushed
the
live-query-teardown
branch
from
August 13, 2026 22:21
db195c6 to
bd6923a
Compare
This was referenced Aug 13, 2026
Rich-Harris
pushed a commit
that referenced
this pull request
Aug 13, 2026
…6793) A request whose body has been fully read never aborts its signal on client disconnect, because `readableAborted` requires the stream to die before `end`. Give `getRequest` the response so disconnects are detected on the response side instead — `writableEnded` rather than `writableFinished` because HTTP/2 marks cancelled streams as finished. Split out of #16790.
Nic-Polumeyv
marked this pull request as ready for review
August 14, 2026 15:43
| try { | ||
| while (true) { | ||
| pending ??= generator.next(); | ||
| const winner = await next_or_keep_alive(pending); |
Contributor
Nic-Polumeyv
marked this pull request as draft
August 14, 2026 17:01
Nic-Polumeyv
force-pushed
the
live-query-teardown
branch
from
August 14, 2026 22:19
bd6923a to
e3b23e4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In the live query handler in
runtime/server/remote-functions.js,pullchecksevent.request.signal.abortedonce, before awaitinggenerator.next(). After that it uses the readable stream controller, which may be closed by then (adapters cancel the response body when the client disconnects, and that can happen whilepullis suspended on the generator). Enqueueing the value throws "Invalid state: Controller is already closed", and thecatchpath then callssend()on the same closed controller. Separately, the generator has no way to notice the teardown: itsevent.request.signalbelongs to the incoming request and doesn't abort when the response stream is cancelled, so a generator parked on anawaitkeeps running and itsfinallynever executes (generator.return()queues behind the pendingnext()).The fix separates producing values from writing them to the stream. Everything the response sends now comes from a
framesasync generator that yields SSE strings and never sees the controller; keep-alives come from racinggenerator.next()against a timer instead of a controller-holding callback. The newstream_from_iteratorhelper inruntime/server/utils.jsis the only code that touches the controller, and it re-checks that the stream is still open after eachawait, dropping late chunks instead of enqueueing into a closed controller. Teardown from either side (request abort or stream cancellation) aborts a single internalcancellationcontroller, and the user's generator runs withnew Request(event.request, { signal: cancellation.signal }), so code awaiting inside it can observe the disconnect and clean up.Fixes #16778