v8embedded-v8

How to demonstrate V8 Weak Pointer callback?


I'm considering using V8 as an embedded JavaScript engine for a project but I'm having trouble figuring out how to manage the lifetime of native C++ objects. This experiment was supposed to demonstrate the Weak Pointer callback.

Near the end of the code below I call v8::Persistent::SetWeak and install a callback. All I want is to be able to create a demonstration of this callback being called.

I half-heartedly hoped it would be as easy as letting the handles fall out of scope, but the code below doesn't call the callback. I also read somewhere that calling Isolate::IdleNotificationDeadline might force a garbage collection, but this didn't work either.

How can I demonstrate the weak pointer callback being called? I'd like to write some code that will result in the the cleanup function being called at some point before the program exits.

I clearly am having trouble understanding how to set this up properly and would appreciate some assistance an an explanation. I'm afraid I just don't get it yet.

My expectation is that it's possible to create a Weak Pointer via a Persistent handle and that when there are no more handles to an object, the callback will (eventually) be called so that native C++ resources associated with that JavaScript object can be freed.

I'm particularly put off by a comment in the v8.h header file:

NOTE: There is no guarantee as to when or even if the callback is invoked. The invocation is performed solely on a best effort basis. As always, GC-based finalization should not be relied upon for any critical form of resource management!

This makes the entire engine seem useless to me for managing a native object with this mechanism. But I'm confident there's at least some minimal contrived scenario in which the callback is called.

My requirement is that I am able to write some JavaScript to allocate an object that will eventually be freed when there are no more references to it.

foo = createFoo(); // creates a JavaScript object wrapping the native C++ Foo object.
doSomethingWith(foo); // do stuff with the Foo here
foo = null; // make sure there are no more JavaScript handles to the wrapper for the Foo object.
// After this point, I'm hoping V8 will eventually let me know that I can delete the native C++ Foo object

I'm assuming I don't actually have to execute any JavaScript to demonstrate the weak pointer and cleanup mechanism. I was hoping I could just create a Persistent handle and install the Weak callback then let it go out of scope. I seem to be wrong in that assumption, or I have failed to demonstrate it here.

#include <iostream>
#include "include/libplatform/libplatform.h"
#include "include/v8.h"

class Foo {};

void cleanup(const v8::WeakCallbackInfo<Foo>& data)
{
  std::cout << "Weak Callback called" << std::endl;
  delete data.GetParameter();
}

int main(int argc, char* argv[]) {
  std::cout << "Start..." << std::endl;

  v8::V8::InitializeICUDefaultLocation(argv[0]);
  v8::V8::InitializeExternalStartupData(argv[0]);
  std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(platform.get());
  v8::V8::Initialize();

  // Create a new isolate and make it the current one
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  v8::Isolate* isolate = v8::Isolate::New(create_params);

  {
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HandleScope handle_scope(isolate);

    v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, v8::ObjectTemplate::New(isolate));
    v8::Context::Scope context_scope(context);

    v8::Local<v8::Object> obj = v8::ObjectTemplate::New(isolate)->NewInstance(context).ToLocalChecked();
    v8::Persistent<v8::Object> persistent;
    persistent.Reset(isolate, obj);
    persistent.SetWeak(new Foo(), cleanup, v8::WeakCallbackType::kParameter);
  }

  isolate->IdleNotificationDeadline(1.0);

  std::cout << "...Finish" << std::endl;
}

Note: The above code example should be built the same way the hello_world example for V8 is built.


Solution

  • For a contrived example, calling isolate->LowMemoryNotification() should do the trick. I wouldn't recommend doing that in production though, as it's a huge waste of CPU time (unless you really have a low memory situation where you're close to crashing due to OOM).

    Apart from that, the comment you found holds. Relying on weak callbacks to free objects is fine; relying on it for managing critical and scarce resources is not recommended. If the objects in question add up to significant size, you should use isolate->AdjustAmountOfExternalAllocatedMemory(...) as appropriate, to let the GC know that there is something to be freed. And you should have your own fallback mechanism to clean up everything when the Isolate goes away (if you're not terminating the entire process then anyway).