fix: defer reactive root disposal in cache cleanups

Same nested-dispose-in-onCleanup bug as 7f36ac2481 but in three more
places: TerminalProvider.disposeAll, PromptProvider.disposeAll, and
scoped-cache.clear() (covers viewCache.clear and comments cache.clear).
All of them synchronously call createRoot dispose() on cached entries
inside onCleanup, which during a server switch nests into the outer
cleanNode cascade and throws TypeError at chunk-*.js:992.

Snapshot the pending disposers, clear the cache synchronously, and
fire the disposers on a microtask so the outer cleanup finishes first.
This commit is contained in:
LukeParkerDev
2026-04-19 13:19:12 +10:00
parent d04d13ea22
commit 33f5b80235
4 changed files with 32 additions and 10 deletions
+6 -3
View File
@@ -232,10 +232,13 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
const cache = new Map<string, PromptCacheEntry>()
const disposeAll = () => {
for (const entry of cache.values()) {
entry.dispose()
}
// Defer the dispose calls to a microtask; synchronous nested dispose
// inside a parent onCleanup corrupts solid-js's in-flight cleanNode
// traversal during mass remounts (see context/terminal.tsx for the
// same pattern).
const pending = Array.from(cache.values(), (entry) => entry.dispose)
cache.clear()
if (pending.length) queueMicrotask(() => pending.forEach((d) => d()))
}
onCleanup(disposeAll)
+8 -3
View File
@@ -364,10 +364,15 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
onCleanup(() => caches.delete(cache))
const disposeAll = () => {
for (const entry of cache.values()) {
entry.dispose()
}
// Snapshot disposers, then defer them to a microtask. When this runs
// from onCleanup during a parent remount (e.g. switching servers),
// calling dispose() synchronously starts a nested cleanNode cascade on
// a sibling root while the outer cascade is mid-traversal, corrupting
// solid-js's graph walk state and throwing `Cannot read properties of
// null (reading '1')` at chunk-*.js:992.
const pending = Array.from(cache.values(), (entry) => entry.dispose)
cache.clear()
if (pending.length) queueMicrotask(() => pending.forEach((d) => d()))
}
onCleanup(disposeAll)