Synchronizing Native Reference Counts with V8

without comments

Update: the method SetPointerInInternalField is a more efficient means of setting custom pointers rather than using a v8::External. I realized the existence of this method shortly after writing the post below.

I’ve written a bit about Google’s V8 Javascript library before on the athile.net wiki as I initially attempted to integrate V8 into LxEngine. The design of V8 is fairly clean and straightforward, but the documentation is sometimes a bit lacking. I’ve gotten back to a bit more V8 work recently and thought I’d post some example code to help others who might be using V8.

Synchronizing Reference Counts

The Javascript language is a garbage-collected language. While the specific implementation of the garbage collection may vary, the basic idea is that after an object is no longer used in a script, the memory for that object is freed. It’s also worth noting that the memory is freed after the object is no longer in use, but not necessarily immediately after the last reference is removed (the last section of this post will show why this matters).

LxEngine uses reference-counting using std::shared_ptrs for most major objects in the native C++ code. This is a very basic “manual” reference counting mechanism common to many C++ libraries.

When an object is created and used only in one context (i.e. natively in C++ or in a Javascript script, but not both), these systems both work fine. The problem we need to solve here is how to coordinate the V8 garbage collector with LxEngine’s shared_ptrs when the same object is being natively and exposed to an arbitrary Javascript script.

The V8 garbage collector and the shared_ptr::use_count need to stay coordinated. We don’t want the native code releasing the native object while there’s potentially still a script using the object nor do we want the garbage collector releasing the object when that object is still being used natively!

How’s it Done?

There’s nothing too clever to the solution: it’s only a matter of knowing the right V8 interfaces. In short, the V8 API includes a class Peristent that is Javascript object Handle that also allows a callback to be registered for when the Javascript object is about to be garbage collected. This is the entirety of what we need to keep LxEngine’s native reference counts in sync with V8′s garbage collection system.

The Code

Here’s a shortened version of the actual LxEngine code:

template <typename T>
v8::Persistent<v8::Object>
_wrapSharedPtr (v8::Handle<v8::Function>& ctor, std::shared_ptr<T>& sharedPtr)
{
    // Allocate the JS object wrapper and assign the native object to its
    // internal field.
    //
    // We store the raw, native pointer on the V8 object to avoid a double redirection 
    // (i.e. both reading the internal field and then dereferencing the shared pointer)
    // on every method that needs the native pointer.
    //
    v8::Handle<v8::Object> obj = ctor->NewInstance();
    obj->SetInternalField(0, v8::External::New(sharedPtr.get()));
 
    // Then store a new shared_ptr as the data for the MakeWeak callback which will be
    // deleted when the JS object is garbage collected.  This will ensure the 
    // reference counting is correctly synchronized.
    //
    v8::Persistent<v8::Object> persistentObj( v8::Persistent<v8::Object>::New(obj));
    persistentObj.MakeWeak(new std::shared_ptr<T>(sharedPtr), detail::_releaseSharedPtr<T>);
 
    return persistentObj;
}

I’ll assume the reader is familiar with the idea of creating the object and storing data in the “internal field.” If not, check out the V8 tutorial on the wiki – and send me a comment or two on how to improve it.

The interesting part is in the second set of two lines of code.

We take the standard V8 Handle and create a Persistent Handle from the local handle. This, in short, means the handle should outlast the HandleScope that it is created within; it will not be automatically disposed at the end of the HandleScope, but rather requires a manual call to Dispose().

Subsequently, the MakeWeak() method is called: this registers the callback function to call when the Persistent object is about to be collected by the V8 garbage collector. The first argument is an arbitrary void* and the second is the callback function itself which will be called with the data pointer and the Persistent object itself.

Here’s the code for that callback:

namespace detail
{
    template <typename T>
    void _releaseSharedPtr (v8::Persistent<v8::Value> persistentObj, void* pData)
    {        
        //
        // Assume this function is used exclusively by _wrapSharedPtr() and thus
        // pData is always a pointer to a std::shared_ptr<>.  This shared_ptr was
        // created at the time the JS wrapper for the object was created - and thus
        // should be disposed when the JS object is disposed in order to keep the
        // reference count on the native object correct.
        //
        auto pspNative = reinterpret_cast<std::shared_ptr<T>*>(pData);
        delete pspNative;
 
        // Manually dispose of the Persistent handle
        persistentObj.Dispose();
        persistentObj.Clear();
    }
}

That shared_ptr we allocated as the data pointer is deleted and the Persistent object is disposed of. This will work with a std::shared_ptr or any other of the myriad custom reference counting classes out there.

Nothing tricky.

What Did We Do?

  1. Create a Persistent handle from the regular handle so a callback can be registered
  2. Allocate a new shared_ptr to the underlying object as the data argument: this will increment the use_count for the duration of the V8 handle’s existence
  3. Delete the shared_ptr on the MakeWeak callback: this decrements the use_count on the underlying native object
  4. Properly dispose of the Persistent handle

This simple technique basically enforces that the native object will have it’s use_count incremented at the creation of any V8 wrapper for the object and decremented when the V8 garbage collector detects the object is no longer used.

Wait…One More Critical Point!

As referenced in Thomas Jansen’s blog post Force Garbage Collection, he notes that disposing of a V8 Context does not necessarily invoke the garbage collector.

For the purposes of this article, this basically translates to that fact that there’s no guarantee that our MakeWeak callbacks will be called before program exit. This is a problem for LxEngine which in many cases does rely on proper clean-up of its objects. So what do we do? When disposing of our V8 context, we follow the blog post’s advice and force garbage collection:

/*
    Credit: http://www.my-ride-home.com/2011/01/v8-garbage-collection/
 */
static void 
_forceGarbageCollection()
{
    for (unsigned int i = 0; i < 4096; ++i)
    {
        if (v8::V8::IdleNotification())
            break;
    }
}
 
JavascriptDoc::~JavascriptDoc()
{
    Context::Scope    context_scope(mContext);
    HandleScope       handle_scope;
    ...
 
    _forceGarbageCollection();
 
    ...
    mContext.Dispose();
}

The above seems a bit kludge-ish (shouldn’t V8 have a more direct mechanism to clean up the Context before/when the Context is disposed?) but it works as expected.

Summary

I’ll try to clean this info up and add it to the wiki eventually!

 

Leave a Reply