From 6f0e43de1a7c2341562b02c2899bb868301e1e3d Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 13 Aug 2025 09:26:08 +0200 Subject: [PATCH] fix: avoid false-positive infinite loop error 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 --- .changeset/shaggy-donuts-wait.md | 5 +++++ .../src/internal/client/reactivity/batch.js | 20 ++++++++++++++++++- .../samples/effect-loop-3/Component.svelte | 8 ++++++++ .../samples/effect-loop-3/_config.js | 11 ++++++++++ .../samples/effect-loop-3/main.svelte | 7 +++++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 .changeset/shaggy-donuts-wait.md create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-loop-3/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-loop-3/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-loop-3/main.svelte diff --git a/.changeset/shaggy-donuts-wait.md b/.changeset/shaggy-donuts-wait.md new file mode 100644 index 0000000000..a155c719ed --- /dev/null +++ b/.changeset/shaggy-donuts-wait.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: avoid false-positive infinite loop error diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 123bc95d16..d80011e9b1 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -78,6 +78,12 @@ let last_scheduled_effect = null; let is_flushing = false; let is_flushing_sync = false; + +/** @type {Map} */ +let effect_execution_count = new Map(); + +let flush_count = 0; + export class Batch { /** * The current values of any sources that are updated in this batch @@ -526,7 +532,7 @@ function flush_effects() { is_flushing = true; try { - var flush_count = 0; + flush_count = 0; set_is_updating_effect(true); while (queued_root_effects.length > 0) { @@ -564,6 +570,7 @@ function flush_effects() { } finally { is_flushing = false; set_is_updating_effect(was_updating_effect); + effect_execution_count.clear(); last_scheduled_effect = null; } @@ -626,6 +633,17 @@ function flush_queued_effects(effects) { current_batch.current.size > n && (effect.f & USER_EFFECT) !== 0 ) { + var execution_count = (effect_execution_count.get(effect) ?? 0) + 1; + effect_execution_count.set(effect, execution_count); + + // If many effects are executed, they cause another flush loop, which could + // lead to false-positives in the infinite loop detection. Therefore decrease + // the counter unless the individual effect has been executed many times, which + // indeed hints at an infinite loop. + if (execution_count < 1000) { + flush_count--; + } + break; } } diff --git a/packages/svelte/tests/runtime-runes/samples/effect-loop-3/Component.svelte b/packages/svelte/tests/runtime-runes/samples/effect-loop-3/Component.svelte new file mode 100644 index 0000000000..f045d5b09c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-loop-3/Component.svelte @@ -0,0 +1,8 @@ + + +{a} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-loop-3/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-loop-3/_config.js new file mode 100644 index 0000000000..61b22e358e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-loop-3/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../test'; + +export default test({ + mode: ['client', 'hydrate'], + + compileOptions: { + dev: true + }, + + html: `1`.repeat(2000) +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-loop-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-loop-3/main.svelte new file mode 100644 index 0000000000..4f137cdb3a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-loop-3/main.svelte @@ -0,0 +1,7 @@ + + +{#each Array(2000) as _, i} + +{/each}