Skip to content
This repository has been archived by the owner on Dec 4, 2023. It is now read-only.

Extended support for context timeouts to function calls #287

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions ext/v8/function.cc
Original file line number Diff line number Diff line change
@@ -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).
Expand All @@ -28,6 +40,25 @@ namespace rr {
return Value(Function(self)->Call(Object(receiver), RARRAY_LENINT(argv), Value::array<Value>(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<Value>(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)));
}
Expand Down
7 changes: 7 additions & 0 deletions ext/v8/rr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down Expand Up @@ -638,6 +644,7 @@ class Function : public Ref<v8::Function> {
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);
Expand Down
13 changes: 0 additions & 13 deletions ext/v8/script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion lib/v8/function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions spec/threading_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down