1
0
Fork 0
mirror of https://github.com/rubyjs/therubyracer synced 2023-03-27 23:21:42 -04:00

Merge pull request #1 from stormbreakerbg/isolate-reference-queue

Isolate reference queue
This commit is contained in:
Charles Lowell 2015-07-05 21:23:50 -05:00
commit 369b0d323d
7 changed files with 3665 additions and 47 deletions

View file

@ -6,10 +6,8 @@ namespace rr {
ClassBuilder("Context"). ClassBuilder("Context").
defineSingletonMethod("New", &New). defineSingletonMethod("New", &New).
defineMethod("Dispose", &Dispose).
defineMethod("Enter", &Enter). defineMethod("Enter", &Enter).
defineMethod("Exit", &Exit). defineMethod("Exit", &Exit).
defineMethod("Global", &Global). defineMethod("Global", &Global).
store(&Class); store(&Class);
@ -26,7 +24,6 @@ namespace rr {
Isolate isolate(rb_isolate); Isolate isolate(rb_isolate);
Locker lock(isolate); Locker lock(isolate);
return Context(v8::Context::New( return Context(v8::Context::New(
isolate isolate
// TODO // TODO
@ -37,11 +34,6 @@ namespace rr {
)); ));
} }
VALUE Context::Dispose(VALUE self) {
Context(self).dispose();
return Qnil;
}
VALUE Context::Enter(VALUE self) { VALUE Context::Enter(VALUE self) {
Context context(self); Context context(self);
Locker lock(context.getIsolate()); Locker lock(context.getIsolate());

View file

@ -8,11 +8,8 @@ namespace rr {
static void Init(); static void Init();
static VALUE New(int argc, VALUE argv[], VALUE self); static VALUE New(int argc, VALUE argv[], VALUE self);
static VALUE Dispose(VALUE self);
static VALUE Enter(VALUE self); static VALUE Enter(VALUE self);
static VALUE Exit(VALUE self); static VALUE Exit(VALUE self);
static VALUE Global(VALUE self); static VALUE Global(VALUE self);
// TODO // TODO

View file

@ -14,12 +14,21 @@ namespace rr {
store(&Class); store(&Class);
} }
VALUE Isolate::New(VALUE self) { VALUE Isolate::New(VALUE self) {
return Isolate(v8::Isolate::New()); Isolate::IsolateData* data = new IsolateData();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = &data->array_buffer_allocator;
Isolate isolate(v8::Isolate::New(create_params));
isolate->SetData(0, new IsolateData());
isolate->AddGCPrologueCallback(&clearReferences);
return isolate;
} }
VALUE Isolate::Dispose(VALUE self) { VALUE Isolate::Dispose(VALUE self) {
Isolate(self)->Dispose(); Isolate isolate(self);
delete isolate.data();
isolate->Dispose();
return Qnil; return Qnil;
} }

View file

@ -2,6 +2,10 @@
#ifndef RR_ISOLATE #ifndef RR_ISOLATE
#define RR_ISOLATE #define RR_ISOLATE
#include "vendor/concurrentqueue.h"
using namespace moodycamel;
namespace rr { namespace rr {
/** /**
* V8::C::Isolate * V8::C::Isolate
@ -9,11 +13,19 @@ namespace rr {
* Represents a fully encapsulated V8 virtual machine. Allocated * Represents a fully encapsulated V8 virtual machine. Allocated
* from Ruby by calling `V8::C::Isolate::New()` * from Ruby by calling `V8::C::Isolate::New()`
* *
* Every v8::Isolate wrapped in Ruby will have an instance of
* `IsolateData` embedded in it that can be used for bookkeeping
* between the V8 and Ruby worlds. For example, when v8 objects are
* no longer needed by ruby, they'll be enqueued for later release
* inside the V8 garbarge collection thread. This queue lives in the
* `IsolateData`
*
* Note: You must call `Dispose()` on the isolate for its resources * Note: You must call `Dispose()` on the isolate for its resources
* to be released. * to be released, otherwise, it will be leaked.
*/ */
class Isolate : public Pointer<v8::Isolate> { class Isolate : public Pointer<v8::Isolate> {
public: public:
struct IsolateData;
static void Init(); static void Init();
static VALUE New(VALUE self); static VALUE New(VALUE self);
@ -21,11 +33,84 @@ namespace rr {
inline Isolate(v8::Isolate* isolate) : Pointer<v8::Isolate>(isolate) {} inline Isolate(v8::Isolate* isolate) : Pointer<v8::Isolate>(isolate) {}
inline Isolate(VALUE value) : Pointer<v8::Isolate>(value) {} inline Isolate(VALUE value) : Pointer<v8::Isolate>(value) {}
/**
* Converts the v8::Isolate into a Ruby Object, while setting up
* its book keeping data. E.g.
* VALUE rubyObject = Isolate(v8::Isolate::New());
*/
inline operator VALUE() { inline operator VALUE() {
return Data_Wrap_Struct(Class, 0, 0, pointer); return Data_Wrap_Struct(Class, 0, 0, pointer);
} }
/**
* Access the book-keeping data. e.g.
*
* Isolate(self).data();
*/
inline IsolateData* data() {
return (IsolateData*)pointer->GetData(0);
}
/**
* Schedule a v8::Persistent reference to be be deleted with the next
* invocation of the V8 Garbarge Collector
*/
template <class T>
inline void scheduleDelete(v8::Persistent<T>* cell) {
data()->queue.enqueue((v8::Persistent<void>*)cell);
}
/**
* An instance of v8::GCPrologueCallback, this will run in the v8
* GC thread, and clear out all the references that have been
* released from Ruby.
*/
static void clearReferences(v8::Isolate* i, v8::GCType type, v8::GCCallbackFlags flags) {
Isolate isolate(i);
v8::Persistent<void>* cell;
while (isolate.data()->queue.try_dequeue(cell)) {
cell->Reset();
delete cell;
}
}
static VALUE Dispose(VALUE self); static VALUE Dispose(VALUE self);
/**
* Recent versions of V8 will segfault unless you pass in an
* ArrayBufferAllocator into the create params of an isolate. This
* is the simplest implementation possible.
*/
class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
public:
virtual void* Allocate(size_t length) {
void* data = AllocateUninitialized(length);
return data == NULL ? data : memset(data, 0, length);
}
virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
virtual void Free(void* data, size_t) { free(data); }
};
/**
* Data specific to the Ruby embedding. It has the same life span
* as the isolate.
*/
struct IsolateData {
/**
* A custom ArrayBufferAllocator for this isolate. Why? because
* if you don't, it will segfault when you try and create an
* Context. That's why.
*/
ArrayBufferAllocator array_buffer_allocator;
/**
* Queue to hold unused references to v8 objects. Once Ruby is
* finished with an object it will be enqueued here so that it
* can be released by the v8 garbarge collector later.
*/
ConcurrentQueue<v8::Persistent<void>*> queue;
};
}; };
} }

View file

@ -32,13 +32,14 @@ namespace rr {
template <class T> template <class T>
class Ref { class Ref {
public: public:
struct Holder;
Ref(VALUE value) { Ref(VALUE value) {
this->value = value; this->value = value;
Holder* holder = unwrapHolder(); Holder* holder = unwrapHolder();
if (holder) { if (holder) {
this->isolate = holder->isolate; this->isolate = holder->isolate;
this->handle = v8::Local<T>::New(holder->isolate, *holder->handle); this->handle = v8::Local<T>::New(holder->isolate, *holder->cell);
} else { } else {
this->isolate = NULL; this->isolate = NULL;
this->handle = v8::Local<T>(); this->handle = v8::Local<T>();
@ -60,7 +61,7 @@ namespace rr {
return Qnil; return Qnil;
} }
return Data_Wrap_Struct(Class, 0, &Holder::destroy, new Holder(isolate, handle)); return Data_Wrap_Struct(Class, 0, &destroy, new Holder(isolate, handle));
} }
/* /*
@ -74,10 +75,8 @@ namespace rr {
return isolate; return isolate;
} }
void dispose() { static void destroy(Holder* holder) {
Holder* holder = NULL; delete holder;
Data_Get_Struct(this->value, class Holder, holder);
holder->dispose();
} }
/* /*
@ -89,37 +88,16 @@ namespace rr {
inline v8::Handle<T> operator->() const { return *this; } inline v8::Handle<T> operator->() const { return *this; }
inline v8::Handle<T> operator*() const { return *this; } inline v8::Handle<T> operator*() const { return *this; }
class Holder { struct Holder {
friend class Ref; Holder(v8::Isolate* isolate, v8::Handle<T> handle) :
public: isolate(isolate), cell(new v8::Persistent<T>(isolate, handle)) {}
Holder(v8::Isolate* isolate, v8::Handle<T> handle) {
this->disposed_p = false;
this->isolate = isolate;
this->handle = new v8::Persistent<T>(isolate, handle);
}
virtual ~Holder() { virtual ~Holder() {
this->dispose(); Isolate(isolate).scheduleDelete<T>(cell);
} }
void dispose() {
if (!this->disposed_p) {
handle->Reset();
delete handle;
this->disposed_p = true;
}
}
protected:
v8::Isolate* isolate; v8::Isolate* isolate;
v8::Persistent<T>* handle; v8::Persistent<T>* cell;
bool disposed_p;
static void destroy(Holder* holder) {
holder->dispose();
// TODO: Use a nonblocking queue with `AddGCPrologueCallback`
}
}; };
static VALUE Class; static VALUE Class;
@ -128,7 +106,7 @@ namespace rr {
Holder* unwrapHolder() const { Holder* unwrapHolder() const {
if (RTEST(this->value)) { if (RTEST(this->value)) {
Holder* holder = NULL; Holder* holder = NULL;
Data_Get_Struct(this->value, class Holder, holder); Data_Get_Struct(this->value, struct Holder, holder);
return holder; return holder;
} else { } else {
return NULL; return NULL;

3542
ext/v8/vendor/concurrentqueue.h vendored Normal file

File diff suppressed because it is too large Load diff

15
spec/c/context_spec.rb Normal file
View file

@ -0,0 +1,15 @@
require 'c_spec_helper'
describe V8::C::Context do
let(:isolate) { V8::C::Isolate::New() }
let(:context) { V8::C::Context::New(isolate) }
around do |example|
V8::C::HandleScope(isolate) do
example.run
end
end
it "can be instantiated" do
expect(context).to be
end
end