diff --git a/.changeset/beige-bobcats-eat.md b/.changeset/beige-bobcats-eat.md new file mode 100644 index 0000000000..95390c3c2e --- /dev/null +++ b/.changeset/beige-bobcats-eat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: unlink errored and otherwise finished batch diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 4952001dd6..b9721b6243 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -326,6 +326,12 @@ export class Batch { this.#traverse(root, effects, render_effects); } catch (e) { reset_all(root); + // If there's no async work left, this branch is now dead and needs + // to be unlinked to not become a zombie that is never cleaned up. + // See https://github.com/sveltejs/svelte/issues/18221#issuecomment-4497918414 + // for a (non-minimal) reproduction that demonstrates a case where this is necessary + // to not get follow-up false-positives via "batch has scheduled roots" invariant errors. + if (!this.#is_deferred()) this.#unlink(); throw e; } } diff --git a/packages/svelte/tests/runtime-runes/samples/error-recovery/_config.js b/packages/svelte/tests/runtime-runes/samples/error-recovery/_config.js index 52c1bbd1bf..1f80251806 100644 --- a/packages/svelte/tests/runtime-runes/samples/error-recovery/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/error-recovery/_config.js @@ -2,7 +2,7 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ - async test({ assert, target, compileOptions }) { + async test({ assert, target }) { const [toggle, increment] = target.querySelectorAll('button'); flushSync(() => increment.click()); @@ -25,8 +25,8 @@ export default test({ ` -

show: ${compileOptions.experimental?.async ? 'false' : 'true'}

- ` +

show: true

+ ` // show: false would also be fine; this is more about ensuring that things continue to work _somehow_ ); } });