Repository navigation
tab freezes when an awaited value feeding a chart changes (async svelte + kit remote functions) #895
Description
Activity
thanks @teidesu, taking a look
definitely feeling like a Svelte/Kit regression, but not 100% sure yet. I'll ask in Discord but might be best to open an issue as well.
btw, I also created a fork of your repo, cleaned up a few things and split the route into 2 - async w/ remote functions and sync with client-side data.
@teidesu fix might be coming soon... sveltejs/svelte#18558 (comment)
Reacted by alina 🌸🦴You may have been chasing a bug that was never in layerchart. I hit the same freeze from an app of my own, and it reduces out of the library entirely.
The cause is in SvelteKit, in
Query.set()(packages/kit/src/runtime/client/remote-functions/query/instance.svelte.js). It unconditionally replaced its internal promise with a fresh resolved one, including when a request was already in flight and#clear_pending()had just resolved the promise that awaiting consumers were suspended on. Replacing it as well re-suspended an already-awaiting consumer in a second batch while the first was still pending. Two live batches put Svelte into "time travelling" mode, where deriveds are deliberately never marked clean, so everything downstream of the query recomputes on every read instead of once, at a cost exponential in the depth of the derived graph.Two things that surprised me on the way. It is not an infinite loop: counting inside Svelte's runtime,
update_effectstays flat (59 with layerchart, 6 with a reduced fixture) whileupdate_derivedclimbs into the millions. It is one effect execution doing exponentially much work. And it is not layerchart specific: it reduces to ~45 lines with no dependencies, a derived graph alternating nodes that return a fresh object with nodes that return a constant, so the constant ones stay permanently dirty against the ones above them. Depth 6 costs 14 recomputations when memoization holds and 27,305 when it does not; depth 12 wedges the tab. layerchart simply has a graph deep enough to make it fatal, and is doing nothing unusual.Fix is sveltejs/kit#16855 (details and measurements there). With it, my reproduction goes from
FROZENon the first press to25ms/11bars / 25ms/12bars / 18ms/13bars / 27ms/14bars, redrawing every press. It works on stocksvelte@5.56.8, so sveltejs/svelte#18558 is not required for this one, though I have not tested whether it would also fix it.One caveat before you close anything: I verified this against
layerchart@2.0.2and my own reproduction, on@sveltejs/kit3.0.0-next.23, and 2.64.0 is clean. @teidesu's report here is against Kit 2.63, so if that one still freezes it may be a different path from the one #16855 closes, and I am not claiming to have explained it. There is an install link on the PR if it is cheap to check.Reacted by Sean Lynch
the bug
if i change the argument of an awaited SvelteKit remote-function
query()inside a<svelte:boundary>, that renders a LayerChart chart inside - the tab hangs in what i can only assume to be a main-thread infinite js loop: cpu usage is at 100%, and the page stays frozen indefinitely. initial render (ssr and hydration) is (usually) fine; the freeze happens only when the awaited value re-resolves while the chart subtree is alive.Filing here because LayerChart's derived graph is what the loop walks, but this might also be a svelte/sveltekit issue
repro
if the tab didn't freeze right away - click the button once.
config:
compilerOptions.experimental.async: true+kit.experimental.remoteFunctions: true.versions
findings by claude fable 5
What I measured
Patched
update_derivedin svelte's client runtime with a counter: page load is ~13k derived updates, then a single click drives it past 2,000,000 — still climbing when I made the counter throw to free the tab. So it's an unboundedderived re-evaluation storm, not a slow render.
Pausing the wedged renderer (debugger attached before the click) shows a ~780-frame stack cycling through:
interleaved with nested
$state-proxyownKeysrecursion throughexclude_from_object(rest-props destructuring) in LayerChart's component chain (restProps = user_derived(() => exclude_from_object(props, [...]))etc. in the Chart component). The graph never settles — deriveds producing fresh object identities keep re-capturing into the async batch/fork during the boundary re-render.Notes / controls
async functionof identical shape (sameawaitin the boundary, same 100ms delay) does not reproduce. The remote-function query object is load-bearing.{#key data}around the chart does not help (the remount itself storms).motion: 'none'doesn't help either.$effect.pending()is not required (this repro has none), but pages reading it freeze the same way.sveltejs/svelte#17883(same symptom, closed),sveltejs/svelte#16548,#626,#630— all marked fixed, but this variant reproduces on latest svelte.