Application using LayerChart hanging after updating Svelte to 5.36.0+ (logging UpdatedAtError in 5.36.5+)
#16548
UpdatedAtError in 5.36.5+)Development PRs
Checks each effect's execution count and only advances the overall flush count if an inidivual effect was executed many times, hinting at a loop
The count overall is kept in place because theoretically there could be other infinite loops happening with no user effect in the mix.
Fixes part of #16548
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
This makes flushing effects much faster in scenarios where there's a lot of effects and many of them write to state.
We added this "bail and reschedule" mechanism mainly to prevent subsequent effects from being called when a prior effect execution did e.g. cause an if block to become false and the effect about to run next therefore being destroyed.
This change addresses that issue by flushing the newly created batch right away, but only executing its branches. Through this we also get rid of the false-positive loop protection error.
The one downside is that effect execution ordering is slightly different: Only things scheduled within the current batch are executed in order, all new (pre-)effects will run afterwards. The worst thing that can happen as a result is rerunning render or user effects more often than they need to, but I expect this to be minimal in comparison to the perf wins.
Fixes #16548
If we agree on the fix being sensible I'll adjust the tests, add some comments etc
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
Alternative to #16612 which I think is probably a bit simpler, and hopefully results in a greater performance boost.
Whereas #16612 works by flushing parts of the effect tree when an invalidation occurs, such that to-be-destroyed effects are destroyed before they can re-execute, this comes from the opposite end — we continue to abort flushing if a state change occurs in a user effect, but only if that state change results in an existing effect being marked (the cause of #16072).
Fixes #16548.
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
Another stab at #16548. The idea is that if a block effect is dirtied by a state change in another effect, it jumps to the head of the queue rather than waiting for the next root-to-tip traversal. That way, if it ends up destroying its children, they get destroyed immediately, rather than running even though they're about to be destroyed (which can result in errors — #16072).
Because we're not causing extra traversals, this (as far as I've been able to determine) fully removes the performance overhead introduced by #16280, without regressing on correctness. It also fixes the false positive infinite loop detection.
The one part I really don't love is the old_values.clear() — it gets the tests passing (one fails without it) but feels wrong. I stole it from #16612 — maybe @dummdidumm can explain if/why/how it's okay.
It also seems weird that we need to schedule the block effects in addition to running them eagerly, but if we don't then one test fails. Will investigate when my brain isn't running on fumes.
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
The upstream fix is first available in 5.38.2, so constrain to at least that version.
Rebuild pre-compiled webui index.html.gz based on these changes.
See also: #16347 huntabyte/bits-ui#1687 sveltejs/svelte#16548
I have tested this webui and no longer see the effect_update_depth_exceeded console errors. I did some basic, non-extensive manual testing and things seemed okay. Unfortunately couldn’t get the npm test suite to run, but had the same problem on master so it is a me problem.
Mirrored from ggml-org#19144
The upstream fix is first available in 5.38.2, so constrain to at least that version.
Rebuild pre-compiled webui index.html.gz based on these changes.
See also: ggml-org#16347 huntabyte/bits-ui#1687 sveltejs/svelte#16548
I have tested this webui and no longer see the effect_update_depth_exceeded console errors. I did some basic, non-extensive manual testing and things seemed okay. Unfortunately couldn’t get the npm test suite to run, but had the same problem on master so it is a me problem.
Issue
Describe the bug
While investigating async compat issues with LayerChart, I noticed after bumping Svelte from 5.34.1 to 5.35.4 (and beyond) begins to throw effect_update_depth_exceeded errors we had not prior.
I believe this stems from how we setup motion state / value tracking. Let me know if this should be handled differently, or is in fact a regression. The only changeset for 5.35.4 is:
fix: abort and reschedule effect processing after state change in user effect
As always, thanks for all the hard work!
Reproduction
- Checkout LayerChart PR 629 /
update-depsbranch pnpm installpnpm dev- Open the example (or many others with motion such as and ). For even more taxing examples, view , , or )
Logs
client:733 [vite] connecting...
client:827 [vite] connected.
Area:24 Last ten effects were: (10) [null, ƒ, ƒ, ƒ, null, ƒ, ƒ, ƒ, ƒ, ƒ]0: null1: () => { motion.set(getValue()); }2: () => { motion.set(getValue()); }3: () => {…}4: null5: () => { motion.set(getValue()); }6: () => { refProp($.get(ref)); }7: () => { refProp($.get(ref)); }8: () => { svgRefProp($.get(svgRef)); }9: () => { motion.set(getValue()); }length: 10[[Prototype]]: Array(0)
log_effect_stack @ chunk-REG52R6L.js?v=13908794:1877
infinite_loop_guard @ chunk-REG52R6L.js?v=13908794:1897
flush_queued_root_effects @ chunk-REG52R6L.js?v=13908794:1918
flushSync @ chunk-REG52R6L.js?v=13908794:2034
Svelte4Component @ chunk-Q6R3MBGE.js?v=13908794:839
(anonymous) @ chunk-Q6R3MBGE.js?v=13908794:789
initialize @ client.js?v=13908794:528
navigate @ client.js?v=13908794:1643
await in navigate
start @ client.js?v=13908794:340
await in start
(anonymous) @ Area:24
Promise.then
(anonymous) @ Area:23
chunk-REG52R6L.js?v=13908794:1877 Last ten effects were: (10) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, null, ƒ, ƒ, ƒ]
log_effect_stack @ chunk-REG52R6L.js?v=13908794:1877
infinite_loop_guard @ chunk-REG52R6L.js?v=13908794:1897
flush_queued_root_effects @ chunk-REG52R6L.js?v=13908794:1918
chunk-REG52R6L.js?v=13908794:199 Uncaught Svelte error: effect_update_depth_exceeded
Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
https://svelte.dev/e/effect_update_depth_exceeded
effect_update_depth_exceeded @ chunk-REG52R6L.js?v=13908794:199
infinite_loop_guard @ chunk-REG52R6L.js?v=13908794:1885
flush_queued_root_effects @ chunk-REG52R6L.js?v=13908794:1918
chunk-REG52R6L.js?v=13908794:199 Uncaught (in promise) Svelte error: effect_update_depth_exceeded
Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
https://svelte.dev/e/effect_update_depth_exceeded
effect_update_depth_exceeded @ chunk-REG52R6L.js?v=13908794:199
infinite_loop_guard @ chunk-REG52R6L.js?v=13908794:1885
flush_queued_root_effects @ chunk-REG52R6L.js?v=13908794:1918
flushSync @ chunk-REG52R6L.js?v=13908794:2034
Svelte4Component @ chunk-Q6R3MBGE.js?v=13908794:839
(anonymous) @ chunk-Q6R3MBGE.js?v=13908794:789
initialize @ client.js?v=13908794:528
navigate @ client.js?v=13908794:1643
await in navigate
start @ client.js?v=13908794:340
await in start
(anonymous) @ Area:24
Promise.then
(anonymous) @ Area:23
System Info
System:
OS: macOS 15.5
CPU: (10) arm64 Apple M1 Max
Memory: 109.75 MB / 32.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.17.0 - ~/Library/Caches/fnm_multishells/70747_1754315319738/bin/node
npm: 10.9.2 - ~/Library/Caches/fnm_multishells/70747_1754315319738/bin/npm
pnpm: 9.1.1 - ~/Library/pnpm/pnpm
bun: 1.1.33 - ~/.bun/bin/bun
Browsers:
Brave Browser: 136.1.78.102
Chrome: 138.0.7204.184
Safari: 18.5
npmPackages:
svelte: 5.35.4 => 5.35.4
Severity
blocking an upgrade
Info
I've been running into this as a false positive when there are more than 1000 elements being rendered on the page that have some sort of common dependency, almost as if it's a false positive infinite loop guard.
For example, in Bits UI, the <Tooltip.Root> will derived some properties from a parent <Tooltip.Provider>, the following works fine:
<Tooltip.Provider>
{#each { length: 999 } as i}
<Tooltip.Root />
{/each}
</Tooltip.Provider>
But as soon as you bump length to 1000 we get effect_update_depth_exceeded errors.
Thanks @huntabyte. This explains some of the intermittent issues I had while investigating effect_update_depth_exceeded.
When running the latest 5.37.3 I'm getting another error and assumed they were latest, but this appears to be 2 separate issues - false positive effect_update_depth_exceeded and another error UpdatedAtError.
Going to trace back the original version when UpdatedAtError started presenting.
Looks like the root issue (causing the application to lock) started in 3.36.0 (i.e. async edition), with UpdatedAtError logging added in 3.36.5. See LayerChart issue comment for more context.
Going to update the issue title to reflect the root issue (effect_update_depth_exceeded being a false postiive, new UpdatedAtError being the primary issue).
Once again, let me know if we're doing something unsupported in LayerChart.
Thanks again!
We have the exact same false positive bug with shadcn tooltip (which uses bits-ui under the hood). The false positive error is annoying, but the freezing it creates for seconds until the errors throw in the console is a problem.
Downgrading is not an option for us, since we had to upgrade to latest version yesterday to fix/workaround a sneaky bug in Firefox that breaks our app. I just removed the tooltips for now, hopefully it won't be triggered otherwise until svelte comes with a fix. Choosing my poison !
We are also experiencing this issue, by simply having a page that renders a lot of components, on hydration we see effect_update_depth_exceeded on versions later than 5.34.1. it seems like it's just when the amount of $effect reach 1000 not because they are recursive or illegal
Same here!
@techniq I'm unable to reproduce this in the latest version of Svelte. If this is reproduble for you still, please provide a reproduction repository with steps on how to reproduce.
@huntabyte I can reproduce this using your snippet, thanks.
@AndreasHald did you check with the latest version? There was a bugfix around deriveds and effects. If it's happening still, are those effects writing to state, and if yes what is that state changing?
Thanks @dummdidumm. I'm still able to reproduce using 5.38.1 on a LayerChart branch, but having trouble boiling it down to root cause. I'll keep poking around and see if a I can create a more minimal use case.
@dummdidumm the UpdatedAtError appears to be a "volume" issue as well
Rendering 10 instances of this chart doesn't produce any errors
<script lang="ts">
import { Area, Axis, Chart, Layer } from 'layerchart';
import { createDateSeries } from '$lib/utils/genData.js';
const dateSeriesData = createDateSeries({ count: 30, min: 50, max: 100, value: 'integer' });
</script>
{#each { length: 10 } as _}
<div class="h-[300px] p-4 border rounded-sm">
<Chart
data={dateSeriesData}
x="date"
y="value"
yDomain={[0, null]}
yNice
padding={{ left: 16, bottom: 24 }}
>
<Layer type="svg">
<Axis placement="left" grid rule />
<Axis placement="bottom" rule />
<Area line={{ class: 'stroke-2 stroke-primary' }} class="fill-primary/30" />
</Layer>
</Chart>
</div>
{/each}
but rendering 11+ does...
If I remove a component such as Area or reduce the data and thus reducing the number of axis ticks/etc, the error goes away
I'll continue to work on a better reproduction, but wanted to share my findings in case its helpful. Thanks!
That's fine, this is already enough to confirm the issue. To clarify: do you also have any performance problems (like mentioned by the people using the bits-ui tooltip)?
I also ran into this issue using a bits-ui Combobox with the Combobox.Viewport Combobox.Item collection built using an {#each} block referencing a 1500-lengh array. Was on Svelte 5.38.1 and bits-ui 1.7.0.
@Rich-Harris Not for me! I tried both on a data-table rendering about ~510 cells (each is a svelte component with multiple parts, and $derived runes). I don't have the infinite error, but there's a severe lag (page unresponsive) when i modify any state in the data-table component.
The issue does not exist in <5.35.4. I can't however pinpoint the issue.
The code did not change, only the svelte versions:
this is < 5.35.4
This is >= 5.35.4 (Page crashed after i stopped the recording)
I have a page that would throw the effect_update_depth_exceeded error immediately from rendering a bunch of layerchart charts and all 3 changes (16612, 16623, and f89e355) each resolve the error for me.
Thanks @Rich-Harris! Tested all 3 versions and they all fixed UpdatedAtError and restored interactivity (tooltips) but have very noticeable performance regressions.
svelte@5.34.1 (baseline)
svelte@16612
svelte@16623
svelte@f89e355
at least when ran in dev / locally. I just tested svelte@f89e355 deployed and it is much more responsive, but still no where near 5.34.1 (including in dev)
svelte@f89e355
compared to 5.34.1 (deployed)
In short, all versions fixed the primary error, but do have performance regressions compared to 5.34.1, especially when ran in development.
@AndreasHald did you check with the latest version? There was a bugfix around deriveds and effects. If it's happening still, are those effects writing to state, and if yes what is that state changing?
Tested with 5.38.1and still get the error there. We sometimes do stuff like this where we sync props to client side only libraries
<script lang="ts">
let { disabled } = $props();
let someClientSideOnlyLibrary = $state();
onMount(() => {
someClientSideOnlyLibrary = new SomeClientSideOnlyLibrary();
});
$effect(() => {
if(disabled) {
someClientSideOnlyLibrary.disable()
} else {
someClientSideOnlyLibrary.enable()
}
})
</script>
or stuff like this
<script lang="ts">
let { value } = $props();
const _value = writable();
$effect(() => {
_value.set(value)
})
</script>
I realise that the last example especially can be simplified with runes, but it takes us a little while in a large codebase to adopt them everywhere.
How about this version?
pnpm add https://pkg.pr.new/svelte@f89e355Context: #16623 (comment)
Tested this one, it also resolved the error for us. I can't simulate the performance diff as well as @techniq but subjectively it does feel a tad slower.
I'm somewhat sure that the performance cliff is more noticeable after the "effect update depth exceeded" bug is resolved simply because the rendering actually finishes and doesn't abort beforehand.
Does this version change anything for y'all?
pnpm add https://pkg.pr.new/svelte@e11e381Thanks @dummdidumm. svelte@e11e381 feels the same as the others.
@Rich-Harris @dummdidumm I would like to say generally for many use cases the performance feels good, and the force components have had a history with Svelte 5 performance regressions.
But I also noticed rendering all our BarChart examples does take 20+ seconds
and before (5.34.1) it was about 3 seconds
btw, I didn't catch this before, but svelte@16623 still throws some UpdatedAtError for at least a few of the force examples (but not for Area and many others that 5.38.1 was)
svelte@16612 doesn't appear to throw any that I've seen.
Tangential to the main point, but @AndreasHald does someClientSideOnlyLibrary need to be state?
<script lang="ts">
let { disabled } = $props();
let someClientSideOnlyLibrary = $state();
onMount(() => {
someClientSideOnlyLibrary = new SomeClientSideOnlyLibrary();
});
$effect(() => {
if(disabled) {
someClientSideOnlyLibrary.disable()
} else {
someClientSideOnlyLibrary.enable()
}
})
</script>
If this is representative of real code, I'm fairly sure you could use a normal variable...
let someClientSideOnlyLibrary = $state();
let someClientSideOnlyLibrary;
...in which case you wouldn't be setting state in an effect, which is the root of all these problems.
It doesn't - it should definitely just be a variable
Okay, spent a few hours debugging this today, mostly using the LayerChart site to test out hypotheses. It turns out there are two reasons 5.38.1 is slower than 5.35.3:
- #16280 (which we knew about)
- #16405 — we now attach stack traces to sources if they get updated during an effect, so that we can print useful diagnostic information if we end up in a loop. This only happens in dev, and is a much less significant factor than #16280 (and doesn't cause false positive infinite loop errors), so while it would be nice to fix it it's not a burning priority
In fact if you dig into node_modules and comment out the break here...
...and this block...
...things should run the same as they did in 5.35.3. For me at least, the force simulations are smooth as butter.
To recap: the reason for the big slowdown is that it's possible (though not easy!) to get yourself into a situation where object.property is read when object is falsy:
{#if object}
<!-- this block could have an `object.property` reference that evaluates before the `if` is destroyed -->
{/if}
#16280 fixes this by aborting effect flushing if a state change occurs inside an effect. That turns out to be much too aggressive. @dummdidumm and I have been trying various ways to make it less so:
- #16612 traverses the effect tree when a state change occurs inside an effect, but only block effects. This helps (it fixes the errors), but still has some overhead because traversal isn't free
- #16623 tries to make the detection more accurate: it aborts, but only if an existing block effect is invalidated by another effect during the flush. This helps a lot (though we get a lot of false positives because 'block effect' also encompasses the effects that manage attributes on elements with spreads, so that we don't needlessly destroy unchanged attachments — long story) but doesn't fix the errors. It just makes you less likely to encounter them
- #16631 is the most recent attempt. This time around, we don't abort flushing, but if a block effect is rescheduled it jumps to the front of the queue. This allows everything to flush correctly but without constantly aborting and restarting
I'm reasonably optimistic about #16631, though I still have some question marks over it that I'll revisit later.
(As ever: if you're able to try out the pkg.pr.new link and report back, it would be very helpful! Thanks)
I really appreciate all the hard work @Rich-Harris (and @dummdidumm / team).
I can confirm running svelte@16631 in dev is substantially better than latest 5.38.1 (but not 5.34.1 level as expected) and does not generate any UpdatedAtError instances.
and svelte@16631 in prod is back to being buttery smooth 🏃♂️
Thanks!