This repository has been archived by the owner on Dec 4, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
9 changed files
with
212 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// -*- mode: c++ -*- | ||
#ifndef RR_DATA_H | ||
#define RR_DATA_H | ||
#include "rr.h" | ||
|
||
namespace rr { | ||
class Handle : public Ref<void> { | ||
public: | ||
struct Finalizer; | ||
inline Handle(VALUE value) : Ref<void>(value) {} | ||
inline Handle(v8::Isolate* isolate, v8::Local<void> data) | ||
: Ref<void>(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<void> handle, VALUE code) : | ||
cell(new v8::Global<void>(isolate, handle)), callback(code) { | ||
|
||
// make sure that this code does not get GC'd by Ruby. | ||
isolate.retainObject(code); | ||
|
||
// install the callback | ||
cell->SetWeak<Finalizer>(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<Finalizer>& info) { | ||
Isolate isolate(info.GetIsolate()); | ||
Finalizer* finalizer(info.GetParameter()); | ||
isolate.v8FinalizerReady(finalizer->callback); | ||
delete finalizer; | ||
} | ||
|
||
/** | ||
* The storage cell that is held weakly. | ||
*/ | ||
v8::Global<void>* cell; | ||
|
||
/** | ||
* The Ruby callable representing this finalizer. | ||
*/ | ||
VALUE callback; | ||
}; | ||
}; | ||
} | ||
|
||
#endif /* RR_DATA_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |