Observable.hasObservers() can incorrectly report false even though there are observers

I had wrapped onBeforeRenderObservable.removeCallback() inside a hasObservers() check to avoid calling removeCallback unnecessarily. But then I noticed that there are situations where the observer would keep firing even though my remove function was called correctly.

It turned out that hasObservers() reported false even though my observer was still there.

After some debugging I managed to figure out why it happens and created a Playground example: https://www.babylonjs-playground.com/#7YJKKH#1

If Observer.remove() is called more than once before the internal this._remove() timeout fires, it causes internal this._numObserversMarkedAsDeleted to incremented twice (once for both remove calls), so even after the observer is removed the counter remains positive.

Oh, and as a related note: While inspecting the source code trying to figure out what could cause this I noticed another potential case where this could happen. (Although that wasn’t happening in my case.)

If notifyObserver() is called with an observer that isn’t actually part of the Observable I believe that would also cause the increment counter to be stuck with too high value. Although I haven’t verified this with actual code.

A good fix would be to make _deferUnregister() itself check for observer._willBeUnregistered === false before doing anything. That way even if in the future another “bad” call of _deferUnregister() is added somewhere, it would not break the counter again. :slight_smile:

You’re right! Here’s the PR:

2 Likes