4

I have a subscription management in my ko model, which saves any subscription by intercepting them and saving reference to them.

I then dispose() them, but I need to understand sometimes if a sub is already disposed. Is the member Gb, a boolean of the subscription object, an active-inactive flag? I see that sometimes when I dispose a subscription its Gb becomes false, sometimes not. Do I have to interpret this as a dispose fail?

And once I dispose a subscription, the subscription object, is ready to be garbage collected?

EDIT: I just need to understand if a subscription is already disposed, as said in the title. I obtain the subscription in the only way I know, saving a reference to it when declared:

var mySub = myObservable.subscribe(function(){/*do something*/});

Some time after I need a way to determine if mySub is already disposed.

function isDisposed(mySubscription) {
    // if is disposed return true else return false
}

I need to perform some logic based on this, not only dispose it if it is not disposed already (or I could simply call again the dispose method). Is it possible to determine subsbcription disposal status?

Nillus
  • 1,131
  • 1
  • 14
  • 32
  • 1
    Your question is not clear. Could you provide the code that you use to intercept subscription? Why do you do it? A subscription in knockout is simply an item in a subscribable's array of subscriptions. So while subscribable exists subscribers will no be collected by garbadge collector. Anyway please clarify – Olga Jun 15 '15 at 11:58
  • The fact is, I store every subscription in an array, in my model. When I drop my model with cleanNode I get memory leaks in IE6, so I am trying to destroy all subscriptions with dispose() on every item of my subscriptions array, and then I do cleanNode. I only want to be able, during dispose phase, to discriminate between already disposed subscriptions and those who aren't, because some models may want to dispose subscriptions by themselves, since they are not aware that I am doing it for all stored subscriptions in my dropModel custom function() – Nillus Jun 15 '15 at 13:49
  • 1
    There is a property `isDisposed` for a subscription, but it is not exported in production version of ko (that is not available to use). But looking at source there is nothing wrong in calling dispose multiple times. Calling dispose for a subscription causes the subscrible (target) to remove subscription from an array: ` var subscription = new ko.subscription(self, boundCallback, function () { ko.utils.arrayRemoveItem(self._subscriptions[event], subscription); }); ` – Olga Jun 16 '15 at 11:38
  • 1
    Does this help, please tell. And about memory leaks - if you are using computeds then it is essential to dispose them manually if their dependencies are not disposed. Because computed store reference to their dependencies, while dependencies store references to computeds. – Olga Jun 16 '15 at 11:38
  • Yes this helps. I take care of disposing every manual subscription and computed/pureComputed with dispose(), but I am afraid that something of the manual subscription remains. In the subscription object, I can find inside a da() object, which is the observable subscribed. That could leak memory even when the subscription is disposed? And if I can't use isDisposed, I can't get what I need, especially now that we understod it is not proper to dispose twice a subscription? – Nillus Jun 16 '15 at 14:24
  • You could reformulate in an answer so I can eventually mark it as resolved – Nillus Jun 16 '15 at 14:26

2 Answers2

5

About manual subscriptions

Knockout source is compiled with Google Closure Compiler so only properties and methods explicetly exported in source appear in compiled library code.

With that said a subscription has a "private" property isDisposed, but it is not exported. Thus the only API exported for subscription is dispose.

Glimps of source code - ko.subscription (knockout-3.1.0.debug):

ko.subscription = function (target, callback, disposeCallback) {
    this.target = target;
    this.callback = callback;
    this.disposeCallback = disposeCallback;
    this.isDisposed = false;
    ko.exportProperty(this, 'dispose', this.dispose);
};
ko.subscription.prototype.dispose = function () {
    this.isDisposed = true;
    this.disposeCallback();
};

About memory leaks and computeds

1) Just an interesting fact about computeds - consider the following computed

var myObservable1 = ko.observable(true);
var myObservable2 = ko.observable('foo');

var myComputed = ko.computed(function () {
    if (myObservable1()) {
        return myObservable2() + 'bar';
    }
});

In this example myComputed has 2 dependecies. But if we were to assing false to myObservable1 myComputed would reevaluate and after reevalution it would only have 1 dependency and subscription to myObservable2 would be disposed.

Why it is disposed:

The explanation lies in how computed is evaluated - it registers dependencies the following way - if any observable is read (meaning code like myObservable1()) during evaluation - computed recieves a callback with this observable, checks its id and stores it in a new dependency array. Once evaluation is complete - old dependency array is disposed. In our example when myObservable1 is set to false myObservable2 is never read - since we never enter if block. So it is not a new dependecy and old dependency is disposed.

2) Another interesting fact. Consider snippet:

(function () {
    var myObservable = ko.observable(0);
    var myModel = {
        myComputed: ko.computed(function () {
            console.log(myObservable());
        })
    };
    myModel = undefined;
    myObservable(42); // Outputs 42
})();

The computed is not collected by garbage collector because infact the reference to it exists inside it's dependency.

Glimps of source code - ko.computed (knockout-3.1.0.debug):

function addSubscriptionToDependency(subscribable, id) {
    if (!_subscriptionsToDependencies[id]) {
        _subscriptionsToDependencies[id] = subscribable.subscribe(evaluatePossiblyAsync);
        ++_dependenciesCount;
    }
}
...
function evaluatePossiblyAsync() {
    var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
    if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
        clearTimeout(evaluationTimeoutInstance);
        evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
    } else if (dependentObservable._evalRateLimited) {
        dependentObservable._evalRateLimited();
    } else {
        evaluateImmediate();
    }
}

Reference to dependentObservable is preserved because reference to evaluatePossiblyAsync is preserved (Closures, JS and all that Jazz).

Whoa, that is all I had to say. Hope some thought come to mind.

Olga
  • 1,648
  • 1
  • 22
  • 31
  • Well explained. However, 2 things: with `ko.pureComputed` subscriptions are automatically disposed (or so the docs say, KO 3.2+ I think), and secondly: could you explain why in case 2 subscription to `myObservable2` is disposed?? When you say 'we assign false', you mean `myObs = false` or `myObs(false)` – webketje Jun 16 '15 at 23:44
  • I haven't given pureComputeds that much attention so no comment there. By assigning I mean `myObservable1(false)` - after this assignment computed is reevaluated. For explanation see updated answer. – Olga Jun 17 '15 at 10:20
  • If I can humbly notice a thing, I wouldn't use directly undefined, as it is not a reserved word, and could be defined to have some type and value like a variabe: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/undefined. I'd use myModel = null – Nillus Jun 17 '15 at 22:40
  • I am fearless (in this aspect) but I do know that some patterns exist to avoid this problem. The choice is yours. Messing with undefined is a bad practice in itself and hopefully no one does it =) LOL – Olga Jun 18 '15 at 15:11
3

One definitive way to determine if an active computed has been disposed is to see how many subscriptions an observable has.

Here, have a look:

>>> y = ko.observable()
>>> y.getSubscriptionsCount()
0
>>> x = ko.computed(function () { y() })
>>> y.getSubscriptionsCount()
1
>>> x.dispose()
>>> y.getSubscriptionsCount()
0

Be mindful that with pure computeds the getSubscriptionsCount will be 0 when the pure compute is asleep i.e. has no subscriptions itself. i.e.

>>> x = ko.pureComputed(function () { y() })
>>> y.getSubscriptionsCount()
0
>>> z = x.subscribe(console.log.bind(console))
>>> y.getSubscriptionsCount()
1

In other words pure computeds may appear to be disposed of when they are merely sleeping.

You could wake up a pure computed by subscribing to it; if the subscription count of the dependency is still zero, it must be disposed.

Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343