This project is read-only.

How to dispose the object?

Nov 11, 2013 at 11:41 AM
James,

I was trying to dispose a V8 object after it goes out of scope. I tried using the Dispose method of V8NativeObject but i still see that the object is still in scope and not garbage collected yet. I am not sure if i am missing something or there is a wrong usage of the object.

The code I use is as follows,
[TestClass]
    public class V8ObjectDisposeTest
    {
        private static V8Engine v8Engine = new V8Engine();
        [TestMethod]
        public void testDisposeOfV8Object()
        {
            v8Engine.WithContextScope = () =>
            {
                string jsSourceRecipe =
                   "function testV8Dispose() {" +
                   "var V8DisposeTest = new V8DisposeTestClass();" +
                    " V8DisposeTest.dispose();  " +
                    " return V8DisposeTest; } " +
                   "testV8Dispose();";

                Handle result = v8Engine.Execute(jsSourceRecipe, "ContextName");
                Assert.IsTrue(result.IsUndefined);
            };
        }

        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            v8Engine.WithContextScope = () =>
            {
                v8Engine.RegisterType(typeof(V8DisposeTestClass), null, true, ScriptMemberSecurity.Locked);
                v8Engine.GlobalObject.SetProperty(typeof(V8DisposeTestClass));

                FunctionTemplate v8DisposeTestClassFunctionTemplate = v8Engine.CreateFunctionTemplate("V8DisposeTestClass");
                V8Function v8DisposeTestClassWrapperConstructorFunction =
                    v8DisposeTestClassFunctionTemplate.GetFunctionObject(
                        V8DisposeTestClass.V8DisposeTestClassConstructorCallBack);
                v8DisposeTestClassFunctionTemplate.SetProperty("V8DisposeTestClass",
                                                           v8DisposeTestClassWrapperConstructorFunction
                                                               .AsInternalHandle,
                                                           V8PropertyAttributes.Locked);
                v8Engine.GlobalObject.SetProperty("V8DisposeTestClass",
                                                  v8DisposeTestClassWrapperConstructorFunction.AsInternalHandle,
                                                  V8PropertyAttributes.Locked);
            };
        }
    }

    public class V8DisposeTestClass : V8NativeObject
    {
        int value = 0;

        public void Dispose(V8Engine engine)
        {          
            base.Dispose();
        }

        public static InternalHandle V8DisposeTestClassConstructorCallBack(V8Engine engine, bool isConstructCall, InternalHandle _this,
                                params InternalHandle[] args)
        {
            V8DisposeTestClass test = engine.CreateObject<V8DisposeTestClass>(true);
            return test.AsInternalHandle;
        }


        public InternalHandle DisposeCallBack(V8Engine engine, bool isConstructCall, InternalHandle _this,
                             params InternalHandle[] args)
        {
            if (!isConstructCall)
            {
                this.Dispose(engine);
            }
            return _this;
        }

        public override void Initialize()
        {
            base.Initialize();
            PostInitialize();
        }

        private void PostInitialize()
        {
            V8HelperClass.ExposeJavaScriptCallBackFunctionToJavaScriptViaV8Engine(Handle, DisposeCallBack, "dispose");
        }
    }
In the above Test we expect that the assert.istrue(result.IsUndefined) yields us true. However the actual result turned out to be false indicating that the Dispose wasn't successful. The above code was written to verify if the Dispose in V8NativeObjects works as it is expected.

I am using the version committed on 18th Sep 2013 [Committed by James on Sep 18, 2013. Commit 5169a9b32881.]
Nov 11, 2013 at 12:49 PM
Hi,

Calling dispose on the object only triggers any custom dispose methods you add to the CLR object itself. You need to call dispose on the handle, not the object; so something like "V8DisposeTest.Handle.dispose()".
This should force the release of the object to be collected (even if there may be other native V8 references).

Nov 11, 2013 at 1:55 PM
Excellent thanks for the quick update, shall try this tomorrow and post an update.
Nov 11, 2013 at 3:02 PM
Note: Just updated the release binaries in the dev branch with a slight fix to the {handle}.dispose() process. Just note that "WithContextScope" no longer exists.
Nov 11, 2013 at 7:33 PM
Hi karthipt,

I never had good time to review the code until now, and I just realized that My suggestion actually won't work. The system will try to protect from destroying objects that have native handles remaining, so you can't normally force the handle to dispose unless the native V8 says it can happen. I'll look into putting in a method to force the handle and object to separate. ;)
Nov 12, 2013 at 4:11 AM
ok, I shall wait for your fix.
Nov 12, 2013 at 6:27 PM
Ok, I've updated the development branch release-built binaries with a new function "{Handle}.ReleaseManagedObject()", which you can use to forcibly separate an object from its handle. Hope that helps! :)
Nov 13, 2013 at 6:43 AM
Hi James,

