Open Halmo opened 4 years ago
You are correct, the WeakJsPtr
is implemented as a strong pointer in JSC because I wasn't able to find a way to create traceable weak pointers in JSC.
When using V8, the v8::Global<T>
can be marked as weak and we get a callback during a GC to indicate that the given object is still alive (through our Trace
method).
In JSC, we have no such method. If we don't call JSValueProtect
, the pointer will become a dangling pointer once the JS value is freed and we can't trace it to conditionally keep it alive (i.e. use our Trace
method).
We probably can't have the JsThis
method return a new object each time since then the JS objects will compare differently even though they refer to the same C++ object.
In the case of MediaSource
, we may be able to mark it as IsShortLived
. While the MediaSource
is running, it is owned by the HTMLVideoElement
, which always has a non-zero ref count by the app. So we'd trace the HTMLVideoElement
and get to the MediaSource
. Once the video is detached, Shaka Player doesn't use it anymore, so it is effectively short lived.
Another option I just thought of is to just store the raw pointer and not protect it. Once the JS object is destroyed, we get a callback where we can clear the pointer on the BackingObject
. Technically this is invalid since properties added on the object should be preserved, even if there isn't an explicit JS reference; but I don't think we use this behavior. It doesn't matter that a new JS object is created because JS can't tell the difference (since it doesn't have a reference).
But in general, yes, this is a limitation of JSC. I haven't been able to figure out how to make a weak pointer that can be traced to conditionally be kept alive.
Thanks for the quick response @TheModMaker. 👍 I had thought about storing the raw JSObjectRef
/JSValueRef
on WeakJsPtr
and clearing the ref when finalize is called for the object as well. But for some reason when I made the change to store the Ref directly instead of a Handle
objects were not living properly and I was getting undefined references in JS. I didn't really dig into that too much further because I wasn't sure whether I was on the right track or not. I will need to dig into this further regardless though because we wouldn't be able to have these objects leaking in a production environment.
After #60, the JavaScript MediaSource
object is no longer in charge of managing the media pipeline. The large media pipeline is either managed by the app or by the ShakaPlayer
instance. The MediaSource
and HTMLVideoElement
objects may live longer than needed; but they are thin wrappers around those objects, so it shouldn't affect memory that much.
Looking some more at our heap (ObjectTracker
), there are only a few JavaScript objects alive over a long time period. So I don't think this leak is that important. Especially since there may be little we can do. JavaScriptCore doesn't allow us to create real weak references, so there may be no other option.
Since this shouldn't cause any more large leaks, I'm moving this to the backlog. I'd like to remove the IsShortLived
hacks, but I don't know of a way to do it.
When using JavaScriptCore instead of v8 it appears that BackingObjects are not cleaned up if they have a JsThis associated, unless they are marked as ShortLived by overriding
IsShortLived
.The
BackingObject
stores the associated JsThis using aWeakJsPtr
. TheWeakJsPtr
implementation for JSC stores the underlying object using a Handle (v8 uses a Global ref). For JSC aHandle
is autil::CFRef
, when aCFRef
of template typeJSValueRef
orJSObjectRef
is constructed it callsJSValueProtect
which prevents JSC from GCing that ref. Unless I'm mistaken this means that theWeakJsPtr
implementation for JSC is not really a weak reference to the JS object. The protect done by the Handle/CFRef is going to prevent the object from being GC'd until the Handle/CFRef is destroyed, which never happens once one exists for aBackingObject
, as far as I can tell.When a
BackingObject
has an associated JsThis it returns true forIsRootedAlive
(for JSC only).IsRootedAlive
is used byObjectTracker::IsJsAlive
to determine if the object is still alive in JS, because of the aboveIsRootedAlive
will always return true once a JsThis is associated with aBackingObject
which means theBackingObject
will never be destroyed. The only case I can see where the objects are destroyed for JSC is whenIsShortLived
returns true and short circuits theIsRootedAlive
check.All the above said this prevents objects such as
MediaSource
(which is quite heavy as it spawns a thread for thePipelineManager
etc) from ever being cleaned up once instanced, even when there is no reference to it left on the JS side. If you are to load enough videos in the lifetime of SPE you will easily run out of system resources (as low as 10 video loads depending on the device).Please let me know if I'm missing something or if this is a known limitation when using JSC with SPE. Thanks!