From cfd6446635e82437081f1d7587154251005ddac8 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Tue, 22 Jul 2025 18:58:15 +0200 Subject: [PATCH] fix: exclude derived writes from effect abort and rescheduling --- .changeset/thick-mice-kick.md | 5 +++++ .../src/internal/client/reactivity/batch.js | 9 ++++++++- .../src/internal/client/reactivity/deriveds.js | 15 +++++++++++++++ .../1000-reading-derived-effects/Component.svelte | 7 +++++++ .../1000-reading-derived-effects/_config.js | 5 +++++ .../1000-reading-derived-effects/main.svelte | 8 ++++++++ 6 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .changeset/thick-mice-kick.md create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte diff --git a/.changeset/thick-mice-kick.md b/.changeset/thick-mice-kick.md new file mode 100644 index 000000000000..eec55b77eee4 --- /dev/null +++ b/.changeset/thick-mice-kick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: exclude derived writes from effect abort and rescheduling diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index ec082bb595ff..fea40438e0dd 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -31,6 +31,7 @@ import { invoke_error_boundary } from '../error-handling.js'; import { old_values } from './sources.js'; import { unlink_effect } from './effects.js'; import { unset_context } from './async.js'; +import { get_derived_writes, reset_derived_writes } from './deriveds.js'; /** @type {Set} */ const batches = new Set(); @@ -546,6 +547,12 @@ function flush_queued_effects(effects) { if (is_dirty(effect)) { var wv = write_version; + // updating a derived for also increase the write version but that doesn't mean + // state was written to in the user effect...so we reset the derived writes + // before running the effect so that we can subtract the amount of derived writes + // from the write version when we detect if state was written to in the user effect + reset_derived_writes(); + update_effect(effect); // Effects with no dependencies or teardown do not get added to the effect tree. @@ -567,7 +574,7 @@ function flush_queued_effects(effects) { // if state is written in a user effect, abort and re-schedule, lest we run // effects that should be removed as a result of the state change - if (write_version > wv && (effect.f & USER_EFFECT) !== 0) { + if (write_version - get_derived_writes() > wv && (effect.f & USER_EFFECT) !== 0) { break; } } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 7f730e365e5a..b77535fa2a5d 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -323,6 +323,20 @@ export function execute_derived(derived) { return value; } +// in process_effects if state is written to in a user effect we reschedule the rest of the +// tree. However if a derived is updated in an effect we also increase the write version +// so we need to keep track of how many deriveds were written to in the effect so +// that we can subtract that from the write version before rescheduling unnnecessarily +let derived_writes = 0; + +export function get_derived_writes() { + return derived_writes; +} + +export function reset_derived_writes() { + derived_writes = 0; +} + /** * @param {Derived} derived * @returns {void} @@ -333,6 +347,7 @@ export function update_derived(derived) { if (!derived.equals(value)) { derived.v = value; derived.wv = increment_write_version(); + derived_writes++; } // don't mark derived clean if we're reading it inside a diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte new file mode 100644 index 000000000000..7a54323cb97f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/Component.svelte @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js new file mode 100644 index 000000000000..2e4a27cf0912 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + async test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte new file mode 100644 index 000000000000..258918e903bd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/1000-reading-derived-effects/main.svelte @@ -0,0 +1,8 @@ + + +{#each arr} + +{/each}