Skip to content

Memory Management

David Komer edited this page Feb 8, 2018 · 4 revisions

Table of Contents

Overview

Sodium manages its own memory via a builtin system. This provides an approach that works generally across various implementations, although there are some language-dependant differences. The goal of this article is to explain the similarities and differences, both for the end-user and the library developer.

There are two basic models of memory management:

  1. Finalizer-based used in languages with finalizers: e.g. Java/C#
  2. Dependency tracking in other languages: e.g. Typescript/C++ (though the C++ version needs work)

Usage

Lambdas in dependency tracked languages

Consider the following code (where c means "Cell" and s is "Stream"):

ccTopping = cFood.map(food => food.isPasta ? cPastaSauce : cSalt);
cTopping = Cell.switchC(ccTopping);

In that code sample, we return a Cell of various toppings depending on the type of food [1].

If those toppings are themselves tracked via the FRP system, outside of this occurrence, then the above code will work as-is without any leaks. One way to ensure that those Cells are tracked would be if we display the current values of cPastaSauce and cSalt by giving them a listener - or passing them through a series of mappings that always results in those Cells being listened to.

However, if the only end-point of those Cells are in this cFood.map, then we must inform Sodium to explicitly track these. After all, cFood might never be pasta[2], in which case cPastaSauce is also never tracked. Manually informing Sodium that it must track these Cells is the purpose of the builtin "lambda" functions. You use it like this:

ccTopping = cFood.map(lambda2(food => food.isPasta ? cPastaSauce : cSalt, [cPastaSauce, cSalt]));

In other words, you call lambdaN where the first argument is the lambda itself, and the second is an Array of the Cells you wish to use in the function.

[1] Why store the toppings in a Cell as opposed to just a plain object? The reason is that we want the topping itself to be dynamic - for example, cPastaSauce itself might vary depending on some "spicyness" setting.

[2] Of course cFood should really be some sort of fish :P

Internals

BOTH the finalizer and dependency-tracked implementations work this way:

  • A listener that is requested will keep the object alive until it is explicitly disposed. If it's discarded, the object is still kept alive.
  • The chain of dependencies for an object is also kept alive. If m = s.Map(... then m depends on s. So, while m is alive, s is kept alive.

For finalizer-based implementations:

  • There is also a listenWeak() variant of listen() where the object is not kept alive if the unlisten handle is discarded. That is, GC of the unlisten handle is an implicit dispose.

For dependency-tracking implementations:

  • When a listener is requested, the machinery for the whole chain of objects it depends on is activated. Before any listener is requested, the objects are in a latent state.
  • If a StreamSink or CellSink is in a latent state (because nothing is listening to it - usually indirectly through a chain of dependencies), then calling send() on it is a runtime error.
  • This also means that calling sample() on a cell is only going to work properly if that cell is activated by a listener. Otherwise, it's a runtime error.
  • Finalizer-based implementations don't have this restriction.
  • There is no listenWeak() variant of listen().

Both approaches achieve the same thing: Things are kept alive only when they are needed. Things are defined as "needed" when they are being listened to (directly or through dependencies). The rules about what happens before they are listened to are different between the two implementations.

The only thing you need to care about is how listen() works. Everything else is automatic.

(copied from original post from Steve @ http://sodium.nz/t/memory-listeners-and-tracking-all-of-it/68/2?u=dakom)

Unreachable Cells in switch

This is currently being investigated - see http://sodium.nz/t/lost-cells-in-switch/267