fix: only abort effect flushing if it causes an existing effect to be scheduled
#16623
Closing 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 Area example (or many others with motion such as Bars and Pack). For even more taxing examples, view Force Disjoint Graph, Force Tree, or Force Lattice)
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
Pull request
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
Info
🦋 Changeset detected
Latest commit: 87e5d00
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
| Name | Type |
|---|---|
| svelte | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
pnpm add https://pkg.pr.new/svelte@16623In f89e355 I tried restricting things to block effects, on the basis that (I think?) the only way #16072 could reoccur is if a block containing a scheduled effect is destroyed. It causes one of the finicky ordering tests to fail, so I don't know if it's the right fix, but I'm curious as to whether it'll address #16548 (comment).
Of note: while it helps in the case that you're rendering many times...
<script>
let inited = $state(false);
$effect(() => {
inited = true;
});
</script>
<p>{inited}</p>
...you do still get an erroneous infinite loop error if the inited state is :
<script>
let inited = $state(false);
$effect(() => {
inited = true;
});
</script>
{#if inited}
<p>{inited}</p>
{/if}Pretty sure you can fix the test by doing the same as I did in my latest commit to #16612 which is collecting all user/render effects of dirty branches regardless of whether they are dirty or not.
closing in favor of #16631
fix: only abort effect flushing if it causes an existing effect to be scheduled
• Aug 14, 2025, 6:28 PM