mini_racer 0.1.4 → 0.1.5.pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|