mini_racer 0.1.15 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }