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) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
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'
|
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
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 '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
|
||||||
|
|
Loading…
Reference in a new issue