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