Chakracore: How do objects created using JsCreateExternalObject get cleared up

Created on 19 Feb 2018  路  12Comments  路  Source: chakra-core/ChakraCore

I was wondering how you are able to clean up objects created on the heap and used via JsCreateExternalObject.

I tried using the FinalizerCallback (in my previous issue #4679) but whenever i have that referenced in my JsCreateExternalObject call (and not even do anything) I get all sorts of crashes and corruptions happening. So I must be doing something wrong.

I tried looking at one of the hosting samples and that doesn't delete the GLPoint objects created so it must be leaking unless JsCreateExternalObject auto-cleans up?

Answered Question

All 12 comments

You can take a look at how we do things in node-chakracore, e.g. in https://github.com/nodejs/node-chakracore/blob/master/deps/chakrashim/src/v8functiontemplate.cc. The general shape of things is we new up some native thing, and then wrap it in an ExternalObject with an appropriate finalizer function. In that finalizer, we cast the void * argument back to the correct type that we expect it to be, and then delete it to clean up.

As long as you are passing plain c-style functions (not member functions etc, but static class functions are fine) as the finalizer callback then nothing strange should happen when calling JsCreateExternalObject.

Hmm, that's effectively what i am doing but its crashing when disposing of the runtime with "Object reference not set to instance" or other strange messages, sometimes even with memory corruption errors. These all go away as soon as I remove the finalizer.

And I guess the hosting sample has a memory leak then as it isn't using a FinalizerCallback so doesnt ever clean up.

I ran into similar issues when trying to read properties off of JS objects in my finalizers. As the finalizer may be called during runtime destruction (i.e. after the JsContextRef is out of scope) you can only safely access native resources in your finalizer.

I get exceptions and all sorts of odd errors if i just setup a FinalizerCallback which is empty and does nothing. As soon as i pass null as my callback in the JsCreateExternalObject function, those exceptions go away.

You are still using your c# wrapper, is that correct? Are you making sure to pass a plan c-style function to JsCreateExternalObject? Could your wrapper be taking or converting references somehow?

Yes still using the C# Wrapper (not really mine, its the one in your hosting samples repo), yes its a plain static function:

private static void ImageFinalizeCallback( IntPtr dataPtr )
{
    // empty
}


public static JavaScriptValue MyImage( JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData )
{
    var image = new MagickImage( "d:\\file.png" );
    var imageHandle = GCHandle.Alloc( image );
    var externalInstanceObject = JavaScriptValue.CreateExternalObject( GCHandle.ToIntPtr( imageHandle ), ImageFinalizeCallback );
    externalInstanceObject.Prototype = ImagePrototype;
}

But even an empty one causes exceptions on disposing of the runtime. The only thing that works is passing null as the callback to JavaScriptValue.CreateExternalObject (which is a simple wrapper around JsCreateExternalObject.

I am really confused!!

What happens if you try something like

c# public static JavaScriptValue MyImage( JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData ) { var image = new MagickImage( "d:\\file.png" ); var imageHandle = GCHandle.Alloc( image ); ChakraHost.Hosting.JavaScriptObjectFinalizeCallback finalizeCallback = ImageFinalizeCallback; var externalInstanceObject = JavaScriptValue.CreateExternalObject( GCHandle.ToIntPtr( imageHandle ), finalizeCallback ); externalInstanceObject.Prototype = ImagePrototype; }

I'm not so experienced with c# <-> native bindings, but perhaps you need to make sure it uses a delegate function?

Maybe your finalizer function gets GC'ed? Try making a delegate holding on to the finalizer and then pass the delegate to JsCreateExternalObject to keep it alive.

Yeah, very good point. This would explain the oddities and also that sometimes it gets called, sometimes it doesn鈥檛. Don鈥檛 know why I didn鈥檛 think of that as you have to do that for the FunctionCallbacks to ensure they don鈥檛 get GC鈥檈d.

I will try in the morning and feedback

Admittedly I'm not that familiar with the inner workings of the .NET CLR, but I'm confused on 1) why a function defined as part of a class and presumably baked right into the IL would (or even could) ever be GC'd, and 2) how wrapping it in a delegate prevents that same function from being GC'd once the delegate goes out of scope.

Basically in .NET when you pass a function to unmanaged code, .NET doesn't know unmanaged side has an implicit reference after that line of code and will proceed and GC. Having a static delegate is an explicit way of keeping that reference in .NET world.

That was it, thanks everyone. Seems obvious now, argh!!

Was this page helpful?
0 / 5 - 0 ratings