Skip to content

Commit fb61f4e

Browse files
authored
fix: correctly cleanup unowned derived dependency memory (#10280)
* fix: correctly cleanup unowned derived dependency memory * recursive * recursive
1 parent 1538264 commit fb61f4e

File tree

3 files changed

+42
-2
lines changed

3 files changed

+42
-2
lines changed

.changeset/three-camels-sell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: correctly cleanup unowned derived dependency memory

packages/svelte/src/internal/client/runtime.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ function destroy_references(signal) {
498498
if ((reference.f & IS_EFFECT) !== 0) {
499499
destroy_signal(reference);
500500
} else {
501+
destroy_references(reference);
501502
remove_consumers(reference, 0);
502503
reference.d = null;
503504
}
@@ -823,6 +824,7 @@ export async function tick() {
823824
function update_derived(signal, force_schedule) {
824825
const previous_updating_derived = updating_derived;
825826
updating_derived = true;
827+
destroy_references(signal);
826828
const value = execute_signal_fn(signal);
827829
updating_derived = previous_updating_derived;
828830
const status = current_skip_consumer || (signal.f & UNOWNED) !== 0 ? DIRTY : CLEAN;
@@ -1304,8 +1306,8 @@ export function derived(init) {
13041306
signal.i = init;
13051307
signal.x = current_component_context;
13061308
signal.e = default_equals;
1307-
if (!is_unowned) {
1308-
push_reference(/** @type {import('./types.js').EffectSignal} */ (current_effect), signal);
1309+
if (current_consumer !== null) {
1310+
push_reference(current_consumer, signal);
13091311
}
13101312
return signal;
13111313
}

packages/svelte/tests/signals/test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, assert, it } from 'vitest';
22
import * as $ from '../../src/internal/client/runtime';
3+
import type { ComputationSignal } from '../../src/internal/client/types';
34

45
/**
56
* @param runes runes mode
@@ -199,6 +200,38 @@ describe('signals', () => {
199200
};
200201
});
201202

203+
test('correctly cleanup onowned nested derived values', () => {
204+
return () => {
205+
const nested: ComputationSignal<string>[] = [];
206+
207+
const a = $.source(0);
208+
const b = $.source(0);
209+
const c = $.derived(() => {
210+
const a_2 = $.derived(() => $.get(a) + '!');
211+
const b_2 = $.derived(() => $.get(b) + '?');
212+
nested.push(a_2, b_2);
213+
214+
return { a: $.get(a_2), b: $.get(b_2) };
215+
});
216+
217+
$.get(c);
218+
219+
$.flushSync(() => $.set(a, 1));
220+
221+
$.get(c);
222+
223+
$.flushSync(() => $.set(b, 1));
224+
225+
$.get(c);
226+
227+
// Ensure we're not leaking dependencies
228+
assert.deepEqual(
229+
nested.slice(0, -2).map((s) => s.d),
230+
[null, null, null, null]
231+
);
232+
};
233+
});
234+
202235
// outside of test function so that they are unowned signals
203236
let count = $.source(0);
204237
let calc = $.derived(() => {

0 commit comments

Comments
 (0)