Skip to content

Commit 13a6d55

Browse files
authored
fix: improve derived connection to ownership graph (#15137)
* fix: improve derived connection to ownership graph * revised * revised * revised * revised * feedback * feedback * invasive change * fix bugs * fix other bug
1 parent 9410ad0 commit 13a6d55

File tree

6 files changed

+199
-123
lines changed

6 files changed

+199
-123
lines changed

.changeset/blue-rocks-play.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: improve derived connection to ownership graph

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

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,24 @@ import { component_context } from '../context.js';
3535
/*#__NO_SIDE_EFFECTS__*/
3636
export function derived(fn) {
3737
var flags = DERIVED | DIRTY;
38+
var parent_derived =
39+
active_reaction !== null && (active_reaction.f & DERIVED) !== 0
40+
? /** @type {Derived} */ (active_reaction)
41+
: null;
3842

39-
if (active_effect === null) {
43+
if (active_effect === null || (parent_derived !== null && (parent_derived.f & UNOWNED) !== 0)) {
4044
flags |= UNOWNED;
4145
} else {
4246
// Since deriveds are evaluated lazily, any effects created inside them are
4347
// created too late to ensure that the parent effect is added to the tree
4448
active_effect.f |= EFFECT_HAS_DERIVED;
4549
}
4650

47-
var parent_derived =
48-
active_reaction !== null && (active_reaction.f & DERIVED) !== 0
49-
? /** @type {Derived} */ (active_reaction)
50-
: null;
51-
5251
/** @type {Derived<V>} */
5352
const signal = {
54-
children: null,
5553
ctx: component_context,
5654
deps: null,
55+
effects: null,
5756
equals,
5857
f: flags,
5958
fn,
@@ -87,19 +86,14 @@ export function derived_safe_equal(fn) {
8786
* @param {Derived} derived
8887
* @returns {void}
8988
*/
90-
function destroy_derived_children(derived) {
91-
var children = derived.children;
92-
93-
if (children !== null) {
94-
derived.children = null;
95-
96-
for (var i = 0; i < children.length; i += 1) {
97-
var child = children[i];
98-
if ((child.f & DERIVED) !== 0) {
99-
destroy_derived(/** @type {Derived} */ (child));
100-
} else {
101-
destroy_effect(/** @type {Effect} */ (child));
102-
}
89+
export function destroy_derived_effects(derived) {
90+
var effects = derived.effects;
91+
92+
if (effects !== null) {
93+
derived.effects = null;
94+
95+
for (var i = 0; i < effects.length; i += 1) {
96+
destroy_effect(/** @type {Effect} */ (effects[i]));
10397
}
10498
}
10599
}
@@ -147,7 +141,7 @@ export function execute_derived(derived) {
147141

148142
stack.push(derived);
149143

150-
destroy_derived_children(derived);
144+
destroy_derived_effects(derived);
151145
value = update_reaction(derived);
152146
} finally {
153147
set_active_effect(prev_active_effect);
@@ -156,7 +150,7 @@ export function execute_derived(derived) {
156150
}
157151
} else {
158152
try {
159-
destroy_derived_children(derived);
153+
destroy_derived_effects(derived);
160154
value = update_reaction(derived);
161155
} finally {
162156
set_active_effect(prev_active_effect);
@@ -188,9 +182,9 @@ export function update_derived(derived) {
188182
* @returns {void}
189183
*/
190184
export function destroy_derived(derived) {
191-
destroy_derived_children(derived);
185+
destroy_derived_effects(derived);
192186
remove_reactions(derived, 0);
193187
set_signal_status(derived, DESTROYED);
194188

195-
derived.v = derived.children = derived.deps = derived.ctx = derived.reactions = null;
189+
derived.v = derived.deps = derived.ctx = derived.reactions = null;
196190
}

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

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function validate_effect(rune) {
5353
e.effect_orphan(rune);
5454
}
5555

56-
if (active_reaction !== null && (active_reaction.f & UNOWNED) !== 0) {
56+
if (active_reaction !== null && (active_reaction.f & UNOWNED) !== 0 && active_effect === null) {
5757
e.effect_in_unowned_derived();
5858
}
5959

@@ -99,7 +99,6 @@ function create_effect(type, fn, sync, push = true) {
9999
var effect = {
100100
ctx: component_context,
101101
deps: null,
102-
deriveds: null,
103102
nodes_start: null,
104103
nodes_end: null,
105104
f: type | DIRTY,
@@ -153,7 +152,7 @@ function create_effect(type, fn, sync, push = true) {
153152
// if we're in a derived, add the effect there too
154153
if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) {
155154
var derived = /** @type {Derived} */ (active_reaction);
156-
(derived.children ??= []).push(effect);
155+
(derived.effects ??= []).push(effect);
157156
}
158157
}
159158

@@ -395,22 +394,6 @@ export function execute_effect_teardown(effect) {
395394
}
396395
}
397396

398-
/**
399-
* @param {Effect} signal
400-
* @returns {void}
401-
*/
402-
export function destroy_effect_deriveds(signal) {
403-
var deriveds = signal.deriveds;
404-
405-
if (deriveds !== null) {
406-
signal.deriveds = null;
407-
408-
for (var i = 0; i < deriveds.length; i += 1) {
409-
destroy_derived(deriveds[i]);
410-
}
411-
}
412-
}
413-
414397
/**
415398
* @param {Effect} signal
416399
* @param {boolean} remove_dom
@@ -468,7 +451,6 @@ export function destroy_effect(effect, remove_dom = true) {
468451
}
469452

470453
destroy_effect_children(effect, remove_dom && !removed);
471-
destroy_effect_deriveds(effect);
472454
remove_reactions(effect, 0);
473455
set_signal_status(effect, DESTROYED);
474456

packages/svelte/src/internal/client/reactivity/types.d.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export interface Reaction extends Signal {
3636
export interface Derived<V = unknown> extends Value<V>, Reaction {
3737
/** The derived function */
3838
fn: () => V;
39-
/** Reactions created inside this signal */
40-
children: null | Reaction[];
39+
/** Effects created inside this signal */
40+
effects: null | Effect[];
4141
/** Parent effect or derived */
4242
parent: Effect | Derived | null;
4343
}
@@ -51,8 +51,6 @@ export interface Effect extends Reaction {
5151
*/
5252
nodes_start: null | TemplateNode;
5353
nodes_end: null | TemplateNode;
54-
/** Reactions created inside this signal */
55-
deriveds: null | Derived[];
5654
/** The effect function */
5755
fn: null | (() => void | (() => void));
5856
/** The teardown function returned from the effect function */

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

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { define_property, get_descriptors, get_prototype_of, index_of } from '..
44
import {
55
destroy_block_effect_children,
66
destroy_effect_children,
7-
destroy_effect_deriveds,
87
execute_effect_teardown,
98
unlink_effect
109
} from './reactivity/effects.js';
@@ -28,7 +27,12 @@ import {
2827
} from './constants.js';
2928
import { flush_tasks } from './dom/task.js';
3029
import { internal_set, set } from './reactivity/sources.js';
31-
import { destroy_derived, execute_derived, update_derived } from './reactivity/deriveds.js';
30+
import {
31+
destroy_derived,
32+
destroy_derived_effects,
33+
execute_derived,
34+
update_derived
35+
} from './reactivity/deriveds.js';
3236
import * as e from './errors.js';
3337
import { FILENAME } from '../../constants.js';
3438
import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js';
@@ -409,7 +413,16 @@ export function update_reaction(reaction) {
409413
skipped_deps = 0;
410414
untracked_writes = null;
411415
active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
412-
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
416+
// prettier-ignore
417+
skip_reaction =
418+
(flags & UNOWNED) !== 0 &&
419+
(!is_flushing_effect ||
420+
// If we were previously not in a reactive context and we're reading an unowned derived
421+
// that was created inside another reaction, then we don't fully know the real owner and thus
422+
// we need to skip adding any reactions for this unowned
423+
((previous_reaction === null || previous_untracking) &&
424+
/** @type {Derived} */ (reaction).parent !== null));
425+
413426
derived_sources = null;
414427
set_component_context(reaction.ctx);
415428
untracking = false;
@@ -517,6 +530,8 @@ function remove_reaction(signal, dependency) {
517530
if ((dependency.f & (UNOWNED | DISCONNECTED)) === 0) {
518531
dependency.f ^= DISCONNECTED;
519532
}
533+
// Disconnect any reactions owned by this reaction
534+
destroy_derived_effects(/** @type {Derived} **/ (dependency));
520535
remove_reactions(/** @type {Derived} **/ (dependency), 0);
521536
}
522537
}
@@ -564,7 +579,6 @@ export function update_effect(effect) {
564579
} else {
565580
destroy_effect_children(effect);
566581
}
567-
destroy_effect_deriveds(effect);
568582

569583
execute_effect_teardown(effect);
570584
var teardown = update_reaction(effect);
@@ -934,30 +948,20 @@ export function get(signal) {
934948
new_deps.push(signal);
935949
}
936950
}
937-
}
938-
939-
if (
951+
} else if (
940952
is_derived &&
941953
/** @type {Derived} */ (signal).deps === null &&
942-
(active_reaction === null || untracking || (active_reaction.f & DERIVED) !== 0)
954+
/** @type {Derived} */ (signal).effects === null
943955
) {
944956
var derived = /** @type {Derived} */ (signal);
945957
var parent = derived.parent;
946958

947959
if (parent !== null) {
948-
// Attach the derived to the nearest parent effect or derived
949-
if ((parent.f & DERIVED) !== 0) {
950-
var parent_derived = /** @type {Derived} */ (parent);
951-
952-
if (!parent_derived.children?.includes(derived)) {
953-
(parent_derived.children ??= []).push(derived);
954-
}
955-
} else {
956-
var parent_effect = /** @type {Effect} */ (parent);
957-
958-
if (!parent_effect.deriveds?.includes(derived)) {
959-
(parent_effect.deriveds ??= []).push(derived);
960-
}
960+
// If the derived is owned by another derived then mark it as unowned
961+
// as the derived value might have been referenced in a different context
962+
// since and thus its parent might not be its true owner anymore
963+
if ((parent.f & UNOWNED) === 0) {
964+
derived.f ^= UNOWNED;
961965
}
962966
}
963967
}

0 commit comments

Comments
 (0)