Skip to content

fix: unset batch before flushing queued effects #16482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
better fix
  • Loading branch information
dummdidumm committed Jul 22, 2025
commit 0c0662bbdca1159c3630f43c5d4d7ef194e4cf89
31 changes: 14 additions & 17 deletions packages/svelte/src/internal/client/reactivity/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ let last_scheduled_effect = null;

let is_flushing = false;

let flushing_sync = false;
export let flushing_sync = false;

export class Batch {
/**
Expand Down Expand Up @@ -204,9 +204,22 @@ export class Batch {
this.#effects = [];
this.#block_effects = [];

// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
// newly updated sources, which could lead to infinite loops when effects run over and over again.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we've been hunting infinite loop problem in our code in 5.35.5 I am wondering if this comment means that there were indeed a possibility that svelte creates them? That'd be a relief to know

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should only happen in case of async though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly! Will be interested to see if this version fixes things

current_batch = null;

flush_queued_effects(render_effects);
flush_queued_effects(effects);

// Reinstate the current batch if there was no new one created, as `process()` runs in a loop in `flush_effects()`.
// That method expects `current_batch` to be set, and could run the loop again if effects result in new effects
// being scheduled but without writes happening in which case no new batch is created.
if (current_batch === null) {
current_batch = this;
} else {
batches.delete(this);
}

this.#deferred?.resolve();
} else {
// otherwise mark effects clean so they get scheduled on the next run
Expand Down Expand Up @@ -551,7 +564,6 @@ function flush_queued_effects(effects) {

if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
var wv = write_version;
var current_size = /** @type {Batch} */ (current_batch).current.size;

update_effect(effect);

Expand All @@ -575,21 +587,6 @@ 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) {
// Work needs to happen in a separate batch, else prior sources would be mixed with
// newly updated sources, which could lead to infinite loops when effects run over and over again.
// We need to bring over the just written sources though to correctly mark the right reactions as dirty.
var old_batch = /** @type {Batch} */ (current_batch);
batches.delete(old_batch);
current_batch = null;
var new_batch = Batch.ensure(!flushing_sync);
var current_idx = 0;
// We're taking advantage of the spec here which says that entries in a Map are traversed by insertion order
for (const source of old_batch.current) {
if (current_idx >= current_size) {
new_batch.capture(source[0], source[1]);
}
current_idx++;
}
break;
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/reactivity/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack, tag_proxy } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
import { Batch, schedule_effect } from './batch.js';
import { Batch, flushing_sync, schedule_effect } from './batch.js';
import { proxy } from '../proxy.js';
import { execute_derived } from './deriveds.js';

Expand Down Expand Up @@ -179,7 +179,7 @@ export function internal_set(source, value) {

source.v = value;

const batch = Batch.ensure();
const batch = Batch.ensure(!flushing_sync);
batch.capture(source, old_value);

if (DEV) {
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy