mini_racer 0.1.15 → 0.5.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/.dockerignore +12 -0
- data/.github/workflows/ci.yml +78 -0
- data/.gitignore +2 -0
- data/.travis.yml +17 -13
- data/CHANGELOG +135 -0
- data/Dockerfile +21 -0
- data/LICENSE.txt +1 -1
- data/README.md +82 -7
- data/Rakefile +42 -0
- data/ext/mini_racer_extension/extconf.rb +25 -8
- data/ext/mini_racer_extension/mini_racer_extension.cc +1295 -495
- data/ext/mini_racer_loader/extconf.rb +8 -0
- data/ext/mini_racer_loader/mini_racer_loader.c +123 -0
- data/lib/mini_racer/version.rb +4 -1
- data/lib/mini_racer.rb +200 -42
- data/mini_racer.gemspec +14 -9
- metadata +41 -18
@@ -1,12 +1,15 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include <ruby.h>
|
3
3
|
#include <ruby/thread.h>
|
4
|
+
#include <ruby/io.h>
|
4
5
|
#include <v8.h>
|
6
|
+
#include <v8-profiler.h>
|
5
7
|
#include <libplatform/libplatform.h>
|
6
8
|
#include <ruby/encoding.h>
|
7
9
|
#include <pthread.h>
|
8
10
|
#include <unistd.h>
|
9
11
|
#include <mutex>
|
12
|
+
#include <atomic>
|
10
13
|
#include <math.h>
|
11
14
|
|
12
15
|
using namespace v8;
|
@@ -16,21 +19,79 @@ typedef struct {
|
|
16
19
|
int raw_size;
|
17
20
|
} SnapshotInfo;
|
18
21
|
|
19
|
-
|
22
|
+
class IsolateInfo {
|
23
|
+
public:
|
20
24
|
Isolate* isolate;
|
21
25
|
ArrayBuffer::Allocator* allocator;
|
22
26
|
StartupData* startup_data;
|
23
27
|
bool interrupted;
|
24
|
-
bool
|
28
|
+
bool added_gc_cb;
|
25
29
|
pid_t pid;
|
30
|
+
VALUE mutex;
|
31
|
+
|
32
|
+
class Lock {
|
33
|
+
VALUE &mutex;
|
34
|
+
|
35
|
+
public:
|
36
|
+
Lock(VALUE &mutex) : mutex(mutex) {
|
37
|
+
rb_mutex_lock(mutex);
|
38
|
+
}
|
39
|
+
~Lock() {
|
40
|
+
rb_mutex_unlock(mutex);
|
41
|
+
}
|
42
|
+
};
|
43
|
+
|
44
|
+
|
45
|
+
IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
|
46
|
+
interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
|
47
|
+
VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
|
48
|
+
mutex = rb_class_new_instance(0, nullptr, cMutex);
|
49
|
+
}
|
50
|
+
|
51
|
+
~IsolateInfo();
|
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
|
+
int refs() {
|
74
|
+
return refs_count;
|
75
|
+
}
|
26
76
|
|
77
|
+
static void* operator new(size_t size) {
|
78
|
+
return ruby_xmalloc(size);
|
79
|
+
}
|
80
|
+
|
81
|
+
static void operator delete(void *block) {
|
82
|
+
xfree(block);
|
83
|
+
}
|
84
|
+
private:
|
27
85
|
// how many references to this isolate exist
|
28
|
-
// we can't rely on Ruby's GC for this, because
|
29
|
-
//
|
30
|
-
//
|
31
|
-
//
|
32
|
-
|
33
|
-
|
86
|
+
// we can't rely on Ruby's GC for this, because Ruby could destroy the
|
87
|
+
// isolate before destroying the contexts that depend on them. We'd need to
|
88
|
+
// keep a list of linked contexts in the isolate to destroy those first when
|
89
|
+
// isolate destruction was requested. Keeping such a list would require
|
90
|
+
// notification from the context VALUEs when they are constructed and
|
91
|
+
// destroyed. With a ref count, those notifications are still needed, but
|
92
|
+
// we keep a simple int rather than a list of pointers.
|
93
|
+
std::atomic_int refs_count;
|
94
|
+
};
|
34
95
|
|
35
96
|
typedef struct {
|
36
97
|
IsolateInfo* isolate_info;
|
@@ -54,15 +115,170 @@ typedef struct {
|
|
54
115
|
useconds_t timeout;
|
55
116
|
EvalResult* result;
|
56
117
|
size_t max_memory;
|
118
|
+
size_t marshal_stackdepth;
|
57
119
|
} EvalParams;
|
58
120
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
121
|
+
typedef struct {
|
122
|
+
ContextInfo *context_info;
|
123
|
+
char *function_name;
|
124
|
+
int argc;
|
125
|
+
bool error;
|
126
|
+
Local<Function> fun;
|
127
|
+
Local<Value> *argv;
|
128
|
+
EvalResult result;
|
129
|
+
size_t max_memory;
|
130
|
+
size_t marshal_stackdepth;
|
131
|
+
} FunctionCall;
|
132
|
+
|
133
|
+
class IsolateData {
|
134
|
+
public:
|
135
|
+
enum Flag {
|
136
|
+
// first flags are bitfield
|
137
|
+
// max count: sizeof(uintptr_t) * 8
|
138
|
+
IN_GVL, // whether we are inside of ruby gvl or not
|
139
|
+
DO_TERMINATE, // terminate as soon as possible
|
140
|
+
MEM_SOFTLIMIT_REACHED, // we've hit the memory soft limit
|
141
|
+
MEM_SOFTLIMIT_MAX, // maximum memory value
|
142
|
+
MARSHAL_STACKDEPTH_REACHED, // we've hit our max stack depth
|
143
|
+
MARSHAL_STACKDEPTH_VALUE, // current stackdepth
|
144
|
+
MARSHAL_STACKDEPTH_MAX, // maximum stack depth during marshal
|
145
|
+
};
|
146
|
+
|
147
|
+
static void Init(Isolate *isolate) {
|
148
|
+
// zero out all fields in the bitfield
|
149
|
+
isolate->SetData(0, 0);
|
150
|
+
}
|
151
|
+
|
152
|
+
static uintptr_t Get(Isolate *isolate, Flag flag) {
|
153
|
+
Bitfield u = { reinterpret_cast<uint64_t>(isolate->GetData(0)) };
|
154
|
+
switch (flag) {
|
155
|
+
case IN_GVL: return u.IN_GVL;
|
156
|
+
case DO_TERMINATE: return u.DO_TERMINATE;
|
157
|
+
case MEM_SOFTLIMIT_REACHED: return u.MEM_SOFTLIMIT_REACHED;
|
158
|
+
case MEM_SOFTLIMIT_MAX: return static_cast<uintptr_t>(u.MEM_SOFTLIMIT_MAX) << 10;
|
159
|
+
case MARSHAL_STACKDEPTH_REACHED: return u.MARSHAL_STACKDEPTH_REACHED;
|
160
|
+
case MARSHAL_STACKDEPTH_VALUE: return u.MARSHAL_STACKDEPTH_VALUE;
|
161
|
+
case MARSHAL_STACKDEPTH_MAX: return u.MARSHAL_STACKDEPTH_MAX;
|
162
|
+
}
|
163
|
+
|
164
|
+
// avoid compiler warning
|
165
|
+
return u.IN_GVL;
|
166
|
+
}
|
167
|
+
|
168
|
+
static void Set(Isolate *isolate, Flag flag, uintptr_t value) {
|
169
|
+
Bitfield u = { reinterpret_cast<uint64_t>(isolate->GetData(0)) };
|
170
|
+
switch (flag) {
|
171
|
+
case IN_GVL: u.IN_GVL = value; break;
|
172
|
+
case DO_TERMINATE: u.DO_TERMINATE = value; break;
|
173
|
+
case MEM_SOFTLIMIT_REACHED: u.MEM_SOFTLIMIT_REACHED = value; break;
|
174
|
+
// drop least significant 10 bits 'store memory amount in kb'
|
175
|
+
case MEM_SOFTLIMIT_MAX: u.MEM_SOFTLIMIT_MAX = value >> 10; break;
|
176
|
+
case MARSHAL_STACKDEPTH_REACHED: u.MARSHAL_STACKDEPTH_REACHED = value; break;
|
177
|
+
case MARSHAL_STACKDEPTH_VALUE: u.MARSHAL_STACKDEPTH_VALUE = value; break;
|
178
|
+
case MARSHAL_STACKDEPTH_MAX: u.MARSHAL_STACKDEPTH_MAX = value; break;
|
179
|
+
}
|
180
|
+
isolate->SetData(0, reinterpret_cast<void*>(u.dataPtr));
|
181
|
+
}
|
182
|
+
|
183
|
+
private:
|
184
|
+
struct Bitfield {
|
185
|
+
// WARNING: this would explode on platforms below 64 bit ptrs
|
186
|
+
// compiler will fail here, making it clear for them.
|
187
|
+
// Additionally, using the other part of the union to reinterpret the
|
188
|
+
// memory is undefined behavior according to spec, but is / has been stable
|
189
|
+
// across major compilers for decades.
|
190
|
+
static_assert(sizeof(uintptr_t) >= sizeof(uint64_t), "mini_racer not supported on this platform. ptr size must be at least 64 bit.");
|
191
|
+
union {
|
192
|
+
uint64_t dataPtr: 64;
|
193
|
+
// order in this struct matters. For cpu performance keep larger subobjects
|
194
|
+
// aligned on their boundaries (8 16 32), try not to straddle
|
195
|
+
struct {
|
196
|
+
size_t MEM_SOFTLIMIT_MAX:22;
|
197
|
+
bool IN_GVL:1;
|
198
|
+
bool DO_TERMINATE:1;
|
199
|
+
bool MEM_SOFTLIMIT_REACHED:1;
|
200
|
+
bool MARSHAL_STACKDEPTH_REACHED:1;
|
201
|
+
uint8_t :0; // align to next 8bit bound
|
202
|
+
size_t MARSHAL_STACKDEPTH_VALUE:10;
|
203
|
+
uint8_t :0; // align to next 8bit bound
|
204
|
+
size_t MARSHAL_STACKDEPTH_MAX:10;
|
205
|
+
};
|
206
|
+
};
|
207
|
+
};
|
208
|
+
};
|
209
|
+
|
210
|
+
struct StackCounter {
|
211
|
+
static void Reset(Isolate* isolate) {
|
212
|
+
if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0) {
|
213
|
+
IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0);
|
214
|
+
IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
static void SetMax(Isolate* isolate, size_t marshalMaxStackDepth) {
|
219
|
+
if (marshalMaxStackDepth > 0) {
|
220
|
+
IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX, marshalMaxStackDepth);
|
221
|
+
IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0);
|
222
|
+
IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false);
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
StackCounter(Isolate* isolate) {
|
227
|
+
this->isActive = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0;
|
228
|
+
|
229
|
+
if (this->isActive) {
|
230
|
+
this->isolate = isolate;
|
231
|
+
this->IncDepth(1);
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
bool IsTooDeep() {
|
236
|
+
if (!this->IsActive()) {
|
237
|
+
return false;
|
238
|
+
}
|
239
|
+
|
240
|
+
size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE);
|
241
|
+
size_t maxDepth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_MAX);
|
242
|
+
if (depth > maxDepth) {
|
243
|
+
IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, true);
|
244
|
+
return true;
|
245
|
+
}
|
246
|
+
|
247
|
+
return false;
|
248
|
+
}
|
249
|
+
|
250
|
+
bool IsActive() {
|
251
|
+
return this->isActive && !IsolateData::Get(this->isolate, IsolateData::DO_TERMINATE);
|
252
|
+
}
|
253
|
+
|
254
|
+
~StackCounter() {
|
255
|
+
if (this->IsActive()) {
|
256
|
+
this->IncDepth(-1);
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
private:
|
261
|
+
Isolate* isolate;
|
262
|
+
bool isActive;
|
263
|
+
|
264
|
+
void IncDepth(int direction) {
|
265
|
+
int inc = direction > 0 ? 1 : -1;
|
266
|
+
|
267
|
+
size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE);
|
268
|
+
|
269
|
+
// don't decrement past 0
|
270
|
+
if (inc > 0 || depth > 0) {
|
271
|
+
depth += inc;
|
272
|
+
}
|
273
|
+
|
274
|
+
IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, depth);
|
275
|
+
}
|
64
276
|
};
|
65
277
|
|
278
|
+
static VALUE rb_cContext;
|
279
|
+
static VALUE rb_cSnapshot;
|
280
|
+
static VALUE rb_cIsolate;
|
281
|
+
|
66
282
|
static VALUE rb_eScriptTerminatedError;
|
67
283
|
static VALUE rb_eV8OutOfMemoryError;
|
68
284
|
static VALUE rb_eParseError;
|
@@ -75,15 +291,28 @@ static VALUE rb_mJSON;
|
|
75
291
|
static VALUE rb_cFailedV8Conversion;
|
76
292
|
static VALUE rb_cDateTime = Qnil;
|
77
293
|
|
78
|
-
static Platform
|
294
|
+
static std::unique_ptr<Platform> current_platform = NULL;
|
79
295
|
static std::mutex platform_lock;
|
80
296
|
|
297
|
+
static pthread_attr_t *thread_attr_p;
|
298
|
+
static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
|
299
|
+
static bool ruby_exiting = false; // guarded by exit_lock
|
300
|
+
static bool single_threaded = false;
|
301
|
+
|
81
302
|
static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
|
82
303
|
bool platform_already_initialized = false;
|
83
304
|
|
305
|
+
if(TYPE(flag_as_str) != T_STRING) {
|
306
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
|
307
|
+
rb_obj_class(flag_as_str));
|
308
|
+
}
|
309
|
+
|
84
310
|
platform_lock.lock();
|
85
311
|
|
86
312
|
if (current_platform == NULL) {
|
313
|
+
if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
|
314
|
+
single_threaded = true;
|
315
|
+
}
|
87
316
|
V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
|
88
317
|
} else {
|
89
318
|
platform_already_initialized = true;
|
@@ -107,8 +336,8 @@ static void init_v8() {
|
|
107
336
|
|
108
337
|
if (current_platform == NULL) {
|
109
338
|
V8::InitializeICU();
|
110
|
-
current_platform = platform::
|
111
|
-
V8::InitializePlatform(current_platform);
|
339
|
+
current_platform = platform::NewDefaultPlatform();
|
340
|
+
V8::InitializePlatform(current_platform.get());
|
112
341
|
V8::Initialize();
|
113
342
|
}
|
114
343
|
|
@@ -116,17 +345,111 @@ static void init_v8() {
|
|
116
345
|
}
|
117
346
|
|
118
347
|
static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
|
119
|
-
if((
|
348
|
+
if (IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED)) {
|
349
|
+
return;
|
350
|
+
}
|
120
351
|
|
121
|
-
size_t softlimit =
|
352
|
+
size_t softlimit = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_MAX);
|
122
353
|
|
123
|
-
HeapStatistics
|
124
|
-
isolate->GetHeapStatistics(stats);
|
125
|
-
size_t used = stats
|
354
|
+
HeapStatistics stats;
|
355
|
+
isolate->GetHeapStatistics(&stats);
|
356
|
+
size_t used = stats.used_heap_size();
|
126
357
|
|
127
358
|
if(used > softlimit) {
|
128
|
-
isolate
|
129
|
-
|
359
|
+
IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, true);
|
360
|
+
isolate->TerminateExecution();
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
// to be called with active lock and scope
|
365
|
+
static void prepare_result(MaybeLocal<Value> v8res,
|
366
|
+
TryCatch& trycatch,
|
367
|
+
Isolate* isolate,
|
368
|
+
Local<Context> context,
|
369
|
+
EvalResult& evalRes /* out */) {
|
370
|
+
|
371
|
+
// just don't touch .parsed
|
372
|
+
evalRes.terminated = false;
|
373
|
+
evalRes.json = false;
|
374
|
+
evalRes.value = nullptr;
|
375
|
+
evalRes.message = nullptr;
|
376
|
+
evalRes.backtrace = nullptr;
|
377
|
+
evalRes.executed = !v8res.IsEmpty();
|
378
|
+
|
379
|
+
if (evalRes.executed) {
|
380
|
+
// arrays and objects get converted to json
|
381
|
+
Local<Value> local_value = v8res.ToLocalChecked();
|
382
|
+
if ((local_value->IsObject() || local_value->IsArray()) &&
|
383
|
+
!local_value->IsDate() && !local_value->IsFunction()) {
|
384
|
+
Local<Object> JSON = context->Global()->Get(
|
385
|
+
context, String::NewFromUtf8Literal(isolate, "JSON"))
|
386
|
+
.ToLocalChecked().As<Object>();
|
387
|
+
|
388
|
+
Local<Function> stringify = JSON->Get(
|
389
|
+
context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
|
390
|
+
.ToLocalChecked().As<Function>();
|
391
|
+
|
392
|
+
Local<Object> object = local_value->ToObject(context).ToLocalChecked();
|
393
|
+
const unsigned argc = 1;
|
394
|
+
Local<Value> argv[argc] = { object };
|
395
|
+
MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
|
396
|
+
|
397
|
+
if (json.IsEmpty()) {
|
398
|
+
evalRes.executed = false;
|
399
|
+
} else {
|
400
|
+
evalRes.json = true;
|
401
|
+
Persistent<Value>* persistent = new Persistent<Value>();
|
402
|
+
persistent->Reset(isolate, json.ToLocalChecked());
|
403
|
+
evalRes.value = persistent;
|
404
|
+
}
|
405
|
+
|
406
|
+
} else {
|
407
|
+
Persistent<Value>* persistent = new Persistent<Value>();
|
408
|
+
persistent->Reset(isolate, local_value);
|
409
|
+
evalRes.value = persistent;
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
if (!evalRes.executed || !evalRes.parsed) {
|
414
|
+
if (trycatch.HasCaught()) {
|
415
|
+
if (!trycatch.Exception()->IsNull()) {
|
416
|
+
evalRes.message = new Persistent<Value>();
|
417
|
+
Local<Message> message = trycatch.Message();
|
418
|
+
char buf[1000];
|
419
|
+
int len, line, column;
|
420
|
+
|
421
|
+
if (!message->GetLineNumber(context).To(&line)) {
|
422
|
+
line = 0;
|
423
|
+
}
|
424
|
+
|
425
|
+
if (!message->GetStartColumn(context).To(&column)) {
|
426
|
+
column = 0;
|
427
|
+
}
|
428
|
+
|
429
|
+
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
|
430
|
+
*String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
|
431
|
+
line,
|
432
|
+
column);
|
433
|
+
|
434
|
+
if ((size_t) len >= sizeof(buf)) {
|
435
|
+
len = sizeof(buf) - 1;
|
436
|
+
buf[len] = '\0';
|
437
|
+
}
|
438
|
+
|
439
|
+
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
|
440
|
+
evalRes.message->Reset(isolate, v8_message);
|
441
|
+
} else if(trycatch.HasTerminated()) {
|
442
|
+
evalRes.terminated = true;
|
443
|
+
evalRes.message = new Persistent<Value>();
|
444
|
+
Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
445
|
+
evalRes.message->Reset(isolate, tmp);
|
446
|
+
}
|
447
|
+
if (!trycatch.StackTrace(context).IsEmpty()) {
|
448
|
+
evalRes.backtrace = new Persistent<Value>();
|
449
|
+
evalRes.backtrace->Reset(isolate,
|
450
|
+
trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
|
451
|
+
}
|
452
|
+
}
|
130
453
|
}
|
131
454
|
}
|
132
455
|
|
@@ -135,7 +458,8 @@ nogvl_context_eval(void* arg) {
|
|
135
458
|
|
136
459
|
EvalParams* eval_params = (EvalParams*)arg;
|
137
460
|
EvalResult* result = eval_params->result;
|
138
|
-
|
461
|
+
IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
|
462
|
+
Isolate* isolate = isolate_info->isolate;
|
139
463
|
|
140
464
|
Isolate::Scope isolate_scope(isolate);
|
141
465
|
HandleScope handle_scope(isolate);
|
@@ -144,25 +468,26 @@ nogvl_context_eval(void* arg) {
|
|
144
468
|
Context::Scope context_scope(context);
|
145
469
|
v8::ScriptOrigin *origin = NULL;
|
146
470
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
471
|
+
IsolateData::Init(isolate);
|
472
|
+
|
473
|
+
if (eval_params->max_memory > 0) {
|
474
|
+
IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_MAX, eval_params->max_memory);
|
475
|
+
if (!isolate_info->added_gc_cb) {
|
476
|
+
isolate->AddGCEpilogueCallback(gc_callback);
|
477
|
+
isolate_info->added_gc_cb = true;
|
478
|
+
}
|
479
|
+
}
|
155
480
|
|
156
481
|
MaybeLocal<Script> parsed_script;
|
157
482
|
|
158
483
|
if (eval_params->filename) {
|
159
|
-
|
484
|
+
origin = new v8::ScriptOrigin(*eval_params->filename);
|
160
485
|
}
|
161
486
|
|
162
487
|
parsed_script = Script::Compile(context, *eval_params->eval, origin);
|
163
488
|
|
164
489
|
if (origin) {
|
165
|
-
|
490
|
+
delete origin;
|
166
491
|
}
|
167
492
|
|
168
493
|
result->parsed = !parsed_script.IsEmpty();
|
@@ -171,130 +496,90 @@ nogvl_context_eval(void* arg) {
|
|
171
496
|
result->json = false;
|
172
497
|
result->value = NULL;
|
173
498
|
|
499
|
+
MaybeLocal<Value> maybe_value;
|
174
500
|
if (!result->parsed) {
|
175
|
-
|
176
|
-
|
501
|
+
result->message = new Persistent<Value>();
|
502
|
+
result->message->Reset(isolate, trycatch.Exception());
|
177
503
|
} else {
|
504
|
+
// parsing successful
|
505
|
+
if (eval_params->marshal_stackdepth > 0) {
|
506
|
+
StackCounter::SetMax(isolate, eval_params->marshal_stackdepth);
|
507
|
+
}
|
178
508
|
|
179
|
-
|
180
|
-
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
|
181
|
-
isolate->AddGCEpilogueCallback(gc_callback);
|
509
|
+
maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
182
510
|
}
|
183
511
|
|
184
|
-
|
512
|
+
prepare_result(maybe_value, trycatch, isolate, context, *result);
|
185
513
|
|
186
|
-
|
514
|
+
IsolateData::Set(isolate, IsolateData::IN_GVL, true);
|
187
515
|
|
188
|
-
|
516
|
+
return NULL;
|
517
|
+
}
|
189
518
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
String::NewFromUtf8(isolate, "JSON"))->ToObject();
|
519
|
+
static VALUE new_empty_failed_conv_obj() {
|
520
|
+
// TODO isolate code that translates execption to ruby
|
521
|
+
// exception so we can properly return it
|
522
|
+
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
523
|
+
}
|
196
524
|
|
197
|
-
|
198
|
-
|
525
|
+
// assumes isolate locking is in place
|
526
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
|
527
|
+
Local<Value> value) {
|
199
528
|
|
200
|
-
|
201
|
-
|
202
|
-
Local<Value> argv[argc] = { object };
|
203
|
-
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
|
529
|
+
Isolate::Scope isolate_scope(isolate);
|
530
|
+
HandleScope scope(isolate);
|
204
531
|
|
205
|
-
|
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
|
-
}
|
532
|
+
StackCounter stackCounter(isolate);
|
213
533
|
|
214
|
-
|
215
|
-
|
216
|
-
persistent->Reset(isolate, local_value);
|
217
|
-
result->value = persistent;
|
218
|
-
}
|
219
|
-
}
|
534
|
+
if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED)) {
|
535
|
+
return Qnil;
|
220
536
|
}
|
221
537
|
|
222
|
-
if (
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
}
|
538
|
+
if (stackCounter.IsTooDeep()) {
|
539
|
+
IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
|
540
|
+
isolate->TerminateExecution();
|
541
|
+
return Qnil;
|
249
542
|
}
|
250
543
|
|
251
|
-
isolate->SetData(IN_GVL, (void*)true);
|
252
|
-
|
253
|
-
|
254
|
-
return NULL;
|
255
|
-
}
|
256
|
-
|
257
|
-
static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
258
|
-
|
259
|
-
Isolate::Scope isolate_scope(isolate);
|
260
|
-
HandleScope scope(isolate);
|
261
|
-
|
262
544
|
if (value->IsNull() || value->IsUndefined()){
|
263
|
-
|
545
|
+
return Qnil;
|
264
546
|
}
|
265
547
|
|
266
548
|
if (value->IsInt32()) {
|
267
|
-
|
549
|
+
return INT2FIX(value->Int32Value(context).ToChecked());
|
268
550
|
}
|
269
551
|
|
270
552
|
if (value->IsNumber()) {
|
271
|
-
|
553
|
+
return rb_float_new(value->NumberValue(context).ToChecked());
|
272
554
|
}
|
273
555
|
|
274
556
|
if (value->IsTrue()) {
|
275
|
-
|
557
|
+
return Qtrue;
|
276
558
|
}
|
277
559
|
|
278
560
|
if (value->IsFalse()) {
|
279
|
-
|
561
|
+
return Qfalse;
|
280
562
|
}
|
281
563
|
|
282
564
|
if (value->IsArray()) {
|
283
565
|
VALUE rb_array = rb_ary_new();
|
284
566
|
Local<Array> arr = Local<Array>::Cast(value);
|
285
567
|
for(uint32_t i=0; i < arr->Length(); i++) {
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
568
|
+
MaybeLocal<Value> element = arr->Get(context, i);
|
569
|
+
if (element.IsEmpty()) {
|
570
|
+
continue;
|
571
|
+
}
|
572
|
+
VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
|
573
|
+
if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
574
|
+
return rb_elem;
|
575
|
+
}
|
291
576
|
rb_ary_push(rb_array, rb_elem);
|
292
577
|
}
|
293
578
|
return rb_array;
|
294
579
|
}
|
295
580
|
|
296
581
|
if (value->IsFunction()){
|
297
|
-
|
582
|
+
return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
|
298
583
|
}
|
299
584
|
|
300
585
|
if (value->IsDate()){
|
@@ -306,38 +591,76 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|
306
591
|
}
|
307
592
|
|
308
593
|
if (value->IsObject()) {
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
594
|
+
VALUE rb_hash = rb_hash_new();
|
595
|
+
TryCatch trycatch(isolate);
|
596
|
+
|
597
|
+
Local<Object> object = value->ToObject(context).ToLocalChecked();
|
598
|
+
auto maybe_props = object->GetOwnPropertyNames(context);
|
599
|
+
if (!maybe_props.IsEmpty()) {
|
600
|
+
Local<Array> props = maybe_props.ToLocalChecked();
|
601
|
+
for(uint32_t i=0; i < props->Length(); i++) {
|
602
|
+
MaybeLocal<Value> key = props->Get(context, i);
|
603
|
+
if (key.IsEmpty()) {
|
604
|
+
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
605
|
+
}
|
606
|
+
VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
|
607
|
+
|
608
|
+
MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
|
609
|
+
// this may have failed due to Get raising
|
610
|
+
if (prop_value.IsEmpty() || trycatch.HasCaught()) {
|
611
|
+
return new_empty_failed_conv_obj();
|
612
|
+
}
|
613
|
+
|
614
|
+
VALUE rb_value = convert_v8_to_ruby(
|
615
|
+
isolate, context, prop_value.ToLocalChecked());
|
616
|
+
rb_hash_aset(rb_hash, rb_key, rb_value);
|
617
|
+
}
|
618
|
+
}
|
619
|
+
return rb_hash;
|
620
|
+
}
|
621
|
+
|
622
|
+
if (value->IsSymbol()) {
|
623
|
+
v8::String::Utf8Value symbol_name(isolate,
|
624
|
+
Local<Symbol>::Cast(value)->Name());
|
625
|
+
|
626
|
+
VALUE str_symbol = rb_enc_str_new(
|
627
|
+
*symbol_name,
|
628
|
+
symbol_name.length(),
|
629
|
+
rb_enc_find("utf-8")
|
630
|
+
);
|
631
|
+
|
632
|
+
return rb_str_intern(str_symbol);
|
633
|
+
}
|
634
|
+
|
635
|
+
MaybeLocal<String> rstr_maybe = value->ToString(context);
|
636
|
+
|
637
|
+
if (rstr_maybe.IsEmpty()) {
|
638
|
+
return Qnil;
|
639
|
+
} else {
|
640
|
+
Local<String> rstr = rstr_maybe.ToLocalChecked();
|
641
|
+
return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
|
334
642
|
}
|
643
|
+
}
|
335
644
|
|
336
|
-
|
337
|
-
|
645
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
646
|
+
const Persistent<Context>& context,
|
647
|
+
Local<Value> value) {
|
648
|
+
HandleScope scope(isolate);
|
649
|
+
return convert_v8_to_ruby(isolate,
|
650
|
+
Local<Context>::New(isolate, context),
|
651
|
+
value);
|
652
|
+
}
|
653
|
+
|
654
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
655
|
+
const Persistent<Context>& context,
|
656
|
+
const Persistent<Value>& value) {
|
657
|
+
HandleScope scope(isolate);
|
658
|
+
return convert_v8_to_ruby(isolate,
|
659
|
+
Local<Context>::New(isolate, context),
|
660
|
+
Local<Value>::New(isolate, value));
|
338
661
|
}
|
339
662
|
|
340
|
-
static
|
663
|
+
static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value) {
|
341
664
|
EscapableHandleScope scope(isolate);
|
342
665
|
|
343
666
|
Local<Array> array;
|
@@ -371,7 +694,8 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
371
694
|
length = RARRAY_LEN(value);
|
372
695
|
array = Array::New(isolate, (int)length);
|
373
696
|
for(i=0; i<length; i++) {
|
374
|
-
|
697
|
+
Maybe<bool> success = array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
|
698
|
+
(void)(success);
|
375
699
|
}
|
376
700
|
return scope.Escape(array);
|
377
701
|
case T_HASH:
|
@@ -380,8 +704,9 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
380
704
|
length = RARRAY_LEN(hash_as_array);
|
381
705
|
for(i=0; i<length; i++) {
|
382
706
|
pair = rb_ary_entry(hash_as_array, i);
|
383
|
-
|
384
|
-
|
707
|
+
Maybe<bool> success = object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
|
708
|
+
convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
|
709
|
+
(void)(success);
|
385
710
|
}
|
386
711
|
return scope.Escape(object);
|
387
712
|
case T_SYMBOL:
|
@@ -396,7 +721,7 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
396
721
|
value = rb_funcall(value, rb_intern("to_time"), 0);
|
397
722
|
}
|
398
723
|
value = rb_funcall(value, rb_intern("to_f"), 0);
|
399
|
-
return scope.Escape(Date::New(
|
724
|
+
return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
|
400
725
|
}
|
401
726
|
case T_OBJECT:
|
402
727
|
case T_CLASS:
|
@@ -410,16 +735,90 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
|
410
735
|
case T_UNDEF:
|
411
736
|
case T_NODE:
|
412
737
|
default:
|
413
|
-
return scope.Escape(String::
|
738
|
+
return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
|
739
|
+
}
|
414
740
|
}
|
415
|
-
|
416
|
-
}
|
417
741
|
|
418
742
|
static void unblock_eval(void *ptr) {
|
419
743
|
EvalParams* eval = (EvalParams*)ptr;
|
420
744
|
eval->context_info->isolate_info->interrupted = true;
|
421
745
|
}
|
422
746
|
|
747
|
+
/*
|
748
|
+
* The implementations of the run_extra_code(), create_snapshot_data_blob() and
|
749
|
+
* warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
|
750
|
+
*/
|
751
|
+
static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
|
752
|
+
const char *utf8_source, const char *name) {
|
753
|
+
Context::Scope context_scope(context);
|
754
|
+
TryCatch try_catch(isolate);
|
755
|
+
Local<String> source_string;
|
756
|
+
if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
|
757
|
+
return false;
|
758
|
+
}
|
759
|
+
Local<String> resource_name =
|
760
|
+
String::NewFromUtf8(isolate, name).ToLocalChecked();
|
761
|
+
ScriptOrigin origin(resource_name);
|
762
|
+
ScriptCompiler::Source source(source_string, origin);
|
763
|
+
Local<Script> script;
|
764
|
+
if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
|
765
|
+
return false;
|
766
|
+
if (script->Run(context).IsEmpty()) return false;
|
767
|
+
return true;
|
768
|
+
}
|
769
|
+
|
770
|
+
static StartupData
|
771
|
+
create_snapshot_data_blob(const char *embedded_source = nullptr) {
|
772
|
+
Isolate *isolate = Isolate::Allocate();
|
773
|
+
|
774
|
+
// Optionally run a script to embed, and serialize to create a snapshot blob.
|
775
|
+
SnapshotCreator snapshot_creator(isolate);
|
776
|
+
{
|
777
|
+
HandleScope scope(isolate);
|
778
|
+
Local<v8::Context> context = v8::Context::New(isolate);
|
779
|
+
if (embedded_source != nullptr &&
|
780
|
+
!run_extra_code(isolate, context, embedded_source, "<embedded>")) {
|
781
|
+
return {};
|
782
|
+
}
|
783
|
+
snapshot_creator.SetDefaultContext(context);
|
784
|
+
}
|
785
|
+
return snapshot_creator.CreateBlob(
|
786
|
+
SnapshotCreator::FunctionCodeHandling::kClear);
|
787
|
+
}
|
788
|
+
|
789
|
+
StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
|
790
|
+
const char *warmup_source) {
|
791
|
+
// Use following steps to create a warmed up snapshot blob from a cold one:
|
792
|
+
// - Create a new isolate from the cold snapshot.
|
793
|
+
// - Create a new context to run the warmup script. This will trigger
|
794
|
+
// compilation of executed functions.
|
795
|
+
// - Create a new context. This context will be unpolluted.
|
796
|
+
// - Serialize the isolate and the second context into a new snapshot blob.
|
797
|
+
StartupData result = {nullptr, 0};
|
798
|
+
|
799
|
+
if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
|
800
|
+
warmup_source != NULL) {
|
801
|
+
SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
|
802
|
+
Isolate *isolate = snapshot_creator.GetIsolate();
|
803
|
+
{
|
804
|
+
HandleScope scope(isolate);
|
805
|
+
Local<Context> context = Context::New(isolate);
|
806
|
+
if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
|
807
|
+
return result;
|
808
|
+
}
|
809
|
+
}
|
810
|
+
{
|
811
|
+
HandleScope handle_scope(isolate);
|
812
|
+
isolate->ContextDisposedNotification(false);
|
813
|
+
Local<Context> context = Context::New(isolate);
|
814
|
+
snapshot_creator.SetDefaultContext(context);
|
815
|
+
}
|
816
|
+
result = snapshot_creator.CreateBlob(
|
817
|
+
SnapshotCreator::FunctionCodeHandling::kKeep);
|
818
|
+
}
|
819
|
+
return result;
|
820
|
+
}
|
821
|
+
|
423
822
|
static VALUE rb_snapshot_size(VALUE self, VALUE str) {
|
424
823
|
SnapshotInfo* snapshot_info;
|
425
824
|
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
@@ -431,9 +830,14 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
|
|
431
830
|
SnapshotInfo* snapshot_info;
|
432
831
|
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
433
832
|
|
833
|
+
if(TYPE(str) != T_STRING) {
|
834
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
835
|
+
rb_obj_class(str));
|
836
|
+
}
|
837
|
+
|
434
838
|
init_v8();
|
435
839
|
|
436
|
-
StartupData startup_data =
|
840
|
+
StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
|
437
841
|
|
438
842
|
if (startup_data.data == NULL && startup_data.raw_size == 0) {
|
439
843
|
rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
|
@@ -445,14 +849,26 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
|
|
445
849
|
return Qnil;
|
446
850
|
}
|
447
851
|
|
448
|
-
static VALUE
|
852
|
+
static VALUE rb_snapshot_dump(VALUE self, VALUE str) {
|
853
|
+
SnapshotInfo* snapshot_info;
|
854
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
855
|
+
|
856
|
+
return rb_str_new(snapshot_info->data, snapshot_info->raw_size);
|
857
|
+
}
|
858
|
+
|
859
|
+
static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
|
449
860
|
SnapshotInfo* snapshot_info;
|
450
861
|
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
451
862
|
|
863
|
+
if(TYPE(str) != T_STRING) {
|
864
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
865
|
+
rb_obj_class(str));
|
866
|
+
}
|
867
|
+
|
452
868
|
init_v8();
|
453
869
|
|
454
870
|
StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
|
455
|
-
StartupData warm_startup_data =
|
871
|
+
StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
|
456
872
|
|
457
873
|
if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
|
458
874
|
rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
|
@@ -466,27 +882,16 @@ static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
|
|
466
882
|
return self;
|
467
883
|
}
|
468
884
|
|
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;
|
885
|
+
void IsolateInfo::init(SnapshotInfo* snapshot_info) {
|
886
|
+
allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
478
887
|
|
479
888
|
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);
|
889
|
+
create_params.array_buffer_allocator = allocator;
|
486
890
|
|
891
|
+
if (snapshot_info) {
|
487
892
|
int raw_size = snapshot_info->raw_size;
|
488
893
|
char* data = new char[raw_size];
|
489
|
-
memcpy(data, snapshot_info->data,
|
894
|
+
memcpy(data, snapshot_info->data, raw_size);
|
490
895
|
|
491
896
|
startup_data = new StartupData;
|
492
897
|
startup_data->data = data;
|
@@ -495,8 +900,22 @@ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
|
|
495
900
|
create_params.snapshot_blob = startup_data;
|
496
901
|
}
|
497
902
|
|
498
|
-
|
499
|
-
|
903
|
+
isolate = Isolate::New(create_params);
|
904
|
+
}
|
905
|
+
|
906
|
+
static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
|
907
|
+
IsolateInfo* isolate_info;
|
908
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
909
|
+
|
910
|
+
init_v8();
|
911
|
+
|
912
|
+
SnapshotInfo* snapshot_info = nullptr;
|
913
|
+
if (!NIL_P(snapshot)) {
|
914
|
+
Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
|
915
|
+
}
|
916
|
+
|
917
|
+
isolate_info->init(snapshot_info);
|
918
|
+
isolate_info->hold();
|
500
919
|
|
501
920
|
return Qnil;
|
502
921
|
}
|
@@ -505,30 +924,70 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
|
|
505
924
|
IsolateInfo* isolate_info;
|
506
925
|
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
507
926
|
|
508
|
-
|
927
|
+
if (current_platform == NULL) return Qfalse;
|
928
|
+
|
929
|
+
double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
|
930
|
+
double now = current_platform->MonotonicallyIncreasingTime();
|
931
|
+
return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
|
932
|
+
}
|
933
|
+
|
934
|
+
static VALUE rb_isolate_low_memory_notification(VALUE self) {
|
935
|
+
IsolateInfo* isolate_info;
|
936
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
937
|
+
|
938
|
+
if (current_platform == NULL) return Qfalse;
|
939
|
+
|
940
|
+
isolate_info->isolate->LowMemoryNotification();
|
941
|
+
return Qnil;
|
509
942
|
}
|
510
943
|
|
511
|
-
static VALUE
|
944
|
+
static VALUE rb_isolate_pump_message_loop(VALUE self) {
|
945
|
+
IsolateInfo* isolate_info;
|
946
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
947
|
+
|
948
|
+
if (current_platform == NULL) return Qfalse;
|
949
|
+
|
950
|
+
if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
|
951
|
+
return Qtrue;
|
952
|
+
} else {
|
953
|
+
return Qfalse;
|
954
|
+
}
|
955
|
+
}
|
956
|
+
|
957
|
+
static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
|
512
958
|
ContextInfo* context_info;
|
513
959
|
Data_Get_Struct(self, ContextInfo, context_info);
|
514
960
|
|
515
961
|
init_v8();
|
516
962
|
|
517
963
|
IsolateInfo* isolate_info;
|
518
|
-
|
964
|
+
|
965
|
+
if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
|
966
|
+
isolate_info = new IsolateInfo();
|
967
|
+
|
968
|
+
SnapshotInfo *snapshot_info = nullptr;
|
969
|
+
if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
|
970
|
+
Data_Get_Struct(snap, SnapshotInfo, snapshot_info);
|
971
|
+
}
|
972
|
+
isolate_info->init(snapshot_info);
|
973
|
+
} else { // given isolate or snapshot
|
974
|
+
Data_Get_Struct(isolate, IsolateInfo, isolate_info);
|
975
|
+
}
|
519
976
|
|
520
977
|
context_info->isolate_info = isolate_info;
|
521
|
-
isolate_info->
|
978
|
+
isolate_info->hold();
|
522
979
|
|
523
980
|
{
|
524
|
-
|
525
|
-
|
526
|
-
|
981
|
+
// the ruby lock is needed if this isn't a new isolate
|
982
|
+
IsolateInfo::Lock ruby_lock(isolate_info->mutex);
|
983
|
+
Locker lock(isolate_info->isolate);
|
984
|
+
Isolate::Scope isolate_scope(isolate_info->isolate);
|
985
|
+
HandleScope handle_scope(isolate_info->isolate);
|
527
986
|
|
528
|
-
|
987
|
+
Local<Context> context = Context::New(isolate_info->isolate);
|
529
988
|
|
530
|
-
|
531
|
-
|
989
|
+
context_info->context = new Persistent<Context>();
|
990
|
+
context_info->context->Reset(isolate_info->isolate, context);
|
532
991
|
}
|
533
992
|
|
534
993
|
if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
|
@@ -539,152 +998,187 @@ static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
|
|
539
998
|
return Qnil;
|
540
999
|
}
|
541
1000
|
|
542
|
-
static VALUE
|
1001
|
+
static VALUE convert_result_to_ruby(VALUE self /* context */,
|
1002
|
+
EvalResult& result) {
|
1003
|
+
ContextInfo *context_info;
|
1004
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
543
1005
|
|
544
|
-
|
545
|
-
|
546
|
-
ContextInfo* context_info;
|
547
|
-
VALUE result;
|
1006
|
+
Isolate *isolate = context_info->isolate_info->isolate;
|
1007
|
+
Persistent<Context> *p_ctx = context_info->context;
|
548
1008
|
|
549
1009
|
VALUE message = Qnil;
|
550
1010
|
VALUE backtrace = Qnil;
|
551
|
-
|
552
|
-
Data_Get_Struct(self, ContextInfo, context_info);
|
553
|
-
Isolate* isolate = context_info->isolate_info->isolate;
|
554
|
-
|
555
1011
|
{
|
556
|
-
|
557
|
-
|
558
|
-
|
1012
|
+
Locker lock(isolate);
|
1013
|
+
if (result.message) {
|
1014
|
+
message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
|
1015
|
+
result.message->Reset();
|
1016
|
+
delete result.message;
|
1017
|
+
result.message = nullptr;
|
1018
|
+
}
|
559
1019
|
|
560
|
-
|
561
|
-
|
1020
|
+
if (result.backtrace) {
|
1021
|
+
backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
|
1022
|
+
result.backtrace->Reset();
|
1023
|
+
delete result.backtrace;
|
1024
|
+
}
|
1025
|
+
}
|
562
1026
|
|
563
|
-
|
1027
|
+
// NOTE: this is very important, we can not do an rb_raise from within
|
1028
|
+
// a v8 scope, if we do the scope is never cleaned up properly and we leak
|
1029
|
+
if (!result.parsed) {
|
1030
|
+
if(TYPE(message) == T_STRING) {
|
1031
|
+
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
|
1032
|
+
} else {
|
1033
|
+
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
|
1034
|
+
}
|
1035
|
+
}
|
564
1036
|
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
1037
|
+
if (!result.executed) {
|
1038
|
+
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
1039
|
+
if (ruby_exception == Qnil) {
|
1040
|
+
bool mem_softlimit_reached = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED);
|
1041
|
+
bool marshal_stack_maxdepth_reached = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED);
|
1042
|
+
// If we were terminated or have the memory softlimit flag set
|
1043
|
+
if (marshal_stack_maxdepth_reached) {
|
1044
|
+
ruby_exception = rb_eScriptRuntimeError;
|
1045
|
+
std::string msg = std::string("Marshal object depth too deep. Script terminated.");
|
1046
|
+
message = rb_enc_str_new(msg.c_str(), msg.length(), rb_enc_find("utf-8"));
|
1047
|
+
} else if (result.terminated || mem_softlimit_reached) {
|
1048
|
+
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
|
1049
|
+
} else {
|
1050
|
+
ruby_exception = rb_eScriptRuntimeError;
|
1051
|
+
}
|
572
1052
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
1053
|
+
// exception report about what happened
|
1054
|
+
if (TYPE(backtrace) == T_STRING) {
|
1055
|
+
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
|
1056
|
+
} else if(TYPE(message) == T_STRING) {
|
1057
|
+
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
|
1058
|
+
} else {
|
1059
|
+
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
|
1060
|
+
}
|
1061
|
+
} else {
|
1062
|
+
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
|
1063
|
+
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
|
1064
|
+
}
|
1065
|
+
}
|
582
1066
|
|
583
|
-
|
584
|
-
if (mem_softlimit != Qnil) {
|
585
|
-
eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
|
586
|
-
}
|
1067
|
+
VALUE ret = Qnil;
|
587
1068
|
|
588
|
-
|
589
|
-
|
1069
|
+
// New scope for return value
|
1070
|
+
{
|
1071
|
+
Locker lock(isolate);
|
1072
|
+
Isolate::Scope isolate_scope(isolate);
|
1073
|
+
HandleScope handle_scope(isolate);
|
590
1074
|
|
591
|
-
|
1075
|
+
Local<Value> tmp = Local<Value>::New(isolate, *result.value);
|
592
1076
|
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
1077
|
+
if (result.json) {
|
1078
|
+
Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
|
1079
|
+
VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
|
1080
|
+
ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
|
1081
|
+
} else {
|
1082
|
+
StackCounter::Reset(isolate);
|
1083
|
+
ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
|
1084
|
+
}
|
599
1085
|
|
600
|
-
|
601
|
-
|
602
|
-
backtrace = convert_v8_to_ruby(isolate, tmp);
|
603
|
-
eval_result.backtrace->Reset();
|
604
|
-
delete eval_result.backtrace;
|
605
|
-
}
|
1086
|
+
result.value->Reset();
|
1087
|
+
delete result.value;
|
606
1088
|
}
|
607
1089
|
|
608
|
-
|
609
|
-
|
610
|
-
|
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
|
-
}
|
1090
|
+
if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
1091
|
+
// TODO try to recover stack trace from the conversion error
|
1092
|
+
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
616
1093
|
}
|
617
1094
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
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
|
-
}
|
1095
|
+
|
1096
|
+
return ret;
|
1097
|
+
}
|
1098
|
+
|
1099
|
+
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
1100
|
+
|
1101
|
+
EvalParams eval_params;
|
1102
|
+
EvalResult eval_result;
|
1103
|
+
ContextInfo* context_info;
|
1104
|
+
|
1105
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1106
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
1107
|
+
|
1108
|
+
if(TYPE(str) != T_STRING) {
|
1109
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
1110
|
+
rb_obj_class(str));
|
1111
|
+
}
|
1112
|
+
if(filename != Qnil && TYPE(filename) != T_STRING) {
|
1113
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
|
1114
|
+
rb_obj_class(filename));
|
641
1115
|
}
|
642
1116
|
|
643
|
-
// New scope for return value
|
644
1117
|
{
|
645
|
-
|
646
|
-
|
647
|
-
|
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
|
-
}
|
1118
|
+
Locker lock(isolate);
|
1119
|
+
Isolate::Scope isolate_scope(isolate);
|
1120
|
+
HandleScope handle_scope(isolate);
|
658
1121
|
|
659
|
-
|
660
|
-
|
661
|
-
}
|
1122
|
+
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
|
1123
|
+
NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
|
662
1124
|
|
663
|
-
|
664
|
-
// TODO try to recover stack trace from the conversion error
|
665
|
-
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
666
|
-
}
|
1125
|
+
Local<String> local_filename;
|
667
1126
|
|
1127
|
+
if (filename != Qnil) {
|
1128
|
+
local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
|
1129
|
+
NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
|
1130
|
+
eval_params.filename = &local_filename;
|
1131
|
+
} else {
|
1132
|
+
eval_params.filename = NULL;
|
1133
|
+
}
|
668
1134
|
|
669
|
-
|
1135
|
+
eval_params.context_info = context_info;
|
1136
|
+
eval_params.eval = &eval;
|
1137
|
+
eval_params.result = &eval_result;
|
1138
|
+
eval_params.timeout = 0;
|
1139
|
+
eval_params.max_memory = 0;
|
1140
|
+
eval_params.marshal_stackdepth = 0;
|
1141
|
+
VALUE timeout = rb_iv_get(self, "@timeout");
|
1142
|
+
if (timeout != Qnil) {
|
1143
|
+
eval_params.timeout = (useconds_t)NUM2LONG(timeout);
|
1144
|
+
}
|
1145
|
+
|
1146
|
+
VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
|
1147
|
+
if (mem_softlimit != Qnil) {
|
1148
|
+
eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
|
1149
|
+
}
|
1150
|
+
|
1151
|
+
VALUE stack_depth = rb_iv_get(self, "@marshal_stack_depth");
|
1152
|
+
if (stack_depth != Qnil) {
|
1153
|
+
eval_params.marshal_stackdepth = (size_t)NUM2ULONG(stack_depth);
|
1154
|
+
}
|
1155
|
+
|
1156
|
+
eval_result.message = NULL;
|
1157
|
+
eval_result.backtrace = NULL;
|
1158
|
+
|
1159
|
+
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
return convert_result_to_ruby(self, eval_result);
|
670
1163
|
}
|
671
1164
|
|
672
1165
|
typedef struct {
|
673
1166
|
VALUE callback;
|
674
1167
|
int length;
|
675
|
-
VALUE
|
1168
|
+
VALUE ruby_args;
|
676
1169
|
bool failed;
|
677
1170
|
} protected_callback_data;
|
678
1171
|
|
679
|
-
static
|
680
|
-
VALUE protected_callback(VALUE rdata) {
|
1172
|
+
static VALUE protected_callback(VALUE rdata) {
|
681
1173
|
protected_callback_data* data = (protected_callback_data*)rdata;
|
682
1174
|
VALUE result;
|
683
1175
|
|
684
1176
|
if (data->length > 0) {
|
685
|
-
|
1177
|
+
result = rb_funcall2(data->callback, rb_intern("call"), data->length,
|
1178
|
+
RARRAY_PTR(data->ruby_args));
|
1179
|
+
RB_GC_GUARD(data->ruby_args);
|
686
1180
|
} else {
|
687
|
-
|
1181
|
+
result = rb_funcall(data->callback, rb_intern("call"), 0);
|
688
1182
|
}
|
689
1183
|
return result;
|
690
1184
|
}
|
@@ -700,80 +1194,97 @@ void*
|
|
700
1194
|
gvl_ruby_callback(void* data) {
|
701
1195
|
|
702
1196
|
FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
|
703
|
-
VALUE
|
1197
|
+
VALUE ruby_args = Qnil;
|
704
1198
|
int length = args->Length();
|
705
1199
|
VALUE callback;
|
706
1200
|
VALUE result;
|
707
1201
|
VALUE self;
|
1202
|
+
VALUE parent;
|
1203
|
+
ContextInfo* context_info;
|
708
1204
|
|
709
1205
|
{
|
710
|
-
|
711
|
-
|
1206
|
+
HandleScope scope(args->GetIsolate());
|
1207
|
+
Local<External> external = Local<External>::Cast(args->Data());
|
712
1208
|
|
713
|
-
|
714
|
-
|
715
|
-
callback = rb_iv_get(self, "@callback");
|
1209
|
+
self = *(VALUE*)(external->Value());
|
1210
|
+
callback = rb_iv_get(self, "@callback");
|
716
1211
|
|
717
|
-
|
718
|
-
|
719
|
-
|
1212
|
+
parent = rb_iv_get(self, "@parent");
|
1213
|
+
if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
|
1214
|
+
return NULL;
|
1215
|
+
}
|
720
1216
|
|
1217
|
+
Data_Get_Struct(parent, ContextInfo, context_info);
|
721
1218
|
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
1219
|
+
if (length > 0) {
|
1220
|
+
ruby_args = rb_ary_tmp_new(length);
|
1221
|
+
}
|
1222
|
+
|
1223
|
+
for (int i = 0; i < length; i++) {
|
1224
|
+
Local<Value> value = ((*args)[i]).As<Value>();
|
1225
|
+
StackCounter::Reset(args->GetIsolate());
|
1226
|
+
VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
|
1227
|
+
*context_info->context, value);
|
1228
|
+
rb_ary_push(ruby_args, tmp);
|
1229
|
+
}
|
726
1230
|
}
|
727
1231
|
|
728
1232
|
// may raise exception stay clear of handle scope
|
729
1233
|
protected_callback_data callback_data;
|
730
1234
|
callback_data.length = length;
|
731
1235
|
callback_data.callback = callback;
|
732
|
-
callback_data.
|
1236
|
+
callback_data.ruby_args = ruby_args;
|
733
1237
|
callback_data.failed = false;
|
734
1238
|
|
735
|
-
if ((
|
736
|
-
|
737
|
-
|
738
|
-
|
1239
|
+
if (IsolateData::Get(args->GetIsolate(), IsolateData::DO_TERMINATE)) {
|
1240
|
+
args->GetIsolate()->ThrowException(
|
1241
|
+
String::NewFromUtf8Literal(args->GetIsolate(),
|
1242
|
+
"Terminated execution during transition from Ruby to JS"));
|
1243
|
+
args->GetIsolate()->TerminateExecution();
|
1244
|
+
if (length > 0) {
|
1245
|
+
rb_ary_clear(ruby_args);
|
1246
|
+
rb_gc_force_recycle(ruby_args);
|
1247
|
+
}
|
1248
|
+
return NULL;
|
739
1249
|
}
|
740
1250
|
|
741
|
-
|
742
|
-
|
1251
|
+
VALUE callback_data_value = (VALUE)&callback_data;
|
1252
|
+
|
1253
|
+
// TODO: use rb_vrescue2 in Ruby 2.7 and above
|
1254
|
+
result = rb_rescue2(RUBY_METHOD_FUNC(protected_callback), callback_data_value,
|
1255
|
+
RUBY_METHOD_FUNC(rescue_callback), callback_data_value, rb_eException, (VALUE)0);
|
743
1256
|
|
744
1257
|
if(callback_data.failed) {
|
745
|
-
|
746
|
-
|
747
|
-
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
|
1258
|
+
rb_iv_set(parent, "@current_exception", result);
|
1259
|
+
args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
|
748
1260
|
}
|
749
1261
|
else {
|
750
|
-
|
751
|
-
|
752
|
-
|
1262
|
+
HandleScope scope(args->GetIsolate());
|
1263
|
+
Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
|
1264
|
+
args->GetReturnValue().Set(v8_result);
|
753
1265
|
}
|
754
1266
|
|
755
1267
|
if (length > 0) {
|
756
|
-
|
1268
|
+
rb_ary_clear(ruby_args);
|
1269
|
+
rb_gc_force_recycle(ruby_args);
|
757
1270
|
}
|
758
1271
|
|
759
|
-
if ((
|
760
|
-
|
761
|
-
V8::TerminateExecution(isolate);
|
1272
|
+
if (IsolateData::Get(args->GetIsolate(), IsolateData::DO_TERMINATE)) {
|
1273
|
+
args->GetIsolate()->TerminateExecution();
|
762
1274
|
}
|
763
1275
|
|
764
1276
|
return NULL;
|
765
1277
|
}
|
766
1278
|
|
767
1279
|
static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
|
768
|
-
|
769
|
-
bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL);
|
1280
|
+
bool has_gvl = IsolateData::Get(args.GetIsolate(), IsolateData::IN_GVL);
|
770
1281
|
|
771
1282
|
if(has_gvl) {
|
772
|
-
|
1283
|
+
gvl_ruby_callback((void*)&args);
|
773
1284
|
} else {
|
774
|
-
|
775
|
-
|
776
|
-
|
1285
|
+
IsolateData::Set(args.GetIsolate(), IsolateData::IN_GVL, true);
|
1286
|
+
rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
|
1287
|
+
IsolateData::Set(args.GetIsolate(), IsolateData::IN_GVL, false);
|
777
1288
|
}
|
778
1289
|
}
|
779
1290
|
|
@@ -794,180 +1305,229 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
|
|
794
1305
|
Isolate* isolate = context_info->isolate_info->isolate;
|
795
1306
|
|
796
1307
|
{
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
1308
|
+
Locker lock(isolate);
|
1309
|
+
Isolate::Scope isolate_scope(isolate);
|
1310
|
+
HandleScope handle_scope(isolate);
|
1311
|
+
|
1312
|
+
Local<Context> context = context_info->context->Get(isolate);
|
1313
|
+
Context::Scope context_scope(context);
|
1314
|
+
|
1315
|
+
Local<String> v8_str =
|
1316
|
+
String::NewFromUtf8(isolate, RSTRING_PTR(name),
|
1317
|
+
NewStringType::kNormal, (int)RSTRING_LEN(name))
|
1318
|
+
.ToLocalChecked();
|
1319
|
+
|
1320
|
+
// copy self so we can access from v8 external
|
1321
|
+
VALUE* self_copy;
|
1322
|
+
Data_Get_Struct(self, VALUE, self_copy);
|
1323
|
+
*self_copy = self;
|
1324
|
+
|
1325
|
+
Local<Value> external = External::New(isolate, self_copy);
|
1326
|
+
|
1327
|
+
if (parent_object == Qnil) {
|
1328
|
+
Maybe<bool> success = context->Global()->Set(
|
1329
|
+
context,
|
1330
|
+
v8_str,
|
1331
|
+
FunctionTemplate::New(isolate, ruby_callback, external)
|
1332
|
+
->GetFunction(context)
|
1333
|
+
.ToLocalChecked());
|
1334
|
+
(void)success;
|
1335
|
+
|
1336
|
+
} else {
|
1337
|
+
Local<String> eval =
|
1338
|
+
String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
|
1339
|
+
NewStringType::kNormal,
|
1340
|
+
(int)RSTRING_LEN(parent_object_eval))
|
1341
|
+
.ToLocalChecked();
|
1342
|
+
|
1343
|
+
MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
|
1344
|
+
if (parsed_script.IsEmpty()) {
|
1345
|
+
parse_error = true;
|
1346
|
+
} else {
|
1347
|
+
MaybeLocal<Value> maybe_value =
|
1348
|
+
parsed_script.ToLocalChecked()->Run(context);
|
1349
|
+
attach_error = true;
|
1350
|
+
|
1351
|
+
if (!maybe_value.IsEmpty()) {
|
1352
|
+
Local<Value> value = maybe_value.ToLocalChecked();
|
1353
|
+
if (value->IsObject()) {
|
1354
|
+
Maybe<bool> success = value.As<Object>()->Set(
|
1355
|
+
context,
|
1356
|
+
v8_str,
|
1357
|
+
FunctionTemplate::New(isolate, ruby_callback, external)
|
1358
|
+
->GetFunction(context)
|
1359
|
+
.ToLocalChecked());
|
1360
|
+
(void)success;
|
1361
|
+
attach_error = false;
|
1362
|
+
}
|
1363
|
+
}
|
1364
|
+
}
|
1365
|
+
}
|
837
1366
|
}
|
838
1367
|
|
839
1368
|
// always raise out of V8 context
|
840
1369
|
if (parse_error) {
|
841
|
-
|
1370
|
+
rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
|
842
1371
|
}
|
843
1372
|
|
844
1373
|
if (attach_error) {
|
845
|
-
|
1374
|
+
rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
|
846
1375
|
}
|
847
1376
|
|
848
1377
|
return Qnil;
|
849
1378
|
}
|
850
1379
|
|
851
|
-
|
1380
|
+
static VALUE rb_context_isolate_mutex(VALUE self) {
|
1381
|
+
ContextInfo* context_info;
|
1382
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
852
1383
|
|
853
|
-
if (isolate_info
|
854
|
-
|
1384
|
+
if (!context_info->isolate_info) {
|
1385
|
+
rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
|
855
1386
|
}
|
856
1387
|
|
857
|
-
|
858
|
-
|
859
|
-
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.\n");
|
860
|
-
} else {
|
1388
|
+
return context_info->isolate_info->mutex;
|
1389
|
+
}
|
861
1390
|
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
1391
|
+
IsolateInfo::~IsolateInfo() {
|
1392
|
+
if (isolate) {
|
1393
|
+
if (this->interrupted) {
|
1394
|
+
fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
|
1395
|
+
"it can not be disposed and memory will not be "
|
1396
|
+
"reclaimed till the Ruby process exits.\n");
|
1397
|
+
} else {
|
1398
|
+
if (this->pid != getpid() && !single_threaded) {
|
1399
|
+
fprintf(stderr, "WARNING: V8 isolate was forked, "
|
1400
|
+
"it can not be disposed and "
|
1401
|
+
"memory will not be reclaimed "
|
1402
|
+
"till the Ruby process exits.\n"
|
1403
|
+
"It is VERY likely your process will hang.\n"
|
1404
|
+
"If you wish to use v8 in forked environment "
|
1405
|
+
"please ensure the platform is initialized with:\n"
|
1406
|
+
"MiniRacer::Platform.set_flags! :single_threaded\n"
|
1407
|
+
);
|
1408
|
+
} else {
|
1409
|
+
isolate->Dispose();
|
1410
|
+
}
|
867
1411
|
}
|
868
|
-
|
1412
|
+
isolate = nullptr;
|
869
1413
|
}
|
870
1414
|
|
871
|
-
if (
|
872
|
-
delete[]
|
873
|
-
delete
|
1415
|
+
if (startup_data) {
|
1416
|
+
delete[] startup_data->data;
|
1417
|
+
delete startup_data;
|
874
1418
|
}
|
875
1419
|
|
876
|
-
delete
|
877
|
-
isolate_info->disposed = true;
|
1420
|
+
delete allocator;
|
878
1421
|
}
|
879
1422
|
|
880
|
-
void
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
1423
|
+
static void free_context_raw(void *arg) {
|
1424
|
+
ContextInfo* context_info = (ContextInfo*)arg;
|
1425
|
+
IsolateInfo* isolate_info = context_info->isolate_info;
|
1426
|
+
Persistent<Context>* context = context_info->context;
|
1427
|
+
|
1428
|
+
if (context && isolate_info && isolate_info->isolate) {
|
1429
|
+
Locker lock(isolate_info->isolate);
|
1430
|
+
v8::Isolate::Scope isolate_scope(isolate_info->isolate);
|
1431
|
+
context->Reset();
|
1432
|
+
delete context;
|
1433
|
+
}
|
1434
|
+
|
1435
|
+
if (isolate_info) {
|
1436
|
+
isolate_info->release();
|
888
1437
|
}
|
889
1438
|
|
890
|
-
|
1439
|
+
xfree(context_info);
|
891
1440
|
}
|
892
1441
|
|
1442
|
+
static void *free_context_thr(void* arg) {
|
1443
|
+
if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
|
1444
|
+
return NULL;
|
1445
|
+
}
|
1446
|
+
if (ruby_exiting) {
|
1447
|
+
return NULL;
|
1448
|
+
}
|
893
1449
|
|
894
|
-
|
1450
|
+
free_context_raw(arg);
|
895
1451
|
|
896
|
-
|
897
|
-
IsolateInfo* isolate_info = context_info->isolate_info;
|
1452
|
+
pthread_rwlock_unlock(&exit_lock);
|
898
1453
|
|
899
|
-
|
900
|
-
Locker lock(isolate_info->isolate);
|
901
|
-
v8::Isolate::Scope isolate_scope(isolate_info->isolate);
|
902
|
-
context_info->context->Reset();
|
903
|
-
delete context_info->context;
|
904
|
-
context_info->context = NULL;
|
905
|
-
}
|
1454
|
+
return NULL;
|
906
1455
|
}
|
907
1456
|
|
908
|
-
|
1457
|
+
// destroys everything except freeing the ContextInfo struct (see deallocate())
|
1458
|
+
static void free_context(ContextInfo* context_info) {
|
909
1459
|
|
910
|
-
ContextInfo* context_info = (ContextInfo*)data;
|
911
1460
|
IsolateInfo* isolate_info = context_info->isolate_info;
|
912
1461
|
|
913
|
-
|
1462
|
+
ContextInfo* context_info_copy = ALLOC(ContextInfo);
|
1463
|
+
context_info_copy->isolate_info = context_info->isolate_info;
|
1464
|
+
context_info_copy->context = context_info->context;
|
914
1465
|
|
915
|
-
if (isolate_info) {
|
916
|
-
|
917
|
-
|
1466
|
+
if (isolate_info && isolate_info->refs() > 1) {
|
1467
|
+
pthread_t free_context_thread;
|
1468
|
+
if (pthread_create(&free_context_thread, thread_attr_p,
|
1469
|
+
free_context_thr, (void*)context_info_copy)) {
|
1470
|
+
fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
|
1471
|
+
}
|
1472
|
+
} else {
|
1473
|
+
free_context_raw(context_info_copy);
|
918
1474
|
}
|
1475
|
+
|
1476
|
+
context_info->context = NULL;
|
1477
|
+
context_info->isolate_info = NULL;
|
919
1478
|
}
|
920
1479
|
|
921
|
-
void deallocate_isolate(void* data) {
|
1480
|
+
static void deallocate_isolate(void* data) {
|
922
1481
|
|
923
1482
|
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
924
1483
|
|
925
|
-
isolate_info->
|
926
|
-
|
1484
|
+
isolate_info->release();
|
1485
|
+
}
|
927
1486
|
|
928
|
-
|
929
|
-
|
930
|
-
|
1487
|
+
static void mark_isolate(void* data) {
|
1488
|
+
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
1489
|
+
isolate_info->mark();
|
931
1490
|
}
|
932
1491
|
|
933
|
-
void deallocate(void* data) {
|
1492
|
+
static void deallocate(void* data) {
|
934
1493
|
ContextInfo* context_info = (ContextInfo*)data;
|
935
|
-
IsolateInfo* isolate_info = context_info->isolate_info;
|
936
|
-
|
937
|
-
deallocate_context(data);
|
938
1494
|
|
939
|
-
|
940
|
-
xfree(isolate_info);
|
941
|
-
}
|
1495
|
+
free_context(context_info);
|
942
1496
|
|
943
1497
|
xfree(data);
|
944
1498
|
}
|
945
1499
|
|
1500
|
+
static void mark_context(void* data) {
|
1501
|
+
ContextInfo* context_info = (ContextInfo*)data;
|
1502
|
+
if (context_info->isolate_info) {
|
1503
|
+
context_info->isolate_info->mark();
|
1504
|
+
}
|
1505
|
+
}
|
946
1506
|
|
947
|
-
void deallocate_external_function(void * data) {
|
1507
|
+
static void deallocate_external_function(void * data) {
|
948
1508
|
xfree(data);
|
949
1509
|
}
|
950
1510
|
|
951
|
-
void deallocate_snapshot(void * data) {
|
1511
|
+
static void deallocate_snapshot(void * data) {
|
952
1512
|
SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
|
953
1513
|
delete[] snapshot_info->data;
|
954
1514
|
xfree(snapshot_info);
|
955
1515
|
}
|
956
1516
|
|
957
|
-
VALUE allocate_external_function(VALUE klass) {
|
1517
|
+
static VALUE allocate_external_function(VALUE klass) {
|
958
1518
|
VALUE* self = ALLOC(VALUE);
|
959
1519
|
return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
|
960
1520
|
}
|
961
1521
|
|
962
|
-
VALUE allocate(VALUE klass) {
|
1522
|
+
static VALUE allocate(VALUE klass) {
|
963
1523
|
ContextInfo* context_info = ALLOC(ContextInfo);
|
964
1524
|
context_info->isolate_info = NULL;
|
965
1525
|
context_info->context = NULL;
|
966
1526
|
|
967
|
-
return Data_Wrap_Struct(klass,
|
1527
|
+
return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
|
968
1528
|
}
|
969
1529
|
|
970
|
-
VALUE allocate_snapshot(VALUE klass) {
|
1530
|
+
static VALUE allocate_snapshot(VALUE klass) {
|
971
1531
|
SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
|
972
1532
|
snapshot_info->data = NULL;
|
973
1533
|
snapshot_info->raw_size = 0;
|
@@ -975,18 +1535,10 @@ VALUE allocate_snapshot(VALUE klass) {
|
|
975
1535
|
return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
|
976
1536
|
}
|
977
1537
|
|
978
|
-
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;
|
1538
|
+
static VALUE allocate_isolate(VALUE klass) {
|
1539
|
+
IsolateInfo* isolate_info = new IsolateInfo();
|
988
1540
|
|
989
|
-
return Data_Wrap_Struct(klass,
|
1541
|
+
return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
|
990
1542
|
}
|
991
1543
|
|
992
1544
|
static VALUE
|
@@ -1003,25 +1555,90 @@ rb_heap_stats(VALUE self) {
|
|
1003
1555
|
|
1004
1556
|
if (!isolate) {
|
1005
1557
|
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1558
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
|
1559
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
|
1560
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
|
1561
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
|
1562
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
|
1011
1563
|
|
1012
1564
|
} else {
|
1013
|
-
|
1565
|
+
isolate->GetHeapStatistics(&stats);
|
1014
1566
|
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1567
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
|
1568
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
|
1569
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
|
1570
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
|
1571
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
|
1020
1572
|
}
|
1021
1573
|
|
1022
1574
|
return rval;
|
1023
1575
|
}
|
1024
1576
|
|
1577
|
+
// https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
|
1578
|
+
class FileOutputStream : public OutputStream {
|
1579
|
+
public:
|
1580
|
+
FileOutputStream(FILE* stream) : stream_(stream) {}
|
1581
|
+
|
1582
|
+
virtual int GetChunkSize() {
|
1583
|
+
return 65536;
|
1584
|
+
}
|
1585
|
+
|
1586
|
+
virtual void EndOfStream() {}
|
1587
|
+
|
1588
|
+
virtual WriteResult WriteAsciiChunk(char* data, int size) {
|
1589
|
+
const size_t len = static_cast<size_t>(size);
|
1590
|
+
size_t off = 0;
|
1591
|
+
|
1592
|
+
while (off < len && !feof(stream_) && !ferror(stream_))
|
1593
|
+
off += fwrite(data + off, 1, len - off, stream_);
|
1594
|
+
|
1595
|
+
return off == len ? kContinue : kAbort;
|
1596
|
+
}
|
1597
|
+
|
1598
|
+
private:
|
1599
|
+
FILE* stream_;
|
1600
|
+
};
|
1601
|
+
|
1602
|
+
|
1603
|
+
static VALUE
|
1604
|
+
rb_heap_snapshot(VALUE self, VALUE file) {
|
1605
|
+
|
1606
|
+
rb_io_t *fptr;
|
1607
|
+
|
1608
|
+
fptr = RFILE(file)->fptr;
|
1609
|
+
|
1610
|
+
if (!fptr) return Qfalse;
|
1611
|
+
|
1612
|
+
FILE* fp;
|
1613
|
+
fp = fdopen(fptr->fd, "w");
|
1614
|
+
if (fp == NULL) return Qfalse;
|
1615
|
+
|
1616
|
+
|
1617
|
+
ContextInfo* context_info;
|
1618
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1619
|
+
Isolate* isolate;
|
1620
|
+
isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
|
1621
|
+
|
1622
|
+
if (!isolate) return Qfalse;
|
1623
|
+
|
1624
|
+
Locker lock(isolate);
|
1625
|
+
Isolate::Scope isolate_scope(isolate);
|
1626
|
+
HandleScope handle_scope(isolate);
|
1627
|
+
|
1628
|
+
HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
|
1629
|
+
|
1630
|
+
const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
|
1631
|
+
|
1632
|
+
FileOutputStream stream(fp);
|
1633
|
+
snap->Serialize(&stream, HeapSnapshot::kJSON);
|
1634
|
+
|
1635
|
+
fflush(fp);
|
1636
|
+
|
1637
|
+
const_cast<HeapSnapshot*>(snap)->Delete();
|
1638
|
+
|
1639
|
+
return Qtrue;
|
1640
|
+
}
|
1641
|
+
|
1025
1642
|
static VALUE
|
1026
1643
|
rb_context_stop(VALUE self) {
|
1027
1644
|
|
@@ -1031,9 +1648,9 @@ rb_context_stop(VALUE self) {
|
|
1031
1648
|
Isolate* isolate = context_info->isolate_info->isolate;
|
1032
1649
|
|
1033
1650
|
// flag for termination
|
1034
|
-
isolate
|
1651
|
+
IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
|
1035
1652
|
|
1036
|
-
|
1653
|
+
isolate->TerminateExecution();
|
1037
1654
|
rb_funcall(self, rb_intern("stop_attached"), 0);
|
1038
1655
|
|
1039
1656
|
return Qnil;
|
@@ -1047,61 +1664,244 @@ rb_context_dispose(VALUE self) {
|
|
1047
1664
|
|
1048
1665
|
free_context(context_info);
|
1049
1666
|
|
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
1667
|
return Qnil;
|
1056
1668
|
}
|
1057
1669
|
|
1058
|
-
|
1670
|
+
static void*
|
1671
|
+
nogvl_context_call(void *args) {
|
1059
1672
|
|
1060
|
-
|
1673
|
+
FunctionCall *call = (FunctionCall *) args;
|
1674
|
+
if (!call) {
|
1675
|
+
return NULL;
|
1676
|
+
}
|
1677
|
+
IsolateInfo *isolate_info = call->context_info->isolate_info;
|
1678
|
+
Isolate* isolate = isolate_info->isolate;
|
1679
|
+
|
1680
|
+
IsolateData::Set(isolate, IsolateData::IN_GVL, false);
|
1681
|
+
IsolateData::Set(isolate, IsolateData::DO_TERMINATE, false);
|
1682
|
+
|
1683
|
+
if (call->max_memory > 0) {
|
1684
|
+
IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_MAX, call->max_memory);
|
1685
|
+
IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, false);
|
1686
|
+
if (!isolate_info->added_gc_cb) {
|
1687
|
+
isolate->AddGCEpilogueCallback(gc_callback);
|
1688
|
+
isolate_info->added_gc_cb = true;
|
1689
|
+
}
|
1690
|
+
}
|
1691
|
+
|
1692
|
+
if (call->marshal_stackdepth > 0) {
|
1693
|
+
StackCounter::SetMax(isolate, call->marshal_stackdepth);
|
1694
|
+
}
|
1695
|
+
|
1696
|
+
Isolate::Scope isolate_scope(isolate);
|
1697
|
+
EscapableHandleScope handle_scope(isolate);
|
1698
|
+
TryCatch trycatch(isolate);
|
1699
|
+
|
1700
|
+
Local<Context> context = call->context_info->context->Get(isolate);
|
1701
|
+
Context::Scope context_scope(context);
|
1702
|
+
|
1703
|
+
Local<Function> fun = call->fun;
|
1704
|
+
|
1705
|
+
EvalResult& eval_res = call->result;
|
1706
|
+
eval_res.parsed = true;
|
1707
|
+
|
1708
|
+
MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
|
1709
|
+
prepare_result(res, trycatch, isolate, context, eval_res);
|
1710
|
+
|
1711
|
+
IsolateData::Set(isolate, IsolateData::IN_GVL, true);
|
1712
|
+
|
1713
|
+
return NULL;
|
1714
|
+
}
|
1715
|
+
|
1716
|
+
static void unblock_function(void *args) {
|
1717
|
+
FunctionCall *call = (FunctionCall *) args;
|
1718
|
+
call->context_info->isolate_info->interrupted = true;
|
1719
|
+
}
|
1720
|
+
|
1721
|
+
static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
|
1722
|
+
ContextInfo* context_info;
|
1723
|
+
FunctionCall call;
|
1724
|
+
VALUE *call_argv = NULL;
|
1725
|
+
|
1726
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1727
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
1728
|
+
|
1729
|
+
if (argc < 1) {
|
1730
|
+
rb_raise(rb_eArgError, "need at least one argument %d", argc);
|
1731
|
+
}
|
1732
|
+
|
1733
|
+
VALUE function_name = argv[0];
|
1734
|
+
if (TYPE(function_name) != T_STRING) {
|
1735
|
+
rb_raise(rb_eTypeError, "first argument should be a String");
|
1736
|
+
}
|
1737
|
+
|
1738
|
+
char *fname = RSTRING_PTR(function_name);
|
1739
|
+
if (!fname) {
|
1740
|
+
return Qnil;
|
1741
|
+
}
|
1742
|
+
|
1743
|
+
call.context_info = context_info;
|
1744
|
+
call.error = false;
|
1745
|
+
call.function_name = fname;
|
1746
|
+
call.argc = argc - 1;
|
1747
|
+
call.argv = NULL;
|
1748
|
+
if (call.argc > 0) {
|
1749
|
+
// skip first argument which is the function name
|
1750
|
+
call_argv = argv + 1;
|
1751
|
+
}
|
1752
|
+
|
1753
|
+
call.max_memory = 0;
|
1754
|
+
VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
|
1755
|
+
if (mem_softlimit != Qnil) {
|
1756
|
+
unsigned long sl_int = NUM2ULONG(mem_softlimit);
|
1757
|
+
call.max_memory = (size_t)sl_int;
|
1758
|
+
}
|
1759
|
+
|
1760
|
+
call.marshal_stackdepth = 0;
|
1761
|
+
VALUE marshal_stackdepth = rb_iv_get(self, "@marshal_stack_depth");
|
1762
|
+
if (marshal_stackdepth != Qnil) {
|
1763
|
+
unsigned long sl_int = NUM2ULONG(marshal_stackdepth);
|
1764
|
+
call.marshal_stackdepth = (size_t)sl_int;
|
1765
|
+
}
|
1766
|
+
|
1767
|
+
bool missingFunction = false;
|
1061
1768
|
{
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1769
|
+
Locker lock(isolate);
|
1770
|
+
Isolate::Scope isolate_scope(isolate);
|
1771
|
+
HandleScope handle_scope(isolate);
|
1772
|
+
|
1773
|
+
Local<Context> context = context_info->context->Get(isolate);
|
1774
|
+
Context::Scope context_scope(context);
|
1775
|
+
|
1776
|
+
// examples of such usage can be found in
|
1777
|
+
// https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
|
1778
|
+
MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
|
1779
|
+
MaybeLocal<v8::Value> val;
|
1780
|
+
if (!fname.IsEmpty()) {
|
1781
|
+
val = context->Global()->Get(context, fname.ToLocalChecked());
|
1782
|
+
}
|
1067
1783
|
|
1068
|
-
|
1784
|
+
if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
|
1785
|
+
missingFunction = true;
|
1786
|
+
} else {
|
1069
1787
|
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1788
|
+
Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
|
1789
|
+
call.fun = fun;
|
1790
|
+
int fun_argc = call.argc;
|
1791
|
+
|
1792
|
+
if (fun_argc > 0) {
|
1793
|
+
call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
|
1794
|
+
if (!call.argv) {
|
1795
|
+
return Qnil;
|
1796
|
+
}
|
1797
|
+
for(int i=0; i < fun_argc; i++) {
|
1798
|
+
call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
|
1799
|
+
}
|
1800
|
+
}
|
1801
|
+
rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
|
1802
|
+
free(call.argv);
|
1075
1803
|
|
1076
|
-
|
1077
|
-
|
1078
|
-
rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
|
1079
|
-
rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
|
1080
|
-
rb_mJSON = rb_define_module("JSON");
|
1804
|
+
}
|
1805
|
+
}
|
1081
1806
|
|
1082
|
-
|
1807
|
+
if (missingFunction) {
|
1808
|
+
rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
|
1809
|
+
}
|
1083
1810
|
|
1084
|
-
|
1085
|
-
|
1086
|
-
rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
|
1811
|
+
return convert_result_to_ruby(self, call.result);
|
1812
|
+
}
|
1087
1813
|
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1814
|
+
static VALUE rb_context_create_isolate_value(VALUE self) {
|
1815
|
+
ContextInfo* context_info;
|
1816
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1817
|
+
IsolateInfo *isolate_info = context_info->isolate_info;
|
1818
|
+
|
1819
|
+
if (!isolate_info) {
|
1820
|
+
return Qnil;
|
1821
|
+
}
|
1091
1822
|
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
1823
|
+
isolate_info->hold();
|
1824
|
+
return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
|
1825
|
+
}
|
1096
1826
|
|
1097
|
-
|
1098
|
-
|
1099
|
-
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
|
1827
|
+
static void set_ruby_exiting(VALUE value) {
|
1828
|
+
(void)value;
|
1100
1829
|
|
1101
|
-
|
1102
|
-
rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
|
1830
|
+
int res = pthread_rwlock_wrlock(&exit_lock);
|
1103
1831
|
|
1104
|
-
|
1832
|
+
ruby_exiting = true;
|
1833
|
+
if (res == 0) {
|
1834
|
+
pthread_rwlock_unlock(&exit_lock);
|
1105
1835
|
}
|
1836
|
+
}
|
1106
1837
|
|
1838
|
+
extern "C" {
|
1839
|
+
|
1840
|
+
__attribute__((visibility("default"))) void Init_mini_racer_extension ( void )
|
1841
|
+
{
|
1842
|
+
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
|
1843
|
+
rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
1844
|
+
rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
|
1845
|
+
rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
|
1846
|
+
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
|
1847
|
+
|
1848
|
+
VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
|
1849
|
+
|
1850
|
+
VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
|
1851
|
+
rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
|
1852
|
+
rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
|
1853
|
+
rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
|
1854
|
+
rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
|
1855
|
+
|
1856
|
+
rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
|
1857
|
+
rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
|
1858
|
+
rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
|
1859
|
+
rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
|
1860
|
+
rb_mJSON = rb_define_module("JSON");
|
1861
|
+
|
1862
|
+
VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
|
1863
|
+
|
1864
|
+
rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
|
1865
|
+
rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
|
1866
|
+
rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
|
1867
|
+
rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
|
1868
|
+
|
1869
|
+
rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
|
1870
|
+
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
1871
|
+
rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
|
1872
|
+
rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
|
1873
|
+
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
|
1874
|
+
|
1875
|
+
rb_define_alloc_func(rb_cContext, allocate);
|
1876
|
+
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
1877
|
+
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
1878
|
+
|
1879
|
+
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
1880
|
+
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
1881
|
+
|
1882
|
+
rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
|
1883
|
+
rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
|
1884
|
+
rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
|
1885
|
+
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
|
1886
|
+
|
1887
|
+
rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
|
1888
|
+
rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
|
1889
|
+
rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
|
1890
|
+
rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
|
1891
|
+
|
1892
|
+
rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
|
1893
|
+
|
1894
|
+
rb_set_end_proc(set_ruby_exiting, Qnil);
|
1895
|
+
|
1896
|
+
static pthread_attr_t attr;
|
1897
|
+
if (pthread_attr_init(&attr) == 0) {
|
1898
|
+
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
|
1899
|
+
thread_attr_p = &attr;
|
1900
|
+
}
|
1901
|
+
}
|
1902
|
+
auto on_fork_for_child = []() {
|
1903
|
+
exit_lock = PTHREAD_RWLOCK_INITIALIZER;
|
1904
|
+
};
|
1905
|
+
pthread_atfork(nullptr, nullptr, on_fork_for_child);
|
1906
|
+
}
|
1107
1907
|
}
|