Merge branch 'master' into wk8/one_isolate_per_snapshot

This commit is contained in:
Jean Rouge 2016-06-29 08:56:46 -07:00
commit 33d367d99f
4 changed files with 177 additions and 13 deletions

View File

@ -185,6 +185,47 @@ This can come in handy to force V8 GC runs for example in between requests if yo
Note that this method maps directly to [`v8::Isolate::IdleNotification`](http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#aea16cbb2e351de9a3ae7be2b7cb48297), and that in particular its return value is the same (true if there is no further garbage to collect, false otherwise) and the same caveats apply, in particular that `there is no guarantee that the [call will return] within the time limit.`
### V8 Runtime flags
It is possible to set V8 Runtime flags:
```ruby
MiniRacer::Platform.set_flags! :noconcurrent_recompilation, max_inlining_levels: 10
```
This can come in handy if you want to use MiniRacer with Unicorn, which doesn't seem to alwatys appreciate V8's liberal use of threading:
```ruby
MiniRacer::Platform.set_flags! :noconcurrent_recompilation, :noconcurrent_sweeping
```
Or else to unlock experimental features in V8, for example tail recursion optimization:
```ruby
MiniRacer::Platform.set_flags! :harmony
js = <<-JS
'use strict';
var f = function f(n){
if (n <= 0) {
return 'foo';
}
return f(n - 1);
}
f(1e6);
JS
context = MiniRacer::Context.new
context.eval js
# => "foo"
```
The same code without the harmony runtime flag results in a `MiniRacer::RuntimeError: RangeError: Maximum call stack size exceeded` exception.
Please refer to http://node.green/ as a reference on other harmony features.
A list of all V8 runtime flags can be found using `node --v8-options`, or else by perusing [the V8 source code for flags (make sure to use the right version of V8)](https://github.com/v8/v8/blob/master/src/flag-definitions.h).
Note that runtime flags must be set before any other operation (e.g. creating a context, a snapshot or an isolate), otherwise an exception will be thrown.
## Performance
The `bench` folder contains benchmark.

View File

@ -6,6 +6,7 @@
#include <ruby/encoding.h>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
using namespace v8;
@ -64,18 +65,48 @@ static VALUE rb_eParseError;
static VALUE rb_eScriptRuntimeError;
static VALUE rb_cJavaScriptFunction;
static VALUE rb_eSnapshotError;
static VALUE rb_ePlatformAlreadyInitializedError;
static VALUE rb_cDateTime = Qnil;
static Platform* current_platform = NULL;
static std::mutex platform_lock;
static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
bool platform_already_initialized = false;
platform_lock.lock();
if (current_platform == NULL) {
V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
} else {
platform_already_initialized = true;
}
platform_lock.unlock();
// important to raise outside of the lock
if (platform_already_initialized) {
rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
}
return Qnil;
}
static void init_v8() {
// no need to wait for the lock if already initialized
if (current_platform != NULL) return;
platform_lock.lock();
if (current_platform == NULL) {
V8::InitializeICU();
current_platform = platform::CreateDefaultPlatform();
V8::InitializePlatform(current_platform);
V8::Initialize();
V8::InitializeICU();
current_platform = platform::CreateDefaultPlatform();
V8::InitializePlatform(current_platform);
V8::Initialize();
}
platform_lock.unlock();
}
void* breaker(void *d) {
@ -811,6 +842,7 @@ extern "C" {
VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
VALUE rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
@ -818,6 +850,7 @@ extern "C" {
rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eStandardError);
rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eStandardError);
VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
@ -836,6 +869,8 @@ extern "C" {
rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
}
}

View File

@ -8,6 +8,7 @@ module MiniRacer
class ScriptTerminatedError < EvalError; end
class ParseError < EvalError; end
class SnapshotError < StandardError; end
class PlatformAlreadyInitialized < StandardError; end
class RuntimeError < EvalError
def initialize(message)
@ -56,6 +57,36 @@ module MiniRacer
end
end
class Platform
class << self
def set_flags!(*args, **kwargs)
flags_to_strings([args, kwargs]).each do |flag|
# defined in the C class
set_flag_as_str!(flag)
end
end
private
def flags_to_strings(flags)
flags.flatten.map { |flag| flag_to_string(flag) }.flatten
end
# normalize flags to strings, and adds leading dashes if needed
def flag_to_string(flag)
if flag.is_a?(Hash)
flag.map do |key, value|
"#{flag_to_string(key)} #{value}"
end
else
str = flag.to_s
str = "--#{str}" unless str.start_with?('--')
str
end
end
end
end
# eval is defined in the C class
class Context
@ -98,7 +129,7 @@ module MiniRacer
options ||= {}
check_init_options!(options)
@functions = {}
@timeout = nil
@current_exception = nil

View File

@ -1,6 +1,8 @@
require 'test_helper'
class MiniRacerTest < Minitest::Test
# see `test_platform_set_flags_works` below
MiniRacer::Platform.set_flags! :use_strict
def test_that_it_has_a_version_number
refute_nil ::MiniRacer::VERSION
@ -25,7 +27,7 @@ class MiniRacerTest < Minitest::Test
def test_object
context = MiniRacer::Context.new
# remember JavaScript is quirky {"1" : 1} magically turns to {1: 1} cause magic
assert_equal({1 => 2, "two" => "two"}, context.eval('a={"1" : 2, "two" : "two"}'))
assert_equal({1 => 2, "two" => "two"}, context.eval('var a={"1" : 2, "two" : "two"}; a'))
end
def test_it_returns_runtime_error
@ -77,7 +79,7 @@ class MiniRacerTest < Minitest::Test
def test_returns_javascript_function
context = MiniRacer::Context.new
assert_equal MiniRacer::JavaScriptFunction, context.eval("a = function(){}").class
assert_same MiniRacer::JavaScriptFunction, context.eval("var a = function(){}; a").class
end
def test_it_handles_malformed_js
@ -108,18 +110,19 @@ class MiniRacerTest < Minitest::Test
def test_can_attach_functions
context = MiniRacer::Context.new
context.eval 'var adder'
context.attach("adder", proc{|a,b| a+b})
assert_equal 3, context.eval('adder(1,2)')
end
def test_es6_arrow_functions
context = MiniRacer::Context.new
assert_equal 42, context.eval('adder=(x,y)=>x+y; adder(21,21);')
assert_equal 42, context.eval('var adder=(x,y)=>x+y; adder(21,21);')
end
def test_concurrent_access
context = MiniRacer::Context.new
context.eval('counter=0; plus=()=>counter++;')
context.eval('var counter=0; var plus=()=>counter++;')
(1..10).map do
Thread.new {
@ -154,18 +157,21 @@ raise FooError, "I like foos"
def test_attached_on_object
context = MiniRacer::Context.new
context.eval 'var minion'
context.attach("minion.speak", proc{"banana"})
assert_equal "banana", context.eval("minion.speak()")
end
def test_attached_on_nested_object
context = MiniRacer::Context.new
context.eval 'var minion'
context.attach("minion.kevin.speak", proc{"banana"})
assert_equal "banana", context.eval("minion.kevin.speak()")
end
def test_return_arrays
context = MiniRacer::Context.new
context.eval 'var nose'
context.attach("nose.type", proc{["banana",["nose"]]})
assert_equal ["banana", ["nose"]], context.eval("nose.type()")
end
@ -259,13 +265,14 @@ raise FooError, "I like foos"
def test_can_attach_method
context = MiniRacer::Context.new
context.eval 'var Echo'
context.attach("Echo.say", Echo.method(:say))
assert_equal "hello", context.eval("Echo.say('hello')")
end
def test_attach_error
context = MiniRacer::Context.new
context.eval("minion = 2")
context.eval("var minion = 2")
assert_raises do
begin
context.attach("minion.kevin.speak", proc{"banana"})
@ -451,7 +458,7 @@ raise FooError, "I like foos"
def test_concurrent_access_over_the_same_isolate_1
isolate = MiniRacer::Isolate.new
context = MiniRacer::Context.new(isolate: isolate)
context.eval('counter=0; plus=()=>counter++;')
context.eval('var counter=0; var plus=()=>counter++;')
(1..10).map do
Thread.new {
@ -459,7 +466,7 @@ raise FooError, "I like foos"
}
end.each(&:join)
assert_equal 10, context.eval("counter")
assert_equal 10, context.eval('counter')
end
def test_concurrent_access_over_the_same_isolate_2
@ -473,7 +480,7 @@ raise FooError, "I like foos"
context = MiniRacer::Context.new(isolate: isolate)
context.eval('var now = new Date().getTime(); while(new Date().getTime() < now + 20) {}')
context.eval("a='#{random}'")
context.eval("var a='#{random}'")
context.eval('var now = new Date().getTime(); while(new Date().getTime() < now + 20) {}')
# cruby hashes are thread safe as long as you don't mess with the
@ -485,4 +492,54 @@ raise FooError, "I like foos"
assert_equal 10, equals_after_sleep.size
assert equals_after_sleep.values.all?
end
def test_platform_set_flags_raises_an_exception_if_already_initialized
# makes sure it's initialized
MiniRacer::Snapshot.new
assert_raises(MiniRacer::PlatformAlreadyInitialized) do
MiniRacer::Platform.set_flags! :noconcurrent_recompilation
end
end
def test_platform_set_flags_works
context = MiniRacer::Context.new
assert_raises(MiniRacer::RuntimeError) do
# should fail because of strict mode set for all these tests
context.eval 'x = 28'
end
end
class TestPlatform < MiniRacer::Platform
def self.public_flags_to_strings(flags)
flags_to_strings(flags)
end
end
def test_platform_flags_to_strings
flags = [
:flag1,
[[[:flag2]]],
{key1: :value1},
{key2: 42,
key3: 8.7},
'--i_already_have_leading_hyphens',
[:'--me_too',
'i_dont']
]
expected_string_flags = [
'--flag1',
'--flag2',
'--key1 value1',
'--key2 42',
'--key3 8.7',
'--i_already_have_leading_hyphens',
'--me_too',
'--i_dont'
]
assert_equal expected_string_flags, TestPlatform.public_flags_to_strings(flags)
end
end