1
0
Fork 0
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:
Charles Lowell 2011-04-13 16:20:48 -05:00
parent fe90ba39b7
commit 624ce94568
10 changed files with 171 additions and 69 deletions

View file

@ -69,7 +69,8 @@ namespace {
VALUE MakeWeak(VALUE self) { 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); rr_v8_handle<void>(self).MakeWeak((void*)self, RubyWeakReferenceCallback);
return Qnil; return Qnil;
} }
@ -83,17 +84,18 @@ namespace {
return rr_v82rb(rr_v8_handle<void>(self).IsNearDeath()); 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) { VALUE IsWeak(VALUE self) {
return rr_v82rb(rr_v8_handle<void>(self).IsWeak()); 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() { void rr_init_handle() {
VALUE HandleClass = rr_define_class("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_singleton_method(HandleClass, "New", New, 1);
rr_define_method(HandleClass, "IsEmpty", IsEmpty, 0); rr_define_method(HandleClass, "IsEmpty", IsEmpty, 0);
rr_define_method(HandleClass, "Clear", Clear, 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, "MakeWeak", MakeWeak, 0);
rr_define_method(HandleClass, "ClearWeak", ClearWeak, 0); rr_define_method(HandleClass, "ClearWeak", ClearWeak, 0);
rr_define_method(HandleClass, "IsNearDeath", IsNearDeath, 0); rr_define_method(HandleClass, "IsNearDeath", IsNearDeath, 0);
rr_define_method(HandleClass, "IsDead", IsDead, 0);
rr_define_method(HandleClass, "IsWeak", IsWeak, 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); Data_Get_Struct(value, struct v8_handle, handle);
return handle; return handle;
} }

View file

@ -1,4 +1,5 @@
#include "v8_handle.h" #include "v8_handle.h"
#include "v8_weakref.h"
#include "v8_object.h" #include "v8_object.h"
#include "v8_value.h" #include "v8_value.h"
#include "v8_template.h" #include "v8_template.h"
@ -91,9 +92,26 @@ void rr_init_object() {
rr_define_method(ObjectClass, "SetPrototype", SetPrototype, 1); rr_define_method(ObjectClass, "SetPrototype", SetPrototype, 1);
} }
VALUE rr_reflect_v8_object(Handle<Value> value) { VALUE rr_reflect_v8_object_as(Handle<Object> object, VALUE ruby_class) {
Local<Object> object(Object::Cast(*value)); VALUE handle;
Local<Value> peer = object->GetHiddenValue(String::NewSymbol("TheRubyRacer::RubyObject")); v8_weakref* backref;
return peer.IsEmpty() ? rr_v8_handle_new(ObjectClass, object) : (VALUE)External::Unwrap(peer); 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);
} }

View file

@ -3,7 +3,6 @@
#include "rr.h" #include "rr.h"
void rr_init_object(); void rr_init_object();
VALUE rr_v8_object_class(); VALUE rr_v8_object_class();
VALUE rr_reflect_v8_object(v8::Handle<v8::Value> value); VALUE rr_reflect_v8_object(v8::Handle<v8::Value> value);

33
ext/v8/v8_weakref.cpp Normal file
View 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
View 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

View 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

View file

@ -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

View file

@ -2,53 +2,43 @@
require 'spec_helper' require 'spec_helper'
describe "Memory:" do describe "Memory:" do
context "A JavaScript Object reflected into ruby" do include V8::ExtSpec
before(:all) {c::V8::SetFlagsFromString("--expose-gc")} context "A JavaScript Object reflected into Ruby" do
before do before do
@cxt = c::Context::New() @weakref_callback = WeakrefCallback.new
@cxt.Enter()
end
after do
@cxt.Exit()
end 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()) handle = c::Handle::New(object = c::Object::New())
collected = false handle.MakeWeak(&@weakref_callback)
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}
ruby_gc do ruby_gc do
v8_gc v8_gc
collected.should be_true @weakref_callback.should_not have_been_invoked
handle.IsDead().should be_true 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
end end
private
def v8_gc private
c::Script::New(c::String::New("gc()"), c::String::New("gc.js")).Run()
end class WeakrefCallback
def to_proc
def ruby_gc method(:invoke).to_proc
GC.stress = true end
yield if block_given?
ensure def invoke
GC.stress = false @invoked = true
end end
def c def has_been_invoked?
V8::C @invoked
end
end end
end end
end end

21
spec/ext/object_spec.rb Normal file
View 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

View file

@ -1,7 +1,7 @@
require 'v8' require 'v8'
require 'erb' require 'erb'
require Pathname(__FILE__).dirname.join('ext/ext_spec_helper')
def rputs(msg) def rputs(msg)
puts "<pre>#{ERB::Util.h(msg)}</pre>" puts "<pre>#{ERB::Util.h(msg)}</pre>"
$stdout.flush $stdout.flush