mirror of https://github.com/sveltejs/svelte
fix: avoid traversing clean roots (#17928)
Before #17805, all batches drew from the same `queued_root_effects` and did reset them to the empty array when starting a flush. After the refactoring roots are scheduled per batch. This introduces a possible race condition where the same root is scheduled multiple times. It was possible because of the rebase logic in `#commit` not clearing the array of roots, so if you somehow flush that same batch later, you will end up traversing a clean root. (it is possible a bug like this always existed with rebasing it was just impossible hard to trigger it before because everyone drew from the same root effects array) The fix is a bit more complicated than just checking if new roots where added, we gotta check if we actually created async work before traversing. Fixes #17918 --------- Co-authored-by: Rich Harris <rich.harris@vercel.com>pull/17919/head
parent
965f2a0ac8
commit
1ebed68322
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: avoid traversing clean roots
|
||||
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
let x = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
x = true;
|
||||
|
||||
return () => {
|
||||
x = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,16 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
// This test causes two batches to be scheduled such that the same root is traversed multiple times,
|
||||
// some of the time while it was already marked clean by a previous batch processing. It tests
|
||||
// that the app stays reactive after, i.e. that the root is not improperly marked as unclean.
|
||||
await tick();
|
||||
const [button] = target.querySelectorAll('button');
|
||||
|
||||
button.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `<button>toggle</button><p>hello</p>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import Component from './Component.svelte';
|
||||
|
||||
let condition = $state(false);
|
||||
</script>
|
||||
|
||||
<button onclick={() => (condition = !condition)}>toggle</button>
|
||||
|
||||
<svelte:boundary>
|
||||
<Component whatever={await 1} />
|
||||
|
||||
{#snippet pending()}
|
||||
<Component />
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
|
||||
{#if condition}
|
||||
<p>hello</p>
|
||||
{/if}
|
||||
Loading…
Reference in new issue