prevent infinite loop in eager batches

entangle-batches-2
Simon Holthausen 5 days ago
parent 2481374e95
commit 17d3d817a7
No known key found for this signature in database

@ -235,6 +235,14 @@ export class Batch {
*/
#maybe_dirty_effects = null;
/**
* Effects run by an eager batch that must be re-established in this batch's world.
* Unlike deferred effects, these are scheduled only once.
* Lazily initialised for perf reasons
* @type {Set<Effect> | null}
*/
#reestablish_effects = null;
/**
* A map of branches that still exist, but will be destroyed when this batch
* is committed we skip over these during `process`.
@ -512,8 +520,8 @@ export class Batch {
owner.linked &&
!owner.is_fork
) {
// TODO
(owner.#dirty_effects ??= new Set()).add(/** @type {Effect} */ (signal));
// Not putting this into #dirty_effects to prevent infinite loops
(owner.#reestablish_effects ??= new Set()).add(/** @type {Effect} */ (signal));
}
return false;
@ -641,6 +649,9 @@ export class Batch {
other.#dirty_effects = null;
other.#maybe_dirty_effects = null;
this.#reestablish_effects = transfer_set(this.#reestablish_effects, other.#reestablish_effects);
other.#reestablish_effects = null;
if (other.#skipped_branches !== null) {
this.#skipped_branches ??= new Map();
@ -782,6 +793,18 @@ export class Batch {
}
}
if (this.#reestablish_effects !== null) {
var reestablish_effects = this.#reestablish_effects;
this.#reestablish_effects = null;
for (const e of reestablish_effects) {
this.#dirty_effects?.delete(e);
this.#maybe_dirty_effects?.delete(e);
set_signal_status(e, DIRTY);
this.schedule(e);
}
}
// We always reschedule previously-deferred effects, not just when
// #is_deferred() is true, because traversing the tree could make
// an if block that contains the last blocking pending effect falsy,

@ -0,0 +1,16 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
await tick();
const [increment] = target.querySelectorAll('button');
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>0</p>');
increment.click();
await tick();
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>2</p>');
}
});

@ -0,0 +1,14 @@
<script>
let count = $state(0);
let runs = 0;
function delay(value, eager_value) {
if (runs++ > 10) throw new Error('infinite loop detected')
if (value === 0) return 0;
return Promise.resolve(value + eager_value);
}
</script>
<button onclick={() => count += 1}>increment</button>
<p>{await delay(count, $state.eager(count))}</p>
Loading…
Cancel
Save