diff --git a/ext/v8/external.cc b/ext/v8/external.cc new file mode 100644 index 0000000..fa6adfd --- /dev/null +++ b/ext/v8/external.cc @@ -0,0 +1,37 @@ +#include "rr.h" +#include "external.h" + +namespace rr { + void External::Init() { + ClassBuilder("External"). + defineSingletonMethod("New", &New). + + defineMethod("Value", &Value). + + store(&Class); + } + + VALUE External::New(VALUE self, VALUE r_isolate, VALUE object) { + Isolate isolate(r_isolate); + isolate.retainObject(object); + + Locker lock(isolate); + + Container* container = new Container(object); + v8::Local external(v8::External::New(isolate, (void*)container)); + + v8::Global* global(new v8::Global(isolate, external)); + container->global = global; + + global->SetWeak(container, &release, v8::WeakCallbackType::kParameter); + + return External(isolate, external); + } + + VALUE External::Value(VALUE self) { + External external(self); + Locker lock(external); + Container* container((Container*)external->Value()); + return container->object; + } +} diff --git a/ext/v8/external.h b/ext/v8/external.h new file mode 100644 index 0000000..b9b9e32 --- /dev/null +++ b/ext/v8/external.h @@ -0,0 +1,38 @@ +// -*- mode: c++ -*- +#ifndef EXTERNAL_H +#define EXTERNAL_H + +namespace rr { + class External : Ref { + public: + + static void Init(); + static VALUE New(VALUE self, VALUE isolate, VALUE object); + static VALUE Value(VALUE self); + + inline External(VALUE value) : Ref(value) {} + inline External(v8::Isolate* isolate, v8::Handle handle) : + Ref(isolate, handle) {} + + struct Container { + Container(VALUE v) : object(v) {} + + v8::Global* global; + VALUE object; + }; + + static void release(const v8::WeakCallbackInfo& info) { + Container* container(info.GetParameter()); + if (info.IsFirstPass()) { + container->global->Reset(); + info.SetSecondPassCallback(&release); + } else { + Isolate isolate(info.GetIsolate()); + isolate.scheduleReleaseObject(container->object); + delete container; + } + } + }; +} + +#endif /* EXTERNAL_H */ diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 4ac6e99..1d0a2f5 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -20,6 +20,7 @@ extern "C" { Function::Init(); Script::Init(); Array::Init(); + External::Init(); // Accessor::Init(); // Invocation::Init(); diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 5f5afa2..9bf9939 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -5,6 +5,7 @@ namespace rr { void Isolate::Init() { + rb_eval_string("require 'v8/retained_objects'"); ClassBuilder("Isolate"). defineSingletonMethod("New", &New). @@ -17,11 +18,16 @@ namespace rr { VALUE Isolate::New(VALUE self) { Isolate::IsolateData* data = new IsolateData(); + VALUE rb_cRetainedObjects = rb_eval_string("V8::RetainedObjects"); + data->retained_objects = rb_funcall(rb_cRetainedObjects, rb_intern("new"), 0); + 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->SetData(0, data); isolate->AddGCPrologueCallback(&clearReferences); + return isolate; } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 7a2c045..d458e63 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -38,8 +38,8 @@ namespace rr { * its book keeping data. E.g. * VALUE rubyObject = Isolate(v8::Isolate::New()); */ - inline operator VALUE() { - return Data_Wrap_Struct(Class, 0, 0, pointer); + virtual operator VALUE() { + return Data_Wrap_Struct(Class, &releaseAndMarkRetainedObjects, 0, pointer); } /** @@ -60,6 +60,28 @@ namespace rr { data()->queue.enqueue((v8::Persistent*)cell); } + inline void retainObject(VALUE object) { + rb_funcall(data()->retained_objects, rb_intern("add"), 1, object); + } + + inline void releaseObject(VALUE object) { + rb_funcall(data()->retained_objects, rb_intern("remove"), 1, object); + } + + inline void scheduleReleaseObject(VALUE object) { + data()->release_queue.enqueue(object); + } + + static void releaseAndMarkRetainedObjects(v8::Isolate* isolate_) { + Isolate isolate(isolate_); + IsolateData* data = isolate.data(); + VALUE object; + while (data->release_queue.try_dequeue(object)) { + isolate.releaseObject(object); + } + rb_gc_mark(data->retained_objects); + } + /** * An instance of v8::GCPrologueCallback, this will run in the v8 * GC thread, and clear out all the references that have been @@ -97,6 +119,14 @@ namespace rr { * as the isolate. */ struct IsolateData { + + /** + * An instance of `V8::RetainedObjects` that contains all + * references held from from V8. As long as they are in this + * list, they won't be gc'd by Ruby. + */ + VALUE retained_objects; + /** * A custom ArrayBufferAllocator for this isolate. Why? because * if you don't, it will segfault when you try and create an @@ -110,6 +140,11 @@ namespace rr { * can be released by the v8 garbarge collector later. */ ConcurrentQueue*> queue; + + /** + * Queue to hold + */ + ConcurrentQueue release_queue; }; }; } diff --git a/ext/v8/ref.h b/ext/v8/ref.h index ee00882..d58dd8c 100644 --- a/ext/v8/ref.h +++ b/ext/v8/ref.h @@ -75,6 +75,10 @@ namespace rr { return isolate; } + inline operator v8::Isolate*() const { + return isolate; + } + static void destroy(Holder* holder) { delete holder; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 2e7c6fa..fb4f8df 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -37,6 +37,7 @@ inline VALUE not_implemented(const char* message) { #include "object.h" #include "array.h" #include "primitive.h" +#include "external.h" // This one is named v8_string to avoid name collisions with C's string.h #include "rr_string.h" diff --git a/spec/c/external_spec.rb b/spec/c/external_spec.rb new file mode 100644 index 0000000..e337245 --- /dev/null +++ b/spec/c/external_spec.rb @@ -0,0 +1,23 @@ +require 'c_spec_helper' + +describe V8::C::External do + let(:isolate) { V8::C::Isolate::New() } + let(:value) { @external::Value() } + around { |example| V8::C::HandleScope(isolate) { example.run } } + + before do + Object.new.tap do |object| + @object_id = object.object_id + @external = V8::C::External::New(isolate, object) + end + end + + it "exists" do + expect(@external).to be + end + + it "can retrieve the ruby object out from V8 land" do + expect(value).to be + expect(value.object_id).to eql @object_id + end +end