fix(routing): decode the flash cookie once per request, not per router - #647
Merged
Merged
Conversation
A server render can re-create the router while it retries a suspension.
A `useSubmissions` read in a component body under `<Errored>` suspends on
the async flash decode; the server boundary escalates that to the nearest
retryable hole, and in a document shell that hole is `{props.children}`
above the router, so each retry renders a new router. The decode was cached
on the router instance, so every new router started it again: the seeding
read never settled, each pass added a fresh blocking promise, and the
stream retried until the process ran out of memory. Each new router also
appended another one-shot `Set-Cookie` clear.
The cookie header and its decode now live in a WeakMap keyed by the request
event, shared by every router created for that request.
The server test config now compiles hydratable, as apps do. The hang only
reproduces under the hydratable server output, which is what places the
retry above the router; the other server specs pass unchanged under it.
Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: bd4d58c 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 |
Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Rendering a page after a failed no-JavaScript form post hangs the server: the stream retries forever, allocating until the process runs out of memory. Found in
examples/todos-serverin solidjs/solid (#3717) once the flash cookie had a secret to be written with.Cause
A server render can re-create the router while it retries a suspension. The trigger is ordinary app code:
useSubmissionsinHeader's body reads the flash seeding memo, which suspends on the async (encrypted) decode. The server<Errored>escalates a suspension in its children to the nearest retryable hole, and under the hydratable server output that hole is the document's{props.children}, above the router — so each retry renders a new router. The decode was cached on the router instance, so each new router started it again: the read never settled, each pass added a fresh blocking promise, and the stream retried without end. Each new router also appended another one-shotSet-Cookieclear.The cookie header and its decode now live in a
WeakMapkeyed by the request event, shared by every router created for that request.Re-creating components above a suspension on retry is how the server renderer already behaves (core's
lazy()asset lookup was made per-request for the same reason); whether<Errored>should widen the retry that far is a separate question I'm raising in solidjs/solid.Public API changes
None. Behavior: the flash cookie is detected, cleared and decoded once per request instead of once per router instance — a render that re-creates the router now settles, and appends a single
Set-Cookieclear.Tests
test/server/flash-decode-per-request.spec.tsx: the shape above, streamed with a flash cookie. Asserts the outcome renders, the router was in fact rendered more than once, the decode ran once and the cookie was cleared once. Times out before the fix.Server suite 77 passed, unit suite 469 passed,
tscclean. Independent of #646.