Skip to content

Commit 51bd24e

Browse files
committed
split effect collection and execution when rebasing - prevents each loops breaking in async when certain race conditions occur
1 parent a793d91 commit 51bd24e

File tree

1 file changed

+42
-18
lines changed
  • packages/svelte/src/internal/client/reactivity

1 file changed

+42
-18
lines changed

packages/svelte/src/internal/client/reactivity/batch.js

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,13 @@ export class Batch {
348348
var previous_batch_values = batch_values;
349349
var is_earlier = true;
350350

351-
/** @type {EffectTarget} */
352-
var dummy_target = {
353-
parent: null,
354-
effect: null,
355-
effects: [],
356-
render_effects: [],
357-
block_effects: []
358-
};
351+
/** @type {Map<Batch, Set<Effect>>} */
352+
const batch_effects = new Map();
359353

354+
// First loop: collect effects to run for each batch
355+
// Do this before running them because rerunning an effect
356+
// might change its dependencies and so other batches could
357+
// run effects when they shouldn't or not when they should.
360358
for (const batch of batches) {
361359
if (batch === this) {
362360
is_earlier = false;
@@ -388,27 +386,53 @@ export class Batch {
388386
// Re-run async/block effects that depend on distinct values changed in both batches
389387
const others = [...batch.current.keys()].filter((s) => !this.current.has(s));
390388
if (others.length > 0) {
389+
/** @type {Set<Effect>} */
390+
const effects = new Set();
391391
/** @type {Set<Value>} */
392392
const marked = new Set();
393393
/** @type {Map<Reaction, boolean>} */
394394
const checked = new Map();
395395
for (const source of sources) {
396-
mark_effects(source, others, marked, checked);
396+
mark_effects(source, others, effects, marked, checked);
397397
}
398398

399-
if (queued_root_effects.length > 0) {
400-
current_batch = batch;
401-
batch.apply();
399+
if (effects.size > 0) {
400+
batch_effects.set(batch, effects);
401+
}
402+
}
403+
}
402404

405+
// Second loop: schedule effects and traverse effect trees
406+
if (batch_effects.size > 0) {
407+
/** @type {EffectTarget} */
408+
var dummy_target = {
409+
parent: null,
410+
effect: null,
411+
effects: [],
412+
render_effects: [],
413+
block_effects: []
414+
};
415+
416+
for (const [batch, effects] of batch_effects) {
417+
current_batch = batch;
418+
batch.apply();
419+
420+
for (const effect of effects) {
421+
set_signal_status(effect, DIRTY);
422+
schedule_effect(effect);
423+
}
424+
425+
if (queued_root_effects.length > 0) {
403426
for (const root of queued_root_effects) {
404427
batch.#traverse_effect_tree(root, dummy_target);
405428
}
406429

407430
// TODO do we need to do anything with `target`? defer block effects?
408431

409432
queued_root_effects = [];
410-
batch.deactivate();
411433
}
434+
435+
batch.deactivate();
412436
}
413437
}
414438

@@ -710,10 +734,11 @@ function flush_queued_effects(effects) {
710734
* these effects can re-run after another batch has been committed
711735
* @param {Value} value
712736
* @param {Source[]} sources
737+
* @param {Set<Effect>} effects
713738
* @param {Set<Value>} marked
714739
* @param {Map<Reaction, boolean>} checked
715740
*/
716-
function mark_effects(value, sources, marked, checked) {
741+
function mark_effects(value, sources, effects, marked, checked) {
717742
if (marked.has(value)) return;
718743
marked.add(value);
719744

@@ -722,14 +747,13 @@ function mark_effects(value, sources, marked, checked) {
722747
const flags = reaction.f;
723748

724749
if ((flags & DERIVED) !== 0) {
725-
mark_effects(/** @type {Derived} */ (reaction), sources, marked, checked);
750+
mark_effects(/** @type {Derived} */ (reaction), sources, effects, marked, checked);
726751
} else if (
727752
(flags & (ASYNC | BLOCK_EFFECT)) !== 0 &&
728-
(flags & DIRTY) === 0 && // we may have scheduled this one already
753+
!effects.has(/** @type {Effect} */ (reaction)) &&
729754
depends_on(reaction, sources, checked)
730755
) {
731-
set_signal_status(reaction, DIRTY);
732-
schedule_effect(/** @type {Effect} */ (reaction));
756+
effects.add(/** @type {Effect} */ (reaction));
733757
}
734758
}
735759
}

0 commit comments

Comments
 (0)