fix: ensure deferred effects can be rescheduled later on (#17147)

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/17159/head
Simon H 4 days ago committed by GitHub
parent a7a7d1e014
commit 6e571dc909
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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';
@ -274,11 +275,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