From f8bf9bb461d6fe5a2132ea6cfaeaa184e34c37f7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 18 Feb 2026 16:18:42 -0500 Subject: [PATCH] chore: deactivate current_batch by default in unset_context (#17738) Small tweak: except for `{#await ...}` blocks, which are a bit of an anomaly, I'm pretty sure we _always_ want to deactivate the current batch when unsetting context, otherwise it could incorrectly pick up unrelated state changes. There might even be some subtle bugs lurking in the system at present because we _don't_ always do this ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [ ] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` --- .changeset/rich-months-glow.md | 5 +++++ .../svelte/src/internal/client/dom/blocks/await.js | 4 ++-- .../svelte/src/internal/client/reactivity/async.js | 6 ++---- .../svelte/src/internal/client/reactivity/batch.js | 3 --- .../src/internal/client/reactivity/deriveds.js | 12 +----------- 5 files changed, 10 insertions(+), 20 deletions(-) create mode 100644 .changeset/rich-months-glow.md diff --git a/.changeset/rich-months-glow.md b/.changeset/rich-months-glow.md new file mode 100644 index 0000000000..3108777750 --- /dev/null +++ b/.changeset/rich-months-glow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: deactivate current_batch by default in unset_context diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 87d64df23e..b4cd72df99 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -12,7 +12,7 @@ import { import { queue_micro_task } from '../task.js'; import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js'; import { is_runes } from '../../context.js'; -import { Batch, flushSync, is_flushing_sync } from '../../reactivity/batch.js'; +import { Batch, current_batch, flushSync, is_flushing_sync } from '../../reactivity/batch.js'; import { BranchManager } from './branches.js'; import { capture, unset_context } from '../../reactivity/async.js'; @@ -84,7 +84,7 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) { try { fn(); } finally { - unset_context(); + unset_context(false); // without this, the DOM does not update until two ticks after the promise // resolves, which is unexpected behaviour (and somewhat irksome to test) diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index b3c5248179..31408ee8f9 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -66,7 +66,6 @@ export function flatten(blockers, sync, async, fn) { } } - batch?.deactivate(); unset_context(); } @@ -207,10 +206,11 @@ export async function* for_await_track_reactivity_loss(iterable) { } } -export function unset_context() { +export function unset_context(deactivate_batch = true) { set_active_effect(null); set_active_reaction(null); set_component_context(null); + if (deactivate_batch) current_batch?.deactivate(); if (DEV) { set_from_async_derived(null); @@ -271,9 +271,7 @@ export function run(thunks) { promise.finally(() => { blocker.settled = true; - unset_context(); - current_batch?.deactivate(); }); } diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 297049fd6b..3832438f47 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -72,8 +72,6 @@ let is_flushing = false; export let is_flushing_sync = false; export class Batch { - committed = false; - /** * The current values of any sources that are updated in this batch * They keys of this map are identical to `this.#previous` @@ -425,7 +423,6 @@ export class Batch { batch_values = previous_batch_values; } - this.committed = true; batches.delete(this); } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 80da5528c8..7df7651294 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -133,17 +133,7 @@ export function async_derived(fn, label, location) { // If this code is changed at some point, make sure to still access the then property // of fn() to read any signals it might access, so that we track them as dependencies. // We call `unset_context` to undo any `save` calls that happen inside `fn()` - Promise.resolve(fn()) - .then(d.resolve, d.reject) - .then(() => { - if (batch === current_batch && batch.committed) { - // if the batch was rejected as stale, we need to cleanup - // after any `$.save(...)` calls inside `fn()` - batch.deactivate(); - } - - unset_context(); - }); + Promise.resolve(fn()).then(d.resolve, d.reject).finally(unset_context); } catch (error) { d.reject(error); unset_context();