diff --git a/.travis.yml b/.travis.yml index 1bbff0b..8e6e8eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,7 @@ before_install: - gem update --system 2.1.11 script: - bundle exec rake compile - - bundle exec rspec spec/c - - bundle exec rspec spec/v8/context_spec.rb + - bundle exec rspec spec/c spec/v8/context_spec.rb spec/v8/isolate_spec.rb - bundle exec rspec spec/mem sudo: false addons: diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc index 97808d5..b14e532 100644 --- a/ext/v8/isolate.cc +++ b/ext/v8/isolate.cc @@ -9,6 +9,9 @@ namespace rr { rb_eval_string("require 'v8/retained_objects'"); ClassBuilder("Isolate"). defineSingletonMethod("New", &New). + defineMethod("TerminateExecution", &TerminateExecution). + defineMethod("IsExecutionTerminating", &IsExecutionTerminating). + defineMethod("CancelTerminateExecution", &CancelTerminateExecution). defineMethod("ThrowException", &ThrowException). defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions). defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline). @@ -33,6 +36,19 @@ namespace rr { return Isolate(isolate); } + VALUE Isolate::TerminateExecution(VALUE self) { + Isolate(self)->TerminateExecution(); + return Qnil; + }; + + VALUE Isolate::IsExecutionTerminating(VALUE self) { + return Bool(Isolate(self)->IsExecutionTerminating()); + } + VALUE Isolate::CancelTerminateExecution(VALUE self) { + Isolate(self)->CancelTerminateExecution(); + return Qnil; + } + VALUE Isolate::ThrowException(VALUE self, VALUE error) { Isolate isolate(self); Locker lock(isolate); diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h index 9899a86..c60c571 100644 --- a/ext/v8/isolate.h +++ b/ext/v8/isolate.h @@ -30,8 +30,11 @@ namespace rr { static void Init(); static VALUE New(VALUE self); - static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); + static VALUE TerminateExecution(VALUE self); + static VALUE IsExecutionTerminating(VALUE self); + static VALUE CancelTerminateExecution(VALUE self); static VALUE ThrowException(VALUE self, VALUE error); + static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options); inline Isolate(IsolateData* data_) : data(data_) {} inline Isolate(v8::Isolate* isolate) : diff --git a/lib/v8/isolate.rb b/lib/v8/isolate.rb index b18a5db..b294885 100644 --- a/lib/v8/isolate.rb +++ b/lib/v8/isolate.rb @@ -1,9 +1,58 @@ module V8 + ## + # A completely encapsulated JavaScript virtual machine. No state of + # any kind is shared between isolates. + # + # The isolate is the primany API for understanding and configuring + # the runtime profile of the JavaScript interpreter. For example, it + # is through the isolate that you can take heap and stack samples + # for memory and cpu profiling. It is also through the isolate that + # you can place restraints on the amount of memory and CPU that your + # JavaScript execution will consume. + # + # An isolate can have many exceutions contexts that each have their + # own scopes, and so significant resource savings can be had by + # reusing the same isolate for multiple contexts. class Isolate + + # @!attribute [r] native + # @return [V8::C::Isolate] the underlying C++ object attr_reader :native + ## + # Create an new isolate. def initialize() @native = V8::C::Isolate::New() end + + ## + # Terminates any currently executing JavaScript in this Isolate. + # + # This will throw an uncatchable exception inside the JavaScript + # code which will forcibly unwind the stack. + # + # @return [NilClass] nil + def terminate_execution! + @native.TerminateExecution() + end + + ## + # Returns true if execution is terminating, but there are still + # JavaScript frames left on the stack. + # + # @return [TrueClass | FalseClass] whether isolate is terminating execution + def execution_terminating? + @native.IsExecutionTerminating() + end + + ## + # Cancels any scheduled termination of JavaScript. Not really sure + # why you would want to do this, but if you figure out a use-case, + # would love for you to document it here. + # + # @return [NilClass] nil + def cancel_terminate_execution! + @native.CancelTerminateExecution() + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b31ecb4..d2b1abb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,35 +1 @@ require 'v8' - -def rputs(msg) - puts "
#{ERB::Util.h(msg)}" - $stdout.flush -end - -module V8ContextHelpers - module GroupMethods - def requires_v8_context - around(:each) do |example| - bootstrap_v8_context(&example) - end - end - end - - def bootstrap_v8_context - V8::C::Locker() do - V8::C::HandleScope() do - @cxt = V8::C::Context::New() - begin - @cxt.Enter() - yield - ensure - @cxt.Exit() - end - end - end - end -end - -RSpec.configure do |c| - c.include V8ContextHelpers - c.extend V8ContextHelpers::GroupMethods -end diff --git a/spec/v8/isolate_spec.rb b/spec/v8/isolate_spec.rb new file mode 100644 index 0000000..3589fd8 --- /dev/null +++ b/spec/v8/isolate_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe V8::Isolate do + let(:isolate) { V8::Isolate.new } + let(:context) { V8::Context.new isolate: isolate } + + it "allows you to terminate javascript execution" do + context['doTerminate'] = lambda { isolate.terminate_execution! } + expect(isolate).to_not be_execution_terminating + expect { + context.eval <<-JS +function doesTerminate() { + doTerminate(); +} +function returnFive() { + return 5; +} +doesTerminate(); +returnFive(); +JS + }.to raise_error V8::C::PendingExceptionError + end + + it "allows you to cancel terminated javascript executing" do + context['doTerminate'] = lambda do + isolate.terminate_execution! + isolate.cancel_terminate_execution! + end + result = context.eval <<-JS +function doesTerminate() { + doTerminate(); +} +function returnFive() { + return 5; +} +doesTerminate(); +returnFive(); +JS + expect(result).to eql 5 + end + + +end