From 02630d7b5af329b42a68b60da6a99263120a62c8 Mon Sep 17 00:00:00 2001 From: Jean Rouge Date: Mon, 13 Jun 2016 16:13:19 -0700 Subject: [PATCH 1/2] Allowing to quickly duplicates contexts With a new `deep_clone` method. Comes in handy for caching base contexts then used to evalaute further scripts. With proper unit tests on it. --- ext/v8/context.cc | 48 +++++++++++++++++++++ ext/v8/rr.h | 1 + lib/v8/context.rb | 11 +++++ spec/v8/context_spec.rb | 93 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 143 insertions(+), 10 deletions(-) diff --git a/ext/v8/context.cc b/ext/v8/context.cc index f5849f54..a89324d7 100644 --- a/ext/v8/context.cc +++ b/ext/v8/context.cc @@ -10,6 +10,7 @@ void Context::Init() { defineSingletonMethod("GetCalling", &GetCalling). defineSingletonMethod("InContext", &InContext). defineMethod("Dispose", &Dispose). + defineMethod("Clone", &Clone). defineMethod("Global", &Global). defineMethod("DetachGlobal", &Global). defineMethod("ReattachGlobal", &ReattachGlobal). @@ -33,6 +34,53 @@ VALUE Context::Dispose(VALUE self) { Void(Context(self).dispose()) } +VALUE Context::Clone(VALUE self, VALUE new_rr_context) { + // acquire the lock + v8::Locker locker; + v8::HandleScope handle_scope; + + // unwrap V8 contexts + v8::Context* original_context = *((v8::Handle) Context(self)); + v8::Context* new_context = *((v8::Handle) Context(new_rr_context)); + + // get the original global context + v8::Local key, value; + + original_context->Enter(); + + v8::Local original_global(original_context->Global()); + v8::Local property_names = original_global->GetPropertyNames(); + uint32_t i, length = property_names->Length(); + std::vector< v8::Local > values; + + for (i = 0; i < length; i++) { + key = property_names->Get(i); + value = original_global->Get(key); + values.push_back(value); + } + + original_context->Exit(); + + // and copy everything to the new context + new_context->Enter(); + + v8::Local new_global(new_context->Global()); + std::vector< v8::Local >::iterator it; + + for (i = 0, it = values.begin(); i < length; i++, it++) { + key = property_names->Get(i); + value = *it; + new_global->Set(key, value); + } + + new_context->Exit(); + + // unlock + v8::Unlocker unlocker; + + return Qnil; +} + VALUE Context::Global(VALUE self) { return Object(Context(self)->Global()); } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 6c76bc09..a8abb07f 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -306,6 +306,7 @@ class Context : public Ref { static void Init(); static VALUE New(int argc, VALUE argv[], VALUE self); static VALUE Dispose(VALUE self); + static VALUE Clone(VALUE self, VALUE new_context); static VALUE Enter(VALUE self); static VALUE Exit(VALUE self); static VALUE Global(VALUE self); diff --git a/lib/v8/context.rb b/lib/v8/context.rb index 05f362f0..234c9290 100644 --- a/lib/v8/context.rb +++ b/lib/v8/context.rb @@ -62,9 +62,12 @@ class Context # * :with scope serves as the global scope of the new context # @yield [V8::Context] the newly created context def initialize(options = {}) + @options = options + @conversion = Conversion.new @access = Access.new @timeout = options[:timeout] + if global = options[:with] Context.new.enter do global_template = global.class.to_template.InstanceTemplate() @@ -137,6 +140,14 @@ def self.enter end end + def deep_clone + fail ArgumentError, 'cannot clone an empty context' unless @native + + self.class.new(@options).tap do |new_context| + native.Clone(new_context.native) + end + end + # Returns this context's global object. This will be a `V8::Object` # if no scope was provided or just an `Object` if a Ruby object # is serving as the global scope. diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 850dcf0a..39163e48 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -1,19 +1,92 @@ require 'spec_helper' describe V8::Context do - it "can be disposed of" do - cxt = V8::Context.new - cxt.enter do - cxt['object'] = V8::Object.new + describe '.dispose' do + it "can be disposed of" do + cxt = V8::Context.new + cxt.enter do + cxt['object'] = V8::Object.new + end + cxt.dispose() + + lambda {cxt.eval('1 + 1')}.should raise_error + lambda {cxt['object']}.should raise_error end - cxt.dispose() - lambda {cxt.eval('1 + 1')}.should raise_error - lambda {cxt['object']}.should raise_error + it "can be disposed of any number of times" do + cxt = V8::Context.new + 10.times {cxt.dispose()} + end end - it "can be disposed of any number of times" do - cxt = V8::Context.new - 10.times {cxt.dispose()} + describe '.deep_clone' do + let(:js) do + <<-js + if (typeof x === 'undefined') { + x = 1; + } else { + x = x + 1; + } + js + end + + it 'duplicates the underlying native object' do + cxt = V8::Context.new + + cxt.eval(js) + + cloned_cxt = cxt.deep_clone + + cxt.eval(js) + cxt["x"].should == 2 + + cloned_cxt["x"].should == 1 + cloned_cxt.eval(js) + cloned_cxt["x"].should == 2 + + cxt.eval(js) + cxt["x"].should == 3 + end + + it 'works on empty contexts' do + cxt = V8::Context.new + + cloned_cxt = cxt.deep_clone + + cxt.eval(js) + cxt["x"].should == 1 + + cloned_cxt["x"].should be(nil) + end + + it 'keeps original options' do + cxt = V8::Context.new(:timeout => 10) + + cloned_cxt = cxt.deep_clone + + cloned_cxt.timeout.should == 10 + end + + context 'when the original context has been disposed of' do + it 'should not allow cloning any more' do + cxt = V8::Context.new + + cxt.dispose + + lambda { cxt.deep_clone }.should raise_error + end + + it 'should not affect previously cloned contexts' do + cxt = V8::Context.new + cxt.eval(js) + + cloned_cxt = cxt.deep_clone + + cxt.dispose + + cloned_cxt.eval(js) + cloned_cxt["x"].should == 2 + end + end end end From 6d5210e354afc2cff021822da630bba0d1b2ecf8 Mon Sep 17 00:00:00 2001 From: Jean Rouge Date: Tue, 14 Jun 2016 17:52:44 -0700 Subject: [PATCH 2/2] Adding more tests --- spec/v8/context_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/v8/context_spec.rb b/spec/v8/context_spec.rb index 39163e48..3b1f8bfc 100644 --- a/spec/v8/context_spec.rb +++ b/spec/v8/context_spec.rb @@ -48,6 +48,33 @@ cxt["x"].should == 3 end + it 'duplicates functions' do + cxt = V8::Context.new + + cxt.eval('var val = {num: 5, isTruthy: function (arg) { return !!arg }}') + + cloned_cxt = cxt.deep_clone + + cloned_cxt["val"].isTruthy(1).should be(true) + end + + it "keeps closures' contexts" do + js = <<-js + function wrapper(x) { return function() { return x; } }; + var f = wrapper(42); + js + + cxt = V8::Context.new + + cxt.eval(js) + + cloned_cxt = cxt.deep_clone + + cloned_cxt.eval('x = f();') + + cloned_cxt["x"].should == 42 + end + it 'works on empty contexts' do cxt = V8::Context.new