mirror of
https://github.com/rubyjs/therubyracer
synced 2023-03-27 23:21:42 -04:00
add preliminary support for weak backreferences.
This commit is contained in:
parent
fe90ba39b7
commit
624ce94568
10 changed files with 171 additions and 69 deletions
|
@ -69,7 +69,8 @@ namespace {
|
|||
|
||||
|
||||
VALUE MakeWeak(VALUE self) {
|
||||
rr_v8_handle_set_internal(self,"weakref_callback", rb_block_proc());
|
||||
VALUE callback = rb_block_given_p() ? rb_block_proc() : Qnil;
|
||||
rr_v8_handle_set_internal(self,"weakref_callback", callback);
|
||||
rr_v8_handle<void>(self).MakeWeak((void*)self, RubyWeakReferenceCallback);
|
||||
return Qnil;
|
||||
}
|
||||
|
@ -83,17 +84,18 @@ namespace {
|
|||
return rr_v82rb(rr_v8_handle<void>(self).IsNearDeath());
|
||||
}
|
||||
|
||||
VALUE IsDead(VALUE self) {
|
||||
return rr_v82rb(rr_v8_handle_raw(self)->dead);
|
||||
}
|
||||
|
||||
VALUE IsWeak(VALUE self) {
|
||||
return rr_v82rb(rr_v8_handle<void>(self).IsWeak());
|
||||
}
|
||||
|
||||
VALUE dead_p(VALUE self) {
|
||||
return rr_v8_handle_raw(self)->dead ? Qtrue : Qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
void rr_init_handle() {
|
||||
VALUE HandleClass = rr_define_class("Handle");
|
||||
rr_define_method(HandleClass, "dead?", dead_p, 0);
|
||||
rr_define_singleton_method(HandleClass, "New", New, 1);
|
||||
rr_define_method(HandleClass, "IsEmpty", IsEmpty, 0);
|
||||
rr_define_method(HandleClass, "Clear", Clear, 0);
|
||||
|
@ -101,7 +103,6 @@ void rr_init_handle() {
|
|||
rr_define_method(HandleClass, "MakeWeak", MakeWeak, 0);
|
||||
rr_define_method(HandleClass, "ClearWeak", ClearWeak, 0);
|
||||
rr_define_method(HandleClass, "IsNearDeath", IsNearDeath, 0);
|
||||
rr_define_method(HandleClass, "IsDead", IsDead, 0);
|
||||
rr_define_method(HandleClass, "IsWeak", IsWeak, 0);
|
||||
}
|
||||
|
||||
|
@ -126,4 +127,3 @@ v8_handle* rr_v8_handle_raw(VALUE value) {
|
|||
Data_Get_Struct(value, struct v8_handle, handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "v8_handle.h"
|
||||
#include "v8_weakref.h"
|
||||
#include "v8_object.h"
|
||||
#include "v8_value.h"
|
||||
#include "v8_template.h"
|
||||
|
@ -91,9 +92,26 @@ void rr_init_object() {
|
|||
rr_define_method(ObjectClass, "SetPrototype", SetPrototype, 1);
|
||||
}
|
||||
|
||||
VALUE rr_reflect_v8_object(Handle<Value> value) {
|
||||
Local<Object> object(Object::Cast(*value));
|
||||
Local<Value> peer = object->GetHiddenValue(String::NewSymbol("TheRubyRacer::RubyObject"));
|
||||
return peer.IsEmpty() ? rr_v8_handle_new(ObjectClass, object) : (VALUE)External::Unwrap(peer);
|
||||
VALUE rr_reflect_v8_object_as(Handle<Object> object, VALUE ruby_class) {
|
||||
VALUE handle;
|
||||
v8_weakref* backref;
|
||||
Local<Value> holder = object->GetHiddenValue(String::NewSymbol("TheRubyRacer::Backref"));
|
||||
if (holder.IsEmpty()) {
|
||||
handle = rr_v8_handle_new(ruby_class, object);
|
||||
backref = new v8_weakref(handle);
|
||||
object->SetHiddenValue(String::NewSymbol("TheRubyRacer::Backref"), backref->external);
|
||||
} else {
|
||||
backref = (v8_weakref*)External::Unwrap(holder);
|
||||
handle = backref->get();
|
||||
if (!RTEST(handle)) {
|
||||
handle = rr_v8_handle_new(ruby_class, object);
|
||||
backref->set(handle);
|
||||
}
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
VALUE rr_reflect_v8_object(Handle<Value> value) {
|
||||
return rr_reflect_v8_object_as(value->ToObject(), ObjectClass);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "rr.h"
|
||||
|
||||
|
||||
void rr_init_object();
|
||||
VALUE rr_v8_object_class();
|
||||
VALUE rr_reflect_v8_object(v8::Handle<v8::Value> value);
|
||||
|
|
33
ext/v8/v8_weakref.cpp
Normal file
33
ext/v8/v8_weakref.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "v8.h"
|
||||
#include "v8_weakref.h"
|
||||
|
||||
using namespace v8;
|
||||
|
||||
v8_weakref::v8_weakref(VALUE object) {
|
||||
this->external = Persistent<External>::New(External::New((void *)this));
|
||||
this->external.MakeWeak(this, v8_weakref_finalize);
|
||||
this->set(object);
|
||||
}
|
||||
|
||||
void v8_weakref::set(VALUE value) {
|
||||
this->object_id = rb_obj_id(value);
|
||||
}
|
||||
|
||||
VALUE v8_weakref::get() {
|
||||
return rb_rescue((VALUE (*)(...))v8_weakref_id2ref, this->object_id, (VALUE (*)(...))v8_weakref_nil, Qnil);
|
||||
}
|
||||
|
||||
void v8_weakref_finalize(Persistent<Value> value, void* weakref) {
|
||||
value.Dispose();
|
||||
value.Clear();
|
||||
delete (v8_weakref*)weakref;
|
||||
}
|
||||
|
||||
VALUE v8_weakref_nil(VALUE nil, VALUE exception) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
VALUE v8_weakref_id2ref(VALUE id) {
|
||||
return rb_funcall(rb_const_get(rb_cObject, rb_intern("ObjectSpace")), rb_intern("_id2ref"), 1, id);
|
||||
}
|
||||
|
22
ext/v8/v8_weakref.h
Normal file
22
ext/v8/v8_weakref.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef _RR_V8_WEAKREF_
|
||||
#define _RR_V8_WEAKREF_
|
||||
|
||||
#include <v8.h>
|
||||
#include "ruby.h"
|
||||
|
||||
struct v8_weakref {
|
||||
v8_weakref(VALUE object);
|
||||
VALUE get();
|
||||
void set(VALUE object);
|
||||
void retain();
|
||||
void release();
|
||||
|
||||
VALUE object_id;
|
||||
v8::Persistent<v8::External> external;
|
||||
};
|
||||
|
||||
void v8_weakref_finalize(v8::Persistent<v8::Value> value, void* weakref);
|
||||
VALUE v8_weakref_nil(VALUE nil, VALUE exception);
|
||||
VALUE v8_weakref_id2ref(VALUE id);
|
||||
|
||||
#endif
|
37
spec/ext/ext_spec_helper.rb
Normal file
37
spec/ext/ext_spec_helper.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
|
||||
module V8::ExtSpec
|
||||
|
||||
def self.included(object)
|
||||
object.class_eval do
|
||||
before(:all) {c::V8::SetFlagsFromString("--expose-gc")}
|
||||
before do
|
||||
@cxt = c::Context::New()
|
||||
@cxt.Enter()
|
||||
end
|
||||
after do
|
||||
@cxt.Exit()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def v8_eval(script)
|
||||
c::Script::New(c::String::New(script), c::String::New('<eval>')).Run()
|
||||
end
|
||||
|
||||
def c
|
||||
V8::C
|
||||
end
|
||||
|
||||
def ruby_gc
|
||||
current = GC.stress
|
||||
GC.stress = true
|
||||
yield
|
||||
ensure
|
||||
GC.stress = current
|
||||
end
|
||||
|
||||
def v8_gc
|
||||
c::Script::New(c::String::New("gc()"), c::String::New("gc.js")).Run()
|
||||
end
|
||||
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe V8::C::Handle do
|
||||
|
||||
before(:all) do
|
||||
V8::C::V8::SetFlagsFromString("--expose-gc")
|
||||
@cxt = V8::Context.new
|
||||
end
|
||||
|
||||
def c
|
||||
V8::C
|
||||
end
|
||||
|
||||
def gc
|
||||
@cxt.eval('gc()');
|
||||
end
|
||||
end
|
|
@ -2,53 +2,43 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Memory:" do
|
||||
context "A JavaScript Object reflected into ruby" do
|
||||
before(:all) {c::V8::SetFlagsFromString("--expose-gc")}
|
||||
include V8::ExtSpec
|
||||
context "A JavaScript Object reflected into Ruby" do
|
||||
|
||||
before do
|
||||
@cxt = c::Context::New()
|
||||
@cxt.Enter()
|
||||
end
|
||||
|
||||
after do
|
||||
@cxt.Exit()
|
||||
@weakref_callback = WeakrefCallback.new
|
||||
end
|
||||
|
||||
it "has a strong reference from the ruby side" do
|
||||
it "has a strong reference from the ruby side, which is not released until the Ruby reference goes away" do
|
||||
handle = c::Handle::New(object = c::Object::New())
|
||||
collected = false
|
||||
handle.MakeWeak() {collected = true; puts "yo"}
|
||||
v8_gc
|
||||
collected.should_not be_true
|
||||
handle.IsDead().should be_false
|
||||
end
|
||||
|
||||
it "will be garbarge collected if there are no more ruby references and is weakly reachable from V8" do
|
||||
handle = c::Handle::New(object = c::Object::New())
|
||||
object = nil
|
||||
collected = false
|
||||
handle.MakeWeak() {collected = true}
|
||||
handle.MakeWeak(&@weakref_callback)
|
||||
ruby_gc do
|
||||
v8_gc
|
||||
collected.should be_true
|
||||
handle.IsDead().should be_true
|
||||
@weakref_callback.should_not have_been_invoked
|
||||
handle.should_not be_dead
|
||||
end
|
||||
ruby_gc do
|
||||
object = nil
|
||||
v8_gc
|
||||
@weakref_callback.should have_been_invoked
|
||||
handle.should be_dead
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def v8_gc
|
||||
c::Script::New(c::String::New("gc()"), c::String::New("gc.js")).Run()
|
||||
end
|
||||
|
||||
def ruby_gc
|
||||
GC.stress = true
|
||||
yield if block_given?
|
||||
ensure
|
||||
GC.stress = false
|
||||
end
|
||||
|
||||
def c
|
||||
V8::C
|
||||
private
|
||||
|
||||
class WeakrefCallback
|
||||
def to_proc
|
||||
method(:invoke).to_proc
|
||||
end
|
||||
|
||||
def invoke
|
||||
@invoked = true
|
||||
end
|
||||
|
||||
def has_been_invoked?
|
||||
@invoked
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
spec/ext/object_spec.rb
Normal file
21
spec/ext/object_spec.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe V8::C::Object do
|
||||
include V8::ExtSpec
|
||||
|
||||
it "always returns a copy of the same object if it is the same object" do
|
||||
v8_eval('var o = new Object()')
|
||||
v8_eval('o').should be(v8_eval('o'))
|
||||
end
|
||||
|
||||
it "will return a new peer and not barf if the old peer has been garbage collected" do
|
||||
v8_eval('var o = {foo: "bar"}')
|
||||
o = v8_eval('o')
|
||||
old_id = o.object_id
|
||||
ruby_gc do
|
||||
o = nil
|
||||
v8_eval('o').Get(c::String::New("foo")).Utf8Value().should == "bar"
|
||||
v8_eval('o').object_id.should_not be(old_id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
require 'v8'
|
||||
|
||||
require 'erb'
|
||||
require Pathname(__FILE__).dirname.join('ext/ext_spec_helper')
|
||||
def rputs(msg)
|
||||
puts "<pre>#{ERB::Util.h(msg)}</pre>"
|
||||
$stdout.flush
|
||||
|
|
Loading…
Reference in a new issue