I have tried the following lines of code with the new binaries,

handle.ReleaseManagedObject();
handle.Object.Dispose();
handle.Dispose();
Engine.ForceV8GarabageCollection();

in the above example and i could see that the object is still not removed from the memory. Any tips as where i am going wrong?
Nov 13, 2013 at 2:56 PM
Hmmm ... might be a bug in the object's finalizer (something I forgot to do after the object is detached). I'll double check ...
Nov 13, 2013 at 3:09 PM
Edited Nov 13, 2013 at 3:22 PM
I double checked and everything is working as expected on my end. You only need these lines:
handle.ReleaseManagedObject(); // (separate the object from the handle - a "place holder" object is used to swap out the object)
handle.Dispose(); // (the handle will clean itself up later after this [along with the place holder])
//Engine.ForceV8GarabageCollection(); // (this will not help you, you cannot GC a CLR object using this, do the following instead)
GC.Collect();
GC.WaitForPendingFinalizers();
Note: "ForceV8GarabageCollection()" is only for the NATIVE V8 side, not the V8.NET side.

What happens?
  1. The CLR object is released and replaced by a "place holder" object (to keep the object ID [index] alive as requried). As well, the handle of the separated object becomes empty.
  2. Disposing the handle starts the collection process on the "place holder" object.
  3. "GC.Collect()" triggers the GC collection, which should catch the objcet you just separated - assuming there are no other references.
  4. "GC.WaitForPendingFinalizers()" will wait for the finalizer of the object to be called. If the object handle is still empty, then the object should be finalized as expected - BUT, if not, it will fail.
So, for the object to be collected (finalized), the handle must be empty, which causes the internal "_ID" value to be null (it all boils down to '_ID' being null in the end, which is required to finalize the object).

Hope that helps. :)
Nov 14, 2013 at 6:09 AM
Edited Nov 14, 2013 at 6:11 AM
James,

Yes you are right. The object is seperated from the handle. However the scope of the javascript variable is still valid and therefore i decided to assign the jsvariable to the return value of the dispose call. I have the following that i believe is probably releasing the memory, however need to validate this through a JS memory profiler.

The code that i have is,
public class SamplePoint : V8NativeObject
{
    [ScriptMember("x")]
    public int x;
    [ScriptMember("y")]
    public int y;

    public SamplePoint()
    {
        this.x = 0;
        this.y = 0;
    }

    public SamplePoint(int x, int y)
    {
        this.x = x;
        this.y = y;
    }


    public int SumCoOrdinates()
    {
        return x + y;
    }

    public override ObjectHandle Initialize(bool isConstructCall, params InternalHandle[] args)
    {
        Handle.SetAccessor("x", XGetter, XSetter, V8PropertyAttributes.DontDelete);
        Handle.SetAccessor("y", YGetter, YSetter, V8PropertyAttributes.DontDelete);
        Handle.SetAccessor("sumcoordinates", SumCoOrdinatesPropertyCallBack, null, V8PropertyAttributes.Locked);
        PostInitialize(Handle);
        return this.Handle;
    }

    private void PostInitialize(ObjectHandle handle)
    {
        FunctionTemplate funcTemplate = handle.Engine.CreateFunctionTemplate("SumCoordinates");
        V8Function sumCoOrdiantesFunction = funcTemplate.GetFunctionObject(SumCoOrdinatesFunctionCallBack);
        handle.SetProperty("SumCoordinates", sumCoOrdiantesFunction.AsInternalHandle, V8PropertyAttributes.Locked);

        FunctionTemplate disposeTemplate = handle.Engine.CreateFunctionTemplate("dispose");
        V8Function disposeFunctionTemplate = disposeTemplate.GetFunctionObject(DisposeCallBack);
        handle.SetProperty("dispose", disposeFunctionTemplate.AsInternalHandle, V8PropertyAttributes.Locked);
    }

    public InternalHandle XGetter(InternalHandle _this, string propertyName)
    {
        return Engine.CreateValue(x);
    }

    public InternalHandle XSetter(InternalHandle _this, string propertyName, InternalHandle value)
    {
        x = value.AsInt32;
        return Engine.CreateValue(x);
    }

    public InternalHandle YGetter(InternalHandle _this, string propertyName)
    {
        return Engine.CreateValue(y);
    }

    public InternalHandle YSetter(InternalHandle _this, string propertyName, InternalHandle value)
    {
        y = value.AsInt32;
        return Engine.CreateValue(y);
    }


