Skip to content
This repository has been archived by the owner on Dec 4, 2023. It is now read-only.

Allow registering Ruby callbacks for V8 objects. #387

Open
wants to merge 5 commits into
base: upgrade-to-v8-4.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions ext/v8/handle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// -*- mode: c++ -*-
#ifndef RR_HANDLE_H
#define RR_HANDLE_H
#include "rr.h"

namespace rr {
class Handle : public Ref<void> {
public:
struct Finalizer;
inline Handle(VALUE value) : Ref<void>(value) {}

static inline void Init() {
ClassBuilder("Handle").
defineSingletonMethod("New", &New).
defineMethod("IsEmpty", &IsEmpty).
defineMethod("SetWeak", &SetWeak).
defineMethod("ClearWeak", &ClearWeak).
store(&Class);
}

/**
* Creates a New Handle to this object of the same class. If you
* have a V8::C::Object, then V8::C::Handle::New(object) will also
* be a V8::C::Object and represent a completely new handle to the
* object that will also prevent the object from being garbage
* collected by v8 (provided it has not be Reset())
*/
static VALUE New(VALUE self, VALUE other) {
if (!rb_funcall(other, rb_intern("kind_of?"), 1, Handle::Class)) {
rb_raise(rb_eArgError, "not a V8::C::Handle");
return Qnil;
} else {
VALUE cls = rb_class_of(other);
Ref<void> ref(other);
v8::Isolate* isolate(ref);
v8::Local<void> handle(ref);
return Data_Wrap_Struct(cls, 0, &destroy, new Holder(isolate, handle));
}
}

/**
* Calls v8::Handle::SetWeak, but the API is slightly different
* than the C++. The only parameter is a callable object that will
* be enqueued when value referenced by this handle is garbage
* collected. This code will not be called immediately. Instead,
* each callable must be iterated through from within Ruby code
* using the Isolate#__EachV8Finalizer__ method. Which will
* dequeue all finalizers that have not been yet run and yield
* them to the passed block. The sequence is roughly this:
*
* 1. value becomes finalizable
* 2. the v8 native finalizer runs.
* 3. Ruby callable is enqueued and will be seen by the next
* invocation of __EachV8Finalizer__
*/
static VALUE SetWeak(VALUE self, VALUE callback) {
Handle handle(self);
Isolate isolate((v8::Isolate*)handle);

// make sure this callback is not garbage collected
isolate.retainObject(callback);

Holder* holder(handle.unwrapHolder());
Finalizer* finalizer = new Finalizer(holder->cell, callback);

// mark weak and install the callback
holder->cell->SetWeak<Finalizer>(finalizer, &finalize, v8::WeakCallbackType::kParameter);
return Qnil;
}

static VALUE ClearWeak(VALUE self) {
Handle handle(self);
Locker lock(handle);

Holder* holder(handle.unwrapHolder());

Finalizer* finalizer = holder->cell->ClearWeak<Finalizer>();
delete finalizer;
return Qnil;
}

static VALUE IsEmpty(VALUE self) {
Handle handle(self);
Locker lock(handle);

Holder* holder(handle.unwrapHolder());
return Bool(holder->cell->IsEmpty());
}

static void finalize(const v8::WeakCallbackInfo<Finalizer>& info) {
Isolate isolate(info.GetIsolate());
Finalizer* finalizer = info.GetParameter();

// clear the storage cell. This is required by the V8 API.
finalizer->cell->Reset();

// notify that this finalizer is ready to run.
isolate.v8FinalizerReady(finalizer->callback);
delete finalizer;
}

/**
* A simple data structure to hold the objects necessary for
* finalization.
*/
struct Finalizer {
Finalizer(v8::Persistent<void>* cell_, VALUE code) :
cell(cell_), callback(code) {
}

/**
* The storage cell that is held weakly.
*/
v8::Persistent<void>* cell;

/**
* The Ruby callable representing this finalizer.
*/
VALUE callback;
};
};
}

#endif /* RR_HANDLE_H */
1 change: 1 addition & 0 deletions ext/v8/init.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern "C" {
V8::Init();
DefineEnums();
Isolate::Init();
Handle::Init();
Handles::Init();
Context::Init();
Maybe::Init();
Expand Down
28 changes: 25 additions & 3 deletions ext/v8/isolate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace rr {
defineMethod("ThrowException", &ThrowException).
defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions).
defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline).

defineMethod("RequestGarbageCollectionForTesting", &RequestGarbageCollectionForTesting).
defineMethod("__EachV8Finalizer__", &__EachV8Finalizer__).
store(&Class);
}

Expand All @@ -30,7 +31,7 @@ namespace rr {
v8::Isolate* isolate = v8::Isolate::New(create_params);

isolate->SetData(0, data);
isolate->AddGCPrologueCallback(&clearReferences);
isolate->AddGCPrologueCallback(&clearReferences, v8::kGCTypeAll);

data->isolate = isolate;
return Isolate(isolate);
Expand Down Expand Up @@ -66,10 +67,31 @@ namespace rr {
return Qnil;
}


VALUE Isolate::IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds) {
Isolate isolate(self);
Locker lock(isolate);
return Bool(isolate->IdleNotificationDeadline(NUM2DBL(deadline_in_seconds)));
}

