From 750e6b4af96014d5f2a04a49af79c356142f7323 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 28 Aug 2015 12:09:29 -0500 Subject: [PATCH 1/4] Allow registering Ruby callbacks for V8 objects. This allows Ruby code to listen directly for when V8 object is garbage collected. This is done with the `__DefineFinalizer__` method which can be invoked on any handle. E.g. v8_object.__DefineFinalizer__(method_that_generates_callable) Just like in Ruby, care should be taken to ensure that the finalizer does not reference the V8 object at all, otherwise, it might prevent it from being garbage collected. The later, once v8_object has been gc'd, the finalizer will be enqueued into an internal data structure that can be accessed via the isolate's `__EachV8Finalizer` isolate.__EachV8Finalizer__ do |finalizer| finalizer.call() end There was a question of whether to follow the strict V8 API for this, and expose the `SetWeak` method, but this would mean actually making a handle weak, which is fine, but then we would have to add a mechanism to capture a strong reference from any reference which we don't have. We may want to revisit this at some later date. --- ext/v8/handle.h | 81 +++++++++++++++++++++++++++++++++++++++++++ ext/v8/init.cc | 1 + ext/v8/isolate.cc | 28 +++++++++++++-- ext/v8/isolate.h | 58 +++++++++++++++++++++++++++++-- ext/v8/rr.h | 2 +- ext/v8/v8.cc | 7 +++- ext/v8/v8.h | 2 +- ext/v8/value.cc | 2 +- spec/c/handle_spec.rb | 41 ++++++++++++++++++++++ 9 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 ext/v8/handle.h create mode 100644 spec/c/handle_spec.rb diff --git a/ext/v8/handle.h b/ext/v8/handle.h new file mode 100644 index 00000000..d13eba45 --- /dev/null +++ b/ext/v8/handle.h @@ -0,0 +1,81 @@ +// -*- mode: c++ -*- +#ifndef RR_HANDLE_H +#define RR_HANDLE_H +#include "rr.h" + +namespace rr { + class Handle : public Ref { + public: + struct Finalizer; + inline Handle(VALUE value) : Ref(value) {} + inline Handle(v8::Isolate* isolate, v8::Local data) + : Ref(isolate, data) {} + + static inline void Init() { + ClassBuilder("Handle"). + defineMethod("__DefineFinalizer__", &__DefineFinalizer__). + store(&Class); + } + static VALUE __DefineFinalizer__(VALUE self, VALUE code) { + Handle handle(self); + v8::Isolate* isolate(handle); + new Finalizer(isolate, handle, code); + return Qnil; + } + + /** + * Finalizer is responsible for capturing a piece of Ruby code and + * pushing it onto a queue once the V8 object points to is garbage + * collected. It is passed a handle and a Ruby object at which + * point it allocates a new storage cell that it holds + * weakly. Once the object referenced by its storage cell is + * garbage collected, the Finalizer enqueues the Ruby code so that + * it can be run later from Ruby. + */ + struct Finalizer { + Finalizer(Isolate isolate, v8::Local handle, VALUE code) : + cell(new v8::Global(isolate, handle)), callback(code) { + + // make sure that this code does not get GC'd by Ruby. + isolate.retainObject(code); + + // install the callback + cell->SetWeak(this, &finalize, v8::WeakCallbackType::kParameter); + } + + /** + * When this finalizer container is destroyed, also clear out + * the V8 storage cell and delete it. + */ + inline ~Finalizer() { + cell->Reset(); + delete cell; + } + + /** + * This implements a V8 GC WeakCallback, which will be invoked + * whenever the given object is garbage collected. It's job is to + * notify the Ruby isolate that the Ruby finalizer is ready to be + * run, as well as to clean up the + */ + static void finalize(const v8::WeakCallbackInfo& info) { + Isolate isolate(info.GetIsolate()); + Finalizer* finalizer(info.GetParameter()); + isolate.v8FinalizerReady(finalizer->callback); + delete finalizer; + } + + /** + * The storage cell that is held weakly. + */ + v8::Global* cell; + + /** + * The Ruby callable representing this finalizer. + */ + VALUE callback; + }; + }; +} + +#endif /* RR_HANDLE_H */ diff --git a/ext/v8/init.cc b/ext/v8/init.cc index 0a0a9a29..28f68243 100644 --- a/ext/v8/init.cc +++ b/ext/v8/init.cc @@ -16,6 +16,7 @@ extern "C" { V8::Init(); DefineEnums(); Isolate::Init(); + Handle::Init(); Handles::Init(); Context::Init(); Maybe::Init(); diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 97808d5c..5fe390cc 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -12,7 +12,8 @@ namespace rr { defineMethod("ThrowException", &ThrowException). defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). - + defineMethod("RequestGarbageCollectionForTesting", &RequestGarbageCollectionForTesting). + defineMethod("__EachV8Finalizer__", &__EachV8Finalizer__). store(&Class); } @@ -27,7 +28,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); @@ -50,10 +51,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; + } } diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 9899a866..2da53b6c 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -32,6 +32,9 @@ namespace rr { static VALUE New(VALUE self); static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); static VALUE ThrowException(VALUE self, VALUE error); + static VALUE IdleNotificationDeadline(VALUE self, VALUE deadline_in_seconds); + static VALUE RequestGarbageCollectionForTesting(VALUE self); + static VALUE __EachV8Finalizer__(VALUE self); inline Isolate(IsolateData* data_) : data(data_) {} inline Isolate(v8::Isolate* isolate) : @@ -152,6 +155,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. @@ -187,9 +229,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 @@ -245,6 +284,19 @@ namespace rr { */ ConcurrentQueue 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 v8_finalizer_queue; + /** * Contains a number of tokens representing all of the live Ruby * references that are currently active in this Isolate. Every diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 37a5e4d0..0e01d375 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -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" diff --git a/ext/v8/v8.cc b/ext/v8/v8.cc index 4bdc60d5..16dce5e2 100644 --- a/ext/v8/v8.cc +++ b/ext/v8/v8.cc @@ -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). @@ -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(); diff --git a/ext/v8/v8.h b/ext/v8/v8.h index a8f5ca55..fa4b7104 100644 --- a/ext/v8/v8.h +++ b/ext/v8/v8.h @@ -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); diff --git a/ext/v8/value.cc b/ext/v8/value.cc index ed59380e..9decc1bc 100644 --- a/ext/v8/value.cc +++ b/ext/v8/value.cc @@ -3,7 +3,7 @@ namespace rr { void Value::Init() { - ClassBuilder("Value"). + ClassBuilder("Value", Handle::Class). defineMethod("IsUndefined", &IsUndefined). defineMethod("IsNull", &IsNull). defineMethod("IsTrue", &IsTrue). diff --git a/spec/c/handle_spec.rb b/spec/c/handle_spec.rb new file mode 100644 index 00000000..5ce7fa17 --- /dev/null +++ b/spec/c/handle_spec.rb @@ -0,0 +1,41 @@ +require 'c_spec_helper' + +describe V8::C::Handle do + before do + V8::C::V8::SetFlagsFromString("--expose_gc") + @isolate = V8::C::Isolate::New() + V8::C::HandleScope(@isolate) do + @context = V8::C::Context::New(@isolate) + @context.Enter() + GC.stress = true + 2.times { v8_c_handle_spec_define_finalized_object(@isolate, self)} + @context.Exit() + end + @isolate.RequestGarbageCollectionForTesting() + @isolate.__EachV8Finalizer__ do |finalizer| + finalizer.call + end + end + after do + GC.stress = false + V8::C::V8::SetFlagsFromString("") + end + + it "runs registered V8 finalizer procs when a v8 object is garbage collected" do + expect(@did_finalize).to be >= 1 + end +end + +def v8_c_handle_spec_did_finalize(spec) + proc { + spec.instance_eval do + @did_finalize ||= 0 + @did_finalize += 1 + end + } +end + +def v8_c_handle_spec_define_finalized_object(isolate, spec) + object = V8::C::Object::New(isolate) + object.__DefineFinalizer__(v8_c_handle_spec_did_finalize(spec)) +end From f9027297e13cb902dbb6f680b9c6e3b339b30b18 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 28 Aug 2015 13:02:51 -0500 Subject: [PATCH 2/4] bump the finalizer to allocate 10 objects. That will ensure that at least one object triggers finalization. --- spec/c/handle_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/c/handle_spec.rb b/spec/c/handle_spec.rb index 5ce7fa17..290d5fa7 100644 --- a/spec/c/handle_spec.rb +++ b/spec/c/handle_spec.rb @@ -8,7 +8,7 @@ @context = V8::C::Context::New(@isolate) @context.Enter() GC.stress = true - 2.times { v8_c_handle_spec_define_finalized_object(@isolate, self)} + 10.times { v8_c_handle_spec_define_finalized_object(@isolate, self)} @context.Exit() end @isolate.RequestGarbageCollectionForTesting() From 57271fd3721f48c51a1800bd1bddd71e810c6028 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 1 Sep 2015 18:19:30 -0500 Subject: [PATCH 3/4] Fully implement Handle::SetWeak This implementation hues very closesly to the actual V8 API, so is more desirable. It also adds the ability to capture a handle to an existing object. --- ext/v8/handle.h | 129 ++++++++++++++++++++++++++++-------------- spec/c/handle_spec.rb | 108 +++++++++++++++++++++++++---------- 2 files changed, 164 insertions(+), 73 deletions(-) diff --git a/ext/v8/handle.h b/ext/v8/handle.h index d13eba45..a79841ff 100644 --- a/ext/v8/handle.h +++ b/ext/v8/handle.h @@ -8,67 +8,110 @@ namespace rr { public: struct Finalizer; inline Handle(VALUE value) : Ref(value) {} - inline Handle(v8::Isolate* isolate, v8::Local data) - : Ref(isolate, data) {} static inline void Init() { ClassBuilder("Handle"). - defineMethod("__DefineFinalizer__", &__DefineFinalizer__). + defineSingletonMethod("New", &New). + defineMethod("IsEmpty", &IsEmpty). + defineMethod("SetWeak", &SetWeak). + defineMethod("ClearWeak", &ClearWeak). store(&Class); } - static VALUE __DefineFinalizer__(VALUE self, VALUE code) { - Handle handle(self); - v8::Isolate* isolate(handle); - new Finalizer(isolate, handle, code); - return Qnil; + + /** + * 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 ref(other); + v8::Isolate* isolate(ref); + v8::Local handle(ref); + return Data_Wrap_Struct(cls, 0, &destroy, new Holder(isolate, handle)); + } } /** - * Finalizer is responsible for capturing a piece of Ruby code and - * pushing it onto a queue once the V8 object points to is garbage - * collected. It is passed a handle and a Ruby object at which - * point it allocates a new storage cell that it holds - * weakly. Once the object referenced by its storage cell is - * garbage collected, the Finalizer enqueues the Ruby code so that - * it can be run later from Ruby. + * 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__ */ - struct Finalizer { - Finalizer(Isolate isolate, v8::Local handle, VALUE code) : - cell(new v8::Global(isolate, handle)), callback(code) { + static VALUE SetWeak(VALUE self, VALUE callback) { + Handle handle(self); + Isolate isolate((v8::Isolate*)handle); - // make sure that this code does not get GC'd by Ruby. - isolate.retainObject(code); + // make sure this callback is not garbage collected + isolate.retainObject(callback); - // install the callback - cell->SetWeak(this, &finalize, v8::WeakCallbackType::kParameter); - } + Holder* holder(handle.unwrapHolder()); + Finalizer* finalizer = new Finalizer(holder->cell, callback); - /** - * When this finalizer container is destroyed, also clear out - * the V8 storage cell and delete it. - */ - inline ~Finalizer() { - cell->Reset(); - delete cell; - } + // mark weak and install the callback + holder->cell->SetWeak(finalizer, &finalize, v8::WeakCallbackType::kParameter); + return Qnil; + } - /** - * This implements a V8 GC WeakCallback, which will be invoked - * whenever the given object is garbage collected. It's job is to - * notify the Ruby isolate that the Ruby finalizer is ready to be - * run, as well as to clean up the - */ - static void finalize(const v8::WeakCallbackInfo& info) { - Isolate isolate(info.GetIsolate()); - Finalizer* finalizer(info.GetParameter()); - isolate.v8FinalizerReady(finalizer->callback); - delete finalizer; + static VALUE ClearWeak(VALUE self) { + Handle handle(self); + Locker lock(handle); + + Holder* holder(handle.unwrapHolder()); + + Finalizer* finalizer = holder->cell->ClearWeak(); + 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& 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* cell_, VALUE code) : + cell(cell_), callback(code) { } /** * The storage cell that is held weakly. */ - v8::Global* cell; + v8::Persistent* cell; /** * The Ruby callable representing this finalizer. diff --git a/spec/c/handle_spec.rb b/spec/c/handle_spec.rb index 290d5fa7..3404e39a 100644 --- a/spec/c/handle_spec.rb +++ b/spec/c/handle_spec.rb @@ -1,41 +1,89 @@ require 'c_spec_helper' describe V8::C::Handle do - before do - V8::C::V8::SetFlagsFromString("--expose_gc") - @isolate = V8::C::Isolate::New() - V8::C::HandleScope(@isolate) do - @context = V8::C::Context::New(@isolate) - @context.Enter() - GC.stress = true - 10.times { v8_c_handle_spec_define_finalized_object(@isolate, self)} - @context.Exit() + before { V8::C::V8::SetFlagsFromString("--expose_gc") } + after { V8::C::V8::SetFlagsFromString("") } + + describe "Registering a v8 GC callback" do + before do + @isolate = V8::C::Isolate::New() + V8::C::HandleScope(@isolate) do + @context = V8::C::Context::New(@isolate) + @context.Enter() + @object = V8::C::Object::New(@isolate) + @results = {} + @object.SetWeak(v8_c_handle_spec_did_finalize(@results)) + @context.Exit() + end + @isolate.RequestGarbageCollectionForTesting() + @isolate.__EachV8Finalizer__(&:call) end - @isolate.RequestGarbageCollectionForTesting() - @isolate.__EachV8Finalizer__ do |finalizer| - finalizer.call + after do + V8::C::V8::SetFlagsFromString("") end - end - after do - GC.stress = false - V8::C::V8::SetFlagsFromString("") - end - it "runs registered V8 finalizer procs when a v8 object is garbage collected" do - expect(@did_finalize).to be >= 1 + it "runs registered V8 finalizer procs when a v8 object is garbage collected" do + expect(@results[:did_finalize]).to be_truthy + end end -end -def v8_c_handle_spec_did_finalize(spec) - proc { - spec.instance_eval do - @did_finalize ||= 0 - @did_finalize += 1 + describe "Capturing a new handle to an existing object" do + before do + @isolate = V8::C::Isolate::New() + V8::C::HandleScope(@isolate) do + @context = V8::C::Context::New(@isolate) + @context.Enter() + @object = V8::C::Object::New(@isolate) + @other = V8::C::Handle::New(@object) + @context.Exit() + end end - } -end + it "can call methods on it" do + V8::C::HandleScope(@isolate) do + expect(@other.GetIdentityHash()).to eq @object.GetIdentityHash() + end + end + + describe ". Then making the original handle weak" do + before do + V8::C::HandleScope(@isolate) do + @results = {} + @object.SetWeak(v8_c_handle_spec_did_finalize(@results)) + end + @isolate.RequestGarbageCollectionForTesting() + @isolate.__EachV8Finalizer__(&:call) + end + it "does not gc the v8 object" do + V8::C::HandleScope(@isolate) do + expect(@object.IsEmpty()).to_not be_truthy + expect(@results[:did_finalize]).to_not be_truthy + end + end + + describe ". Then making the second handle weak" do + before do + V8::C::HandleScope(@isolate) do + @other.SetWeak(v8_c_handle_spec_did_finalize(@results)) + end + @isolate.RequestGarbageCollectionForTesting() + @isolate.__EachV8Finalizer__(&:call) + end + it "garbage collects the v8 object" do + expect(@results[:did_finalize]).to be_truthy + end + it "indicates that both object handles are now empty" do + V8::C::HandleScope(@isolate) do + expect(@object.IsEmpty()).to be_truthy + expect(@other.IsEmpty()).to be_truthy + end + end + end + end + end -def v8_c_handle_spec_define_finalized_object(isolate, spec) - object = V8::C::Object::New(isolate) - object.__DefineFinalizer__(v8_c_handle_spec_did_finalize(spec)) + def v8_c_handle_spec_did_finalize(results) + proc { + results[:did_finalize] = true + } + end end From e6d67c74082b8be3c86e6e6984599f37d72607a4 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 2 Sep 2015 10:36:54 -0500 Subject: [PATCH 4/4] V8 GC does not depend on the Ruby GC --- spec/c/handle_spec.rb | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/spec/c/handle_spec.rb b/spec/c/handle_spec.rb index 3404e39a..38e2d3cd 100644 --- a/spec/c/handle_spec.rb +++ b/spec/c/handle_spec.rb @@ -12,7 +12,7 @@ @context.Enter() @object = V8::C::Object::New(@isolate) @results = {} - @object.SetWeak(v8_c_handle_spec_did_finalize(@results)) + @object.SetWeak(proc { @did_finalize = true}) @context.Exit() end @isolate.RequestGarbageCollectionForTesting() @@ -23,7 +23,7 @@ end it "runs registered V8 finalizer procs when a v8 object is garbage collected" do - expect(@results[:did_finalize]).to be_truthy + expect(@did_finalize).to be_truthy end end @@ -48,7 +48,7 @@ before do V8::C::HandleScope(@isolate) do @results = {} - @object.SetWeak(v8_c_handle_spec_did_finalize(@results)) + @object.SetWeak(proc { @did_finalize = true}) end @isolate.RequestGarbageCollectionForTesting() @isolate.__EachV8Finalizer__(&:call) @@ -56,20 +56,20 @@ it "does not gc the v8 object" do V8::C::HandleScope(@isolate) do expect(@object.IsEmpty()).to_not be_truthy - expect(@results[:did_finalize]).to_not be_truthy + expect(@did_finalize).to_not be_truthy end end describe ". Then making the second handle weak" do before do V8::C::HandleScope(@isolate) do - @other.SetWeak(v8_c_handle_spec_did_finalize(@results)) + @other.SetWeak(proc { @did_finalize = true }) end @isolate.RequestGarbageCollectionForTesting() @isolate.__EachV8Finalizer__(&:call) end it "garbage collects the v8 object" do - expect(@results[:did_finalize]).to be_truthy + expect(@did_finalize).to be_truthy end it "indicates that both object handles are now empty" do V8::C::HandleScope(@isolate) do @@ -80,10 +80,4 @@ end end end - - def v8_c_handle_spec_did_finalize(results) - proc { - results[:did_finalize] = true - } - end end