From f19b652af2e626f61decd981d8a286015689ed8c Mon Sep 17 00:00:00 2001 From: Peter Dedene Date: Sun, 5 Jan 2014 18:18:20 +0100 Subject: [PATCH 1/2] Extended support for context timeouts to function calls --- README.md | 8 ++++++++ ext/v8/function.cc | 35 +++++++++++++++++++++++++++++++++++ ext/v8/rr.h | 1 + lib/v8/function.rb | 6 +++++- spec/threading_spec.rb | 11 +++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) 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..2262ab79 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -1,10 +1,13 @@ #include "rr.h" +#include "pthread.h" +#include "unistd.h" namespace rr { 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 +31,38 @@ namespace rr { return Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); } + typedef struct { + v8::Isolate *isolate; + long timeout; + } fct_timeout_data; + + void* fct_breaker(void *d) { + fct_timeout_data* data = (fct_timeout_data*)d; + usleep(data->timeout*1000); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + v8::V8::TerminateExecution(data->isolate); + return NULL; + } + + VALUE Function::CallWithTimeout(VALUE self, VALUE receiver, VALUE argv, VALUE timeout) { + pthread_t fct_breaker_thread; + fct_timeout_data data; + VALUE rval; + void *res; + + data.isolate = v8::Isolate::GetCurrent(); + data.timeout = NUM2LONG(timeout); + + pthread_create(&fct_breaker_thread, NULL, fct_breaker, &data); + + rval = Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); + + pthread_cancel(fct_breaker_thread); + pthread_join(fct_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..2321721c 100644 --- a/ext/v8/rr.h +++ b/ext/v8/rr.h @@ -638,6 +638,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/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 From 6253b9888ae2f80b0b2c9c56a379ac0ee2848069 Mon Sep 17 00:00:00 2001 From: Peter Dedene Date: Mon, 6 Jan 2014 16:03:19 +0100 Subject: [PATCH 2/2] Reuse data type definitions for timeout --- ext/v8/function.cc | 32 ++++++++++++++------------------ ext/v8/rr.h | 6 ++++++ ext/v8/script.cc | 13 ------------- 3 files changed, 20 insertions(+), 31 deletions(-) diff --git a/ext/v8/function.cc b/ext/v8/function.cc index 2262ab79..dabbf7f4 100644 --- a/ext/v8/function.cc +++ b/ext/v8/function.cc @@ -3,6 +3,15 @@ #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). @@ -31,34 +40,21 @@ namespace rr { return Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); } - typedef struct { - v8::Isolate *isolate; - long timeout; - } fct_timeout_data; - - void* fct_breaker(void *d) { - fct_timeout_data* data = (fct_timeout_data*)d; - usleep(data->timeout*1000); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); - v8::V8::TerminateExecution(data->isolate); - return NULL; - } - VALUE Function::CallWithTimeout(VALUE self, VALUE receiver, VALUE argv, VALUE timeout) { - pthread_t fct_breaker_thread; - fct_timeout_data data; + pthread_t breaker_thread; + timeout_data data; VALUE rval; void *res; data.isolate = v8::Isolate::GetCurrent(); data.timeout = NUM2LONG(timeout); - pthread_create(&fct_breaker_thread, NULL, fct_breaker, &data); + pthread_create(&breaker_thread, NULL, rr::breaker, &data); rval = Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array(argv))); - pthread_cancel(fct_breaker_thread); - pthread_join(fct_breaker_thread, &res); + pthread_cancel(breaker_thread); + pthread_join(breaker_thread, &res); return rval; } diff --git a/ext/v8/rr.h b/ext/v8/rr.h index 2321721c..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) {} 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;