Memory Management

The need for memory management arises mostly in situations where you are constantly creating new pins, agents and flows, while discarding the old ones. Typically these flows are subscribed to, i.e. there are callback functions that are supposed to respond to some event/data coming through the flow. Elements of the flow can hold references to these callbacks and vice-versa, while also these callbacks can hold references to their clojure. This means after you are done with some flow if you do not dispose of its subsriptions properly, it can lead to some obscure reference path to a lot of objects that the garbage collector cannot clear (because of the reference), and so will stack-up and start taking a toll on memory consumption in cases where you constantly create new copies of them.

Clearing up

CONNECTIVE's pins and agents generally track all subscriptions made to them via their standard API, i.e. with the use of .subscribe() or .bind() methods. Every pin and agent is also equipped with a .clear() method, which will unsubscribe all internal subscriptions alongside all tracked external subscriptions.
let myAgent = ...;
let myPin = ...;

...
...

/*!*/myAgent.clear();
/*!*/myPin.clear();


WARNING: Calling .clear() will effectively make the pin or the agent useless, so you must call it WHEN you are truly want to dispose the pin or the agent.

Additionally, clearing sources will cause them to send a complete notification through-out the flow, which will cause receiving pins who do not have any other sources to also become unusable. Note that in this case you might need to clear out such pins on your own, as receiving a complete signal does not mean the pin will be cleared out. You can read more about this behavior here.

Custom Agents

When creating custom agents, you must be extra careful with tracking internal subscriptions, pins and agents, and clear them when necessary. This is partly the reason why it is highly recommended to utilize the Composition class for creating custom agents and using its .add() method to add components of your sub-flow:
class X extends Composition {
  ...

  build() {
/*!*/    this.someState = this.add(state()) as State; // --> this will now be cleared properly
/*!*/    this.add('whatever', expr((x, y) => x + y)); // --> so will this guy
  }

  ...
}


Other subscriptions

All subscriptions you create via CONNECTIVE's API are RxJS's Subscriptions, which means you can hold a reference to them and call their .unsubscribe() method later. This is specifically useful when you create subscriptions that are not tracked by default, for example for when you subscribe to the underlying Observable of a pin directly:
let p = pin();

...

let sub = p.subscribe(() => ...);
...
/*!*/sub.unsubscribe();


Proxies

A common case of constantly disposed agents is when you have a static-flow and you use proxy() to swap in and out a lot of shorter-lived agents. Though you need to clear up these proxied agents using their .clear() methods, you also need to un-proxy them from your flow.

The .proxy() method of a proxy() returns a Subscription object, holding references to all internal subscriptions that cause the agent to be proxied. Simply calling .unsubscribe() method of this Subscription will cause the agent to be un-proxied:
let p = proxy();
...

let myAgent = ...;
/*!*/let sub = p.proxy(myAgent);

...

/*!*/sub.unsubscribe(); // --> now myAgent is unproxied
myAgent.clear();   // --> clear it up if this was its only use.


You can see a more elaborate example of this behaviour here.




Copied to Clipboard!