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.
@@ -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
- typedef struct {
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 disposed;
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 when destroying
29
- // objects, Ruby will destroy ruby objects first, then call the
30
- // extenstion's deallocators. In this case, that means it would
31
- // call `deallocate_isolate` _before_ `deallocate`, causing a segfault
32
- volatile int refs_count;
33
- } IsolateInfo;
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
- enum IsolateFlags {
60
- IN_GVL,
61
- DO_TERMINATE,
62
- MEM_SOFTLIMIT_VALUE,
63
- MEM_SOFTLIMIT_REACHED,
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* current_platform = NULL;
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::CreateDefaultPlatform();
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((bool)isolate->GetData(MEM_SOFTLIMIT_REACHED)) return;
348
+ if (IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED)) {
349
+ return;
350
+ }
120
351
 
121
- size_t softlimit = *(size_t*) isolate->GetData(MEM_SOFTLIMIT_VALUE);
352
+ size_t softlimit = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_MAX);
122
353
 
123
- HeapStatistics* stats = new HeapStatistics();
124
- isolate->GetHeapStatistics(stats);
125
- size_t used = stats->used_heap_size();
354
+ HeapStatistics stats;
355
+ isolate->GetHeapStatistics(&stats);
356
+ size_t used = stats.used_heap_size();
126
357
 
127
358
  if(used > softlimit) {
128
- isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)true);
129
- V8::TerminateExecution(isolate);
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
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
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
- // in gvl flag
148
- isolate->SetData(IN_GVL, (void*)false);
149
- // terminate ASAP
150
- isolate->SetData(DO_TERMINATE, (void*)false);
151
- // Memory softlimit
152
- isolate->SetData(MEM_SOFTLIMIT_VALUE, (void*)false);
153
- // Memory softlimit hit flag
154
- isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
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
- origin = new v8::ScriptOrigin(*eval_params->filename);
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
- delete origin;
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
- result->message = new Persistent<Value>();
176
- result->message->Reset(isolate, trycatch.Exception());
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
- if(eval_params->max_memory > 0) {
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
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
512
+ prepare_result(maybe_value, trycatch, isolate, context, *result);
185
513
 
186
- result->executed = !maybe_value.IsEmpty();
514
+ IsolateData::Set(isolate, IsolateData::IN_GVL, true);
187
515
 
188
- if (result->executed) {
516
+ return NULL;
517
+ }
189
518
 
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();
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
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
198
- .As<Function>();
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
- 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);
529
+ Isolate::Scope isolate_scope(isolate);
530
+ HandleScope scope(isolate);
204
531
 
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
- }
532
+ StackCounter stackCounter(isolate);
213
533
 
214
- } else {
215
- Persistent<Value>* persistent = new Persistent<Value>();
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 (!result->executed || !result->parsed) {
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
- }
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
- return Qnil;
545
+ return Qnil;
264
546
  }
265
547
 
266
548
  if (value->IsInt32()) {
267
- return INT2FIX(value->Int32Value());
549
+ return INT2FIX(value->Int32Value(context).ToChecked());
268
550
  }
269
551
 
270
552
  if (value->IsNumber()) {
271
- return rb_float_new(value->NumberValue());
553
+ return rb_float_new(value->NumberValue(context).ToChecked());
272
554
  }
273
555
 
274
556
  if (value->IsTrue()) {
275
- return Qtrue;
557
+ return Qtrue;
276
558
  }
277
559
 
