From 885ea08cb0bbe150e53d6d59ce60ef434245e6f3 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 9 Dec 2013 12:15:03 +1100 Subject: [PATCH] Added support for context timeouts --- ext/v8/rr.h | 1 + ext/v8/script.cc | 36 +++++++++++++++++++++++++++++++++++- lib/v8/context.rb | 15 +++++++++++++-- lib/v8/error.rb | 9 ++++++--- spec/threading_spec.rb | 14 +++++++++++++- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 5e001cf..6c76bc0 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -372,6 +372,7 @@ public: static void Init(); static VALUE New(int argc, VALUE argv[], VALUE self); static VALUE Run(VALUE self); + static VALUE RunWithTimeout(VALUE self, VALUE timeout); inline Script(VALUE value) : Ref(value) {} inline Script(v8::Handle script) : Ref(script) {} diff --git a/ext/v8/script.cc b/ext/v8/script.cc index bac9957..56ca9df 100644 --- a/ext/v8/script.cc +++ b/ext/v8/script.cc @@ -1,4 +1,6 @@ #include "rr.h" +#include "pthread.h" +#include "unistd.h" namespace rr { @@ -6,6 +8,7 @@ void Script::Init() { ClassBuilder("Script"). defineSingletonMethod("New", &New). defineMethod("Run", &Run). + defineMethod("RunWithTimeout", &RunWithTimeout). store(&Class); ClassBuilder("ScriptOrigin"). defineSingletonMethod("new", &ScriptOrigin::initialize). @@ -69,6 +72,37 @@ VALUE Script::Run(VALUE self) { return Value(Script(self)->Run()); } +typedef struct { + v8::Isolate *isolate; + long timeout; +} timeout_data; + +void* breaker(void *d) { + timeout_data* data = (timeout_data*)d; + usleep(data->timeout*1000); + v8::V8::TerminateExecution(data->isolate); + return NULL; +} + +VALUE Script::RunWithTimeout(VALUE self, VALUE timeout) { + pthread_t breaker_thread; + timeout_data data; + VALUE rval; + void *res; + + data.isolate = v8::Isolate::GetCurrent(); + data.timeout = NUM2LONG(timeout); + + pthread_create(&breaker_thread, NULL, breaker, &data); + + rval = Value(Script(self)->Run()); + + pthread_cancel(breaker_thread); + pthread_join(breaker_thread, &res); + + return rval; +} + template <> void Pointer::unwrap(VALUE value) { Data_Get_Struct(value, class v8::ScriptData, pointer); } @@ -77,4 +111,4 @@ template <> void Pointer::unwrap(VALUE value) { Data_Get_Struct(value, class v8::ScriptOrigin, pointer); } -} //namespace rr \ No newline at end of file +} //namespace rr diff --git a/lib/v8/context.rb b/lib/v8/context.rb index d8dc0b5..3927ab8 100644 --- a/lib/v8/context.rb +++ b/lib/v8/context.rb @@ -39,6 +39,9 @@ module V8 # @return [V8::C::Context] the underlying C++ object attr_reader :native + # maximum execution time for script in milliseconds + attr_reader :timeout + # Creates a new context. # # If passed the `:with` option, that object will be used as @@ -50,12 +53,16 @@ module V8 # cxt['hello'] #=> 'Hi' # end # + # If passed the `:timeout` option, every eval will timeout once + # N milliseconds elapse + # # @param [Hash] options initial context configuration # * :with scope serves as the global scope of the new context # @yield [V8::Context] the newly created context def initialize(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() @@ -84,7 +91,11 @@ module V8 end enter do script = try { V8::C::Script::New(source.to_s, filename.to_s) } - to_ruby try {script.Run()} + if @timeout + to_ruby try {script.RunWithTimeout(@timeout)} + else + to_ruby try {script.Run()} + end end end @@ -242,4 +253,4 @@ module V8 Context.current = current end end -end \ No newline at end of file +end diff --git a/lib/v8/error.rb b/lib/v8/error.rb index 1c8c2f8..92a9c9e 100644 --- a/lib/v8/error.rb +++ b/lib/v8/error.rb @@ -139,11 +139,14 @@ module V8 # @return [V8::Error] the error generated by this try/catch def self.Error(trycatch) exception = trycatch.Exception() + value = exception.to_ruby cause = nil - javascript_backtrace = V8::StackTrace.new(trycatch.Message().GetStackTrace()) + message = trycatch.Message() + javascript_backtrace = V8::StackTrace.new(message.GetStackTrace()) if message + message = if !exception.kind_of?(V8::C::Value) - exception.to_s + exception.to_s==""?"Script Timed Out":exception.to_s elsif exception.IsNativeError() if cause = exception.GetHiddenValue("rr::Cause") cause = cause.Value() @@ -163,4 +166,4 @@ module V8 V8::Error.new(message, value, javascript_backtrace, cause) end const_set :JSError, Error -end \ No newline at end of file +end diff --git a/spec/threading_spec.rb b/spec/threading_spec.rb index 5992dfc..1e51c1f 100644 --- a/spec/threading_spec.rb +++ b/spec/threading_spec.rb @@ -1,5 +1,17 @@ require 'spec_helper' +describe "Timeouts" do + it "allows for timeout on context" do + ctx = V8::Context.new(:timeout => 10) + lambda {ctx.eval("while(true){}")}.should(raise_error) + + # context should not be bust after it exploded once + ctx["x"] = 1; + ctx.eval("x=2;") + ctx["x"].should == 2 + end +end + describe "using v8 from multiple threads", :threads => true do it "creates contexts from within threads" do @@ -49,4 +61,4 @@ describe "using v8 from multiple threads", :threads => true do V8::C::Locker::StopPreemption() end end -end \ No newline at end of file +end