V8 wrapped objects lifecycle

Objects in V8, for the types of handles that can hold them can be primarily: Local or Persistent. Though there’s another handle type: Eternal which live for the lifetime of the Isolate, thus never being garbage collected.

Local represents a short lived object, from v8 header file itself: light-weight and transient and typically used in local operations. As soon as the HandleScope managing this Local handler is destroyed, the object wrapped is invalid, and eventually, garbage collected.

Persistent handles, can be used to store objects across several execution units. These objects will eventually be garbage collected.

While embedding v8, I mostly always need to get a native object exposed in javascript, and this is done by pairing a Persistent handler with a native object. On average, these native objects are created/destroyed as my javascript code flows, and its native object counterpart needs to be destroyed and freed accordingly. For this purpose, I set a weak handle callback like:

class JavascriptWrapper {
private:
    v8::Persistent<v8::Object> wrapper_;
public:
    public void Wrap( v8::Isolate*, ... ) {
        ...
        // set weak handler
        wrapper_.SetWeak( this, 
                          weakCallbackForObjectHolder,     
                          v8::WeakCallbackType::kParameter);
    }

    // the weak handler function is as follows:
    static void weakCallbackForObjectHolder(
        const v8::WeakCallbackInfo<HC::Wrapper>& data) {
        delete data.GetParameter();
    }
}

This function will be invoked as soon as the javascript object is garbage collected, allowing me to reclaim all native side resources this object held.

Sometimes, I need to keep an object around until certain operations finish. For example, an Image object should be around until its async download process ends and gets the opportunity to notify its callbacks, avoiding garbage collection during the process. This is accomplished by tagging the Persistent handle as not weak by calling:

wrapper_->ClearWeak();

This prevents the GC from reclaiming my object. Think of a javascript object like this:

const image = new Image();
image.addEventListener(“load”, (e)=> {...});
image.addEventListener(“error”, (e)=> {...});
image.src= 'http://...';

This code is likely to destroy the image object before its callbacks had been notified. (Note thatimage is not referenced at all, it is just defined and forgotten in javascript).
Since I expect either load or error callbacks to be notified, I must prevent GC from kicking in, and ClearWeak does exactly that. Later, when the callbacks have been notified, I can natively flag the Persistent handler available for garbage collection by calling SetWeak(...) as in the example above. This ClearWeak/SetWeak combo gives me full control over my wrapped objects’ lifecycle.

Private References

There are some other times when I just need to bind the lifecycle of an object to another one. For example, a TouchEvent contains a TouchList object, and I want to bind their lifecycle together.

For this purpose, v8 also provides a Private property utility. As you can image, these properties will be inaccessible from javascript. To create a private property, just call:

v8::Local<v8::Value> v8Value= obj->Wrap(info.GetIsolate(), ...);

// create a private property
v8::Local<v8::Private> priv= v8::Private::ForApi(
        info.GetIsolate(),
        v8::String::NewFromUtf8(
           info.GetIsolate(), 
           "KeepAlive#TouchEvent#ChangedTouches"));

// assign this property to the object:
info.Holder()->SetPrivate(
        info.GetIsolate()->GetCurrentContext(),
        priv,
        v8Value);

With this I just get an interesting effect, which is keep the TouchList object alive while the TouchEvent exists and no one can modify or break this bond from javascript.

There’s nonetheless another stage where my wrapped native objects deserve special attention, and this is at Isolate destruction time.

Isolate destruction

Garbage collection must not be relied on to reclaim any object. In fact it might not fire during the javascript program lifecycle.

Under this premise, all native wrapped objects need a chance to be freed upon Isolate destruction. Specially if you expect to create another Isolate and avoid expensive memory leaks. How can we Identify our Persistent handles for special treatment is done by tagging them with a call to:

wrapper_.SetWrapperClassId( int16_t_tag );

Later on, when the Isolate is being destroyed, I must do an explicit call to:

isolate_->VisitHandlesWithClassIds( &phv );

phv is an instance of a class like:

class PHV : public v8::PersistentHandleVisitor {
public:

    v8::Isolate* isolate_;

    PHV(v8::Isolate* isolate) : isolate_(isolate) {}
    virtual ~PHV() {}

    virtual void VisitPersistentHandle(
        v8::Persistent<v8::Value>* value,
        uint16_t class_id) {

        // delete persistent handles on isolate disposal.
        if ( class_id==HC_GARBAGE_COLLECTED_CLASS_ID ) {
            v8::HandleScope hs(isolate_);
            Wrapper* w = // extract your wrapped object from
                         // the passed-in value object.
            delete w;
        }
    }
};

As you can see, handling native objects is actually pretty straightforward. Another demonstration of how delightful is to work with embedded v8.

Published by ibon

Chocolate engineer, software eater. Data visualisation at Workday. Past: Platochat, SdkBox, Chukong, Ludei.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: