fix: handle cancellation in injectRSCPayload to avoid post-abort server crash#15286
Merged
jacob-ebey merged 2 commits intoJul 9, 2026
Merged
Conversation
When a client aborts a document request, the readable side of the injectRSCPayload TransformStream is cancelled while a buffered flush (setTimeout(..., 0)) may still be pending. The timer then enqueues into a cancelled stream, throwing "TypeError: Invalid state: Unable to enqueue" from a timer callback — an unhandled rejection that kills the server process. Add a cancel() handler that clears the pending flush, drops the buffer, and cancels the underlying RSC payload stream, plus cancellation guards on the flush timer and the RSC writer loop. Fixes remix-run#15275
Contributor
✅ CLA SignedThanks for signing the Contributor License Agreement. |
Contributor
✅ Change File FoundOne or more change files found.
|
Merged
Contributor
|
🤖 Hello there, We recently published version Thanks! |
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.
Fixes #15275
Problem
In RSC Framework Mode, an aborted/cancelled document request can crash the production server:
injectRSCPayloadbuffers HTML chunks and flushes them in asetTimeout(..., 0)callback. When the client aborts, the readable side of theTransformStreamis cancelled while that flush is still pending — the timer then callscontroller.enqueue()on a cancelled stream. Because the timer callback isasync, the throw becomes an unhandled rejection, which exits the Node process. The transformer had nocancel()hook, so the pending timer and the RSC payload stream were never cleaned up.Fix
cancel(reason)handler to the transformer: it flags cancellation, clears the pending flush timer, drops the buffered chunks, cancels the underlying RSC payload stream (via its reader when streaming has started), and resolves the flight-data promise.writeRSCStreamwrite loop so late timers / in-flight reads become no-ops after cancellation instead of enqueueing into a cancelled stream.The transformer is typed with a small extension (
Transformer & { cancel? }) since the repo's current TS lib doesn't yet include the standardTransformer#cancelmember.Tests
Added
packages/react-router/__tests__/rsc/html-stream-test.tswith two regression tests, covering an abort while a flush is pending (the reported crash) and an abort mid-RSC-stream. Both assert no unhandled rejection escapes a timer and that the RSC payload stream gets cancelled.Invalid state: Unable to enqueuerejection).rsc/server-test.tssuite currently fails to load on a cleanmaincheckout in the same way, unrelated to this change.)A change file is included (
patch.rsc-html-stream-cancel.md), and I've signed the CLA incontributors.ymlas part of this PR.