fix: ensure deferred effects can be rescheduled later on

When deferring effects we didn't unmark the deriveds that lead to those effects. This means that they might not be reached in subsequent runs of `mark_reactions`.

Fixes https://github.com/sveltejs/svelte/issues/17118#issuecomment-3521488865
pull/17147/head
Simon Holthausen 1 week ago
parent e238e6611e
commit 8f7e8b936e

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure deferred effects can be rescheduled later on

@ -16,7 +16,8 @@ import {
BOUNDARY_EFFECT,
EAGER_EFFECT,
HEAD_EFFECT,
ERROR_VALUE
ERROR_VALUE,
WAS_MARKED
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property } from '../../shared/utils.js';
@ -270,11 +271,32 @@ export class Batch {
const target = (e.f & DIRTY) !== 0 ? this.#dirty_effects : this.#maybe_dirty_effects;
target.push(e);
// Since we're not executing these effects now, we need to clear any WAS_MARKED flags
// so that other batches can correctly reach these effects during their own traversal
this.#clear_marked(e.deps);
// mark as clean so they get scheduled if they depend on pending async state
set_signal_status(e, CLEAN);
}
}
/**
* @param {Value[] | null} deps
*/
#clear_marked(deps) {
if (deps === null) return;
for (const dep of deps) {
if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
continue;
}
dep.f ^= WAS_MARKED;
this.#clear_marked(/** @type {Derived} */ (dep).deps);
}
}
/**
* Associate a change to a given source with the current
* batch, noting its previous and current values

@ -0,0 +1,49 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
assert.deepEqual(logs, [0]);
const [fork1, fork2, commit] = target.querySelectorAll('button');
fork1.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork 1</button>
<button>fork 2</button>
<button>commit</button>
<p>0</p>
`
);
assert.deepEqual(logs, [0]);
fork2.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork 1</button>
<button>fork 2</button>
<button>commit</button>
<p>0</p>
`
);
assert.deepEqual(logs, [0]);
commit.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork 1</button>
<button>fork 2</button>
<button>commit</button>
<p>1</p>
`
);
assert.deepEqual(logs, [0, 1]);
}
});

@ -0,0 +1,37 @@
<script>
import { fork } from "svelte";
let state = $state(0);
let count = $derived(state);
$effect.pre(() => {
console.log(count);
});
let forked;
</script>
<button onclick={()=>{
forked?.discard?.();
forked = fork(()=>{
state++;
});
}}>
fork 1
</button>
<button onclick={()=>{
forked?.discard?.();
forked = fork(()=>{
state++;
})
}}>
fork 2
</button>
<button onclick={()=>{
forked?.commit();
}}>commit</button>
<p>{count}</p>
Loading…
Cancel
Save