278
560
  if (value->IsFalse()) {
279
- return Qfalse;
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
- Local<Value> element = arr->Get(i);
287
- VALUE rb_elem = convert_v8_to_ruby(isolate, element);
288
- if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
289
- return rb_elem;
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
- return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
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
- VALUE rb_hash = rb_hash_new();
310
- TryCatch trycatch(isolate);
311
- Local<Context> context = Context::New(isolate);
312
-
313
- Local<Object> object = value->ToObject();
314
- MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
315
- if (!maybe_props.IsEmpty()) {
316
- Local<Array> props = maybe_props.ToLocalChecked();
317
- for(uint32_t i=0; i < props->Length(); i++) {
318
- Local<Value> key = props->Get(i);
319
- VALUE rb_key = convert_v8_to_ruby(isolate, key);
320
- Local<Value> value = object->Get(key);
321
- // this may have failed due to Get raising
322
-
323
- if (trycatch.HasCaught()) {
324
- // TODO isolate code that translates execption to ruby
325
- // exception so we can properly return it
326
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
327
- }
328
-
329
- VALUE rb_value = convert_v8_to_ruby(isolate, value);
330
- rb_hash_aset(rb_hash, rb_key, rb_value);
331
- }
332
- }
333
- return rb_hash;
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
- Local<String> rstr = value->ToString();
337
- return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
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 Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
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
- array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
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
- object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
384
- convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
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(isolate, NUM2DBL(value) * 1000));
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::NewFromUtf8(isolate, "Undefined Conversion"));
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 = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
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 rb_snapshot_warmup(VALUE self, VALUE str) {
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 = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
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
- static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
470
- IsolateInfo* isolate_info;
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 = isolate_info->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, sizeof(char) * raw_size);
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
- isolate_info->startup_data = startup_data;
499
- isolate_info->isolate = Isolate::New(create_params);
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
- return isolate_info->isolate->IdleNotification(NUM2INT(idle_time_in_ms)) ? Qtrue : Qfalse;
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 rb_context_init_with_isolate(VALUE self, VALUE isolate) {
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
- Data_Get_Struct(isolate, IsolateInfo, isolate_info);
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->refs_count++;
978
+ isolate_info->hold();
522
979
 
523
980
  {
524
- Locker lock(isolate_info->isolate);
525
- Isolate::Scope isolate_scope(isolate_info->isolate);
526
- HandleScope handle_scope(isolate_info->isolate);
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
- Local<Context> context = Context::New(isolate_info->isolate);
987
+ Local<Context> context = Context::New(isolate_info->isolate);
529
988
 
530
- context_info->context = new Persistent<Context>();
531
- context_info->context->Reset(isolate_info->isolate, context);
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 rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
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
- EvalParams eval_params;
545
- EvalResult eval_result;
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
- Locker lock(isolate);
557
- Isolate::Scope isolate_scope(isolate);
558
- HandleScope handle_scope(isolate);
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
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
561
- NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
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
- Local<String> local_filename;
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
- if (filename != Qnil) {
566
- local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
567
- NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
568
- eval_params.filename = &local_filename;
569
- } else {
570
- eval_params.filename = NULL;
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
- eval_params.context_info = context_info;
574
- eval_params.eval = &eval;
575
- eval_params.result = &eval_result;
576
- eval_params.timeout = 0;
577
- eval_params.max_memory = 0;
578
- VALUE timeout = rb_iv_get(self, "@timeout");
579
- if (timeout != Qnil) {
580
- eval_params.timeout = (useconds_t)NUM2LONG(timeout);
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
- VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
584
- if (mem_softlimit != Qnil) {
585
- eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
586
- }
1067
+ VALUE ret = Qnil;
587
1068
 
588
- eval_result.message = NULL;
589
- eval_result.backtrace = NULL;
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
- rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
1075
+ Local<Value> tmp = Local<Value>::New(isolate, *result.value);
592
1076
 
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
- }
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
- 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
- }
1086
+ result.value->Reset();
1087
+ delete result.value;
606
1088
  }
607
1089
 
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
- }
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
- 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
- }
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
- 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
- }
1118
+ Locker lock(isolate);
1119
+ Isolate::Scope isolate_scope(isolate);
1120
+ HandleScope handle_scope(isolate);
658
1121
 
659
- eval_result.value->Reset();
660
- delete eval_result.value;
661
- }
1122
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
1123
+ NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
662
1124
 
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
- }
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
- return result;
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* args;
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
- result = rb_funcall2(data->callback, rb_intern("call"), data->length, data->args);
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
- result = rb_funcall(data->callback, rb_intern("call"), 0);
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* ruby_args = NULL;
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
- HandleScope scope(args->GetIsolate());
711
- Handle<External> external = Handle<External>::Cast(args->Data());
1206
+ HandleScope scope(args->GetIsolate());
1207
+ Local<External> external = Local<External>::Cast(args->Data());
712
1208
 
713
- VALUE* self_pointer = (VALUE*)(external->Value());
714
- self = *self_pointer;
715
- callback = rb_iv_get(self, "@callback");
1209
+ self = *(VALUE*)(external->Value());
1210
+ callback = rb_iv_get(self, "@callback");
716
1211
 
