Work in progress:
- support releasing GVL while running JS - support API to stop execution - run 1 isolate per context - introduce locking
This commit is contained in:
parent
653fbc7159
commit
56621a5c4a
|
@ -1,8 +1,6 @@
|
|||
# MiniRacer
|
||||
|
||||
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/mini_racer`. To experiment with that code, run `bin/console` for an interactive prompt.
|
||||
|
||||
TODO: Delete this and the text above, and describe your gem
|
||||
Minimal embedded V8 for Ruby
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -22,13 +20,9 @@ Or install it yourself as:
|
|||
|
||||
## Usage
|
||||
|
||||
TODO: Write usage instructions here
|
||||
|
||||
## Development
|
||||
|
||||
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||
|
||||
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -20,22 +20,33 @@ end
|
|||
|
||||
LIBV8_COMPATIBILITY = '~> 5.0.71.35.0'
|
||||
|
||||
begin
|
||||
require 'rubygems'
|
||||
gem 'libv8', LIBV8_COMPATIBILITY
|
||||
rescue Gem::LoadError
|
||||
warn "Warning! Unable to load libv8 #{LIBV8_COMPATIBILITY}."
|
||||
rescue LoadError
|
||||
warn "Warning! Could not load rubygems. Please make sure you have libv8 #{LIBV8_COMPATIBILITY} installed."
|
||||
ensure
|
||||
require 'libv8'
|
||||
end
|
||||
# begin
|
||||
# require 'rubygems'
|
||||
# gem 'libv8', LIBV8_COMPATIBILITY
|
||||
# rescue Gem::LoadError
|
||||
# warn "Warning! Unable to load libv8 #{LIBV8_COMPATIBILITY}."
|
||||
# rescue LoadError
|
||||
# warn "Warning! Could not load rubygems. Please make sure you have libv8 #{LIBV8_COMPATIBILITY} installed."
|
||||
# ensure
|
||||
# require 'libv8'
|
||||
# end
|
||||
#
|
||||
# Libv8.configure_makefile
|
||||
|
||||
Libv8.configure_makefile
|
||||
NODE_PATH = "/home/sam/Source/libv8"
|
||||
NODE_INCLUDE = NODE_PATH + "/vendor/v8/include"
|
||||
NODE_LIBS = NODE_PATH + "/vendor/v8/out/x64.release/obj.target/tools/gyp"
|
||||
|
||||
$INCFLAGS.insert 0, "-I#{NODE_INCLUDE} -I#{NODE_PATH}/vendor/v8 "
|
||||
$LDFLAGS.insert 0, " #{NODE_LIBS}/libv8_base.a #{NODE_LIBS}/libv8_libbase.a #{NODE_LIBS}/libv8_snapshot.a #{NODE_LIBS}/libv8_libplatform.a "
|
||||
|
||||
dir_config('v8')
|
||||
find_header('v8.h')
|
||||
have_library('v8')
|
||||
|
||||
# Temp Hack
|
||||
find_header('v8.h', '/home/sam/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/libv8-5.0.71.35.0/vendor/v8/include')
|
||||
#find_header('v8.h', '/home/sam/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/libv8-5.0.71.35.0/vendor/v8/include')
|
||||
|
||||
find_header('libplatform/libplatform.h', '/home/sam/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/libv8-5.0.71.35.0/vendor/v8/include')
|
||||
#find_header('libplatform/libplatform.h', '/home/sam/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/libv8-5.0.71.35.0/vendor/v8/include')
|
||||
|
||||
create_makefile 'mini_racer_extension'
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
#include <stdio.h>
|
||||
#include <ruby.h>
|
||||
#include <ruby/thread.h>
|
||||
#include <v8.h>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <ruby/encoding.h>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
typedef struct {
|
||||
Isolate* isolate;
|
||||
Persistent<Context>* context;
|
||||
} ContextInfo;
|
||||
|
||||
typedef struct {
|
||||
bool parsed;
|
||||
bool executed;
|
||||
Persistent<Value>* value;
|
||||
} EvalResult;
|
||||
|
||||
typedef struct {
|
||||
ContextInfo* context_info;
|
||||
Local<String>* eval;
|
||||
EvalResult* result;
|
||||
} EvalParams;
|
||||
|
||||
class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
|
||||
public:
|
||||
virtual void* Allocate(size_t length) {
|
||||
|
@ -19,7 +37,6 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
|
|||
|
||||
Platform* current_platform = NULL;
|
||||
ArrayBufferAllocator* allocator = NULL;
|
||||
Isolate* isolate = NULL;
|
||||
|
||||
static void init_v8() {
|
||||
if (current_platform == NULL) {
|
||||
|
@ -27,12 +44,6 @@ static void init_v8() {
|
|||
current_platform = platform::CreateDefaultPlatform();
|
||||
V8::InitializePlatform(current_platform);
|
||||
V8::Initialize();
|
||||
allocator = new ArrayBufferAllocator();
|
||||
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = allocator;
|
||||
isolate = Isolate::New(create_params);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,70 +52,145 @@ void shutdown_v8() {
|
|||
|
||||
}
|
||||
|
||||
static VALUE rb_context_eval(VALUE self, VALUE str) {
|
||||
init_v8();
|
||||
void*
|
||||
nogvl_context_eval(void* arg) {
|
||||
EvalParams* eval_params = (EvalParams*)arg;
|
||||
EvalResult* result = eval_params->result;
|
||||
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
Persistent<Context>* persistent_context;
|
||||
|
||||
Data_Get_Struct(self, Persistent<Context>, persistent_context);
|
||||
Local<v8::Context> context = Local<Context>::New(isolate, *persistent_context);
|
||||
Isolate::Scope isolate_scope(eval_params->context_info->isolate);
|
||||
HandleScope handle_scope(eval_params->context_info->isolate);
|
||||
|
||||
Local<v8::Context> context = Local<Context>::New(eval_params->context_info->isolate,
|
||||
*eval_params->context_info->context);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
|
||||
MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
|
||||
result->parsed = !parsed_script.IsEmpty();
|
||||
result->executed = false;
|
||||
result->value = NULL;
|
||||
|
||||
if (result->parsed) {
|
||||
MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
||||
result->executed = !maybe_value.IsEmpty();
|
||||
|
||||
if (result->executed) {
|
||||
Persistent<Value>* persistent = new Persistent<Value>();
|
||||
persistent->Reset(eval_params->context_info->isolate, maybe_value.ToLocalChecked());
|
||||
result->value = persistent;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VALUE convert_v8_to_ruby(Local<Value> &value) {
|
||||
|
||||
if (value->IsNull() || value->IsUndefined()){
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
if (value->IsInt32()) {
|
||||
return INT2FIX(value->Int32Value());
|
||||
}
|
||||
|
||||
if (value->IsNumber()) {
|
||||
return rb_float_new(value->NumberValue());
|
||||
}
|
||||
|
||||
Local<String> rstr = value->ToString();
|
||||
return rb_enc_str_new(*v8::String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
||||
}
|
||||
|
||||
static VALUE rb_context_eval(VALUE self, VALUE str) {
|
||||
EvalParams eval_params;
|
||||
EvalResult eval_result;
|
||||
ContextInfo* context_info;
|
||||
VALUE result;
|
||||
|
||||
Data_Get_Struct(self, ContextInfo, context_info);
|
||||
|
||||
Locker lock(context_info->isolate);
|
||||
Isolate::Scope isolate_scope(context_info->isolate);
|
||||
HandleScope handle_scope(context_info->isolate);
|
||||
|
||||
Local<String> eval = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(str),
|
||||
NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
|
||||
|
||||
Local<Script> script = Script::Compile(context, eval).ToLocalChecked();
|
||||
eval_params.context_info = context_info;
|
||||
eval_params.eval = &eval;
|
||||
eval_params.result = &eval_result;
|
||||
|
||||
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, RUBY_UBF_IO, 0);
|
||||
|
||||
MaybeLocal<Value> initial_result = script->Run(context);
|
||||
if (!eval_result.parsed) {
|
||||
// exception report about what happened
|
||||
rb_raise(rb_eStandardError, "Error Parsing JS");
|
||||
}
|
||||
|
||||
if (initial_result.IsEmpty()) {
|
||||
if (!eval_result.executed) {
|
||||
// exception report about what happened
|
||||
rb_raise(rb_eStandardError, "JavaScript Error");
|
||||
}
|
||||
|
||||
Local<Value> result = initial_result.ToLocalChecked();
|
||||
Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.value);
|
||||
//Local<String> rstr = tmp->ToString();
|
||||
//result = rb_enc_str_new(*v8::String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
||||
|
||||
if (result->IsNull() || result->IsUndefined()){
|
||||
return Qnil;
|
||||
}
|
||||
result = convert_v8_to_ruby(tmp);
|
||||
|
||||
if (result->IsInt32()) {
|
||||
return INT2FIX(result->Int32Value());
|
||||
}
|
||||
eval_result.value->Reset();
|
||||
delete eval_result.value;
|
||||
|
||||
if (result->IsNumber()) {
|
||||
return rb_float_new(result->NumberValue());
|
||||
}
|
||||
|
||||
Local<String> rstr = result->ToString();
|
||||
return rb_enc_str_new(*v8::String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
||||
return result;
|
||||
}
|
||||
|
||||
void deallocate(void * context) {
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
void deallocate(void * data) {
|
||||
ContextInfo* context_info = (ContextInfo*)data;
|
||||
{
|
||||
Locker lock(context_info->isolate);
|
||||
Isolate::Scope isolate_scope(context_info->isolate);
|
||||
HandleScope handle_scope(context_info->isolate);
|
||||
context_info->context->Reset();
|
||||
}
|
||||
|
||||
Persistent<Context>* cast_context = context;
|
||||
{
|
||||
delete context_info->context;
|
||||
// FIXME
|
||||
//context_info->isolate->Dispose();
|
||||
}
|
||||
|
||||
xfree(context_info);
|
||||
|
||||
cast_context->Reset();
|
||||
delete cast_context;
|
||||
}
|
||||
|
||||
|
||||
VALUE allocate(VALUE klass) {
|
||||
init_v8();
|
||||
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
Local<Context> context = Context::New(isolate);
|
||||
ContextInfo* context_info = ALLOC(ContextInfo);
|
||||
|
||||
Persistent<Context>* persistent_context = new Persistent<Context>();
|
||||
persistent_context->Reset(isolate, context);
|
||||
allocator = new ArrayBufferAllocator();
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = allocator;
|
||||
context_info->isolate = Isolate::New(create_params);
|
||||
|
||||
return Data_Wrap_Struct(klass, NULL, deallocate, (void*)persistent_context);
|
||||
|
||||
Locker lock(context_info->isolate);
|
||||
Isolate::Scope isolate_scope(context_info->isolate);
|
||||
HandleScope handle_scope(context_info->isolate);
|
||||
Local<Context> context = Context::New(context_info->isolate);
|
||||
|
||||
context_info->context = new Persistent<Context>();
|
||||
context_info->context->Reset(context_info->isolate, context);
|
||||
|
||||
return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_context_stop(VALUE self) {
|
||||
ContextInfo* context_info;
|
||||
Data_Get_Struct(self, ContextInfo, context_info);
|
||||
V8::TerminateExecution(context_info->isolate);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
|
@ -121,6 +207,7 @@ extern "C" {
|
|||
VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
||||
rb_define_method(rb_cContext, "eval", rb_context_eval, 1);
|
||||
rb_define_method(rb_cContext, "initialize", rb_context_init, 0);
|
||||
rb_define_method(rb_cContext, "stop", rb_context_stop, 0);
|
||||
rb_define_alloc_func(rb_cContext, allocate);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,24 @@ class MiniRacerTest < Minitest::Test
|
|||
end
|
||||
end
|
||||
|
||||
def test_it_can_stop
|
||||
context = MiniRacer::Context.new
|
||||
assert_raises do
|
||||
Thread.new do
|
||||
sleep 0.001
|
||||
context.stop
|
||||
end
|
||||
context.eval('while(true){}')
|
||||
end
|
||||
end
|
||||
|
||||
def test_it_handles_malformed_js
|
||||
context = MiniRacer::Context.new
|
||||
assert_raises do
|
||||
context.eval('I am not JavaScript {')
|
||||
end
|
||||
end
|
||||
|
||||
def test_floats
|
||||
context = MiniRacer::Context.new
|
||||
assert_equal 1.2, context.eval('1.2')
|
||||
|
|
Loading…
Reference in New Issue