mini_racer 0.1.15 → 0.2.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 +5 -5
- data/.travis.yml +10 -6
- data/CHANGELOG +9 -0
- data/README.md +22 -0
- data/ext/mini_racer_extension/extconf.rb +1 -0
- data/ext/mini_racer_extension/mini_racer_extension.cc +562 -275
- data/lib/mini_racer.rb +84 -32
- data/lib/mini_racer/version.rb +1 -1
- data/mini_racer.gemspec +2 -2
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b2bb1ce568e44bcf3f6fcc469942ee12bd418cb21faaedbf1ed60a1b89e4ee47
|
4
|
+
data.tar.gz: 4a38af517388189628e22c7f9bf6b8129489847ff1b4d79b741e248b0f88fc6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e8f42aad8b92fc27e9d430edea330fd583743ea7c81c6bbb5097de797af02b2905b87006798559e61e95ab2a0a9fb667e4d0758a3021876c81be138bc890e3d
|
7
|
+
data.tar.gz: 5e17012b06081f0b3f303f549908c348bed54ef9151069c1a6efcfb778708fde80fe2108a6f5f9f9908a9aec52449ad65a85fd2f0ae49892d89dfba59cc568c2
|
data/.travis.yml
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.
|
4
|
-
- 2.
|
5
|
-
- 2.
|
6
|
-
-
|
3
|
+
- 2.3
|
4
|
+
- 2.4
|
5
|
+
- 2.5
|
6
|
+
- ruby-head
|
7
7
|
matrix:
|
8
8
|
include:
|
9
|
+
- rvm: 2.5.1
|
10
|
+
os: osx
|
11
|
+
osx_image: xcode9.4
|
9
12
|
- rvm: 2.4.0
|
10
13
|
os: osx
|
11
|
-
osx_image: xcode8.
|
14
|
+
osx_image: xcode8.3
|
12
15
|
- rvm: 2.4.0
|
13
16
|
os: osx
|
14
17
|
osx_image: xcode7.3
|
15
18
|
dist: trusty
|
16
19
|
sudo: true
|
17
20
|
before_install:
|
18
|
-
- gem
|
21
|
+
- gem update --system
|
22
|
+
- gem install bundler -v 1.16.2
|
19
23
|
cache: bundler
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
- 06-07-2018
|
2
|
+
|
3
|
+
- 0.2.0
|
4
|
+
- FEATURE: context#call to allow for cheaper invocation of functions
|
5
|
+
- FIX: rare memory leak when terminating a long running attached function
|
6
|
+
- FIX: rare segfault when terminating a long running attached function
|
7
|
+
- FIX: Reimplement Isolate#idle_notification using idle_notification_deadline, API remains the same @ignisf
|
8
|
+
- Account for changes in the upstream V8 API @ignisf
|
9
|
+
- Support for libv8 6.7
|
1
10
|
|
2
11
|
23-08-2017
|
3
12
|
|
data/README.md
CHANGED
@@ -10,6 +10,12 @@ It was created as an alternative to the excellent [therubyracer](https://github.
|
|
10
10
|
|
11
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
|
+
### A note about Ruby version Support
|
14
|
+
|
15
|
+
MiniRacer only supports non-EOL versions of Ruby. See [Ruby](https://www.ruby-lang.org/en/downloads) to see the list of non-EOL Rubies.
|
16
|
+
|
17
|
+
If you require support for older versions of Ruby install an older version of the gem.
|
18
|
+
|
13
19
|
## Features
|
14
20
|
|
15
21
|
### Simple eval for JavaScript
|
@@ -283,6 +289,22 @@ context.eval("a = 2")
|
|
283
289
|
# nothing works on the context from now on, its a shell waiting to be disposed
|
284
290
|
```
|
285
291
|
|
292
|
+
### Function call
|
293
|
+
|
294
|
+
This calls the function passed as first argument:
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
context = MiniRacer::Context.new
|
298
|
+
context.eval('function hello(name) { return "Hello, " + name + "!" }')
|
299
|
+
context.call('hello', 'George')
|
300
|
+
# "Hello, George!"
|
301
|
+
```
|
302
|
+
|
303
|
+
Performance is slightly better than running `eval('hello("George")')` since:
|
304
|
+
|
305
|
+
- compilation of eval'd string is avoided
|
306
|
+
- function arguments don't need to be converted to JSON
|
307
|
+
|
286
308
|
## Performance
|
287
309
|
|
288
310
|
The `bench` folder contains benchmark.
|
@@ -9,6 +9,7 @@ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
|
|
9
9
|
$CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/
|
10
10
|
$CPPFLAGS += " -std=c++0x"
|
11
11
|
$CPPFLAGS += " -fpermissive"
|
12
|
+
$CPPFLAGS += " -Wno-reserved-user-defined-literal" if RUBY_PLATFORM =~ /darwin/
|
12
13
|
|
13
14
|
$LDFLAGS.insert 0, " -stdlib=libstdc++ " if RUBY_PLATFORM =~ /darwin/
|
14
15
|
|
@@ -7,6 +7,7 @@
|
|
7
7
|
#include <pthread.h>
|
8
8
|
#include <unistd.h>
|
9
9
|
#include <mutex>
|
10
|
+
#include <atomic>
|
10
11
|
#include <math.h>
|
11
12
|
|
12
13
|
using namespace v8;
|
@@ -16,21 +17,77 @@ typedef struct {
|
|
16
17
|
int raw_size;
|
17
18
|
} SnapshotInfo;
|
18
19
|
|
19
|
-
|
20
|
+
class IsolateInfo {
|
21
|
+
public:
|
20
22
|
Isolate* isolate;
|
21
23
|
ArrayBuffer::Allocator* allocator;
|
22
24
|
StartupData* startup_data;
|
23
25
|
bool interrupted;
|
24
|
-
bool disposed;
|
25
26
|
pid_t pid;
|
27
|
+
VALUE mutex;
|
28
|
+
|
29
|
+
class Lock {
|
30
|
+
VALUE &mutex;
|
31
|
+
|
32
|
+
public:
|
33
|
+
Lock(VALUE &mutex) : mutex(mutex) {
|
34
|
+
rb_mutex_lock(mutex);
|
35
|
+
}
|
36
|
+
~Lock() {
|
37
|
+
rb_mutex_unlock(mutex);
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
|
42
|
+
IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
|
43
|
+
interrupted(false), pid(getpid()), refs_count(0) {
|
44
|
+
VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
|
45
|
+
mutex = rb_class_new_instance(0, nullptr, cMutex);
|
46
|
+
}
|
47
|
+
|
48
|
+
~IsolateInfo() {
|
49
|
+
void free_isolate(IsolateInfo*);
|
50
|
+
free_isolate(this);
|
51
|
+
}
|
52
|
+
|
53
|
+
void init(SnapshotInfo* snapshot_info = nullptr);
|
54
|
+
|
55
|
+
void mark() {
|
56
|
+
rb_gc_mark(mutex);
|
57
|
+
}
|
58
|
+
|
59
|
+
Lock createLock() {
|
60
|
+
Lock lock(mutex);
|
61
|
+
return lock;
|
62
|
+
}
|
63
|
+
|
64
|
+
void hold() {
|
65
|
+
refs_count++;
|
66
|
+
}
|
67
|
+
void release() {
|
68
|
+
if (--refs_count <= 0) {
|
69
|
+
delete this;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
static void* operator new(size_t size) {
|
74
|
+
return ruby_xmalloc(size);
|
75
|
+
}
|
26
76
|
|
77
|
+
static void operator delete(void *block) {
|
78
|
+
xfree(block);
|
79
|
+
}
|
80
|
+
private:
|
27
81
|
// how many references to this isolate exist
|
28
|
-
// we can't rely on Ruby's GC for this, because
|
29
|
-
//
|
30
|
-
//
|
31
|
-
//
|
32
|
-
|
33
|
-
|
82
|
+
// we can't rely on Ruby's GC for this, because Ruby could destroy the
|
83
|
+
// isolate before destroying the contexts that depend on them. We'd need to
|
84
|
+
// keep a list of linked contexts in the isolate to destroy those first when
|
85
|
+
// isolate destruction was requested. Keeping such a list would require
|
86
|
+
// notification from the context VALUEs when they are constructed and
|
87
|
+
// destroyed. With a ref count, those notifications are still needed, but
|
88
|
+
// we keep a simple int rather than a list of pointers.
|
89
|
+
std::atomic_int refs_count;
|
90
|
+
};
|
34
91
|
|
35
92
|
typedef struct {
|
36
93
|
IsolateInfo* isolate_info;
|
@@ -56,6 +113,16 @@ typedef struct {
|
|
56
113
|
size_t max_memory;
|
57
114
|
} EvalParams;
|
58
115
|
|
116
|
+
typedef struct {
|
117
|
+
ContextInfo *context_info;
|
118
|
+
char *function_name;
|
119
|
+
int argc;
|
120
|
+
bool error;
|
121
|
+
Local<Function> fun;
|
122
|
+
Local<Value> *argv;
|
123
|
+
EvalResult result;
|
124
|
+
} FunctionCall;
|
125
|
+
|
59
126
|
enum IsolateFlags {
|
60
127
|
IN_GVL,
|
61
128
|
DO_TERMINATE,
|
@@ -63,6 +130,10 @@ enum IsolateFlags {
|
|
63
130
|
MEM_SOFTLIMIT_REACHED,
|
64
131
|
};
|
65
132
|
|
133
|
+
static VALUE rb_cContext;
|
134
|
+
static VALUE rb_cSnapshot;
|
135
|
+
static VALUE rb_cIsolate;
|
136
|
+
|
66
137
|
static VALUE rb_eScriptTerminatedError;
|
67
138
|
static VALUE rb_eV8OutOfMemoryError;
|
68
139
|
static VALUE rb_eParseError;
|
@@ -81,6 +152,11 @@ static std::mutex platform_lock;
|
|
81
152
|
static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
|
82
153
|
bool platform_already_initialized = false;
|
83
154
|
|
155
|
+
if(TYPE(flag_as_str) != T_STRING) {
|
156
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
|
157
|
+
rb_obj_class(flag_as_str));
|
158
|
+
}
|
159
|
+
|
84
160
|
platform_lock.lock();
|
85
161
|
|
86
162
|
if (current_platform == NULL) {
|
@@ -126,7 +202,86 @@ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
|
|
126
202
|
|
127
203
|
if(used > softlimit) {
|
128
204
|
isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)true);
|
129
|
-
|
205
|
+
isolate->TerminateExecution();
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
// to be called with active lock and scope
|
210
|
+
static void prepare_result(MaybeLocal<Value> v8res,
|
211
|
+
TryCatch& trycatch,
|
212
|
+
Isolate* isolate,
|
213
|
+
Local<Context> context,
|
214
|
+
EvalResult& evalRes /* out */) {
|
215
|
+
|
216
|
+
// just don't touch .parsed
|
217
|
+
evalRes.terminated = false;
|
218
|
+
evalRes.json = false;
|
219
|
+
evalRes.value = nullptr;
|
220
|
+
evalRes.message = nullptr;
|
221
|
+
evalRes.backtrace = nullptr;
|
222
|
+
evalRes.executed = !v8res.IsEmpty();
|
223
|
+
|
224
|
+
if (evalRes.executed) {
|
225
|
+
// arrays and objects get converted to json
|
226
|
+
Local<Value> local_value = v8res.ToLocalChecked();
|
227
|
+
if ((local_value->IsObject() || local_value->IsArray()) &&
|
228
|
+
!local_value->IsDate() && !local_value->IsFunction()) {
|
229
|
+
Local<Object> JSON = context->Global()->Get(
|
230
|
+
String::NewFromUtf8(isolate, "JSON"))->ToObject();
|
231
|
+
|
232
|
+
Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
|
233
|
+
.As<Function>();
|
234
|
+
|
235
|
+
Local<Object> object = local_value->ToObject();
|
236
|
+
const unsigned argc = 1;
|
237
|
+
Local<Value> argv[argc] = { object };
|
238
|
+
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
|
239
|
+
|
240
|
+
if (json.IsEmpty()) {
|
241
|
+
evalRes.executed = false;
|
242
|
+
} else {
|
243
|
+
evalRes.json = true;
|
244
|
+
Persistent<Value>* persistent = new Persistent<Value>();
|
245
|
+
persistent->Reset(isolate, json.ToLocalChecked());
|
246
|
+
evalRes.value = persistent;
|
247
|
+
}
|
248
|
+
|
249
|
+
} else {
|
250
|
+
Persistent<Value>* persistent = new Persistent<Value>();
|
251
|
+
persistent->Reset(isolate, local_value);
|
252
|
+
evalRes.value = persistent;
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
if (!evalRes.executed || !evalRes.parsed) {
|
257
|
+
if (trycatch.HasCaught()) {
|
258
|
+
if (!trycatch.Exception()->IsNull()) {
|
259
|
+
evalRes.message = new Persistent<Value>();
|
260
|
+
Local<Message> message = trycatch.Message();
|
261
|
+
char buf[1000];
|
262
|
+
int len;
|
263
|
+
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
|
264
|
+
*String::Utf8Value(message->GetScriptResourceName()->ToString()),
|
265
|
+
message->GetLineNumber(),
|
266
|
+
message->GetStartColumn());
|
267
|
+
if ((size_t) len >= sizeof(buf)) {
|
268
|
+
len = sizeof(buf) - 1;
|
269
|
+
buf[len] = '\0';
|
270
|
+
}
|
271
|
+
|
272
|
+
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
|
273
|
+
evalRes.message->Reset(isolate, v8_message);
|
274
|
+
} else if(trycatch.HasTerminated()) {
|
275
|
+
evalRes.terminated = true;
|
276
|
+
evalRes.message = new Persistent<Value>();
|
277
|
+
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
278
|
+
evalRes.message->Reset(isolate, tmp);
|
279
|
+
}
|
280
|
+
if (!trycatch.StackTrace().IsEmpty()) {
|
281
|
+
evalRes.backtrace = new Persistent<Value>();
|
282
|
+
evalRes.backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
|
283
|
+
}
|
284
|
+
}
|
130
285
|
}
|
131
286
|
}
|
132
287
|
|
@@ -171,90 +326,30 @@ nogvl_context_eval(void* arg) {
|
|
171
326
|
result->json = false;
|
172
327
|
result->value = NULL;
|
173
328
|
|
329
|
+
MaybeLocal<Value> maybe_value;
|
174
330
|
if (!result->parsed) {
|
175
331
|
result->message = new Persistent<Value>();
|
176
332
|
result->message->Reset(isolate, trycatch.Exception());
|
177
333
|
} else {
|
334
|
+
// parsing successful
|
335
|
+
if (eval_params->max_memory > 0) {
|
336
|
+
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
|
337
|
+
isolate->AddGCEpilogueCallback(gc_callback);
|
338
|
+
}
|
178
339
|
|
179
|
-
|
180
|
-
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
|
181
|
-
isolate->AddGCEpilogueCallback(gc_callback);
|
182
|
-
}
|
183
|
-
|
184
|
-
MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
185
|
-
|
186
|
-
result->executed = !maybe_value.IsEmpty();
|
187
|
-
|
188
|
-
if (result->executed) {
|
189
|
-
|
190
|
-
// arrays and objects get converted to json
|
191
|
-
Local<Value> local_value = maybe_value.ToLocalChecked();
|
192
|
-
if ((local_value->IsObject() || local_value->IsArray()) &&
|
193
|
-
!local_value->IsDate() && !local_value->IsFunction()) {
|
194
|
-
Local<Object> JSON = context->Global()->Get(
|
195
|
-
String::NewFromUtf8(isolate, "JSON"))->ToObject();
|
196
|
-
|
197
|
-
Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
|
198
|
-
.As<Function>();
|
199
|
-
|
200
|
-
Local<Object> object = local_value->ToObject();
|
201
|
-
const unsigned argc = 1;
|
202
|
-
Local<Value> argv[argc] = { object };
|
203
|
-
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
|
204
|
-
|
205
|
-
if (json.IsEmpty()) {
|
206
|
-
result->executed = false;
|
207
|
-
} else {
|
208
|
-
result->json = true;
|
209
|
-
Persistent<Value>* persistent = new Persistent<Value>();
|
210
|
-
persistent->Reset(isolate, json.ToLocalChecked());
|
211
|
-
result->value = persistent;
|
212
|
-
}
|
213
|
-
|
214
|
-
} else {
|
215
|
-
Persistent<Value>* persistent = new Persistent<Value>();
|
216
|
-
persistent->Reset(isolate, local_value);
|
217
|
-
result->value = persistent;
|
218
|
-
}
|
219
|
-
}
|
340
|
+
maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
220
341
|
}
|
221
342
|
|
222
|
-
|
223
|
-
if (trycatch.HasCaught()) {
|
224
|
-
if (!trycatch.Exception()->IsNull()) {
|
225
|
-
result->message = new Persistent<Value>();
|
226
|
-
Local<Message> message = trycatch.Message();
|
227
|
-
char buf[1000];
|
228
|
-
int len;
|
229
|
-
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
|
230
|
-
*String::Utf8Value(message->GetScriptResourceName()->ToString()),
|
231
|
-
message->GetLineNumber(),
|
232
|
-
message->GetStartColumn());
|
233
|
-
|
234
|
-
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
|
235
|
-
result->message->Reset(isolate, v8_message);
|
236
|
-
} else if(trycatch.HasTerminated()) {
|
237
|
-
|
238
|
-
|
239
|
-
result->terminated = true;
|
240
|
-
result->message = new Persistent<Value>();
|
241
|
-
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
242
|
-
result->message->Reset(isolate, tmp);
|
243
|
-
}
|
244
|
-
if (!trycatch.StackTrace().IsEmpty()) {
|
245
|
-
result->backtrace = new Persistent<Value>();
|
246
|
-
result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
|
247
|
-
}
|
248
|
-
}
|
249
|
-
}
|
343
|
+
prepare_result(maybe_value, trycatch, isolate, context, *result);
|
250
344
|
|
251
345
|
isolate->SetData(IN_GVL, (void*)true);
|
252
346
|
|
253
|
-
|
254
347
|
return NULL;
|
255
348
|
}
|
256
349
|
|
257
|
-
|
350
|
+
// assumes isolate locking is in place
|
351
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
|
352
|
+
Local<Value> value) {
|
258
353
|
|
259
354
|
Isolate::Scope isolate_scope(isolate);
|
260
355
|
HandleScope scope(isolate);
|
@@ -284,7 +379,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
284
379
|
Local<Array> arr = Local<Array>::Cast(value);
|
285
380
|
for(uint32_t i=0; i < arr->Length(); i++) {
|
286
381
|
Local<Value> element = arr->Get(i);
|
287
|
-
VALUE rb_elem = convert_v8_to_ruby(isolate, element);
|
382
|
+
VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
|
288
383
|
if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
289
384
|
return rb_elem;
|
290
385
|
}
|
@@ -308,16 +403,15 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
308
403
|
if (value->IsObject()) {
|
309
404
|
VALUE rb_hash = rb_hash_new();
|
310
405
|
TryCatch trycatch(isolate);
|
311
|
-
Local<Context> context = Context::New(isolate);
|
312
406
|
|
313
407
|
Local<Object> object = value->ToObject();
|
314
|
-
|
408
|
+
auto maybe_props = object->GetOwnPropertyNames(context);
|
315
409
|
if (!maybe_props.IsEmpty()) {
|
316
410
|
Local<Array> props = maybe_props.ToLocalChecked();
|
317
411
|
for(uint32_t i=0; i < props->Length(); i++) {
|
318
412
|
Local<Value> key = props->Get(i);
|
319
|
-
VALUE rb_key = convert_v8_to_ruby(isolate, key);
|
320
|
-
Local<Value>
|
413
|
+
VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
|
414
|
+
Local<Value> prop_value = object->Get(key);
|
321
415
|
// this may have failed due to Get raising
|
322
416
|
|
323
417
|
if (trycatch.HasCaught()) {
|
@@ -326,7 +420,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
326
420
|
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
327
421
|
}
|
328
422
|
|
329
|
-
VALUE rb_value = convert_v8_to_ruby(isolate,
|
423
|
+
VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
|
330
424
|
rb_hash_aset(rb_hash, rb_key, rb_value);
|
331
425
|
}
|
332
426
|
}
|
@@ -337,7 +431,25 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
337
431
|
return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
338
432
|
}
|
339
433
|
|
340
|
-
static
|
434
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
435
|
+
const Persistent<Context>& context,
|
436
|
+
Local<Value> value) {
|
437
|
+
HandleScope scope(isolate);
|
438
|
+
return convert_v8_to_ruby(isolate,
|
439
|
+
Local<Context>::New(isolate, context),
|
440
|
+
value);
|
441
|
+
}
|
442
|
+
|
443
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
444
|
+
const Persistent<Context>& context,
|
445
|
+
const Persistent<Value>& value) {
|
446
|
+
HandleScope scope(isolate);
|
447
|
+
return convert_v8_to_ruby(isolate,
|
448
|
+
Local<Context>::New(isolate, context),
|
449
|
+
Local<Value>::New(isolate, value));
|
450
|
+
}
|
451
|
+
|
452
|
+
static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
341
453
|
EscapableHandleScope scope(isolate);
|
342
454
|
|
343
455
|
Local<Array> array;
|
@@ -431,6 +543,11 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
|
|
431
543
|
SnapshotInfo* snapshot_info;
|
432
544
|
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
433
545
|
|
546
|
+
if(TYPE(str) != T_STRING) {
|
547
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
548
|
+
rb_obj_class(str));
|
549
|
+
}
|
550
|
+
|
434
551
|
init_v8();
|
435
552
|
|
436
553
|
StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
|
@@ -445,10 +562,15 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
|
|
445
562
|
return Qnil;
|
446
563
|
}
|
447
564
|
|
448
|
-
static VALUE
|
565
|
+
static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
|
449
566
|
SnapshotInfo* snapshot_info;
|
450
567
|
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
451
568
|
|
569
|
+
if(TYPE(str) != T_STRING) {
|
570
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
571
|
+
rb_obj_class(str));
|
572
|
+
}
|
573
|
+
|
452
574
|
init_v8();
|
453
575
|
|
454
576
|
StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
|
@@ -466,27 +588,16 @@ static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
|
|
466
588
|
return self;
|
467
589
|
}
|
468
590
|
|
469
|
-
|
470
|
-
|
471
|
-
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
472
|
-
|
473
|
-
init_v8();
|
474
|
-
|
475
|
-
isolate_info->allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
476
|
-
isolate_info->interrupted = false;
|
477
|
-
isolate_info->refs_count = 1;
|
591
|
+
void IsolateInfo::init(SnapshotInfo* snapshot_info) {
|
592
|
+
allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
478
593
|
|
479
594
|
Isolate::CreateParams create_params;
|
480
|
-
create_params.array_buffer_allocator =
|
481
|
-
|
482
|
-
StartupData* startup_data = NULL;
|
483
|
-
if (!NIL_P(snapshot)) {
|
484
|
-
SnapshotInfo* snapshot_info;
|
485
|
-
Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
|
595
|
+
create_params.array_buffer_allocator = allocator;
|
486
596
|
|
597
|
+
if (snapshot_info) {
|
487
598
|
int raw_size = snapshot_info->raw_size;
|
488
599
|
char* data = new char[raw_size];
|
489
|
-
memcpy(data, snapshot_info->data,
|
600
|
+
memcpy(data, snapshot_info->data, raw_size);
|
490
601
|
|
491
602
|
startup_data = new StartupData;
|
492
603
|
startup_data->data = data;
|
@@ -495,8 +606,22 @@ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
|
|
495
606
|
create_params.snapshot_blob = startup_data;
|
496
607
|
}
|
497
608
|
|
498
|
-
|
499
|
-
|
609
|
+
isolate = Isolate::New(create_params);
|
610
|
+
}
|
611
|
+
|
612
|
+
static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
|
613
|
+
IsolateInfo* isolate_info;
|
614
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
615
|
+
|
616
|
+
init_v8();
|
617
|
+
|
618
|
+
SnapshotInfo* snapshot_info = nullptr;
|
619
|
+
if (!NIL_P(snapshot)) {
|
620
|
+
Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
|
621
|
+
}
|
622
|
+
|
623
|
+
isolate_info->init(snapshot_info);
|
624
|
+
isolate_info->hold();
|
500
625
|
|
501
626
|
return Qnil;
|
502
627
|
}
|
@@ -505,23 +630,40 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
|
|
505
630
|
IsolateInfo* isolate_info;
|
506
631
|
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
507
632
|
|
508
|
-
|
633
|
+
if (current_platform == NULL) return Qfalse;
|
634
|
+
|
635
|
+
double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
|
636
|
+
double now = current_platform->MonotonicallyIncreasingTime();
|
637
|
+
return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
|
509
638
|
}
|
510
639
|
|
511
|
-
static VALUE
|
640
|
+
static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
|
512
641
|
ContextInfo* context_info;
|
513
642
|
Data_Get_Struct(self, ContextInfo, context_info);
|
514
643
|
|
515
644
|
init_v8();
|
516
645
|
|
517
646
|
IsolateInfo* isolate_info;
|
518
|
-
|
647
|
+
|
648
|
+
if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
|
649
|
+
isolate_info = new IsolateInfo();
|
650
|
+
|
651
|
+
SnapshotInfo *snapshot_info = nullptr;
|
652
|
+
if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
|
653
|
+
Data_Get_Struct(snap, SnapshotInfo, snapshot_info);
|
654
|
+
}
|
655
|
+
isolate_info->init(snapshot_info);
|
656
|
+
} else { // given isolate or snapshot
|
657
|
+
Data_Get_Struct(isolate, IsolateInfo, isolate_info);
|
658
|
+
}
|
519
659
|
|
520
660
|
context_info->isolate_info = isolate_info;
|
521
|
-
isolate_info->
|
661
|
+
isolate_info->hold();
|
522
662
|
|
523
663
|
{
|
524
|
-
|
664
|
+
// the ruby lock is needed if this isn't a new isolate
|
665
|
+
IsolateInfo::Lock ruby_lock(isolate_info->mutex);
|
666
|
+
Locker lock(isolate_info->isolate);
|
525
667
|
Isolate::Scope isolate_scope(isolate_info->isolate);
|
526
668
|
HandleScope handle_scope(isolate_info->isolate);
|
527
669
|
|
@@ -539,19 +681,116 @@ static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
|
|
539
681
|
return Qnil;
|
540
682
|
}
|
541
683
|
|
684
|
+
static VALUE convert_result_to_ruby(VALUE self /* context */,
|
685
|
+
EvalResult& result) {
|
686
|
+
ContextInfo *context_info;
|
687
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
688
|
+
|
689
|
+
Isolate *isolate = context_info->isolate_info->isolate;
|
690
|
+
Persistent<Context> *p_ctx = context_info->context;
|
691
|
+
|
692
|
+
VALUE message = Qnil;
|
693
|
+
VALUE backtrace = Qnil;
|
694
|
+
{
|
695
|
+
Locker lock(isolate);
|
696
|
+
if (result.message) {
|
697
|
+
message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
|
698
|
+
result.message->Reset();
|
699
|
+
delete result.message;
|
700
|
+
result.message = nullptr;
|
701
|
+
}
|
702
|
+
|
703
|
+
if (result.backtrace) {
|
704
|
+
backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
|
705
|
+
result.backtrace->Reset();
|
706
|
+
delete result.backtrace;
|
707
|
+
}
|
708
|
+
}
|
709
|
+
|
710
|
+
// NOTE: this is very important, we can not do an rb_raise from within
|
711
|
+
// a v8 scope, if we do the scope is never cleaned up properly and we leak
|
712
|
+
if (!result.parsed) {
|
713
|
+
if(TYPE(message) == T_STRING) {
|
714
|
+
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
|
715
|
+
} else {
|
716
|
+
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
if (!result.executed) {
|
721
|
+
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
722
|
+
if (ruby_exception == Qnil) {
|
723
|
+
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
|
724
|
+
// If we were terminated or have the memory softlimit flag set
|
725
|
+
if (result.terminated || mem_softlimit_reached) {
|
726
|
+
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
|
727
|
+
} else {
|
728
|
+
ruby_exception = rb_eScriptRuntimeError;
|
729
|
+
}
|
730
|
+
|
731
|
+
// exception report about what happened
|
732
|
+
if (TYPE(backtrace) == T_STRING) {
|
733
|
+
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
|
734
|
+
} else if(TYPE(message) == T_STRING) {
|
735
|
+
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
|
736
|
+
} else {
|
737
|
+
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
|
738
|
+
}
|
739
|
+
} else {
|
740
|
+
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
|
741
|
+
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
|
742
|
+
}
|
743
|
+
}
|
744
|
+
|
745
|
+
VALUE ret = Qnil;
|
746
|
+
|
747
|
+
// New scope for return value
|
748
|
+
{
|
749
|
+
Locker lock(isolate);
|
750
|
+
Isolate::Scope isolate_scope(isolate);
|
751
|
+
HandleScope handle_scope(isolate);
|
752
|
+
|
753
|
+
Local<Value> tmp = Local<Value>::New(isolate, *result.value);
|
754
|
+
|
755
|
+
if (result.json) {
|
756
|
+
Local<String> rstr = tmp->ToString();
|
757
|
+
VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
758
|
+
ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
|
759
|
+
} else {
|
760
|
+
ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
|
761
|
+
}
|
762
|
+
|
763
|
+
result.value->Reset();
|
764
|
+
delete result.value;
|
765
|
+
}
|
766
|
+
|
767
|
+
if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
768
|
+
// TODO try to recover stack trace from the conversion error
|
769
|
+
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
770
|
+
}
|
771
|
+
|
772
|
+
|
773
|
+
return ret;
|
774
|
+
}
|
775
|
+
|
542
776
|
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
543
777
|
|
544
778
|
EvalParams eval_params;
|
545
779
|
EvalResult eval_result;
|
546
780
|
ContextInfo* context_info;
|
547
|
-
VALUE result;
|
548
|
-
|
549
|
-
VALUE message = Qnil;
|
550
|
-
VALUE backtrace = Qnil;
|
551
781
|
|
552
782
|
Data_Get_Struct(self, ContextInfo, context_info);
|
553
783
|
Isolate* isolate = context_info->isolate_info->isolate;
|
554
784
|
|
785
|
+
if(TYPE(str) != T_STRING) {
|
786
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
787
|
+
rb_obj_class(str));
|
788
|
+
}
|
789
|
+
if(filename != Qnil && TYPE(filename) != T_STRING) {
|
790
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
|
791
|
+
rb_obj_class(filename));
|
792
|
+
}
|
793
|
+
|
555
794
|
{
|
556
795
|
Locker lock(isolate);
|
557
796
|
Isolate::Scope isolate_scope(isolate);
|
@@ -589,90 +828,15 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
|
589
828
|
eval_result.backtrace = NULL;
|
590
829
|
|
591
830
|
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
|
592
|
-
|
593
|
-
if (eval_result.message != NULL) {
|
594
|
-
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
|
595
|
-
message = convert_v8_to_ruby(isolate, tmp);
|
596
|
-
eval_result.message->Reset();
|
597
|
-
delete eval_result.message;
|
598
|
-
}
|
599
|
-
|
600
|
-
if (eval_result.backtrace != NULL) {
|
601
|
-
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
|
602
|
-
backtrace = convert_v8_to_ruby(isolate, tmp);
|
603
|
-
eval_result.backtrace->Reset();
|
604
|
-
delete eval_result.backtrace;
|
605
|
-
}
|
606
|
-
}
|
607
|
-
|
608
|
-
// NOTE: this is very important, we can not do an rb_raise from within
|
609
|
-
// a v8 scope, if we do the scope is never cleaned up properly and we leak
|
610
|
-
if (!eval_result.parsed) {
|
611
|
-
if(TYPE(message) == T_STRING) {
|
612
|
-
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
|
613
|
-
} else {
|
614
|
-
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
|
615
|
-
}
|
616
|
-
}
|
617
|
-
|
618
|
-
if (!eval_result.executed) {
|
619
|
-
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
620
|
-
if (ruby_exception == Qnil) {
|
621
|
-
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
|
622
|
-
// If we were terminated or have the memory softlimit flag set
|
623
|
-
if(eval_result.terminated || mem_softlimit_reached) {
|
624
|
-
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
|
625
|
-
} else {
|
626
|
-
ruby_exception = rb_eScriptRuntimeError;
|
627
|
-
}
|
628
|
-
|
629
|
-
// exception report about what happened
|
630
|
-
if(TYPE(backtrace) == T_STRING) {
|
631
|
-
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
|
632
|
-
} else if(TYPE(message) == T_STRING) {
|
633
|
-
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
|
634
|
-
} else {
|
635
|
-
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
|
636
|
-
}
|
637
|
-
} else {
|
638
|
-
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
|
639
|
-
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
|
640
|
-
}
|
641
|
-
}
|
642
|
-
|
643
|
-
// New scope for return value
|
644
|
-
{
|
645
|
-
Locker lock(isolate);
|
646
|
-
Isolate::Scope isolate_scope(isolate);
|
647
|
-
HandleScope handle_scope(isolate);
|
648
|
-
|
649
|
-
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
|
650
|
-
|
651
|
-
if (eval_result.json) {
|
652
|
-
Local<String> rstr = tmp->ToString();
|
653
|
-
VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
654
|
-
result = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
|
655
|
-
} else {
|
656
|
-
result = convert_v8_to_ruby(isolate, tmp);
|
657
|
-
}
|
658
|
-
|
659
|
-
eval_result.value->Reset();
|
660
|
-
delete eval_result.value;
|
661
|
-
}
|
662
|
-
|
663
|
-
if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
664
|
-
// TODO try to recover stack trace from the conversion error
|
665
|
-
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
666
831
|
}
|
667
832
|
|
668
|
-
|
669
|
-
return result;
|
833
|
+
return convert_result_to_ruby(self, eval_result);
|
670
834
|
}
|
671
835
|
|
672
836
|
typedef struct {
|
673
837
|
VALUE callback;
|
674
838
|
int length;
|
675
|
-
VALUE
|
839
|
+
VALUE ruby_args;
|
676
840
|
bool failed;
|
677
841
|
} protected_callback_data;
|
678
842
|
|
@@ -682,7 +846,9 @@ VALUE protected_callback(VALUE rdata) {
|
|
682
846
|
VALUE result;
|
683
847
|
|
684
848
|
if (data->length > 0) {
|
685
|
-
result = rb_funcall2(data->callback, rb_intern("call"), data->length,
|
849
|
+
result = rb_funcall2(data->callback, rb_intern("call"), data->length,
|
850
|
+
RARRAY_PTR(data->ruby_args));
|
851
|
+
RB_GC_GUARD(data->ruby_args);
|
686
852
|
} else {
|
687
853
|
result = rb_funcall(data->callback, rb_intern("call"), 0);
|
688
854
|
}
|
@@ -700,28 +866,36 @@ void*
|
|
700
866
|
gvl_ruby_callback(void* data) {
|
701
867
|
|
702
868
|
FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
|
703
|
-
VALUE
|
869
|
+
VALUE ruby_args = Qnil;
|
704
870
|
int length = args->Length();
|
705
871
|
VALUE callback;
|
706
872
|
VALUE result;
|
707
873
|
VALUE self;
|
708
|
-
|
874
|
+
VALUE parent;
|
709
875
|
{
|
710
|
-
|
711
|
-
|
876
|
+
HandleScope scope(args->GetIsolate());
|
877
|
+
Local<External> external = Local<External>::Cast(args->Data());
|
712
878
|
|
713
|
-
|
714
|
-
|
715
|
-
|
879
|
+
self = *(VALUE*)(external->Value());
|
880
|
+
callback = rb_iv_get(self, "@callback");
|
881
|
+
|
882
|
+
parent = rb_iv_get(self, "@parent");
|
883
|
+
if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
|
884
|
+
return NULL;
|
885
|
+
}
|
886
|
+
|
887
|
+
ContextInfo* context_info;
|
888
|
+
Data_Get_Struct(parent, ContextInfo, context_info);
|
716
889
|
|
717
890
|
if (length > 0) {
|
718
|
-
ruby_args =
|
891
|
+
ruby_args = rb_ary_tmp_new(length);
|
719
892
|
}
|
720
893
|
|
721
|
-
|
722
894
|
for (int i = 0; i < length; i++) {
|
723
895
|
Local<Value> value = ((*args)[i]).As<Value>();
|
724
|
-
|
896
|
+
VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
|
897
|
+
*context_info->context, value);
|
898
|
+
rb_ary_push(ruby_args, tmp);
|
725
899
|
}
|
726
900
|
}
|
727
901
|
|
@@ -729,12 +903,16 @@ gvl_ruby_callback(void* data) {
|
|
729
903
|
protected_callback_data callback_data;
|
730
904
|
callback_data.length = length;
|
731
905
|
callback_data.callback = callback;
|
732
|
-
callback_data.
|
906
|
+
callback_data.ruby_args = ruby_args;
|
733
907
|
callback_data.failed = false;
|
734
908
|
|
735
909
|
if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
|
736
910
|
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
|
737
|
-
|
911
|
+
args->GetIsolate()->TerminateExecution();
|
912
|
+
if (length > 0) {
|
913
|
+
rb_ary_clear(ruby_args);
|
914
|
+
rb_gc_force_recycle(ruby_args);
|
915
|
+
}
|
738
916
|
return NULL;
|
739
917
|
}
|
740
918
|
|
@@ -742,7 +920,6 @@ gvl_ruby_callback(void* data) {
|
|
742
920
|
(VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
|
743
921
|
|
744
922
|
if(callback_data.failed) {
|
745
|
-
VALUE parent = rb_iv_get(self, "@parent");
|
746
923
|
rb_iv_set(parent, "@current_exception", result);
|
747
924
|
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
|
748
925
|
}
|
@@ -753,27 +930,27 @@ gvl_ruby_callback(void* data) {
|
|
753
930
|
}
|
754
931
|
|
755
932
|
if (length > 0) {
|
756
|
-
|
933
|
+
rb_ary_clear(ruby_args);
|
934
|
+
rb_gc_force_recycle(ruby_args);
|
757
935
|
}
|
758
936
|
|
759
937
|
if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
|
760
938
|
Isolate* isolate = args->GetIsolate();
|
761
|
-
|
939
|
+
isolate->TerminateExecution();
|
762
940
|
}
|
763
941
|
|
764
942
|
return NULL;
|
765
943
|
}
|
766
944
|
|
767
945
|
static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
|
768
|
-
|
769
946
|
bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL);
|
770
947
|
|
771
948
|
if(has_gvl) {
|
772
949
|
gvl_ruby_callback((void*)&args);
|
773
950
|
} else {
|
774
|
-
|
951
|
+
args.GetIsolate()->SetData(IN_GVL, (void*)true);
|
775
952
|
rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
|
776
|
-
|
953
|
+
args.GetIsolate()->SetData(IN_GVL, (void*)false);
|
777
954
|
}
|
778
955
|
}
|
779
956
|
|
@@ -838,16 +1015,27 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
|
|
838
1015
|
|
839
1016
|
// always raise out of V8 context
|
840
1017
|
if (parse_error) {
|
841
|
-
rb_raise(rb_eParseError, "Invalid object %
|
1018
|
+
rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
|
842
1019
|
}
|
843
1020
|
|
844
1021
|
if (attach_error) {
|
845
|
-
rb_raise(rb_eParseError, "Was expecting %
|
1022
|
+
rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
|
846
1023
|
}
|
847
1024
|
|
848
1025
|
return Qnil;
|
849
1026
|
}
|
850
1027
|
|
1028
|
+
static VALUE rb_context_isolate_mutex(VALUE self) {
|
1029
|
+
ContextInfo* context_info;
|
1030
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1031
|
+
|
1032
|
+
if (!context_info->isolate_info) {
|
1033
|
+
rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
|
1034
|
+
}
|
1035
|
+
|
1036
|
+
return context_info->isolate_info->mutex;
|
1037
|
+
}
|
1038
|
+
|
851
1039
|
void free_isolate(IsolateInfo* isolate_info) {
|
852
1040
|
|
853
1041
|
if (isolate_info->isolate) {
|
@@ -874,26 +1062,11 @@ void free_isolate(IsolateInfo* isolate_info) {
|
|
874
1062
|
}
|
875
1063
|
|
876
1064
|
delete isolate_info->allocator;
|
877
|
-
isolate_info->disposed = true;
|
878
1065
|
}
|
879
1066
|
|
880
|
-
|
881
|
-
|
882
|
-
// still need it
|
883
|
-
//
|
884
|
-
// there is a sequence issue here where Ruby may call the deallocator on the
|
885
|
-
// context object after it calles the dallocator on the isolate
|
886
|
-
if (isolate_info->refs_count != 0 || isolate_info->disposed) {
|
887
|
-
return;
|
888
|
-
}
|
889
|
-
|
890
|
-
free_isolate(isolate_info);
|
891
|
-
}
|
892
|
-
|
893
|
-
|
894
|
-
void free_context(void* data) {
|
1067
|
+
// destroys everything except freeing the ContextInfo struct (see deallocate())
|
1068
|
+
static void free_context(ContextInfo* context_info) {
|
895
1069
|
|
896
|
-
ContextInfo* context_info = (ContextInfo*)data;
|
897
1070
|
IsolateInfo* isolate_info = context_info->isolate_info;
|
898
1071
|
|
899
1072
|
if (context_info->context && isolate_info && isolate_info->isolate) {
|
@@ -903,46 +1076,39 @@ void free_context(void* data) {
|
|
903
1076
|
delete context_info->context;
|
904
1077
|
context_info->context = NULL;
|
905
1078
|
}
|
906
|
-
}
|
907
|
-
|
908
|
-
void deallocate_context(void* data) {
|
909
|
-
|
910
|
-
ContextInfo* context_info = (ContextInfo*)data;
|
911
|
-
IsolateInfo* isolate_info = context_info->isolate_info;
|
912
|
-
|
913
|
-
free_context(data);
|
914
1079
|
|
915
1080
|
if (isolate_info) {
|
916
|
-
|
917
|
-
|
1081
|
+
isolate_info->release();
|
1082
|
+
context_info->isolate_info = NULL;
|
918
1083
|
}
|
919
1084
|
}
|
920
1085
|
|
921
|
-
void deallocate_isolate(void* data) {
|
1086
|
+
static void deallocate_isolate(void* data) {
|
922
1087
|
|
923
1088
|
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
924
1089
|
|
925
|
-
isolate_info->
|
926
|
-
|
1090
|
+
isolate_info->release();
|
1091
|
+
}
|
927
1092
|
|
928
|
-
|
929
|
-
|
930
|
-
|
1093
|
+
static void mark_isolate(void* data) {
|
1094
|
+
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
1095
|
+
isolate_info->mark();
|
931
1096
|
}
|
932
1097
|
|
933
1098
|
void deallocate(void* data) {
|
934
1099
|
ContextInfo* context_info = (ContextInfo*)data;
|
935
|
-
IsolateInfo* isolate_info = context_info->isolate_info;
|
936
1100
|
|
937
|
-
|
938
|
-
|
939
|
-
if (isolate_info && isolate_info->refs_count == 0) {
|
940
|
-
xfree(isolate_info);
|
941
|
-
}
|
1101
|
+
free_context(context_info);
|
942
1102
|
|
943
1103
|
xfree(data);
|
944
1104
|
}
|
945
1105
|
|
1106
|
+
static void mark_context(void* data) {
|
1107
|
+
ContextInfo* context_info = (ContextInfo*)data;
|
1108
|
+
if (context_info->isolate_info) {
|
1109
|
+
context_info->isolate_info->mark();
|
1110
|
+
}
|
1111
|
+
}
|
946
1112
|
|
947
1113
|
void deallocate_external_function(void * data) {
|
948
1114
|
xfree(data);
|
@@ -964,7 +1130,7 @@ VALUE allocate(VALUE klass) {
|
|
964
1130
|
context_info->isolate_info = NULL;
|
965
1131
|
context_info->context = NULL;
|
966
1132
|
|
967
|
-
return Data_Wrap_Struct(klass,
|
1133
|
+
return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
|
968
1134
|
}
|
969
1135
|
|
970
1136
|
VALUE allocate_snapshot(VALUE klass) {
|
@@ -976,17 +1142,9 @@ VALUE allocate_snapshot(VALUE klass) {
|
|
976
1142
|
}
|
977
1143
|
|
978
1144
|
VALUE allocate_isolate(VALUE klass) {
|
979
|
-
IsolateInfo* isolate_info =
|
980
|
-
|
981
|
-
isolate_info->isolate = NULL;
|
982
|
-
isolate_info->allocator = NULL;
|
983
|
-
isolate_info->startup_data = NULL;
|
984
|
-
isolate_info->interrupted = false;
|
985
|
-
isolate_info->refs_count = 0;
|
986
|
-
isolate_info->pid = getpid();
|
987
|
-
isolate_info->disposed = false;
|
1145
|
+
IsolateInfo* isolate_info = new IsolateInfo();
|
988
1146
|
|
989
|
-
return Data_Wrap_Struct(klass,
|
1147
|
+
return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
|
990
1148
|
}
|
991
1149
|
|
992
1150
|
static VALUE
|
@@ -1033,7 +1191,7 @@ rb_context_stop(VALUE self) {
|
|
1033
1191
|
// flag for termination
|
1034
1192
|
isolate->SetData(DO_TERMINATE, (void*)true);
|
1035
1193
|
|
1036
|
-
|
1194
|
+
isolate->TerminateExecution();
|
1037
1195
|
rb_funcall(self, rb_intern("stop_attached"), 0);
|
1038
1196
|
|
1039
1197
|
return Qnil;
|
@@ -1047,22 +1205,148 @@ rb_context_dispose(VALUE self) {
|
|
1047
1205
|
|
1048
1206
|
free_context(context_info);
|
1049
1207
|
|
1050
|
-
if (context_info->isolate_info && context_info->isolate_info->refs_count == 2) {
|
1051
|
-
// special case, we only have isolate + context so we can burn the
|
1052
|
-
// isolate as well
|
1053
|
-
free_isolate(context_info->isolate_info);
|
1054
|
-
}
|
1055
1208
|
return Qnil;
|
1056
1209
|
}
|
1057
1210
|
|
1211
|
+
static void*
|
1212
|
+
nogvl_context_call(void *args) {
|
1213
|
+
|
1214
|
+
FunctionCall *call = (FunctionCall *) args;
|
1215
|
+
if (!call) {
|
1216
|
+
return NULL;
|
1217
|
+
}
|
1218
|
+
Isolate* isolate = call->context_info->isolate_info->isolate;
|
1219
|
+
|
1220
|
+
// in gvl flag
|
1221
|
+
isolate->SetData(IN_GVL, (void*)false);
|
1222
|
+
// terminate ASAP
|
1223
|
+
isolate->SetData(DO_TERMINATE, (void*)false);
|
1224
|
+
|
1225
|
+
Isolate::Scope isolate_scope(isolate);
|
1226
|
+
EscapableHandleScope handle_scope(isolate);
|
1227
|
+
TryCatch trycatch(isolate);
|
1228
|
+
|
1229
|
+
Local<Context> context = call->context_info->context->Get(isolate);
|
1230
|
+
Context::Scope context_scope(context);
|
1231
|
+
|
1232
|
+
Local<Function> fun = call->fun;
|
1233
|
+
|
1234
|
+
EvalResult& eval_res = call->result;
|
1235
|
+
eval_res.parsed = true;
|
1236
|
+
|
1237
|
+
MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
|
1238
|
+
prepare_result(res, trycatch, isolate, context, eval_res);
|
1239
|
+
|
1240
|
+
isolate->SetData(IN_GVL, (void*)true);
|
1241
|
+
|
1242
|
+
return NULL;
|
1243
|
+
}
|
1244
|
+
|
1245
|
+
static void unblock_function(void *args) {
|
1246
|
+
FunctionCall *call = (FunctionCall *) args;
|
1247
|
+
call->context_info->isolate_info->interrupted = true;
|
1248
|
+
}
|
1249
|
+
|
1250
|
+
static VALUE
|
1251
|
+
rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
|
1252
|
+
ContextInfo* context_info;
|
1253
|
+
FunctionCall call;
|
1254
|
+
VALUE *call_argv = NULL;
|
1255
|
+
|
1256
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1257
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
1258
|
+
|
1259
|
+
if (argc < 1) {
|
1260
|
+
rb_raise(rb_eArgError, "need at least one argument %d", argc);
|
1261
|
+
}
|
1262
|
+
|
1263
|
+
VALUE function_name = argv[0];
|
1264
|
+
if (TYPE(function_name) != T_STRING) {
|
1265
|
+
rb_raise(rb_eTypeError, "first argument should be a String");
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
char *fname = RSTRING_PTR(function_name);
|
1269
|
+
if (!fname) {
|
1270
|
+
return Qnil;
|
1271
|
+
}
|
1272
|
+
|
1273
|
+
call.context_info = context_info;
|
1274
|
+
call.error = false;
|
1275
|
+
call.function_name = fname;
|
1276
|
+
call.argc = argc - 1;
|
1277
|
+
call.argv = NULL;
|
1278
|
+
if (call.argc > 0) {
|
1279
|
+
// skip first argument which is the function name
|
1280
|
+
call_argv = argv + 1;
|
1281
|
+
}
|
1282
|
+
|
1283
|
+
bool missingFunction = false;
|
1284
|
+
|
1285
|
+
{
|
1286
|
+
Locker lock(isolate);
|
1287
|
+
Isolate::Scope isolate_scope(isolate);
|
1288
|
+
HandleScope handle_scope(isolate);
|
1289
|
+
|
1290
|
+
Local<Context> context = context_info->context->Get(isolate);
|
1291
|
+
Context::Scope context_scope(context);
|
1292
|
+
|
1293
|
+
// examples of such usage can be found in
|
1294
|
+
// https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
|
1295
|
+
Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
|
1296
|
+
MaybeLocal<v8::Value> val = context->Global()->Get(fname);
|
1297
|
+
|
1298
|
+
if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
|
1299
|
+
missingFunction = true;
|
1300
|
+
} else {
|
1301
|
+
|
1302
|
+
Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
|
1303
|
+
call.fun = fun;
|
1304
|
+
int fun_argc = call.argc;
|
1305
|
+
|
1306
|
+
if (fun_argc > 0) {
|
1307
|
+
call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
|
1308
|
+
if (!call.argv) {
|
1309
|
+
return Qnil;
|
1310
|
+
}
|
1311
|
+
for(int i=0; i < fun_argc; i++) {
|
1312
|
+
call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
|
1313
|
+
}
|
1314
|
+
}
|
1315
|
+
|
1316
|
+
rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
|
1317
|
+
free(call.argv);
|
1318
|
+
|
1319
|
+
}
|
1320
|
+
}
|
1321
|
+
|
1322
|
+
if (missingFunction) {
|
1323
|
+
rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
|
1324
|
+
}
|
1325
|
+
|
1326
|
+
return convert_result_to_ruby(self, call.result);
|
1327
|
+
}
|
1328
|
+
|
1329
|
+
static VALUE rb_context_create_isolate_value(VALUE self) {
|
1330
|
+
ContextInfo* context_info;
|
1331
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1332
|
+
IsolateInfo *isolate_info = context_info->isolate_info;
|
1333
|
+
|
1334
|
+
if (!isolate_info) {
|
1335
|
+
return Qnil;
|
1336
|
+
}
|
1337
|
+
|
1338
|
+
isolate_info->hold();
|
1339
|
+
return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
|
1340
|
+
}
|
1341
|
+
|
1058
1342
|
extern "C" {
|
1059
1343
|
|
1060
1344
|
void Init_mini_racer_extension ( void )
|
1061
1345
|
{
|
1062
1346
|
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1347
|
+
rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
1348
|
+
rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
|
1349
|
+
rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
|
1066
1350
|
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
|
1067
1351
|
|
1068
1352
|
VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
|
@@ -1084,18 +1368,21 @@ extern "C" {
|
|
1084
1368
|
rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
|
1085
1369
|
rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
|
1086
1370
|
rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
|
1371
|
+
rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
|
1372
|
+
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
1373
|
+
rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
|
1374
|
+
rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
|
1375
|
+
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
|
1087
1376
|
|
1088
1377
|
rb_define_alloc_func(rb_cContext, allocate);
|
1089
1378
|
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
1090
1379
|
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
1091
1380
|
|
1092
|
-
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
1093
|
-
rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
|
1094
1381
|
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
1095
1382
|
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
1096
1383
|
|
1097
1384
|
rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
|
1098
|
-
rb_define_method(rb_cSnapshot, "
|
1385
|
+
rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
|
1099
1386
|
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
|
1100
1387
|
|
1101
1388
|
rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
|