proposal-signals icon indicating copy to clipboard operation
proposal-signals copied to clipboard

Consumers can unexpectedly hold unused memory in dependencies

Open DavidANeil opened this issue 11 months ago • 1 comments

Using this mock example

const listOfSignals = new Signal.State([]);

const sumOfList =  new Signal.Computed(() => {
    let sum = 0;
    for (const node of listOfSignals.get()) {
        sum +=  node.get();
    }
    return sum;
});
const watcher = new Signal.subtle.Watcher(() => {});
watcher.watch(sumOfList);

{
    const tempSignal = new Signal.state(3)
    listOfSignals.set([tempSignal]);
}

assert(sumOfList.get() === 3);

listofSignals.set([]);
// 

At the end of this mock code sumOfList is currently "dirty", but it still internally holds a reference to tempSignal as a producer dependency, if it were to recalculate it would be able to drop that dependency, and allow the memory to be freed, I have a few ideas for how this could be resolved.

Idea 1) Use WeakRef to store producers

This would allow the memory to be freed, so the internals don't hold memory longer than the computation closure. Cons:

  • Doesn't help if the cached return value itself is holding memory
  • In my experiments, this significantly hurt runtime performance

It is possible that, similar to #252 we might learn that engine native code is able to accomplish this without significant performance penalties.

Idea 2) Add a free method to Computed signals to return them to their original state

When invoked, this will mark all consumers as dirty, then reset the node to the original state it had when the object was born: no cached values, no consumers, no producers. This could allow a framework to free memory of all signals that have been stale for a certain amount of time. Cons:

  • Requires somewhat manual intervention to free up unused memory

Idea 2.5) Grant authority in the spec for the host to invoke free on any dirty signals, at its own discretion

When a browser is in a high memory pressure scenario, or has extra compute to spend it could be permitted to do what frameworks do: free memory for all signals that have been stale for a certain amount of time. Cons:

  • Significantly alters the user contract with the Signal API: Signals can now recompute even when no dependencies changed.

I am also open to other ideas that allow signals to be have a more intuitive memory profile.

DavidANeil avatar Jan 22 '25 21:01 DavidANeil

This looks related to https://github.com/tc39/proposal-signals/issues/265 (part 3)

jkup avatar Jul 14 '25 14:07 jkup