Skip to content

Chart fed from an awaited remote query freezes the tab (regression in 3.0.0-next; fine on 2.64.0) #16854

Description

@half2me

Describe the bug

A component with a deep reactive graph (here layerchart's BarChart) whose data comes from a remote query that the page also awaits locks the browser's main thread. There is no error, no failed boundary and no recovery short of a reload. The update never lands and the tab never converges. A press that does not wedge leaves the chart on its old data, and the next one wedges. Sometimes the first press already does.

This is a regression in SvelteKit 3. The same page, same layerchart, same svelte, same vite, is fine on @sveltejs/kit 2.64.0. inside is the mode that freezes, plain is a control that redraws the same chart from a plain awaited promise:

svelte 5.56.7 svelte 5.56.9
@sveltejs/kit 2.64.0 inside fine, ~30ms/press inside fine, ~30ms/press
@sveltejs/kit 3.0.0-next.23 inside FROZEN inside FROZEN

It tracks the Kit version, not the Svelte version. Raw output is logs/version-matrix.txt in the reproduction, produced by logs/version-matrix.sh. One caveat on that table: under Kit 2 the latched mode renders no chart at all (its $effect latch never populates), so inside is the only like-for-like cell across the rows. It is also the shape the real application used.

It also needs three ingredients together, which is what took a while to pin down. Two neighbouring shapes are fine and look like counter-examples:

Chart data comes from Awaited region on the page Result
a plain Promise, awaited in the boundary yes (the promise) fine
local $state yes (a remote query) fine
the remote query's value, via an $effect latch yes (the same query) freeze
the remote query's value, awaited in the boundary yes (the same query) freeze

So it is not "an await on the page". The second row awaits a remote query that re-resolves on every interaction, right beside a chart that redraws on every interaction, and stays responsive. And it is not "an awaited promise feeding a chart". The first row does exactly that, redraws every time, and is fine. It is specifically the chart's data being downstream of a remote query the page awaits.

Reproduction

https://github.com/half2me/svelte-layerchart-bug-repro

pnpm install
pnpm dev            # http://localhost:5199

Pick a mode with the radio buttons, then press bump a few times. node verify.mjs drives all four:

plain    33ms/11bars / 24ms/12bars / 29ms/13bars / 28ms/14bars / 28ms/15bars
beside   113ms/11bars / 88ms/12bars / 98ms/13bars / 96ms/14bars / 93ms/15bars
latched  FROZEN
inside   FROZEN

The bar count is part of the evidence. The data is 10 + clicks rows, so 11bars, 12bars and so on is how you know the chart really redrew rather than the mode quietly doing nothing. In the failing modes a press that does not wedge leaves the chart at 10bars, meaning the update never landed at all, and the next press wedges.

Logs

node instrument.mjs counts recomputations inside Svelte's client runtime. instrument/patch-svelte.mjs adds counters to update_derived and is_dirty (Vite serves Svelte from source in dev, and the probe keeps streaming after the tab wedges). Counters are reset before each press, so each line is the cost of that one press. Full output in logs/instrument.txt:

=== mode: plain ===
  page load          update_derived=3430  is_dirty=723853
  click 1  responded in    31ms  update_derived=    1170  is_dirty=    95968  bars=11
  click 2  responded in    21ms  update_derived=    1041  is_dirty=    95513  bars=12
  click 3  responded in    27ms  update_derived=    1152  is_dirty=   101858  bars=13

=== mode: beside ===
  page load          update_derived=3431  is_dirty=724198
  click 1  responded in   102ms  update_derived=    1189  is_dirty=    99435  bars=11
  click 2  responded in    79ms  update_derived=    1057  is_dirty=    98798  bars=12

=== mode: inside ===
  page load          update_derived=3430  is_dirty=723850
  click 1  WEDGED. Tab stopped answering, watching the probe stream.
    [probe] update_derived=250000  is_dirty=1834196  t=4953ms
    [probe] update_derived=500000  is_dirty=3593596  t=7067ms
    [probe] update_derived=1000000 is_dirty=6991365  t=10267ms
    [probe] update_derived=2000000 is_dirty=13787272 t=16548ms
    [probe] update_derived=3000000 is_dirty=20581544 t=23011ms
    [probe] update_derived=4500000 is_dirty=30774685 t=32287ms
    still wedged after 25s

About 1,100 derived recomputations for a press that works, against 4.5 million and climbing for one that does not, with no sign of converging.

Which deriveds, at the 1,000,000 mark:

[probe:top] recomputations  derived
[probe:top]    687650  () => this._getStackConfig()
[probe:top]    137026  () => { const config = get(this.#stackConfig); if (!config || !config.layout.startsWith("stack")) re
[probe:top]     87965  () => context().series.divergingEdgeKeys ? context().series.divergingEdgeKeys.has(get(s).key) ? "edg
[probe:top]     40255  () => { const propsData = this.props.data; if (equals(propsData, null, false) && (!Array.isArray(pro
[probe:top]     17593  () => extractLayerProps(restProps, "lc-bars-bar")
[probe:top]     12580  () => exclude_from_object(props, [ "ssr", "pointerEvents", "width", "height", "position", "children"
[probe:top]      6227  () => this.resolveDomain("y")

and a stack sample from inside the loop:

[probe:stack]
    at update_derived (svelte/src/internal/client/reactivity/deriveds.js)
    at get (svelte/src/internal/client/runtime.js)
    at layerchart.js:3551
    at update_reaction (svelte/src/internal/client/runtime.js)
    at execute_derived (svelte/src/internal/client/reactivity/deriveds.js)
    at update_derived (svelte/src/internal/client/reactivity/deriveds.js)
    at get (svelte/src/internal/client/runtime.js)
    at SeriesState.getStackValue (layerchart.js:3613)

Everything hot is layerchart's own chart-context deriveds re-entering themselves, with the is_dirty walk on top. Nothing in application code is looping. The app's own deriveds each recompute a handful of times while the tab is stuck.

Unwrapping the query into a plain promise first, getRows(args).then((r) => $state.snapshot(r)), does not help either, which suggests the cause is the query's own reactive machinery rather than the value it hands on.

Things that do not help

From the original application, all still freeze:

  • moving the chart out of the boundary into a sibling branch of the page
  • feeding it from $state latched in an $effect, so its update lands after the batch
  • remounting with {#key} instead of updating
  • renderContext="canvas" (one canvas draw instead of a component per bar)
  • extracting the awaiting markup into a child component
  • layerchart@2.2.0 instead of 2.0.2 (better, still freezes)

The only thing that resolves it is not awaiting that query on that page: reading it through .current, latching it, and rendering from the latch, which costs the page its server-rendered content.

What I could not reduce

I could not get below layerchart. A synthetic component with a comparably deep derived DAG (depth 12, fan-in 3, every node returning a fresh object, 90 leaf consumers), with and without an $effect measurement cycle, does not reproduce it in any mode. So I cannot rule out that layerchart is doing something unusual. But the version matrix isolates the SvelteKit half: same chart, same data, same presses, and only the Kit version differs.

Related

Filed here rather than added to those because the bisect points at Kit: the same Svelte builds are fine under Kit 2.64.0. Happy to move it if that reading is wrong.

System Info

  • @sveltejs/kit 3.0.0-next.23, fine on 2.64.0
  • svelte 5.56.9 and 5.56.7, both with compilerOptions.experimental.async and experimental.remoteFunctions
  • layerchart 2.0.2
  • vite 8.2.1, Node 22.22, Chromium

Both experimental flags are required to reproduce. See vite.config.ts in the reproduction.

Severity

Blocking an app from using await on any page that charts query data.

Activity

  1. changed the title [-]Chart fed from an `await`ed remote query freezes the tab on the second update (async + remoteFunctions)[/-] [+]Chart fed from an `await`ed remote query freezes the tab (regression in 3.0.0-next; fine on 2.64.0)[/+] on Aug 20, 2026
  2. added theissue type on Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions