Skip to content

fix(hydrate): wait for waitingElements to drain before destroyWindow - #6865

Open
shoaibyazdani wants to merge 1 commit into
stenciljs:mainfrom
shoaibyazdani:wait-for-waiting-elements
Open

shoaibyazdani wants to merge 1 commit into
stenciljs:mainfrom
shoaibyazdani:wait-for-waiting-elements

Conversation

@shoaibyazdani

Copy link
Copy Markdown

What

When renderToString hits its timeout, the runner goes finalizeHydratedestroyWindowMockWindow.closeresetWindow, which sets globalThis.fetch = null (and friends) on the live window. Components that are still in flight — typically mid-await on componentOnReady or on their own fetch — wake up against the torn-down globalThis and start throwing.

Approach

Apply the maintainer's preferred direction (option 2 from #6864): drain waitingElements to size 0 inside hydratedComplete before afterHydrate fires (which triggers destroyWindow upstream). A hard ceiling of opts.timeout ?? 15000 ms caps the wait so a stuck component can't keep a render's graph alive indefinitely.

hydratedComplete becomes async; hydratedError fires the drain fire-and-forget with a .catch to preserve the prior throw-safety.

Tests

  • New unit test at src/hydrate/platform/test/drain-waiting-elements.spec.ts
  • pnpm typecheck clean against hydrate-app.ts.

Closes #6864

…tenciljs#6864)

When `renderToString` fires its timeout, `finalizeHydrate` calls
`destroyWindow` → `MockWindow.close` → `resetWindow`, which deletes
`globalThis.fetch` and friends on the live window. Components that are
still in flight — typically mid-await on `componentOnReady` or on their
own `fetch` — wake up against a torn-down globalThis and start throwing.
The retained render object graph (which still holds references through the
component closures) prevents GC, and under concurrent `renderToString`
calls the heap climbs until OOM.

Apply the maintainer's preferred direction (option 2 from stenciljs#6864):
drain `waitingElements` to size 0 inside `hydratedComplete` before
allowing `afterHydrate` to fire (which triggers `destroyWindow` upstream
in `render.ts`. A hard ceiling of `opts.timeout ?? 15000` ms caps the
wait so a stuck component can't keep a render's graph alive for minutes.

`hydratedComplete` becomes async; `hydratedError` fires-and-forgets
with a `.catch` so the existing throw-safety stays intact.

Includes a focused unit test at
`src/hydrate/platform/test/drain-waiting-elements.spec.ts` covering
the empty-set fast path, drain-on-emptying, and the ceiling cap.

Closes stenciljs#6864

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hydrate timeout resets the mock window while component promises are still pending

1 participant