mini_racer 0.1.4 → 0.1.5.pre.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG +7 -0
- data/README.md +139 -1
- data/ext/mini_racer_extension/extconf.rb +22 -0
- data/ext/mini_racer_extension/mini_racer_extension.cc +357 -91
- data/lib/mini_racer.rb +159 -7
- data/lib/mini_racer/version.rb +1 -1
- data/mini_racer.gemspec +1 -1
- metadata +6 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e83c651e071c15dd06c626097c2e865b4dbf9fe0
|
4
|
+
data.tar.gz: 9e39ecff4b86fcd9176bcf198fafa7ca99afcf9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c2ad4fb2d10c44ff6018d02d86ae4631b71a6ec95106c6d70329418b6a9f9e0d5b48fba472b5a455c33dca25ed876d57355a867e5b295adcd1716b8645abc68
|
7
|
+
data.tar.gz: cca28fca48a330ce45713f59e485301456a2f2e332bec7fbcfc8d539244e6762a8a9210c75cacb5484a200216d37188673f549ab5735775d26e4c39855e60a99
|
data/.travis.yml
CHANGED
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -8,7 +8,7 @@ MiniRacer provides a minimal two way bridge between the V8 JavaScript engine and
|
|
8
8
|
|
9
9
|
It was created as an alternative to the excellent [therubyracer](https://github.com/cowboyd/therubyracer). Unlike therubyracer, mini_racer only implements a minimal bridge. This reduces the surface area making upgrading v8 much simpler and exhaustive testing simpler.
|
10
10
|
|
11
|
-
MiniRacer has an adapter for [execjs](https://github.com/
|
11
|
+
MiniRacer has an adapter for [execjs](https://github.com/rails/execjs) so it can be used directly with Rails projects to minify assets, run babel or compile CoffeeScript.
|
12
12
|
|
13
13
|
## Features
|
14
14
|
|
@@ -88,6 +88,144 @@ puts context.eval("counter")
|
|
88
88
|
|
89
89
|
```
|
90
90
|
|
91
|
+
### Snapshots
|
92
|
+
|
93
|
+
Contexts can be created with pre-loaded snapshots:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
|
97
|
+
snapshot = MiniRacer::Snapshot.new('function hello() { return "world!"; }')
|
98
|
+
|
99
|
+
context = MiniRacer::Context.new(snapshot: snapshot)
|
100
|
+
|
101
|
+
context.eval("hello()")
|
102
|
+
# => "world!"
|
103
|
+
|
104
|
+
```
|
105
|
+
|
106
|
+
Snapshots can come in handy for example if you want your contexts to be pre-loaded for effiency. It uses [V8 snapshots](http://v8project.blogspot.com/2015/09/custom-startup-snapshots.html) under the hood; see [this link](http://v8project.blogspot.com/2015/09/custom-startup-snapshots.html) for caveats using these, in particular:
|
107
|
+
|
108
|
+
```
|
109
|
+
There is an important limitation to snapshots: they can only capture V8’s
|
110
|
+
heap. Any interaction from V8 with the outside is off-limits when creating the
|
111
|
+
snapshot. Such interactions include:
|
112
|
+
|
113
|
+
* defining and calling API callbacks (i.e. functions created via v8::FunctionTemplate)
|
114
|
+
* creating typed arrays, since the backing store may be allocated outside of V8
|
115
|
+
|
116
|
+
And of course, values derived from sources such as `Math.random` or `Date.now`
|
117
|
+
are fixed once the snapshot has been captured. They are no longer really random
|
118
|
+
nor reflect the current time.
|
119
|
+
```
|
120
|
+
|
121
|
+
Also note that snapshots can be warmed up, using the `warmup!` method, which allows you to call functions which are otherwise lazily compiled to get them to compile right away; any side effect of your warm up code being then dismissed. [More details on warming up here](https://github.com/electron/electron/issues/169#issuecomment-76783481), and a small example:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
|
125
|
+
snapshot = MiniRacer::Snapshot.new('var counter = 0; function hello() { counter++; return "world! "; }')
|
126
|
+
|
127
|
+
snapshot.warmup!('hello()')
|
128
|
+
|
129
|
+
context = MiniRacer::Context.new(snapshot: snapshot)
|
130
|
+
|
131
|
+
context.eval('hello()')
|
132
|
+
# => "world! 1"
|
133
|
+
context.eval('counter')
|
134
|
+
# => 1
|
135
|
+
|
136
|
+
```
|
137
|
+
|
138
|
+
### Shared isolates
|
139
|
+
|
140
|
+
By default, MiniRacer's contexts each have their own isolate (V8 runtime). For efficiency, it is possible to re-use an isolate across contexts:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
|
144
|
+
isolate = MiniRacer::Isolate.new
|
145
|
+
|
146
|
+
context1 = MiniRacer::Context.new(isolate: isolate)
|
147
|
+
context2 = MiniRacer::Context.new(isolate: isolate)
|
148
|
+
|
149
|
+
context1.isolate == context2.isolate
|
150
|
+
# => true
|
151
|
+
```
|
152
|
+
|
153
|
+
The main benefit of this is avoiding creating/destroying isolates when not needed (for example if you use a lot of contexts).
|
154
|
+
|
155
|
+
The caveat with this is that a given isolate can only execute one context at a time, so don't share isolates across contexts that you want to run concurrently.
|
156
|
+
|
157
|
+
Also, note that if you want to use shared isolates together with snapshots, you need to first create an isolate with that snapshot, and then create contexts from that isolate:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
snapshot = MiniRacer::Snapshot.new('function hello() { return "world!"; }')
|
161
|
+
|
162
|
+
isolate = MiniRacer::Isolate.new(snapshot)
|
163
|
+
|
164
|
+
context = MiniRacer::Context.new(isolate: isolate)
|
165
|
+
|
166
|
+
context.eval("hello()")
|
167
|
+
# => "world!"
|
168
|
+
```
|
169
|
+
|
170
|
+
Re-using the same isolate over and over again means V8's garbage collector will have to run to clean it up every now and then; it's possible to trigger a _blocking_ V8 GC run inside your isolate by running the `idle_notification` method on it, which takes a single argument: the amount of time (in milliseconds) that V8 should use at most for garbage collecting:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
isolate = MiniRacer::Isolate.new
|
174
|
+
|
175
|
+
context = MiniRacer::Context.new(isolate: isolate)
|
176
|
+
|
177
|
+
# do stuff with that context...
|
178
|
+
|
179
|
+
# give up to 100ms for V8 garbage collection
|
180
|
+
isolate.idle_notification(100)
|
181
|
+
|
182
|
+
```
|
183
|
+
|
184
|
+
This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
|
185
|
+
|
186
|
+
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.`
|
187
|
+
|
188
|
+
### V8 Runtime flags
|
189
|
+
|
190
|
+
It is possible to set V8 Runtime flags:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
MiniRacer::Platform.set_flags! :noconcurrent_recompilation, max_inlining_levels: 10
|
194
|
+
```
|
195
|
+
|
196
|
+
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:
|
197
|
+
```ruby
|
198
|
+
MiniRacer::Platform.set_flags! :noconcurrent_recompilation, :noconcurrent_sweeping
|
199
|
+
```
|
200
|
+
|
201
|
+
Or else to unlock experimental features in V8, for example tail recursion optimization:
|
202
|
+
```ruby
|
203
|
+
MiniRacer::Platform.set_flags! :harmony
|
204
|
+
|
205
|
+
js = <<-JS
|
206
|
+
'use strict';
|
207
|
+
var f = function f(n){
|
208
|
+
if (n <= 0) {
|
209
|
+
return 'foo';
|
210
|
+
}
|
211
|
+
return f(n - 1);
|
212
|
+
}
|
213
|
+
|
214
|
+
f(1e6);
|
215
|
+
JS
|
216
|
+
|
217
|
+
context = MiniRacer::Context.new
|
218
|
+
|
219
|
+
context.eval js
|
220
|
+
# => "foo"
|
221
|
+
```
|
222
|
+
The same code without the harmony runtime flag results in a `MiniRacer::RuntimeError: RangeError: Maximum call stack size exceeded` exception.
|
223
|
+
Please refer to http://node.green/ as a reference on other harmony features.
|
224
|
+
|
225
|
+
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).
|
226
|
+
|
227
|
+
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.
|
228
|
+
|
91
229
|
## Performance
|
92
230
|
|
93
231
|
The `bench` folder contains benchmark.
|
@@ -17,6 +17,28 @@ if ENV['CXX']
|
|
17
17
|
CONFIG['CXX'] = ENV['CXX']
|
18
18
|
end
|
19
19
|
|
20
|
+
CXX11_TEST = <<EOS
|
21
|
+
#if __cplusplus <= 199711L
|
22
|
+
# error A compiler that supports at least C++11 is required in order to compile this project.
|
23
|
+
#endif
|
24
|
+
EOS
|
25
|
+
|
26
|
+
`echo "#{CXX11_TEST}" | #{CONFIG['CXX']} -std=c++0x -x c++ -E -`
|
27
|
+
unless $?.success?
|
28
|
+
warn <<EOS
|
29
|
+
|
30
|
+
|
31
|
+
WARNING: C++11 support is required for compiling mini_racer. Please make sure
|
32
|
+
you are using a compiler that supports at least C++11. Examples of such
|
33
|
+
compilers are GCC 4.7+ and Clang 3.2+.
|
34
|
+
|
35
|
+
If you are using Travis, consider either migrating your bulid to Ubuntu Trusty or
|
36
|
+
installing GCC 4.8. See mini_racer's README.md for more information.
|
37
|
+
|
38
|
+
|
39
|
+
EOS
|
40
|
+
end
|
41
|
+
|
20
42
|
CONFIG['LDSHARED'] = '$(CXX) -shared' unless RUBY_PLATFORM =~ /darwin/
|
21
43
|
if CONFIG['warnflags']
|
22
44
|
CONFIG['warnflags'].gsub!('-Wdeclaration-after-statement', '')
|
@@ -1,11 +1,12 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include <ruby.h>
|
3
3
|
#include <ruby/thread.h>
|
4
|
-
#include <
|
5
|
-
#include <
|
4
|
+
#include <v8.h>
|
5
|
+
#include <libplatform/libplatform.h>
|
6
6
|
#include <ruby/encoding.h>
|
7
7
|
#include <pthread.h>
|
8
8
|
#include <unistd.h>
|
9
|
+
#include <mutex>
|
9
10
|
|
10
11
|
using namespace v8;
|
11
12
|
|
@@ -19,11 +20,28 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
|
|
19
20
|
virtual void Free(void* data, size_t) { free(data); }
|
20
21
|
};
|
21
22
|
|
23
|
+
typedef struct {
|
24
|
+
const char* data;
|
25
|
+
int raw_size;
|
26
|
+
} SnapshotInfo;
|
27
|
+
|
22
28
|
typedef struct {
|
23
29
|
Isolate* isolate;
|
24
|
-
Persistent<Context>* context;
|
25
30
|
ArrayBufferAllocator* allocator;
|
31
|
+
StartupData* startup_data;
|
26
32
|
bool interrupted;
|
33
|
+
|
34
|
+
// how many references to this isolate exist
|
35
|
+
// we can't rely on Ruby's GC for this, because when destroying
|
36
|
+
// objects, Ruby will destroy ruby objects first, then call the
|
37
|
+
// extenstion's deallocators. In this case, that means it would
|
38
|
+
// call `deallocate_isolate` _before_ `deallocate`, causing a segfault
|
39
|
+
int refs_count;
|
40
|
+
} IsolateInfo;
|
41
|
+
|
42
|
+
typedef struct {
|
43
|
+
IsolateInfo* isolate_info;
|
44
|
+
Persistent<Context>* context;
|
27
45
|
} ContextInfo;
|
28
46
|
|
29
47
|
typedef struct {
|
@@ -46,42 +64,71 @@ static VALUE rb_eScriptTerminatedError;
|
|
46
64
|
static VALUE rb_eParseError;
|
47
65
|
static VALUE rb_eScriptRuntimeError;
|
48
66
|
static VALUE rb_cJavaScriptFunction;
|
67
|
+
static VALUE rb_eSnapshotError;
|
68
|
+
static VALUE rb_ePlatformAlreadyInitializedError;
|
49
69
|
|
70
|
+
static VALUE rb_cFailedV8Conversion;
|
50
71
|
static VALUE rb_cDateTime = Qnil;
|
51
72
|
|
52
73
|
static Platform* current_platform = NULL;
|
74
|
+
static std::mutex platform_lock;
|
75
|
+
|
76
|
+
static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
|
77
|
+
bool platform_already_initialized = false;
|
78
|
+
|
79
|
+
platform_lock.lock();
|
53
80
|
|
54
|
-
static void init_v8() {
|
55
81
|
if (current_platform == NULL) {
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
V8::Initialize();
|
82
|
+
V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
|
83
|
+
} else {
|
84
|
+
platform_already_initialized = true;
|
60
85
|
}
|
86
|
+
|
87
|
+
platform_lock.unlock();
|
88
|
+
|
89
|
+
// important to raise outside of the lock
|
90
|
+
if (platform_already_initialized) {
|
91
|
+
rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
|
92
|
+
}
|
93
|
+
|
94
|
+
return Qnil;
|
61
95
|
}
|
62
96
|
|
63
|
-
void
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
97
|
+
static void init_v8() {
|
98
|
+
// no need to wait for the lock if already initialized
|
99
|
+
if (current_platform != NULL) return;
|
100
|
+
|
101
|
+
platform_lock.lock();
|
102
|
+
|
103
|
+
if (current_platform == NULL) {
|
104
|
+
V8::InitializeICU();
|
105
|
+
current_platform = platform::CreateDefaultPlatform();
|
106
|
+
V8::InitializePlatform(current_platform);
|
107
|
+
V8::Initialize();
|
108
|
+
}
|
109
|
+
|
110
|
+
platform_lock.unlock();
|
69
111
|
}
|
70
112
|
|
71
113
|
void*
|
72
114
|
nogvl_context_eval(void* arg) {
|
115
|
+
|
73
116
|
EvalParams* eval_params = (EvalParams*)arg;
|
74
117
|
EvalResult* result = eval_params->result;
|
75
|
-
Isolate* isolate = eval_params->context_info->isolate;
|
118
|
+
Isolate* isolate = eval_params->context_info->isolate_info->isolate;
|
76
119
|
Isolate::Scope isolate_scope(isolate);
|
77
120
|
HandleScope handle_scope(isolate);
|
78
121
|
|
79
122
|
TryCatch trycatch(isolate);
|
80
123
|
|
81
124
|
Local<Context> context = eval_params->context_info->context->Get(isolate);
|
82
|
-
|
83
125
|
Context::Scope context_scope(context);
|
84
126
|
|
127
|
+
// in gvl flag
|
128
|
+
isolate->SetData(0, (void*)false);
|
129
|
+
// terminate ASAP
|
130
|
+
isolate->SetData(1, (void*)false);
|
131
|
+
|
85
132
|
MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
|
86
133
|
result->parsed = !parsed_script.IsEmpty();
|
87
134
|
result->executed = false;
|
@@ -93,19 +140,8 @@ nogvl_context_eval(void* arg) {
|
|
93
140
|
result->message->Reset(isolate, trycatch.Exception());
|
94
141
|
} else {
|
95
142
|
|
96
|
-
pthread_t breaker_thread;
|
97
|
-
|
98
|
-
if (eval_params->timeout > 0) {
|
99
|
-
pthread_create(&breaker_thread, NULL, breaker, (void*)eval_params);
|
100
|
-
}
|
101
|
-
|
102
143
|
MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
103
144
|
|
104
|
-
if (eval_params->timeout > 0) {
|
105
|
-
pthread_cancel(breaker_thread);
|
106
|
-
pthread_join(breaker_thread, NULL);
|
107
|
-
}
|
108
|
-
|
109
145
|
result->executed = !maybe_value.IsEmpty();
|
110
146
|
|
111
147
|
if (result->executed) {
|
@@ -130,6 +166,8 @@ nogvl_context_eval(void* arg) {
|
|
130
166
|
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
|
131
167
|
result->message->Reset(isolate, v8_message);
|
132
168
|
} else if(trycatch.HasTerminated()) {
|
169
|
+
|
170
|
+
|
133
171
|
result->terminated = true;
|
134
172
|
result->message = new Persistent<Value>();
|
135
173
|
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
@@ -142,6 +180,9 @@ nogvl_context_eval(void* arg) {
|
|
142
180
|
}
|
143
181
|
}
|
144
182
|
|
183
|
+
isolate->SetData(0, (void*)true);
|
184
|
+
|
185
|
+
|
145
186
|
return NULL;
|
146
187
|
}
|
147
188
|
|
@@ -175,6 +216,9 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
175
216
|
for(uint32_t i=0; i < arr->Length(); i++) {
|
176
217
|
Local<Value> element = arr->Get(i);
|
177
218
|
VALUE rb_elem = convert_v8_to_ruby(isolate, element);
|
219
|
+
if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
220
|
+
return rb_elem;
|
221
|
+
}
|
178
222
|
rb_ary_push(rb_array, rb_elem);
|
179
223
|
}
|
180
224
|
return rb_array;
|
@@ -193,6 +237,9 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
193
237
|
}
|
194
238
|
|
195
239
|
if (value->IsObject()) {
|
240
|
+
|
241
|
+
TryCatch trycatch(isolate);
|
242
|
+
|
196
243
|
VALUE rb_hash = rb_hash_new();
|
197
244
|
Local<Context> context = Context::New(isolate);
|
198
245
|
Local<Object> object = value->ToObject();
|
@@ -203,6 +250,14 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
203
250
|
Local<Value> key = props->Get(i);
|
204
251
|
VALUE rb_key = convert_v8_to_ruby(isolate, key);
|
205
252
|
Local<Value> value = object->Get(key);
|
253
|
+
// this may have failed due to Get raising
|
254
|
+
|
255
|
+
if (trycatch.HasCaught()) {
|
256
|
+
// TODO isolate code that translates execption to ruby
|
257
|
+
// exception so we can properly return it
|
258
|
+
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
259
|
+
}
|
260
|
+
|
206
261
|
VALUE rb_value = convert_v8_to_ruby(isolate, value);
|
207
262
|
rb_hash_aset(rb_hash, rb_key, rb_value);
|
208
263
|
}
|
@@ -233,7 +288,6 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
233
288
|
{
|
234
289
|
return scope.Escape(Number::New(isolate, (double)fixnum));
|
235
290
|
}
|
236
|
-
|
237
291
|
return scope.Escape(Integer::New(isolate, (int)fixnum));
|
238
292
|
case T_FLOAT:
|
239
293
|
return scope.Escape(Number::New(isolate, NUM2DBL(value)));
|
@@ -273,7 +327,6 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
273
327
|
{
|
274
328
|
value = rb_funcall(value, rb_intern("to_time"), 0);
|
275
329
|
}
|
276
|
-
|
277
330
|
value = rb_funcall(value, rb_intern("to_f"), 0);
|
278
331
|
return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
|
279
332
|
}
|
@@ -296,9 +349,127 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
296
349
|
|
297
350
|
static void unblock_eval(void *ptr) {
|
298
351
|
EvalParams* eval = (EvalParams*)ptr;
|
299
|
-
eval->context_info->interrupted = true;
|
352
|
+
eval->context_info->isolate_info->interrupted = true;
|
300
353
|
}
|
301
354
|
|
355
|
+
static VALUE rb_snapshot_size(VALUE self, VALUE str) {
|
356
|
+
SnapshotInfo* snapshot_info;
|
357
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
358
|
+
|
359
|
+
return INT2NUM(snapshot_info->raw_size);
|
360
|
+
}
|
361
|
+
|
362
|
+
static VALUE rb_snapshot_load(VALUE self, VALUE str) {
|
363
|
+
SnapshotInfo* snapshot_info;
|
364
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
365
|
+
|
366
|
+
init_v8();
|
367
|
+
|
368
|
+
StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
|
369
|
+
|
370
|
+
if (startup_data.data == NULL && startup_data.raw_size == 0) {
|
371
|
+
rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
|
372
|
+
}
|
373
|
+
|
374
|
+
snapshot_info->data = startup_data.data;
|
375
|
+
snapshot_info->raw_size = startup_data.raw_size;
|
376
|
+
|
377
|
+
return Qnil;
|
378
|
+
}
|
379
|
+
|
380
|
+
static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
|
381
|
+
SnapshotInfo* snapshot_info;
|
382
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
383
|
+
|
384
|
+
init_v8();
|
385
|
+
|
386
|
+
StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
|
387
|
+
StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
|
388
|
+
|
389
|
+
if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
|
390
|
+
rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
|
391
|
+
} else {
|
392
|
+
delete[] snapshot_info->data;
|
393
|
+
|
394
|
+
snapshot_info->data = warm_startup_data.data;
|
395
|
+
snapshot_info->raw_size = warm_startup_data.raw_size;
|
396
|
+
}
|
397
|
+
|
398
|
+
return self;
|
399
|
+
}
|
400
|
+
|
401
|
+
static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
|
402
|
+
IsolateInfo* isolate_info;
|
403
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
404
|
+
|
405
|
+
init_v8();
|
406
|
+
|
407
|
+
isolate_info->allocator = new ArrayBufferAllocator();
|
408
|
+
isolate_info->interrupted = false;
|
409
|
+
isolate_info->refs_count = 1;
|
410
|
+
|
411
|
+
Isolate::CreateParams create_params;
|
412
|
+
create_params.array_buffer_allocator = isolate_info->allocator;
|
413
|
+
|
414
|
+
StartupData* startup_data = NULL;
|
415
|
+
if (!NIL_P(snapshot)) {
|
416
|
+
SnapshotInfo* snapshot_info;
|
417
|
+
Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
|
418
|
+
|
419
|
+
int raw_size = snapshot_info->raw_size;
|
420
|
+
char* data = new char[raw_size];
|
421
|
+
memcpy(data, snapshot_info->data, sizeof(char) * raw_size);
|
422
|
+
|
423
|
+
startup_data = new StartupData;
|
424
|
+
startup_data->data = data;
|
425
|
+
startup_data->raw_size = raw_size;
|
426
|
+
|
427
|
+
create_params.snapshot_blob = startup_data;
|
428
|
+
}
|
429
|
+
|
430
|
+
isolate_info->startup_data = startup_data;
|
431
|
+
isolate_info->isolate = Isolate::New(create_params);
|
432
|
+
|
433
|
+
return Qnil;
|
434
|
+
}
|
435
|
+
|
436
|
+
static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
|
437
|
+
IsolateInfo* isolate_info;
|
438
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
439
|
+
|
440
|
+
return isolate_info->isolate->IdleNotification(NUM2INT(idle_time_in_ms)) ? Qtrue : Qfalse;
|
441
|
+
}
|
442
|
+
|
443
|
+
static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
|
444
|
+
ContextInfo* context_info;
|
445
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
446
|
+
|
447
|
+
init_v8();
|
448
|
+
|
449
|
+
IsolateInfo* isolate_info;
|
450
|
+
Data_Get_Struct(isolate, IsolateInfo, isolate_info);
|
451
|
+
|
452
|
+
context_info->isolate_info = isolate_info;
|
453
|
+
isolate_info->refs_count++;
|
454
|
+
|
455
|
+
{
|
456
|
+
Locker lock(isolate_info->isolate);
|
457
|
+
Isolate::Scope isolate_scope(isolate_info->isolate);
|
458
|
+
HandleScope handle_scope(isolate_info->isolate);
|
459
|
+
|
460
|
+
Local<Context> context = Context::New(isolate_info->isolate);
|
461
|
+
|
462
|
+
context_info->context = new Persistent<Context>();
|
463
|
+
context_info->context->Reset(isolate_info->isolate, context);
|
464
|
+
}
|
465
|
+
|
466
|
+
if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
|
467
|
+
{
|
468
|
+
rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
469
|
+
}
|
470
|
+
|
471
|
+
return Qnil;
|
472
|
+
}
|
302
473
|
|
303
474
|
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
304
475
|
|
@@ -311,13 +482,16 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
|
311
482
|
VALUE backtrace = Qnil;
|
312
483
|
|
313
484
|
Data_Get_Struct(self, ContextInfo, context_info);
|
485
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
486
|
+
|
487
|
+
|
314
488
|
|
315
489
|
{
|
316
|
-
Locker lock(
|
317
|
-
Isolate::Scope isolate_scope(
|
318
|
-
HandleScope handle_scope(
|
490
|
+
Locker lock(isolate);
|
491
|
+
Isolate::Scope isolate_scope(isolate);
|
492
|
+
HandleScope handle_scope(isolate);
|
319
493
|
|
320
|
-
Local<String> eval = String::NewFromUtf8(
|
494
|
+
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
|
321
495
|
NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
|
322
496
|
|
323
497
|
eval_params.context_info = context_info;
|
@@ -335,15 +509,15 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
|
335
509
|
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
|
336
510
|
|
337
511
|
if (eval_result.message != NULL) {
|
338
|
-
Local<Value> tmp = Local<Value>::New(
|
339
|
-
message = convert_v8_to_ruby(
|
512
|
+
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
|
513
|
+
message = convert_v8_to_ruby(isolate, tmp);
|
340
514
|
eval_result.message->Reset();
|
341
515
|
delete eval_result.message;
|
342
516
|
}
|
343
517
|
|
344
518
|
if (eval_result.backtrace != NULL) {
|
345
|
-
Local<Value> tmp = Local<Value>::New(
|
346
|
-
backtrace = convert_v8_to_ruby(
|
519
|
+
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
|
520
|
+
backtrace = convert_v8_to_ruby(isolate, tmp);
|
347
521
|
eval_result.backtrace->Reset();
|
348
522
|
delete eval_result.backtrace;
|
349
523
|
}
|
@@ -377,19 +551,25 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
|
377
551
|
}
|
378
552
|
}
|
379
553
|
|
380
|
-
// New scope for return value
|
554
|
+
// New scope for return value, must release GVL which
|
381
555
|
{
|
382
|
-
Locker lock(
|
383
|
-
Isolate::Scope isolate_scope(
|
384
|
-
HandleScope handle_scope(
|
556
|
+
Locker lock(isolate);
|
557
|
+
Isolate::Scope isolate_scope(isolate);
|
558
|
+
HandleScope handle_scope(isolate);
|
385
559
|
|
386
|
-
Local<Value> tmp = Local<Value>::New(
|
387
|
-
result = convert_v8_to_ruby(
|
560
|
+
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
|
561
|
+
result = convert_v8_to_ruby(isolate, tmp);
|
388
562
|
|
389
563
|
eval_result.value->Reset();
|
390
564
|
delete eval_result.value;
|
391
565
|
}
|
392
566
|
|
567
|
+
if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
568
|
+
// TODO try to recover stack trace from the conversion error
|
569
|
+
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
570
|
+
}
|
571
|
+
|
572
|
+
|
393
573
|
return result;
|
394
574
|
}
|
395
575
|
|
@@ -424,7 +604,7 @@ void*
|
|
424
604
|
gvl_ruby_callback(void* data) {
|
425
605
|
|
426
606
|
FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
|
427
|
-
VALUE* ruby_args;
|
607
|
+
VALUE* ruby_args = NULL;
|
428
608
|
int length = args->Length();
|
429
609
|
VALUE callback;
|
430
610
|
VALUE result;
|
@@ -456,8 +636,14 @@ gvl_ruby_callback(void* data) {
|
|
456
636
|
callback_data.args = ruby_args;
|
457
637
|
callback_data.failed = false;
|
458
638
|
|
459
|
-
|
460
|
-
|
639
|
+
if ((bool)args->GetIsolate()->GetData(1) == true) {
|
640
|
+
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during tansition from Ruby to JS"));
|
641
|
+
V8::TerminateExecution(args->GetIsolate());
|
642
|
+
return NULL;
|
643
|
+
}
|
644
|
+
|
645
|
+
result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
|
646
|
+
(VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
|
461
647
|
|
462
648
|
if(callback_data.failed) {
|
463
649
|
VALUE parent = rb_iv_get(self, "@parent");
|
@@ -474,11 +660,23 @@ gvl_ruby_callback(void* data) {
|
|
474
660
|
xfree(ruby_args);
|
475
661
|
}
|
476
662
|
|
663
|
+
if ((bool)args->GetIsolate()->GetData(1) == true) {
|
664
|
+
Isolate* isolate = args->GetIsolate();
|
665
|
+
V8::TerminateExecution(isolate);
|
666
|
+
}
|
667
|
+
|
477
668
|
return NULL;
|
478
669
|
}
|
479
670
|
|
480
671
|
static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
|
481
|
-
|
672
|
+
|
673
|
+
bool has_gvl = (bool)args.GetIsolate()->GetData(0);
|
674
|
+
|
675
|
+
if(has_gvl) {
|
676
|
+
gvl_ruby_callback((void*)&args);
|
677
|
+
} else {
|
678
|
+
rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
|
679
|
+
}
|
482
680
|
}
|
483
681
|
|
484
682
|
|
@@ -495,16 +693,17 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
|
|
495
693
|
bool attach_error = false;
|
496
694
|
|
497
695
|
Data_Get_Struct(parent, ContextInfo, context_info);
|
696
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
498
697
|
|
499
698
|
{
|
500
|
-
Locker lock(
|
501
|
-
Isolate::Scope isolate_scope(
|
502
|
-
HandleScope handle_scope(
|
699
|
+
Locker lock(isolate);
|
700
|
+
Isolate::Scope isolate_scope(isolate);
|
701
|
+
HandleScope handle_scope(isolate);
|
503
702
|
|
504
|
-
Local<Context> context = context_info->context->Get(
|
703
|
+
Local<Context> context = context_info->context->Get(isolate);
|
505
704
|
Context::Scope context_scope(context);
|
506
705
|
|
507
|
-
Local<String> v8_str = String::NewFromUtf8(
|
706
|
+
Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
|
508
707
|
NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
|
509
708
|
|
510
709
|
// copy self so we can access from v8 external
|
@@ -512,13 +711,13 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
|
|
512
711
|
Data_Get_Struct(self, VALUE, self_copy);
|
513
712
|
*self_copy = self;
|
514
713
|
|
515
|
-
Local<Value> external = External::New(
|
714
|
+
Local<Value> external = External::New(isolate, self_copy);
|
516
715
|
|
517
716
|
if (parent_object == Qnil) {
|
518
|
-
context->Global()->Set(v8_str, FunctionTemplate::New(
|
717
|
+
context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
|
519
718
|
} else {
|
520
719
|
|
521
|
-
Local<String> eval = String::NewFromUtf8(
|
720
|
+
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
|
522
721
|
NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
|
523
722
|
|
524
723
|
MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
|
@@ -531,7 +730,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
|
|
531
730
|
if (!maybe_value.IsEmpty()) {
|
532
731
|
Local<Value> value = maybe_value.ToLocalChecked();
|
533
732
|
if (value->IsObject()){
|
534
|
-
value.As<Object>()->Set(v8_str, FunctionTemplate::New(
|
733
|
+
value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
|
535
734
|
attach_error = false;
|
536
735
|
}
|
537
736
|
}
|
@@ -551,71 +750,120 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
|
|
551
750
|
return Qnil;
|
552
751
|
}
|
553
752
|
|
554
|
-
void
|
555
|
-
|
556
|
-
|
557
|
-
|
753
|
+
void maybe_free_isolate_info(IsolateInfo* isolate_info) {
|
754
|
+
// an isolate can only be freed if no Isolate or Context (ruby) object
|
755
|
+
// still need it
|
756
|
+
if (isolate_info == NULL || isolate_info->refs_count > 0) {
|
757
|
+
return;
|
558
758
|
}
|
559
759
|
|
560
|
-
{
|
561
|
-
|
562
|
-
delete context_info->context;
|
760
|
+
if (isolate_info->isolate) {
|
761
|
+
Locker lock(isolate_info->isolate);
|
563
762
|
}
|
564
763
|
|
565
|
-
{
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
764
|
+
if (isolate_info->isolate) {
|
765
|
+
if (isolate_info->interrupted) {
|
766
|
+
fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.");
|
767
|
+
} else {
|
768
|
+
|
769
|
+
isolate_info->isolate->Dispose();
|
770
|
+
}
|
771
|
+
isolate_info->isolate = NULL;
|
772
|
+
}
|
773
|
+
|
774
|
+
if (isolate_info->startup_data) {
|
775
|
+
delete[] isolate_info->startup_data->data;
|
776
|
+
delete isolate_info->startup_data;
|
777
|
+
}
|
778
|
+
|
779
|
+
delete isolate_info->allocator;
|
780
|
+
xfree(isolate_info);
|
781
|
+
}
|
782
|
+
|
783
|
+
void deallocate_isolate(void* data) {
|
784
|
+
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
785
|
+
|
786
|
+
isolate_info->refs_count--;
|
787
|
+
|
788
|
+
maybe_free_isolate_info(isolate_info);
|
789
|
+
}
|
790
|
+
|
791
|
+
void deallocate(void* data) {
|
792
|
+
ContextInfo* context_info = (ContextInfo*)data;
|
793
|
+
IsolateInfo* isolate_info = context_info->isolate_info;
|
794
|
+
|
795
|
+
if (context_info->context && isolate_info && isolate_info->isolate) {
|
796
|
+
Locker lock(isolate_info->isolate);
|
797
|
+
v8::Isolate::Scope isolate_scope(isolate_info->isolate);
|
798
|
+
context_info->context->Reset();
|
799
|
+
delete context_info->context;
|
571
800
|
}
|
572
801
|
|
573
|
-
|
574
|
-
|
802
|
+
if (isolate_info) {
|
803
|
+
isolate_info->refs_count--;
|
804
|
+
maybe_free_isolate_info(isolate_info);
|
805
|
+
}
|
575
806
|
}
|
576
807
|
|
577
808
|
void deallocate_external_function(void * data) {
|
578
809
|
xfree(data);
|
579
810
|
}
|
580
811
|
|
812
|
+
void deallocate_snapshot(void * data) {
|
813
|
+
SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
|
814
|
+
|
815
|
+
delete[] snapshot_info->data;
|
816
|
+
|
817
|
+
xfree(snapshot_info);
|
818
|
+
}
|
819
|
+
|
581
820
|
VALUE allocate_external_function(VALUE klass) {
|
582
821
|
VALUE* self = ALLOC(VALUE);
|
583
822
|
return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
|
584
823
|
}
|
585
824
|
|
586
825
|
VALUE allocate(VALUE klass) {
|
587
|
-
init_v8();
|
588
|
-
|
589
826
|
ContextInfo* context_info = ALLOC(ContextInfo);
|
590
|
-
context_info->
|
591
|
-
context_info->
|
592
|
-
Isolate::CreateParams create_params;
|
593
|
-
create_params.array_buffer_allocator = context_info->allocator;
|
827
|
+
context_info->isolate_info = NULL;
|
828
|
+
context_info->context = NULL;
|
594
829
|
|
595
|
-
|
830
|
+
return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
|
831
|
+
}
|
596
832
|
|
597
|
-
|
598
|
-
|
599
|
-
|
833
|
+
VALUE allocate_snapshot(VALUE klass) {
|
834
|
+
SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
|
835
|
+
snapshot_info->data = NULL;
|
836
|
+
snapshot_info->raw_size = 0;
|
600
837
|
|
601
|
-
|
838
|
+
return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
|
839
|
+
}
|
602
840
|
|
603
|
-
|
604
|
-
|
841
|
+
VALUE allocate_isolate(VALUE klass) {
|
842
|
+
IsolateInfo* isolate_info = ALLOC(IsolateInfo);
|
605
843
|
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
844
|
+
isolate_info->isolate = NULL;
|
845
|
+
isolate_info->allocator = NULL;
|
846
|
+
isolate_info->startup_data = NULL;
|
847
|
+
isolate_info->interrupted = false;
|
848
|
+
isolate_info->refs_count = 0;
|
610
849
|
|
611
|
-
return Data_Wrap_Struct(klass, NULL,
|
850
|
+
return Data_Wrap_Struct(klass, NULL, deallocate_isolate, (void*)isolate_info);
|
612
851
|
}
|
613
852
|
|
614
853
|
static VALUE
|
615
854
|
rb_context_stop(VALUE self) {
|
855
|
+
|
616
856
|
ContextInfo* context_info;
|
617
857
|
Data_Get_Struct(self, ContextInfo, context_info);
|
618
|
-
|
858
|
+
|
859
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
860
|
+
|
861
|
+
// flag for termination
|
862
|
+
isolate->SetData(1, (void*)true);
|
863
|
+
|
864
|
+
V8::TerminateExecution(isolate);
|
865
|
+
rb_funcall(self, rb_intern("stop_attached"), 0);
|
866
|
+
|
619
867
|
return Qnil;
|
620
868
|
}
|
621
869
|
|
@@ -625,20 +873,38 @@ extern "C" {
|
|
625
873
|
{
|
626
874
|
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
|
627
875
|
VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
876
|
+
VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
|
877
|
+
VALUE rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
|
878
|
+
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
|
628
879
|
|
629
880
|
VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
|
630
881
|
rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
|
631
882
|
rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
|
632
883
|
rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
|
633
884
|
rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
|
885
|
+
rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eStandardError);
|
886
|
+
rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eStandardError);
|
887
|
+
rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
|
634
888
|
|
635
889
|
VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
|
636
890
|
rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
|
637
891
|
rb_define_alloc_func(rb_cContext, allocate);
|
892
|
+
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
893
|
+
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
638
894
|
|
639
895
|
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
|
896
|
+
rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
|
640
897
|
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
641
898
|
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
899
|
+
|
900
|
+
rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
|
901
|
+
rb_define_method(rb_cSnapshot, "warmup!", (VALUE(*)(...))&rb_snapshot_warmup, 1);
|
902
|
+
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
|
903
|
+
|
904
|
+
rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
|
905
|
+
rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
|
906
|
+
|
907
|
+
rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
|
642
908
|
}
|
643
909
|
|
644
910
|
}
|
data/lib/mini_racer.rb
CHANGED
@@ -7,6 +7,15 @@ module MiniRacer
|
|
7
7
|
class EvalError < StandardError; end
|
8
8
|
class ScriptTerminatedError < EvalError; end
|
9
9
|
class ParseError < EvalError; end
|
10
|
+
class SnapshotError < StandardError; end
|
11
|
+
class PlatformAlreadyInitialized < StandardError; end
|
12
|
+
|
13
|
+
class FailedV8Conversion
|
14
|
+
attr_reader :info
|
15
|
+
def initialize(info)
|
16
|
+
@info = info
|
17
|
+
end
|
18
|
+
end
|
10
19
|
|
11
20
|
class RuntimeError < EvalError
|
12
21
|
def initialize(message)
|
@@ -38,6 +47,53 @@ module MiniRacer
|
|
38
47
|
end
|
39
48
|
end
|
40
49
|
|
50
|
+
class Isolate
|
51
|
+
def initialize(snapshot = nil)
|
52
|
+
unless snapshot.nil? || snapshot.is_a?(Snapshot)
|
53
|
+
raise ArgumentError, "snapshot must be a Snapshot object, passed a #{snapshot.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
@lock = Mutex.new
|
57
|
+
|
58
|
+
# defined in the C class
|
59
|
+
init_with_snapshot(snapshot)
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_lock
|
63
|
+
@lock.synchronize { yield }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Platform
|
68
|
+
class << self
|
69
|
+
def set_flags!(*args, **kwargs)
|
70
|
+
flags_to_strings([args, kwargs]).each do |flag|
|
71
|
+
# defined in the C class
|
72
|
+
set_flag_as_str!(flag)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def flags_to_strings(flags)
|
79
|
+
flags.flatten.map { |flag| flag_to_string(flag) }.flatten
|
80
|
+
end
|
81
|
+
|
82
|
+
# normalize flags to strings, and adds leading dashes if needed
|
83
|
+
def flag_to_string(flag)
|
84
|
+
if flag.is_a?(Hash)
|
85
|
+
flag.map do |key, value|
|
86
|
+
"#{flag_to_string(key)} #{value}"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
str = flag.to_s
|
90
|
+
str = "--#{str}" unless str.start_with?('--')
|
91
|
+
str
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
41
97
|
# eval is defined in the C class
|
42
98
|
class Context
|
43
99
|
|
@@ -74,14 +130,27 @@ module MiniRacer
|
|
74
130
|
end
|
75
131
|
end
|
76
132
|
|
133
|
+
attr_reader :isolate
|
134
|
+
|
77
135
|
def initialize(options = nil)
|
136
|
+
options ||= {}
|
137
|
+
|
138
|
+
check_init_options!(options)
|
139
|
+
|
78
140
|
@functions = {}
|
79
|
-
@lock = Mutex.new
|
80
141
|
@timeout = nil
|
81
142
|
@current_exception = nil
|
143
|
+
@timeout = options[:timeout]
|
144
|
+
@isolate = options[:isolate] || Isolate.new(options[:snapshot])
|
145
|
+
|
146
|
+
@callback_mutex = Mutex.new
|
147
|
+
@callback_running = false
|
148
|
+
@thread_raise_called = false
|
149
|
+
@eval_thread = nil
|
82
150
|
|
83
|
-
|
84
|
-
|
151
|
+
isolate.with_lock do
|
152
|
+
# defined in the C class
|
153
|
+
init_with_isolate(@isolate)
|
85
154
|
end
|
86
155
|
end
|
87
156
|
|
@@ -91,19 +160,102 @@ module MiniRacer
|
|
91
160
|
end
|
92
161
|
|
93
162
|
def eval(str)
|
94
|
-
@
|
163
|
+
@eval_thread = Thread.current
|
164
|
+
isolate.with_lock do
|
95
165
|
@current_exception = nil
|
96
|
-
|
166
|
+
timeout do
|
167
|
+
eval_unsafe(str)
|
168
|
+
end
|
97
169
|
end
|
170
|
+
ensure
|
171
|
+
@eval_thread = nil
|
98
172
|
end
|
99
173
|
|
174
|
+
|
100
175
|
def attach(name, callback)
|
101
|
-
|
102
|
-
|
176
|
+
|
177
|
+
wrapped = lambda do |*args|
|
178
|
+
begin
|
179
|
+
@callback_mutex.synchronize{
|
180
|
+
@callback_running = true
|
181
|
+
}
|
182
|
+
|
183
|
+
callback.call(*args)
|
184
|
+
ensure
|
185
|
+
@callback_mutex.synchronize {
|
186
|
+
@callback_running = false
|
187
|
+
|
188
|
+
# this is some odd code, but it is required
|
189
|
+
# if we raised on this thread we better wait for it
|
190
|
+
# otherwise we may end up raising in an unsafe spot
|
191
|
+
if @thread_raise_called
|
192
|
+
sleep 0.1
|
193
|
+
end
|
194
|
+
@thread_raise_called = false
|
195
|
+
}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
isolate.with_lock do
|
200
|
+
external = ExternalFunction.new(name, wrapped, self)
|
103
201
|
@functions["#{name}"] = external
|
104
202
|
end
|
105
203
|
end
|
106
204
|
|
205
|
+
private
|
206
|
+
|
207
|
+
def stop_attached
|
208
|
+
@callback_mutex.synchronize{
|
209
|
+
if @callback_running
|
210
|
+
@eval_thread.raise ScriptTerminatedError, "Terminated during callback"
|
211
|
+
@thread_raise_called = true
|
212
|
+
end
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
def timeout(&blk)
|
217
|
+
return blk.call unless @timeout
|
218
|
+
|
219
|
+
_,wp = IO.pipe
|
220
|
+
|
221
|
+
Thread.new do
|
222
|
+
begin
|
223
|
+
result = IO.select([wp],[],[],(@timeout/1000.0))
|
224
|
+
if !result
|
225
|
+
stop
|
226
|
+
end
|
227
|
+
rescue
|
228
|
+
STDERR.puts "FAILED TO TERMINATE DUE TO TIMEOUT"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
rval = blk.call
|
233
|
+
wp.write("done")
|
234
|
+
|
235
|
+
rval
|
236
|
+
end
|
237
|
+
|
238
|
+
def check_init_options!(options)
|
239
|
+
assert_option_is_nil_or_a('isolate', options[:isolate], Isolate)
|
240
|
+
assert_option_is_nil_or_a('snapshot', options[:snapshot], Snapshot)
|
241
|
+
|
242
|
+
if options[:isolate] && options[:snapshot]
|
243
|
+
raise ArgumentError, 'can only pass one of isolate and snapshot options'
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def assert_option_is_nil_or_a(option_name, object, klass)
|
248
|
+
unless object.nil? || object.is_a?(klass)
|
249
|
+
raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
|
250
|
+
end
|
251
|
+
end
|
107
252
|
end
|
108
253
|
|
254
|
+
# `size` and `warmup!` public methods are defined in the C class
|
255
|
+
class Snapshot
|
256
|
+
def initialize(str = '')
|
257
|
+
# defined in the C class
|
258
|
+
load(str)
|
259
|
+
end
|
260
|
+
end
|
109
261
|
end
|
data/lib/mini_racer/version.rb
CHANGED
data/mini_racer.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_development_dependency "minitest", "~> 5.0"
|
26
26
|
spec.add_development_dependency "rake-compiler"
|
27
27
|
|
28
|
-
spec.add_dependency 'libv8', '~> 5.
|
28
|
+
spec.add_dependency 'libv8', '~> 5.3'
|
29
29
|
spec.require_paths = ["lib", "ext"]
|
30
30
|
|
31
31
|
spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_racer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5.pre.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -72,20 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '5.
|
76
|
-
- - "<"
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: 5.1.11
|
75
|
+
version: '5.3'
|
79
76
|
type: :runtime
|
80
77
|
prerelease: false
|
81
78
|
version_requirements: !ruby/object:Gem::Requirement
|
82
79
|
requirements:
|
83
80
|
- - "~>"
|
84
81
|
- !ruby/object:Gem::Version
|
85
|
-
version: '5.
|
86
|
-
- - "<"
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: 5.1.11
|
82
|
+
version: '5.3'
|
89
83
|
description: Minimal embedded v8 engine for Ruby
|
90
84
|
email:
|
91
85
|
- sam.saffron@gmail.com
|
@@ -125,9 +119,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
119
|
version: '2.0'
|
126
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
121
|
requirements:
|
128
|
-
- - "
|
122
|
+
- - ">"
|
129
123
|
- !ruby/object:Gem::Version
|
130
|
-
version:
|
124
|
+
version: 1.3.1
|
131
125
|
requirements: []
|
132
126
|
rubyforge_project:
|
133
127
|
rubygems_version: 2.5.1
|