-
Notifications
You must be signed in to change notification settings - Fork 71
Description
This issue suggests that all the internal references in the Signal library should be weak references in order to avoid the potential for leaking objects, either temporarily or permanently.
Goal 1: Avoid leaking if we don't call unwatch
Given the code,
const a = Signal.State(0);
const b = Signal.Computed(() => a.get());
b.get();
const w = Signal.Watcher(() => {});
w.watch(b);we end up with the following dependency graph,
graph TD;
root --> a & b & w
a --> b
b --> a
b --> l1(["() => a.get()"])
l1 --> a
w --> l2(["() => {}"])
w --> b
b --> w
if we no longer hold rooted references to w and b, we will leak these objects as long as there is a rooted reference to a and we have yet to call w.unwatch(b).
const a = Signal.State(0);
{
const b = Signal.Computed(() => a.get());
b.get();
const w = Signal.Watcher(() => {});
w.watch(b);
}since there is a strong reference internally held from a to b once a watcher is attached,
graph TD;
root --> a
a --> b
b --> a
b --> l1(["() => a.get()"])
l1 --> a
w --> l2(["() => {}"])
w --> b
b --> w
If however, the back references from source to consumer that are created once a watcher is attached are made to be weak, we no longer have a strong, rooted reference to b and w,
graph TD;
root --> a
a -.-> b
b --> a
b --> l1(["() => a.get()"])
l1 --> a
w --> l2(["() => {}"])
w --> b
b -.-> w
allowing these to be GCed,
graph TD;
root --> a
Recommendation: The references from consumer to source (Watcher to Signal and Computed to Signal) should be made weak.
Goal 2: Avoid leaking if we don't call unwatch (part 2)
Given the code,
const w = Signal.Watcher(() => {
const p = w.getPending();
doAsync(() => doTheWork(p));
});
const b = Signal.Computed(() => getSignalA().get());
const d = Signal.Computed(() => getSignalC().get());
w.watch(b, d);we have the following dependency graph (omitting the lambdas for simplicity),
graph TD;
w --> b --> a
w --> d --> c
root --> d & b & w
b -.-> w
d -.-> w
a -.-> b
c -.-> d
If we no longer hold rooted references to b or d, it is not possible for it to be GCed since w holds strong references to both,
const w = Signal.Watcher(() => {
const p = w.getPending();
doAsync(() => doTheWork(p));
});
const b = Signal.Computed(() => getSignalA().get());
{
const d = Signal.Computed(() => getSignalC().get());
w.watch(b, d);
}d and c are still rooted despite not being reachable from the user's code,
graph TD;
w --> b --> a
w --> d --> c
root --> b & w
b -.-> w
d -.-> w
a -.-> b
c -.-> d
If the Watcher holds weak references to the Signals that it watches, then Computeds not reachable through user code can be GCed.
graph TD;
w -.-> b --> a
w -.-> d --> c
root --> b & w
b -.-> w
d -.-> w
a -.-> b
c -.-> d
Here both d and c can be collected,
graph TD;
w -.-> b --> a
root --> b & w
b -.-> w
a -.-> b
Recommendation: The references from Watcher to Signal should be made weak.
Goal 3: Avoid Computed-capture reference mismatch
Given the code below,
const i = Signal.State(1);
const arr = [
Signal.State("a"),
Signal.State("b"),
];
const c = Signal.Computed(() => arr[i.get()].get());
assert(c.get() === "b");we get the following dependency graph,
graph TD;
root --> c & i & arr
c --> b
b -.-> c
c --> l(["() => arr[i.get()].get()"])
l --> arr
l --> i
arr --> b
arr --> a
If we then change the index and remove the last element of arr,
i.set(0);
arr.pop();we continue to depend on b even though it is not reachable from user code
graph TD;
root --> c & i & arr
c <-.-> b
b -.-> c
c --> l(["() => arr[i.get()].get()"])
l --> arr
l --> i
arr --> a
b ~~~ a
and this is only fixed when c.get() is invoked and the internal dependencies within Signal are updated.
If all of the internal Signal references are made weak, we no longer have a strong, rooted reference to b and it can be collected.
graph TD;
root --> c & i & arr
b <-.-> c
c --> l(["() => arr[i.get()].get()"])
l --> arr
l --> i
arr --> a
Recommendation: The references from Computed to Signal should be made weak.
This is the last remaining internal Signal reference that was not weak.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status