VALUE Isolate::RequestGarbageCollectionForTesting(VALUE self) {
Isolate isolate(self);
Locker lock(isolate);
isolate->RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection);
return Qnil;
}
VALUE Isolate::__EachV8Finalizer__(VALUE self) {
if (!rb_block_given_p()) {
rb_raise(rb_eArgError, "Expected block");
return Qnil;
}
int state(0);
{
Isolate isolate(self);
isolate.eachV8Finalizer(&state);
}
if (state != 0) {
rb_jump_tag(state);
}
return Qnil;
}
}
58 changes: 55 additions & 3 deletions ext/v8/isolate.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ namespace rr {
static VALUE IsExecutionTerminating(VALUE self);
static VALUE CancelTerminateExecution(VALUE self);
static VALUE ThrowException(VALUE self, VALUE error);
static VALUE IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds);
static VALUE RequestGarbageCollectionForTesting(VALUE self);
static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options);
static VALUE __EachV8Finalizer__(VALUE self);

inline Isolate(IsolateData* data_) : data(data_) {}
inline Isolate(v8::Isolate* isolate) :
Expand Down Expand Up @@ -155,6 +158,45 @@ namespace rr {
rb_funcall(data->retained_objects, rb_intern("remove"), 1, object);
}

/**
* Indicate that a finalizer that had been associated with a given
* V8 object is now ready to run because that V8 object has now
* been garbage collected.
*
* This can be called from anywhere and does not need to hold
* either Ruby or V8 locks. It is designed though to be called
* from the V8 GC callback that determines that the object is no
* more.
*/
inline void v8FinalizerReady(VALUE code) {
data->v8_finalizer_queue.enqueue(code);
}

/**
* Iterates through all of the V8 finalizers that have been marked
* as ready and yields them. They wil be dequeued after this
* point, and so will never be seen again.
*/
inline void eachV8Finalizer(int* state) {
VALUE finalizer;
while (data->v8_finalizer_queue.try_dequeue(finalizer)) {
rb_protect(&yieldOneV8Finalizer, finalizer, state);
// we no longer need to retain this object from garbage
// collection.
releaseObject(finalizer);
if (*state != 0) {
break;
}
}
}
/**
* Yield a single value. This is wrapped in a function, so that
* any exceptions that happen don't blow out the stack.
*/
static VALUE yieldOneV8Finalizer(VALUE finalizer) {
return rb_yield(finalizer);
}

/**
* The `gc_mark()` callback for this Isolate's
* Data_Wrap_Struct. It releases all pending Ruby objects.
Expand Down Expand Up @@ -190,9 +232,6 @@ namespace rr {
}
}


static VALUE IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds);

/**
* Recent versions of V8 will segfault unless you pass in an
* ArrayBufferAllocator into the create params of an isolate. This
Expand Down Expand Up @@ -248,6 +287,19 @@ namespace rr {
*/
ConcurrentQueue<VALUE> rb_release_queue;

/**
* Sometimes it is useful to get a callback into Ruby whenever a
* JavaScript object is garbage collected by V8. This is done by
* calling v8_object._DefineFinalizer(some_proc). However, we
* cannot actually run this Ruby code inside the V8 garbage
* collector. It's not safe! It might end up allocating V8
* objects, or doing all kinds of who knows what! Instead, the
* ruby finalizer gets pushed onto this queue where it can be
* invoked later from ruby code with a call to
* isolate.__RunV8Finalizers__!
*/
ConcurrentQueue<VALUE> v8_finalizer_queue;

/**
* Contains a number of tokens representing all of the live Ruby
* references that are currently active in this Isolate. Every
Expand Down
2 changes: 1 addition & 1 deletion ext/v8/rr.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ inline VALUE not_implemented(const char* message) {
#include "isolate.h"

#include "ref.h"

#include "v8.h"
#include "locks.h"
#include "handle.h"
#include "handles.h"
#include "context.h"

Expand Down
7 changes: 6 additions & 1 deletion ext/v8/v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace rr {

ClassBuilder("V8").
// defineSingletonMethod("IdleNotification", &IdleNotification).
// defineSingletonMethod("SetFlagsFromString", &SetFlagsFromString).
defineSingletonMethod("SetFlagsFromString", &SetFlagsFromString).
// defineSingletonMethod("SetFlagsFromCommandLine", &SetFlagsFromCommandLine).
// defineSingletonMethod("PauseProfiler", &PauseProfiler).
// defineSingletonMethod("ResumeProfiler", &ResumeProfiler).
Expand All @@ -30,6 +30,11 @@ namespace rr {
defineSingletonMethod("GetVersion", &GetVersion);
}

VALUE V8::SetFlagsFromString(VALUE self, VALUE string) {
v8::V8::SetFlagsFromString(RSTRING_PTR(string), RSTRING_LEN(string));
return Qnil;
}

VALUE V8::Dispose(VALUE self) {
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
Expand Down
2 changes: 1 addition & 1 deletion ext/v8/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace rr {

static void Init();
// static VALUE IdleNotification(int argc, VALUE argv[], VALUE self);
// static VALUE SetFlagsFromString(VALUE self, VALUE string);
static VALUE SetFlagsFromString(VALUE self, VALUE string);
// static VALUE SetFlagsFromCommandLine(VALUE self, VALUE args, VALUE remove_flags);
// static VALUE AdjustAmountOfExternalAllocatedMemory(VALUE self, VALUE change_in_bytes);
// static VALUE PauseProfiler(VALUE self);
Expand Down
2 changes: 1 addition & 1 deletion ext/v8/value.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace rr {

void Value::Init() {
ClassBuilder("Value").
ClassBuilder("Value", Handle::Class).
defineMethod("IsUndefined", &IsUndefined).
defineMethod("IsNull", &IsNull).
defineMethod("IsTrue", &IsTrue).
Expand Down
Loading