717
- if (length > 0) {
718
- ruby_args = ALLOC_N(VALUE, length);
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
- for (int i = 0; i < length; i++) {
723
- Local<Value> value = ((*args)[i]).As<Value>();
724
- ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
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.args = ruby_args;
1236
+ callback_data.ruby_args = ruby_args;
733
1237
  callback_data.failed = false;
734
1238
 
735
- if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
736
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
737
- V8::TerminateExecution(args->GetIsolate());
738
- return NULL;
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
- result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
742
- (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
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
- VALUE parent = rb_iv_get(self, "@parent");
746
- rb_iv_set(parent, "@current_exception", result);
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
- HandleScope scope(args->GetIsolate());
751
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
752
- args->GetReturnValue().Set(v8_result);
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
- xfree(ruby_args);
1268
+ rb_ary_clear(ruby_args);
1269
+ rb_gc_force_recycle(ruby_args);
757
1270
  }
758
1271
 
759
- if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
760
- Isolate* isolate = args->GetIsolate();
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
- gvl_ruby_callback((void*)&args);
1283
+ gvl_ruby_callback((void*)&args);
773
1284
  } else {
774
- args.GetIsolate()->SetData(IN_GVL, (void*)true);
775
- rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
776
- args.GetIsolate()->SetData(IN_GVL, (void*)false);
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
- Locker lock(isolate);
798
- Isolate::Scope isolate_scope(isolate);
799
- HandleScope handle_scope(isolate);
800
-
801
- Local<Context> context = context_info->context->Get(isolate);
802
- Context::Scope context_scope(context);
803
-
804
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
805
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
806
-
807
- // copy self so we can access from v8 external
808
- VALUE* self_copy;
809
- Data_Get_Struct(self, VALUE, self_copy);
810
- *self_copy = self;
811
-
812
- Local<Value> external = External::New(isolate, self_copy);
813
-
814
- if (parent_object == Qnil) {
815
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
816
- } else {
817
-
818
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
819
- NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
820
-
821
- MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
822
- if (parsed_script.IsEmpty()) {
823
- parse_error = true;
824
- } else {
825
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
826
- attach_error = true;
827
-
828
- if (!maybe_value.IsEmpty()) {
829
- Local<Value> value = maybe_value.ToLocalChecked();
830
- if (value->IsObject()){
831
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
832
- attach_error = false;
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
- rb_raise(rb_eParseError, "Invalid object %s", RSTRING_PTR(parent_object));
1370
+ rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
842
1371
  }
843
1372
 
844
1373
  if (attach_error) {
845
- rb_raise(rb_eParseError, "Was expecting %s to be an object", RSTRING_PTR(parent_object));
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
- void free_isolate(IsolateInfo* isolate_info) {
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->isolate) {
854
- Locker lock(isolate_info->isolate);
1384
+ if (!context_info->isolate_info) {
1385
+ rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
855
1386
  }
856
1387
 
857
- if (isolate_info->isolate) {
858
- if (isolate_info->interrupted) {
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
- if (isolate_info->pid != getpid()) {
863
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
864
- } else {
865
- isolate_info->isolate->Dispose();
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
- isolate_info->isolate = NULL;
1412
+ isolate = nullptr;
869
1413
  }
870
1414
 
871
- if (isolate_info->startup_data) {
872
- delete[] isolate_info->startup_data->data;
873
- delete isolate_info->startup_data;
1415
+ if (startup_data) {
1416
+ delete[] startup_data->data;
1417
+ delete startup_data;
874
1418
  }
875
1419
 
876
- delete isolate_info->allocator;
877
- isolate_info->disposed = true;
1420
+ delete allocator;
878
1421
  }
879
1422
 
880
- void maybe_free_isolate(IsolateInfo* isolate_info) {
881
- // an isolate can only be freed if no Isolate or Context (ruby) object
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;
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
- free_isolate(isolate_info);
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
- void free_context(void* data) {
1450
+ free_context_raw(arg);
895
1451
 
896
- ContextInfo* context_info = (ContextInfo*)data;
897
- IsolateInfo* isolate_info = context_info->isolate_info;
1452
+ pthread_rwlock_unlock(&exit_lock);
898
1453
 
899
- if (context_info->context && isolate_info && isolate_info->isolate) {
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
- void deallocate_context(void* data) {
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
- free_context(data);
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
- isolate_info->refs_count--;
917
- maybe_free_isolate(isolate_info);
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->refs_count--;
926
- maybe_free_isolate(isolate_info);
1484
+ isolate_info->release();
1485
+ }
927
1486
 
928
- if (isolate_info->refs_count == 0) {
929
- xfree(isolate_info);
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
- if (isolate_info && isolate_info->refs_count == 0) {
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, NULL, deallocate, (void*)context_info);
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 = ALLOC(IsolateInfo);
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, NULL, deallocate_isolate, (void*)isolate_info);
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
- rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
1007
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
1008
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
1009
- rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
1010
- rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
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
- isolate->GetHeapStatistics(&stats);
1565
+ isolate->GetHeapStatistics(&stats);
1014
1566
 
1015
- rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
1016
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
1017
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
1018
- rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
1019
- rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
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->SetData(DO_TERMINATE, (void*)true);
1651
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
1035
1652
 
1036
- V8::TerminateExecution(isolate);
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
- extern "C" {
1670
+ static void*
1671
+ nogvl_context_call(void *args) {
1059
1672
 
1060
- void Init_mini_racer_extension ( void )
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
- VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1063
- VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1064
- VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1065
- VALUE rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1066
- VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
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
- VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
1784
+ if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1785
+ missingFunction = true;
1786
+ } else {
1069
1787
 
1070
- VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
1071
- rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
1072
- rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
1073
- rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
1074
- rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
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
- rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1077
- rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
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
- VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
1807
+ if (missingFunction) {
1808
+ rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1809
+ }
1083
1810
 
1084
- rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1085
- rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
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
- rb_define_alloc_func(rb_cContext, allocate);
1089
- rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1090
- rb_define_alloc_func(rb_cIsolate, allocate_isolate);
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
- 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
- rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
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
- rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1098
- rb_define_method(rb_cSnapshot, "warmup!", (VALUE(*)(...))&rb_snapshot_warmup, 1);
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
- rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
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
- rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
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
  }