fix: remove memory leak from bind:this (#11194)

* fix: remove memory leak from bind:this

* alternative approach

* add error

* tidy

* tidy

* add TODO

* add TODO

* alternative approach
pull/11201/head
Dominic Gannaway 1 year ago committed by GitHub
parent 9aebae83a5
commit 63456f1df9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: remove memory leak from bind:this

@ -1,6 +1,7 @@
import { STATE_SYMBOL } from '../../../constants.js'; import { STATE_SYMBOL } from '../../../constants.js';
import { effect, render_effect } from '../../../reactivity/effects.js'; import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js'; import { untrack } from '../../../runtime.js';
import { queue_task } from '../../task.js';
/** /**
* @param {any} bound_value * @param {any} bound_value
@ -47,7 +48,8 @@ export function bind_this(element_or_component, update, get_value, get_parts) {
}); });
return () => { return () => {
effect(() => { // We cannot use effects in the teardown phase, we we use a microtask instead.
queue_task(() => {
if (parts && is_bound_this(get_value(...parts), element_or_component)) { if (parts && is_bound_this(get_value(...parts), element_or_component)) {
update(null, ...parts); update(null, ...parts);
} }

@ -1,12 +1,9 @@
import { run_all } from '../../shared/utils.js'; import { run_all } from '../../shared/utils.js';
let is_task_queued = false; let is_task_queued = false;
let is_raf_queued = false;
/** @type {Array<() => void>} */ /** @type {Array<() => void>} */
let current_queued_tasks = []; let current_queued_tasks = [];
/** @type {Array<() => void>} */
let current_raf_tasks = [];
function process_task() { function process_task() {
is_task_queued = false; is_task_queued = false;
@ -15,11 +12,15 @@ function process_task() {
run_all(tasks); run_all(tasks);
} }
function process_raf_task() { /**
is_raf_queued = false; * @param {() => void} fn
const tasks = current_raf_tasks.slice(); */
current_raf_tasks = []; export function queue_task(fn) {
run_all(tasks); if (!is_task_queued) {
is_task_queued = true;
queueMicrotask(process_task);
}
current_queued_tasks.push(fn);
} }
/** /**
@ -29,7 +30,4 @@ export function flush_tasks() {
if (is_task_queued) { if (is_task_queued) {
process_task(); process_task();
} }
if (is_raf_queued) {
process_raf_task();
}
} }

@ -7,9 +7,11 @@ import {
destroy_effect_children, destroy_effect_children,
execute_effect, execute_effect,
get, get,
is_destroying_effect,
is_flushing_effect, is_flushing_effect,
remove_reactions, remove_reactions,
schedule_effect, schedule_effect,
set_is_destroying_effect,
set_is_flushing_effect, set_is_flushing_effect,
set_signal_status, set_signal_status,
untrack untrack
@ -109,6 +111,12 @@ export function user_effect(fn) {
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '') (DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
); );
} }
if (is_destroying_effect) {
throw new Error(
'ERR_SVELTE_EFFECT_IN_TEARDOWN' +
(DEV ? ': The Svelte $effect rune can not be used in the teardown phase of an effect.' : '')
);
}
// Non-nested `$effect(...)` in a component should be deferred // Non-nested `$effect(...)` in a component should be deferred
// until the component is mounted // until the component is mounted
@ -140,6 +148,14 @@ export function user_pre_effect(fn) {
: '') : '')
); );
} }
if (is_destroying_effect) {
throw new Error(
'ERR_SVELTE_EFFECT_IN_TEARDOWN' +
(DEV
? ': The Svelte $effect.pre rune can not be used in the teardown phase of an effect.'
: '')
);
}
return render_effect(fn); return render_effect(fn);
} }
@ -228,6 +244,22 @@ export function branch(fn) {
return create_effect(RENDER_EFFECT | BRANCH_EFFECT, fn, true); return create_effect(RENDER_EFFECT | BRANCH_EFFECT, fn, true);
} }
/**
* @param {import("#client").Effect} effect
*/
export function execute_effect_teardown(effect) {
var teardown = effect.teardown;
if (teardown !== null) {
const previously_destroying_effect = is_destroying_effect;
set_is_destroying_effect(true);
try {
teardown.call(null);
} finally {
set_is_destroying_effect(previously_destroying_effect);
}
}
}
/** /**
* @param {import('#client').Effect} effect * @param {import('#client').Effect} effect
* @returns {void} * @returns {void}
@ -249,7 +281,7 @@ export function destroy_effect(effect) {
} }
} }
effect.teardown?.call(null); execute_effect_teardown(effect);
var parent = effect.parent; var parent = effect.parent;

@ -8,7 +8,12 @@ import {
object_prototype object_prototype
} from './utils.js'; } from './utils.js';
import { snapshot } from './proxy.js'; import { snapshot } from './proxy.js';
import { destroy_effect, effect, user_pre_effect } from './reactivity/effects.js'; import {
destroy_effect,
effect,
execute_effect_teardown,
user_pre_effect
} from './reactivity/effects.js';
import { import {
EFFECT, EFFECT,
RENDER_EFFECT, RENDER_EFFECT,
@ -37,12 +42,18 @@ let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling // Used for handling scheduling
let is_micro_task_queued = false; let is_micro_task_queued = false;
export let is_flushing_effect = false; export let is_flushing_effect = false;
export let is_destroying_effect = false;
/** @param {boolean} value */ /** @param {boolean} value */
export function set_is_flushing_effect(value) { export function set_is_flushing_effect(value) {
is_flushing_effect = value; is_flushing_effect = value;
} }
/** @param {boolean} value */
export function set_is_destroying_effect(value) {
is_destroying_effect = value;
}
// Used for $inspect // Used for $inspect
export let is_batching_effect = false; export let is_batching_effect = false;
let is_inspecting_signal = false; let is_inspecting_signal = false;
@ -406,7 +417,7 @@ export function execute_effect(effect) {
destroy_effect_children(effect); destroy_effect_children(effect);
} }
effect.teardown?.call(null); execute_effect_teardown(effect);
var teardown = execute_reaction_fn(effect); var teardown = execute_reaction_fn(effect);
effect.teardown = typeof teardown === 'function' ? teardown : null; effect.teardown = typeof teardown === 'function' ? teardown : null;
} finally { } finally {
@ -658,11 +669,11 @@ export function flush_sync(fn, flush_previous = true) {
var result = fn?.(); var result = fn?.();
flush_tasks();
if (current_queued_root_effects.length > 0 || root_effects.length > 0) { if (current_queued_root_effects.length > 0 || root_effects.length > 0) {
flush_sync(); flush_sync();
} }
flush_tasks();
flush_count = 0; flush_count = 0;
return result; return result;

Loading…
Cancel
Save