diff --git a/README.md b/README.md index e1acfa47..11726d8e 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,14 @@ cxt = V8::Context.new timeout: 700 cxt.eval "while (true);" #= exception after 700ms! ``` +The timeout set per context is also taken into account on function calls: + +```ruby +cxt = V8::Context.new timeout: 700 +ctx.eval("var dos = function() { while(true){} };") +ctx['dos'].call #= exception after 700ms! +``` + ### PREREQUISITES The Ruby Racer requires the V8 Javascript engine, but it offloads the diff --git a/ext/v8/function.cc b/ext/v8/function.cc index c4380c28..dabbf7f4 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -1,10 +1,22 @@ #include "rr.h" +#include "pthread.h" +#include "unistd.h" namespace rr { + + void* breaker(void *d) { + timeout_data* data = (timeout_data*)d; + usleep(data->timeout*1000); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + v8::V8::TerminateExecution(data->isolate); + return NULL; + } + void Function::Init() { ClassBuilder("Function", Object::Class). defineMethod("NewInstance", &NewInstance). defineMethod("Call", &Call). + defineMethod("CallWithTimeout", &CallWithTimeout). defineMethod("SetName", &SetName). defineMethod("GetName", &GetName). defineMethod("GetInferredName", &GetInferredName). @@ -28,6 +40,25 @@ namespace rr { return Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); } + VALUE Function::CallWithTimeout(VALUE self, VALUE receiver, VALUE argv, 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, rr::breaker, &data); + + rval = Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); + + pthread_cancel(breaker_thread); + pthread_join(breaker_thread, &res); + + return rval; + } + VALUE Function::SetName(VALUE self, VALUE name) { Void(Function(self)->SetName(String(name))); } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 6c76bc09..7d1bd178 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -33,6 +33,12 @@ namespace rr { #define Void(expr) expr; return Qnil; VALUE not_implemented(const char* message); +void* breaker(void *d); +typedef struct { + v8::Isolate *isolate; + long timeout; +} timeout_data; + class Equiv { public: Equiv(VALUE val) : value(val) {} @@ -638,6 +644,7 @@ class Function : public Ref { static void Init(); static VALUE NewInstance(int argc, VALUE argv[], VALUE self); static VALUE Call(VALUE self, VALUE receiver, VALUE argv); + static VALUE CallWithTimeout(VALUE self, VALUE receiver, VALUE argv, VALUE timeout); static VALUE SetName(VALUE self, VALUE name); static VALUE GetName(VALUE self); static VALUE GetInferredName(VALUE self); diff --git a/ext/v8/script.cc b/ext/v8/script.cc index f93823c5..c64d13d1 100644 --- a/ext/v8/script.cc +++ b/ext/v8/script.cc @@ -72,19 +72,6 @@ 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); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); - v8::V8::TerminateExecution(data->isolate); - return NULL; -} - VALUE Script::RunWithTimeout(VALUE self, VALUE timeout) { pthread_t breaker_thread; timeout_data data; diff --git a/lib/v8/function.rb b/lib/v8/function.rb index c1cbbb4b..a330e0b1 100644 --- a/lib/v8/function.rb +++ b/lib/v8/function.rb @@ -10,7 +10,11 @@ def initialize(native = nil) def methodcall(this, *args) @context.enter do this ||= @context.native.Global() - @context.to_ruby try {native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a})} + if @context.timeout + @context.to_ruby try {native.CallWithTimeout(@context.to_v8(this), args.map {|a| @context.to_v8 a}, @context.timeout)} + else + @context.to_ruby try {native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a})} + end end end diff --git a/spec/threading_spec.rb b/spec/threading_spec.rb index 1e51c1f4..5c91ef7b 100644 --- a/spec/threading_spec.rb +++ b/spec/threading_spec.rb @@ -10,6 +10,17 @@ ctx.eval("x=2;") ctx["x"].should == 2 end + + it "respects the timeout for function calls" do + ctx = V8::Context.new(:timeout => 10) + ctx.eval("var dos = function() { while(true){} };") + lambda { ctx['dos'].call }.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