    public InternalHandle SumCoOrdinatesPropertyCallBack(InternalHandle _this, string propertyName)
    {
        InternalHandle handle = _this.Engine.CreateValue(SumCoOrdinates()).AsInternalHandle;
        return handle;
    }

    public InternalHandle SumCoOrdinatesFunctionCallBack(V8Engine engine, bool isConstructCall, InternalHandle _this,
                                 params InternalHandle[] args)
    {
        InternalHandle handle = null;
        if (!isConstructCall && args.Length == 0)
        {
            handle = _this.Engine.CreateValue(SumCoOrdinates()).AsInternalHandle;
        }
        return handle;
    }

    public override void Dispose()
    {
        this.Object.AsInternalHandle.ReleaseManagedObject();
        base.Object.AsInternalHandle.ReleaseManagedObject();
        base.AsInternalHandle.ReleaseManagedObject();
        base.Dispose();
    }

    public InternalHandle DisposeCallBack(V8Engine engine, bool isConstructCall, InternalHandle _this,
                         params InternalHandle[] args)
    {
        if (!isConstructCall)
        {
                _this.Object.AsInternalHandle.ReleaseManagedObject();
                _this.Object.Dispose();
                Dispose();
                GC.Collect();
                GC.WaitForPendingFinalizers();
        }
       return this.AsInternalHandle;  // i hope this is fine and this would also trigger the JS garbage collector to free memory.  In this case the point variable shall become undefined. 
       //return engine.CreateNullValue().AsInternalHandle; // i also thought this could be an option as setting of null to a variable in JS shall trigger the JS Garbage collector.  In this case the point variable is set to null value.
    }
}
the client code is,
        Handle result = v8Engine.Execute(source, "My V8.NET Console");
        string resultValue = result.AsString;
        Console.WriteLine(resultValue);

        source = "print(point.SumCoordinates()); print(point.x, point.y); print(point.sumcoordinates);";

        result = v8Engine.Execute(source, "My V8.NET Console");
        resultValue = result.AsString;
        Console.WriteLine(resultValue);

        source = "var point=point.dispose(); point.x=100; point.y=200; print(point.x, point.y); print( 'test - ' + point); ";
        result = v8Engine.Execute(source, "My V8.NET Console");
        resultValue = result.AsString;
        Console.WriteLine("at dispose - " + resultValue);
As always thanks for your help and quick response :-)
Nov 14, 2013 at 7:16 AM
Ok. Remember you can always open the console and type "\gctest" to verify that the GC cycle between the native and CLR sides is working as expected.
Nov 14, 2013 at 1:11 PM
James,

We have an interesting situation where the associated object to an handle is disposed. However the scope of the JS variable for that handle is still alive. Ideally speaking the user who is going to script should not use that variable once he knows that he had requested for removal. But the situation could be that multiple references exists for the object that he requested for removal. The user might unknowingly use another reference to access the same handle and request for a new operation on that handle (variable)

In this case the call back to the operation requested is called and in which we observe that the flag "this.AsInternalHandle.IsDiposed" is true and the _this pointer is still alive because not all references are set to null in the JavaScript.

This leads to a situation where the user requests for the removal of an object as it is not required as per the application context but can land up in situation where he could access the same unknowingly.

I am wondering if there could a way or check through which we could use flag "this.AsInternalHandle.IsDiposed" to throw an exception back to the user saying that are probably using another reference to a variable whose object was requested for deletion.

regards
Karthikeyan
Nov 14, 2013 at 8:49 PM
Currently there's no way to force the disposal of an in script reference - which is why the V8 engine has a garbage collector. You can, however, make handles to values as "weak", but I don't think it applies to in script references (which have their own handles).

The disposal cycle of managed objects goes like this:
  1. All managed objects are stored in an array of weak references. When the managed GC kicks in on an object to finalize it, it is blocked if the associated handle is still valid.
  2. The user calls {handle}.Dispose() on the object.
  3. Assuming no other handles exist, check if the object is also weak (exists only as a weak reference). If yes, then the managed object is placed in a queue for the worker to call the native side to make the v8 engine handle weak. If no, the next call to the object's finalizers will do this instead.
  4. The native V8 GC must detect that no other script references exist, then call back into the managed side to let V8.Net know it is safe to release the managed object.
  5. On V8 GC callback, call {object}.dispose() (for any custom handlers), call {object}.{handle}.Dispose() again (until this point, handles to objects cannot be forced to dispose when only one exists), and _ID is set to null to allow the object to be finalized.
  6. The managed GC must run again to finalize the object, which will finally succeed at this point (because there's no valid ID).
Hope this helps to shed some light on the process. :)
Nov 18, 2013 at 3:05 PM