fix: prevent derived connection leak in untracked contexts (#18501)

When a  is only read inside an untracked context such as
(()), is_updating_effect was causing should_connect
to return true even though the active reaction (b) is not connected.
This caused derived (a) to be spuriously marked CONNECTED and added
to its dependencies' reactions arrays. When the component is destroyed,
nothing removes these stale deriveds from their deps' reactions,
creating a memory leak that retains the entire component scope.

Removing is_updating_effect from the should_connect condition ensures
that a derived is only connected when read by a reaction that is
actually part of the reactive graph. A disconnected derived reading
another derived should not cause the inner derived to connect.
pull/18517/head
rabindra789 5 days ago
parent 8fb7ceeba5
commit d9a5cc1802

@ -665,7 +665,7 @@ export function get(signal) {
(derived.f & CONNECTED) === 0 &&
!untracking &&
active_reaction !== null &&
(is_updating_effect || (active_reaction.f & CONNECTED) !== 0);
(active_reaction.f & CONNECTED) !== 0;
var is_new = (derived.f & REACTION_RAN) === 0;

@ -1503,4 +1503,22 @@ describe('signals', () => {
assert.deepEqual(log, ['inner destroyed', 'inner destroyed']);
};
});
test('derived read in an untracked context should not leak in deps reactions', () => {
return () => {
let s = state('hello');
let a = derived(() => $.get(s));
let b = derived(() => $.get(a));
let destroy = effect_root(() => {
$.get(b);
});
destroy();
// a was spuriously added to s.reactions via is_updating_effect
// even though the entire derived chain was read in an untracked context
assert.equal(s.reactions, null);
};
});
});

Loading…
Cancel
Save