mini_racer 0.2.4 → 0.4.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,7 +1,9 @@
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>
@@ -23,6 +25,7 @@ public:
23
25
  ArrayBuffer::Allocator* allocator;
24
26
  StartupData* startup_data;
25
27
  bool interrupted;
28
+ bool added_gc_cb;
26
29
  pid_t pid;
27
30
  VALUE mutex;
28
31
 
@@ -40,15 +43,12 @@ public:
40
43
 
41
44
 
42
45
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
43
- interrupted(false), pid(getpid()), refs_count(0) {
46
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
44
47
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
45
48
  mutex = rb_class_new_instance(0, nullptr, cMutex);
46
49
  }
47
50
 
48
- ~IsolateInfo() {
49
- void free_isolate(IsolateInfo*);
50
- free_isolate(this);
51
- }
51
+ ~IsolateInfo();
52
52
 
53
53
  void init(SnapshotInfo* snapshot_info = nullptr);
54
54
 
@@ -71,7 +71,7 @@ public:
71
71
  }
72
72
 
73
73
  int refs() {
74
- return refs_count;
74
+ return refs_count;
75
75
  }
76
76
 
77
77
  static void* operator new(size_t size) {
@@ -125,6 +125,7 @@ typedef struct {
125
125
  Local<Function> fun;
126
126
  Local<Value> *argv;
127
127
  EvalResult result;
128
+ size_t max_memory;
128
129
  } FunctionCall;
129
130
 
130
131
  enum IsolateFlags {
@@ -150,9 +151,14 @@ static VALUE rb_mJSON;
150
151
  static VALUE rb_cFailedV8Conversion;
151
152
  static VALUE rb_cDateTime = Qnil;
152
153
 
153
- static Platform* current_platform = NULL;
154
+ static std::unique_ptr<Platform> current_platform = NULL;
154
155
  static std::mutex platform_lock;
155
156
 
157
+ static pthread_attr_t *thread_attr_p;
158
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
159
+ static bool ruby_exiting = false; // guarded by exit_lock
160
+ static bool single_threaded = false;
161
+
156
162
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
157
163
  bool platform_already_initialized = false;
158
164
 
@@ -164,6 +170,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
164
170
  platform_lock.lock();
165
171
 
166
172
  if (current_platform == NULL) {
173
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
174
+ single_threaded = true;
175
+ }
167
176
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
168
177
  } else {
169
178
  platform_already_initialized = true;
@@ -187,8 +196,8 @@ static void init_v8() {
187
196
 
188
197
  if (current_platform == NULL) {
189
198
  V8::InitializeICU();
190
- current_platform = platform::CreateDefaultPlatform();
191
- V8::InitializePlatform(current_platform);
199
+ current_platform = platform::NewDefaultPlatform();
200
+ V8::InitializePlatform(current_platform.get());
192
201
  V8::Initialize();
193
202
  }
194
203
 
@@ -200,9 +209,9 @@ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
200
209
 
201
210
  size_t softlimit = *(size_t*) isolate->GetData(MEM_SOFTLIMIT_VALUE);
202
211
 
203
- HeapStatistics* stats = new HeapStatistics();
204
- isolate->GetHeapStatistics(stats);
205
- size_t used = stats->used_heap_size();
212
+ HeapStatistics stats;
213
+ isolate->GetHeapStatistics(&stats);
214
+ size_t used = stats.used_heap_size();
206
215
 
207
216
  if(used > softlimit) {
208
217
  isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)true);
@@ -231,15 +240,17 @@ static void prepare_result(MaybeLocal<Value> v8res,
231
240
  if ((local_value->IsObject() || local_value->IsArray()) &&
232
241
  !local_value->IsDate() && !local_value->IsFunction()) {
233
242
  Local<Object> JSON = context->Global()->Get(
234
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
243
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
244
+ .ToLocalChecked().As<Object>();
235
245
 
236
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
237
- .As<Function>();
246
+ Local<Function> stringify = JSON->Get(
247
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
248
+ .ToLocalChecked().As<Function>();
238
249
 
239
- Local<Object> object = local_value->ToObject();
250
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
240
251
  const unsigned argc = 1;
241
252
  Local<Value> argv[argc] = { object };
242
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
253
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
243
254
 
244
255
  if (json.IsEmpty()) {
245
256
  evalRes.executed = false;
@@ -263,11 +274,21 @@ static void prepare_result(MaybeLocal<Value> v8res,
263
274
  evalRes.message = new Persistent<Value>();
264
275
  Local<Message> message = trycatch.Message();
265
276
  char buf[1000];
266
- int len;
267
- len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
268
- *String::Utf8Value(message->GetScriptResourceName()->ToString()),
269
- message->GetLineNumber(),
270
- message->GetStartColumn());
277
+ int len, line, column;
278
+
279
+ if (!message->GetLineNumber(context).To(&line)) {
280
+ line = 0;
281
+ }
282
+
283
+ if (!message->GetStartColumn(context).To(&column)) {
284
+ column = 0;
285
+ }
286
+
287
+ len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
288
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
289
+ line,
290
+ column);
291
+
271
292
  if ((size_t) len >= sizeof(buf)) {
272
293
  len = sizeof(buf) - 1;
273
294
  buf[len] = '\0';
@@ -278,12 +299,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
278
299
  } else if(trycatch.HasTerminated()) {
279
300
  evalRes.terminated = true;
280
301
  evalRes.message = new Persistent<Value>();
281
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
302
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
282
303
  evalRes.message->Reset(isolate, tmp);
283
304
  }
284
- if (!trycatch.StackTrace().IsEmpty()) {
305
+ if (!trycatch.StackTrace(context).IsEmpty()) {
285
306
  evalRes.backtrace = new Persistent<Value>();
286
- evalRes.backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
307
+ evalRes.backtrace->Reset(isolate,
308
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
287
309
  }
288
310
  }
289
311
  }
@@ -294,7 +316,8 @@ nogvl_context_eval(void* arg) {
294
316
 
295
317
  EvalParams* eval_params = (EvalParams*)arg;
296
318
  EvalResult* result = eval_params->result;
297
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
319
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
320
+ Isolate* isolate = isolate_info->isolate;
298
321
 
299
322
  Isolate::Scope isolate_scope(isolate);
300
323
  HandleScope handle_scope(isolate);
@@ -315,13 +338,13 @@ nogvl_context_eval(void* arg) {
315
338
  MaybeLocal<Script> parsed_script;
316
339
 
317
340
  if (eval_params->filename) {
318
- origin = new v8::ScriptOrigin(*eval_params->filename);
341
+ origin = new v8::ScriptOrigin(*eval_params->filename);
319
342
  }
320
343
 
321
344
  parsed_script = Script::Compile(context, *eval_params->eval, origin);
322
345
 
323
346
  if (origin) {
324
- delete origin;
347
+ delete origin;
325
348
  }
326
349
 
327
350
  result->parsed = !parsed_script.IsEmpty();
@@ -332,13 +355,16 @@ nogvl_context_eval(void* arg) {
332
355
 
333
356
  MaybeLocal<Value> maybe_value;
334
357
  if (!result->parsed) {
335
- result->message = new Persistent<Value>();
336
- result->message->Reset(isolate, trycatch.Exception());
358
+ result->message = new Persistent<Value>();
359
+ result->message->Reset(isolate, trycatch.Exception());
337
360
  } else {
338
361
  // parsing successful
339
362
  if (eval_params->max_memory > 0) {
340
363
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
364
+ if (!isolate_info->added_gc_cb) {
341
365
  isolate->AddGCEpilogueCallback(gc_callback);
366
+ isolate_info->added_gc_cb = true;
367
+ }
342
368
  }
343
369
 
344
370
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -351,6 +377,12 @@ nogvl_context_eval(void* arg) {
351
377
  return NULL;
352
378
  }
353
379
 
380
+ static VALUE new_empty_failed_conv_obj() {
381
+ // TODO isolate code that translates execption to ruby
382
+ // exception so we can properly return it
383
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
384
+ }
385
+
354
386
  // assumes isolate locking is in place
355
387
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
356
388
  Local<Value> value) {
@@ -359,41 +391,44 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
359
391
  HandleScope scope(isolate);
360
392
 
361
393
  if (value->IsNull() || value->IsUndefined()){
362
- return Qnil;
394
+ return Qnil;
363
395
  }
364
396
 
365
397
  if (value->IsInt32()) {
366
- return INT2FIX(value->Int32Value());
398
+ return INT2FIX(value->Int32Value(context).ToChecked());
367
399
  }
368
400
 
369
401
  if (value->IsNumber()) {
370
- return rb_float_new(value->NumberValue());
402
+ return rb_float_new(value->NumberValue(context).ToChecked());
371
403
  }
372
404
 
373
405
  if (value->IsTrue()) {
374
- return Qtrue;
406
+ return Qtrue;
375
407
  }
376
408
 
377
409
  if (value->IsFalse()) {
378
- return Qfalse;
410
+ return Qfalse;
379
411
  }
380
412
 
381
413
  if (value->IsArray()) {
382
414
  VALUE rb_array = rb_ary_new();
383
415
  Local<Array> arr = Local<Array>::Cast(value);
384
416
  for(uint32_t i=0; i < arr->Length(); i++) {
385
- Local<Value> element = arr->Get(i);
386
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
387
- if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
388
- return rb_elem;
389
- }
417
+ MaybeLocal<Value> element = arr->Get(context, i);
418
+ if (element.IsEmpty()) {
419
+ continue;
420
+ }
421
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
422
+ if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
423
+ return rb_elem;
424
+ }
390
425
  rb_ary_push(rb_array, rb_elem);
391
426
  }
392
427
  return rb_array;
393
428
  }
394
429
 
395
430
  if (value->IsFunction()){
396
- return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
431
+ return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
397
432
  }
398
433
 
399
434
  if (value->IsDate()){
@@ -405,34 +440,55 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
405
440
  }
406
441
 
407
442
  if (value->IsObject()) {
408
- VALUE rb_hash = rb_hash_new();
409
- TryCatch trycatch(isolate);
410
-
411
- Local<Object> object = value->ToObject();
412
- auto maybe_props = object->GetOwnPropertyNames(context);
413
- if (!maybe_props.IsEmpty()) {
414
- Local<Array> props = maybe_props.ToLocalChecked();
415
- for(uint32_t i=0; i < props->Length(); i++) {
416
- Local<Value> key = props->Get(i);
417
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
418
- Local<Value> prop_value = object->Get(key);
419
- // this may have failed due to Get raising
420
-
421
- if (trycatch.HasCaught()) {
422
- // TODO isolate code that translates execption to ruby
423
- // exception so we can properly return it
424
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
425
- }
426
-
427
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
428
- rb_hash_aset(rb_hash, rb_key, rb_value);
429
- }
430
- }
431
- return rb_hash;
443
+ VALUE rb_hash = rb_hash_new();
444
+ TryCatch trycatch(isolate);
445
+
446
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
447
+ auto maybe_props = object->GetOwnPropertyNames(context);
448
+ if (!maybe_props.IsEmpty()) {
449
+ Local<Array> props = maybe_props.ToLocalChecked();
450
+ for(uint32_t i=0; i < props->Length(); i++) {
451
+ MaybeLocal<Value> key = props->Get(context, i);
452
+ if (key.IsEmpty()) {
453
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
454
+ }
455
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
456
+
457
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
458
+ // this may have failed due to Get raising
459
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
460
+ return new_empty_failed_conv_obj();
461
+ }
462
+
463
+ VALUE rb_value = convert_v8_to_ruby(
464
+ isolate, context, prop_value.ToLocalChecked());
465
+ rb_hash_aset(rb_hash, rb_key, rb_value);
466
+ }
467
+ }
468
+ return rb_hash;
432
469
  }
433
470
 
434
- Local<String> rstr = value->ToString();
435
- return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
471
+ if (value->IsSymbol()) {
472
+ v8::String::Utf8Value symbol_name(isolate,
473
+ Local<Symbol>::Cast(value)->Name());
474
+
475
+ VALUE str_symbol = rb_enc_str_new(
476
+ *symbol_name,
477
+ symbol_name.length(),
478
+ rb_enc_find("utf-8")
479
+ );
480
+
481
+ return ID2SYM(rb_intern_str(str_symbol));
482
+ }
483
+
484
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
485
+
486
+ if (rstr_maybe.IsEmpty()) {
487
+ return Qnil;
488
+ } else {
489
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
490
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
491
+ }
436
492
  }
437
493
 
438
494
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -453,7 +509,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate,
453
509
  Local<Value>::New(isolate, value));
454
510
  }
455
511
 
456
- static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
512
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value) {
457
513
  EscapableHandleScope scope(isolate);
458
514
 
459
515
  Local<Array> array;
@@ -487,7 +543,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
487
543
  length = RARRAY_LEN(value);
488
544
  array = Array::New(isolate, (int)length);
489
545
  for(i=0; i<length; i++) {
490
- array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
546
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
491
547
  }
492
548
  return scope.Escape(array);
493
549
  case T_HASH:
@@ -496,8 +552,8 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
496
552
  length = RARRAY_LEN(hash_as_array);
497
553
  for(i=0; i<length; i++) {
498
554
  pair = rb_ary_entry(hash_as_array, i);
499
- object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
500
- convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
555
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
556
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
501
557
  }
502
558
  return scope.Escape(object);
503
559
  case T_SYMBOL:
@@ -512,7 +568,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
512
568
  value = rb_funcall(value, rb_intern("to_time"), 0);
513
569
  }
514
570
  value = rb_funcall(value, rb_intern("to_f"), 0);
515
- return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
571
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
516
572
  }
517
573
  case T_OBJECT:
518
574
  case T_CLASS:
@@ -526,16 +582,90 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
526
582
  case T_UNDEF:
527
583
  case T_NODE:
528
584
  default:
529
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
585
+ return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
586
+ }
530
587
  }
531
-
532
- }
533
588
 
534
589
  static void unblock_eval(void *ptr) {
535
590
  EvalParams* eval = (EvalParams*)ptr;
536
591
  eval->context_info->isolate_info->interrupted = true;
537
592
  }
538
593
 
594
+ /*
595
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
596
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
597
+ */
598
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
599
+ const char *utf8_source, const char *name) {
600
+ Context::Scope context_scope(context);
601
+ TryCatch try_catch(isolate);
602
+ Local<String> source_string;
603
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
604
+ return false;
605
+ }
606
+ Local<String> resource_name =
607
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
608
+ ScriptOrigin origin(resource_name);
609
+ ScriptCompiler::Source source(source_string, origin);
610
+ Local<Script> script;
611
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
612
+ return false;
613
+ if (script->Run(context).IsEmpty()) return false;
614
+ return true;
615
+ }
616
+
617
+ static StartupData
618
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
619
+ Isolate *isolate = Isolate::Allocate();
620
+
621
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
622
+ SnapshotCreator snapshot_creator(isolate);
623
+ {
624
+ HandleScope scope(isolate);
625
+ Local<v8::Context> context = v8::Context::New(isolate);
626
+ if (embedded_source != nullptr &&
627
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
628
+ return {};
629
+ }
630
+ snapshot_creator.SetDefaultContext(context);
631
+ }
632
+ return snapshot_creator.CreateBlob(
633
+ SnapshotCreator::FunctionCodeHandling::kClear);
634
+ }
635
+
636
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
637
+ const char *warmup_source) {
638
+ // Use following steps to create a warmed up snapshot blob from a cold one:
639
+ // - Create a new isolate from the cold snapshot.
640
+ // - Create a new context to run the warmup script. This will trigger
641
+ // compilation of executed functions.
642
+ // - Create a new context. This context will be unpolluted.
643
+ // - Serialize the isolate and the second context into a new snapshot blob.
644
+ StartupData result = {nullptr, 0};
645
+
646
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
647
+ warmup_source != NULL) {
648
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
649
+ Isolate *isolate = snapshot_creator.GetIsolate();
650
+ {
651
+ HandleScope scope(isolate);
652
+ Local<Context> context = Context::New(isolate);
653
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
654
+ return result;
655
+ }
656
+ }
657
+ {
658
+ HandleScope handle_scope(isolate);
659
+ isolate->ContextDisposedNotification(false);
660
+ Local<Context> context = Context::New(isolate);
661
+ snapshot_creator.SetDefaultContext(context);
662
+ }
663
+ result = snapshot_creator.CreateBlob(
664
+ SnapshotCreator::FunctionCodeHandling::kKeep);
665
+ }
666
+ return result;
667
+ }
668
+
539
669
  static VALUE rb_snapshot_size(VALUE self, VALUE str) {
540
670
  SnapshotInfo* snapshot_info;
541
671
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
@@ -554,7 +684,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
554
684
 
555
685
  init_v8();
556
686
 
557
- StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
687
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
558
688
 
559
689
  if (startup_data.data == NULL && startup_data.raw_size == 0) {
560
690
  rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
@@ -585,7 +715,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
585
715
  init_v8();
586
716
 
587
717
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
588
- StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
718
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
589
719
 
590
720
  if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
591
721
  rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
@@ -648,6 +778,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
648
778
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
649
779
  }
650
780
 
781
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
782
+ IsolateInfo* isolate_info;
783
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
784
+
785
+ if (current_platform == NULL) return Qfalse;
786
+
787
+ isolate_info->isolate->LowMemoryNotification();
788
+ return Qnil;
789
+ }
790
+
791
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
792
+ IsolateInfo* isolate_info;
793
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
794
+
795
+ if (current_platform == NULL) return Qfalse;
796
+
797
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
798
+ return Qtrue;
799
+ } else {
800
+ return Qfalse;
801
+ }
802
+ }
803
+
651
804
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
652
805
  ContextInfo* context_info;
653
806
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -675,13 +828,13 @@ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
675
828
  // the ruby lock is needed if this isn't a new isolate
676
829
  IsolateInfo::Lock ruby_lock(isolate_info->mutex);
677
830
  Locker lock(isolate_info->isolate);
678
- Isolate::Scope isolate_scope(isolate_info->isolate);
679
- HandleScope handle_scope(isolate_info->isolate);
831
+ Isolate::Scope isolate_scope(isolate_info->isolate);
832
+ HandleScope handle_scope(isolate_info->isolate);
680
833
 
681
- Local<Context> context = Context::New(isolate_info->isolate);
834
+ Local<Context> context = Context::New(isolate_info->isolate);
682
835
 
683
- context_info->context = new Persistent<Context>();
684
- context_info->context->Reset(isolate_info->isolate, context);
836
+ context_info->context = new Persistent<Context>();
837
+ context_info->context->Reset(isolate_info->isolate, context);
685
838
  }
686
839
 
687
840
  if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
@@ -764,8 +917,8 @@ static VALUE convert_result_to_ruby(VALUE self /* context */,
764
917
  Local<Value> tmp = Local<Value>::New(isolate, *result.value);
765
918
 
766
919
  if (result.json) {
767
- Local<String> rstr = tmp->ToString();
768
- VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
920
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
921
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
769
922
  ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
770
923
  } else {
771
924
  ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
@@ -803,42 +956,42 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
803
956
  }
804
957
 
805
958
  {
806
- Locker lock(isolate);
807
- Isolate::Scope isolate_scope(isolate);
808
- HandleScope handle_scope(isolate);
959
+ Locker lock(isolate);
960
+ Isolate::Scope isolate_scope(isolate);
961
+ HandleScope handle_scope(isolate);
809
962
 
810
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
811
- NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
963
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
964
+ NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
812
965
 
813
- Local<String> local_filename;
966
+ Local<String> local_filename;
814
967
 
815
- if (filename != Qnil) {
816
- local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
817
- NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
818
- eval_params.filename = &local_filename;
819
- } else {
820
- eval_params.filename = NULL;
821
- }
968
+ if (filename != Qnil) {
969
+ local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
970
+ NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
971
+ eval_params.filename = &local_filename;
972
+ } else {
973
+ eval_params.filename = NULL;
974
+ }
822
975
 
823
- eval_params.context_info = context_info;
824
- eval_params.eval = &eval;
825
- eval_params.result = &eval_result;
826
- eval_params.timeout = 0;
827
- eval_params.max_memory = 0;
828
- VALUE timeout = rb_iv_get(self, "@timeout");
829
- if (timeout != Qnil) {
830
- eval_params.timeout = (useconds_t)NUM2LONG(timeout);
831
- }
976
+ eval_params.context_info = context_info;
977
+ eval_params.eval = &eval;
978
+ eval_params.result = &eval_result;
979
+ eval_params.timeout = 0;
980
+ eval_params.max_memory = 0;
981
+ VALUE timeout = rb_iv_get(self, "@timeout");
982
+ if (timeout != Qnil) {
983
+ eval_params.timeout = (useconds_t)NUM2LONG(timeout);
984
+ }
832
985
 
833
- VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
834
- if (mem_softlimit != Qnil) {
835
- eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
836
- }
986
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
987
+ if (mem_softlimit != Qnil) {
988
+ eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
989
+ }
837
990
 
838
- eval_result.message = NULL;
839
- eval_result.backtrace = NULL;
991
+ eval_result.message = NULL;
992
+ eval_result.backtrace = NULL;
840
993
 
841
- rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
994
+ rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
842
995
  }
843
996
 
844
997
  return convert_result_to_ruby(self, eval_result);
@@ -851,17 +1004,16 @@ typedef struct {
851
1004
  bool failed;
852
1005
  } protected_callback_data;
853
1006
 
854
- static
855
- VALUE protected_callback(VALUE rdata) {
1007
+ static VALUE protected_callback(VALUE rdata) {
856
1008
  protected_callback_data* data = (protected_callback_data*)rdata;
857
1009
  VALUE result;
858
1010
 
859
1011
  if (data->length > 0) {
860
- result = rb_funcall2(data->callback, rb_intern("call"), data->length,
861
- RARRAY_PTR(data->ruby_args));
862
- RB_GC_GUARD(data->ruby_args);
1012
+ result = rb_funcall2(data->callback, rb_intern("call"), data->length,
1013
+ RARRAY_PTR(data->ruby_args));
1014
+ RB_GC_GUARD(data->ruby_args);
863
1015
  } else {
864
- result = rb_funcall(data->callback, rb_intern("call"), 0);
1016
+ result = rb_funcall(data->callback, rb_intern("call"), 0);
865
1017
  }
866
1018
  return result;
867
1019
  }
@@ -883,6 +1035,8 @@ gvl_ruby_callback(void* data) {
883
1035
  VALUE result;
884
1036
  VALUE self;
885
1037
  VALUE parent;
1038
+ ContextInfo* context_info;
1039
+
886
1040
  {
887
1041
  HandleScope scope(args->GetIsolate());
888
1042
  Local<External> external = Local<External>::Cast(args->Data());
@@ -895,19 +1049,18 @@ gvl_ruby_callback(void* data) {
895
1049
  return NULL;
896
1050
  }
897
1051
 
898
- ContextInfo* context_info;
899
1052
  Data_Get_Struct(parent, ContextInfo, context_info);
900
1053
 
901
- if (length > 0) {
902
- ruby_args = rb_ary_tmp_new(length);
903
- }
1054
+ if (length > 0) {
1055
+ ruby_args = rb_ary_tmp_new(length);
1056
+ }
904
1057
 
905
- for (int i = 0; i < length; i++) {
906
- Local<Value> value = ((*args)[i]).As<Value>();
907
- VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
908
- *context_info->context, value);
909
- rb_ary_push(ruby_args, tmp);
910
- }
1058
+ for (int i = 0; i < length; i++) {
1059
+ Local<Value> value = ((*args)[i]).As<Value>();
1060
+ VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1061
+ *context_info->context, value);
1062
+ rb_ary_push(ruby_args, tmp);
1063
+ }
911
1064
  }
912
1065
 
913
1066
  // may raise exception stay clear of handle scope
@@ -918,31 +1071,33 @@ gvl_ruby_callback(void* data) {
918
1071
  callback_data.failed = false;
919
1072
 
920
1073
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
921
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
922
- args->GetIsolate()->TerminateExecution();
923
- if (length > 0) {
924
- rb_ary_clear(ruby_args);
925
- rb_gc_force_recycle(ruby_args);
926
- }
927
- return NULL;
1074
+ args->GetIsolate()->ThrowException(
1075
+ String::NewFromUtf8Literal(args->GetIsolate(),
1076
+ "Terminated execution during transition from Ruby to JS"));
1077
+ args->GetIsolate()->TerminateExecution();
1078
+ if (length > 0) {
1079
+ rb_ary_clear(ruby_args);
1080
+ rb_gc_force_recycle(ruby_args);
1081
+ }
1082
+ return NULL;
928
1083
  }
929
1084
 
930
1085
  result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
931
- (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
1086
+ (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
932
1087
 
933
1088
  if(callback_data.failed) {
934
- rb_iv_set(parent, "@current_exception", result);
935
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1089
+ rb_iv_set(parent, "@current_exception", result);
1090
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
936
1091
  }
937
1092
  else {
938
- HandleScope scope(args->GetIsolate());
939
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
940
- args->GetReturnValue().Set(v8_result);
1093
+ HandleScope scope(args->GetIsolate());
1094
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1095
+ args->GetReturnValue().Set(v8_result);
941
1096
  }
942
1097
 
943
1098
  if (length > 0) {
944
- rb_ary_clear(ruby_args);
945
- rb_gc_force_recycle(ruby_args);
1099
+ rb_ary_clear(ruby_args);
1100
+ rb_gc_force_recycle(ruby_args);
946
1101
  }
947
1102
 
948
1103
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
@@ -957,11 +1112,11 @@ static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
957
1112
  bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL);
958
1113
 
959
1114
  if(has_gvl) {
960
- gvl_ruby_callback((void*)&args);
1115
+ gvl_ruby_callback((void*)&args);
961
1116
  } else {
962
- args.GetIsolate()->SetData(IN_GVL, (void*)true);
963
- rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
964
- args.GetIsolate()->SetData(IN_GVL, (void*)false);
1117
+ args.GetIsolate()->SetData(IN_GVL, (void*)true);
1118
+ rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
1119
+ args.GetIsolate()->SetData(IN_GVL, (void*)false);
965
1120
  }
966
1121
  }
967
1122
 
@@ -982,55 +1137,71 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
982
1137
  Isolate* isolate = context_info->isolate_info->isolate;
983
1138
 
984
1139
  {
985
- Locker lock(isolate);
986
- Isolate::Scope isolate_scope(isolate);
987
- HandleScope handle_scope(isolate);
988
-
989
- Local<Context> context = context_info->context->Get(isolate);
990
- Context::Scope context_scope(context);
991
-
992
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
993
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
994
-
995
- // copy self so we can access from v8 external
996
- VALUE* self_copy;
997
- Data_Get_Struct(self, VALUE, self_copy);
998
- *self_copy = self;
999
-
1000
- Local<Value> external = External::New(isolate, self_copy);
1001
-
1002
- if (parent_object == Qnil) {
1003
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1004
- } else {
1005
-
1006
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1007
- NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
1008
-
1009
- MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1010
- if (parsed_script.IsEmpty()) {
1011
- parse_error = true;
1012
- } else {
1013
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
1014
- attach_error = true;
1015
-
1016
- if (!maybe_value.IsEmpty()) {
1017
- Local<Value> value = maybe_value.ToLocalChecked();
1018
- if (value->IsObject()){
1019
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1020
- attach_error = false;
1021
- }
1022
- }
1023
- }
1024
- }
1140
+ Locker lock(isolate);
1141
+ Isolate::Scope isolate_scope(isolate);
1142
+ HandleScope handle_scope(isolate);
1143
+
1144
+ Local<Context> context = context_info->context->Get(isolate);
1145
+ Context::Scope context_scope(context);
1146
+
1147
+ Local<String> v8_str =
1148
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1149
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1150
+ .ToLocalChecked();
1151
+
1152
+ // copy self so we can access from v8 external
1153
+ VALUE* self_copy;
1154
+ Data_Get_Struct(self, VALUE, self_copy);
1155
+ *self_copy = self;
1156
+
1157
+ Local<Value> external = External::New(isolate, self_copy);
1158
+
1159
+ if (parent_object == Qnil) {
1160
+ context->Global()->Set(
1161
+ context,
1162
+ v8_str,
1163
+ FunctionTemplate::New(isolate, ruby_callback, external)
1164
+ ->GetFunction(context)
1165
+ .ToLocalChecked());
1166
+
1167
+ } else {
1168
+ Local<String> eval =
1169
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1170
+ NewStringType::kNormal,
1171
+ (int)RSTRING_LEN(parent_object_eval))
1172
+ .ToLocalChecked();
1173
+
1174
+ MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1175
+ if (parsed_script.IsEmpty()) {
1176
+ parse_error = true;
1177
+ } else {
1178
+ MaybeLocal<Value> maybe_value =
1179
+ parsed_script.ToLocalChecked()->Run(context);
1180
+ attach_error = true;
1181
+
1182
+ if (!maybe_value.IsEmpty()) {
1183
+ Local<Value> value = maybe_value.ToLocalChecked();
1184
+ if (value->IsObject()) {
1185
+ value.As<Object>()->Set(
1186
+ context,
1187
+ v8_str,
1188
+ FunctionTemplate::New(isolate, ruby_callback, external)
1189
+ ->GetFunction(context)
1190
+ .ToLocalChecked());
1191
+ attach_error = false;
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1025
1196
  }
1026
1197
 
1027
1198
  // always raise out of V8 context
1028
1199
  if (parse_error) {
1029
- rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
1200
+ rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
1030
1201
  }
1031
1202
 
1032
1203
  if (attach_error) {
1033
- rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
1204
+ rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
1034
1205
  }
1035
1206
 
1036
1207
  return Qnil;
@@ -1047,35 +1218,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1047
1218
  return context_info->isolate_info->mutex;
1048
1219
  }
1049
1220
 
1050
- void free_isolate(IsolateInfo* isolate_info) {
1051
-
1052
- if (isolate_info->isolate) {
1053
- Locker lock(isolate_info->isolate);
1054
- }
1055
-
1056
- if (isolate_info->isolate) {
1057
- if (isolate_info->interrupted) {
1058
- 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");
1221
+ IsolateInfo::~IsolateInfo() {
1222
+ if (isolate) {
1223
+ if (this->interrupted) {
1224
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1225
+ "it can not be disposed and memory will not be "
1226
+ "reclaimed till the Ruby process exits.\n");
1059
1227
  } else {
1060
-
1061
- if (isolate_info->pid != getpid()) {
1062
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1063
- } else {
1064
- isolate_info->isolate->Dispose();
1065
- }
1228
+ if (this->pid != getpid() && !single_threaded) {
1229
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1230
+ "it can not be disposed and "
1231
+ "memory will not be reclaimed "
1232
+ "till the Ruby process exits.\n"
1233
+ "It is VERY likely your process will hang.\n"
1234
+ "If you wish to use v8 in forked environment "
1235
+ "please ensure the platform is initialized with:\n"
1236
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1237
+ );
1238
+ } else {
1239
+ isolate->Dispose();
1240
+ }
1066
1241
  }
1067
- isolate_info->isolate = NULL;
1242
+ isolate = nullptr;
1068
1243
  }
1069
1244
 
1070
- if (isolate_info->startup_data) {
1071
- delete[] isolate_info->startup_data->data;
1072
- delete isolate_info->startup_data;
1245
+ if (startup_data) {
1246
+ delete[] startup_data->data;
1247
+ delete startup_data;
1073
1248
  }
1074
1249
 
1075
- delete isolate_info->allocator;
1250
+ delete allocator;
1076
1251
  }
1077
1252
 
1078
- static void *free_context_raw(void* arg) {
1253
+ static void free_context_raw(void *arg) {
1079
1254
  ContextInfo* context_info = (ContextInfo*)arg;
1080
1255
  IsolateInfo* isolate_info = context_info->isolate_info;
1081
1256
  Persistent<Context>* context = context_info->context;
@@ -1092,6 +1267,20 @@ static void *free_context_raw(void* arg) {
1092
1267
  }
1093
1268
 
1094
1269
  xfree(context_info);
1270
+ }
1271
+
1272
+ static void *free_context_thr(void* arg) {
1273
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1274
+ return NULL;
1275
+ }
1276
+ if (ruby_exiting) {
1277
+ return NULL;
1278
+ }
1279
+
1280
+ free_context_raw(arg);
1281
+
1282
+ pthread_rwlock_unlock(&exit_lock);
1283
+
1095
1284
  return NULL;
1096
1285
  }
1097
1286
 
@@ -1105,22 +1294,17 @@ static void free_context(ContextInfo* context_info) {
1105
1294
  context_info_copy->context = context_info->context;
1106
1295
 
1107
1296
  if (isolate_info && isolate_info->refs() > 1) {
1108
- pthread_t free_context_thread;
1109
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1297
+ pthread_t free_context_thread;
1298
+ if (pthread_create(&free_context_thread, thread_attr_p,
1299
+ free_context_thr, (void*)context_info_copy)) {
1110
1300
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1111
- }
1112
-
1301
+ }
1113
1302
  } else {
1114
- free_context_raw(context_info_copy);
1303
+ free_context_raw(context_info_copy);
1115
1304
  }
1116
1305
 
1117
- if (context_info->context && isolate_info && isolate_info->isolate) {
1118
- context_info->context = NULL;
1119
- }
1120
-
1121
- if (isolate_info) {
1122
- context_info->isolate_info = NULL;
1123
- }
1306
+ context_info->context = NULL;
1307
+ context_info->isolate_info = NULL;
1124
1308
  }
1125
1309
 
1126
1310
  static void deallocate_isolate(void* data) {
@@ -1135,7 +1319,7 @@ static void mark_isolate(void* data) {
1135
1319
  isolate_info->mark();
1136
1320
  }
1137
1321
 
1138
- void deallocate(void* data) {
1322
+ static void deallocate(void* data) {
1139
1323
  ContextInfo* context_info = (ContextInfo*)data;
1140
1324
 
1141
1325
  free_context(context_info);
@@ -1150,22 +1334,22 @@ static void mark_context(void* data) {
1150
1334
  }
1151
1335
  }
1152
1336
 
1153
- void deallocate_external_function(void * data) {
1337
+ static void deallocate_external_function(void * data) {
1154
1338
  xfree(data);
1155
1339
  }
1156
1340
 
1157
- void deallocate_snapshot(void * data) {
1341
+ static void deallocate_snapshot(void * data) {
1158
1342
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1159
1343
  delete[] snapshot_info->data;
1160
1344
  xfree(snapshot_info);
1161
1345
  }
1162
1346
 
1163
- VALUE allocate_external_function(VALUE klass) {
1347
+ static VALUE allocate_external_function(VALUE klass) {
1164
1348
  VALUE* self = ALLOC(VALUE);
1165
1349
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1166
1350
  }
1167
1351
 
1168
- VALUE allocate(VALUE klass) {
1352
+ static VALUE allocate(VALUE klass) {
1169
1353
  ContextInfo* context_info = ALLOC(ContextInfo);
1170
1354
  context_info->isolate_info = NULL;
1171
1355
  context_info->context = NULL;
@@ -1173,7 +1357,7 @@ VALUE allocate(VALUE klass) {
1173
1357
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1174
1358
  }
1175
1359
 
1176
- VALUE allocate_snapshot(VALUE klass) {
1360
+ static VALUE allocate_snapshot(VALUE klass) {
1177
1361
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1178
1362
  snapshot_info->data = NULL;
1179
1363
  snapshot_info->raw_size = 0;
@@ -1181,7 +1365,7 @@ VALUE allocate_snapshot(VALUE klass) {
1181
1365
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1182
1366
  }
1183
1367
 
1184
- VALUE allocate_isolate(VALUE klass) {
1368
+ static VALUE allocate_isolate(VALUE klass) {
1185
1369
  IsolateInfo* isolate_info = new IsolateInfo();
1186
1370
 
1187
1371
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1201,25 +1385,90 @@ rb_heap_stats(VALUE self) {
1201
1385
 
1202
1386
  if (!isolate) {
1203
1387
 
1204
- rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
1205
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
1206
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
1207
- rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
1208
- rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
1388
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
1389
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
1390
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
1391
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
1392
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
1209
1393
 
1210
1394
  } else {
1211
- isolate->GetHeapStatistics(&stats);
1395
+ isolate->GetHeapStatistics(&stats);
1212
1396
 
1213
- rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
1214
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
1215
- rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
1216
- rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
1217
- rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
1397
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
1398
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
1399
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
1400
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
1401
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
1218
1402
  }
1219
1403
 
1220
1404
  return rval;
1221
1405
  }
1222
1406
 
1407
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1408
+ class FileOutputStream : public OutputStream {
1409
+ public:
1410
+ FileOutputStream(FILE* stream) : stream_(stream) {}
1411
+
1412
+ virtual int GetChunkSize() {
1413
+ return 65536;
1414
+ }
1415
+
1416
+ virtual void EndOfStream() {}
1417
+
1418
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1419
+ const size_t len = static_cast<size_t>(size);
1420
+ size_t off = 0;
1421
+
1422
+ while (off < len && !feof(stream_) && !ferror(stream_))
1423
+ off += fwrite(data + off, 1, len - off, stream_);
1424
+
1425
+ return off == len ? kContinue : kAbort;
1426
+ }
1427
+
1428
+ private:
1429
+ FILE* stream_;
1430
+ };
1431
+
1432
+
1433
+ static VALUE
1434
+ rb_heap_snapshot(VALUE self, VALUE file) {
1435
+
1436
+ rb_io_t *fptr;
1437
+
1438
+ fptr = RFILE(file)->fptr;
1439
+
1440
+ if (!fptr) return Qfalse;
1441
+
1442
+ FILE* fp;
1443
+ fp = fdopen(fptr->fd, "w");
1444
+ if (fp == NULL) return Qfalse;
1445
+
1446
+
1447
+ ContextInfo* context_info;
1448
+ Data_Get_Struct(self, ContextInfo, context_info);
1449
+ Isolate* isolate;
1450
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1451
+
1452
+ if (!isolate) return Qfalse;
1453
+
1454
+ Locker lock(isolate);
1455
+ Isolate::Scope isolate_scope(isolate);
1456
+ HandleScope handle_scope(isolate);
1457
+
1458
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1459
+
1460
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1461
+
1462
+ FileOutputStream stream(fp);
1463
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1464
+
1465
+ fflush(fp);
1466
+
1467
+ const_cast<HeapSnapshot*>(snap)->Delete();
1468
+
1469
+ return Qtrue;
1470
+ }
1471
+
1223
1472
  static VALUE
1224
1473
  rb_context_stop(VALUE self) {
1225
1474
 
@@ -1255,13 +1504,23 @@ nogvl_context_call(void *args) {
1255
1504
  if (!call) {
1256
1505
  return NULL;
1257
1506
  }
1258
- Isolate* isolate = call->context_info->isolate_info->isolate;
1507
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1508
+ Isolate* isolate = isolate_info->isolate;
1259
1509
 
1260
1510
  // in gvl flag
1261
1511
  isolate->SetData(IN_GVL, (void*)false);
1262
1512
  // terminate ASAP
1263
1513
  isolate->SetData(DO_TERMINATE, (void*)false);
1264
1514
 
1515
+ if (call->max_memory > 0) {
1516
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1517
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1518
+ if (!isolate_info->added_gc_cb) {
1519
+ isolate->AddGCEpilogueCallback(gc_callback);
1520
+ isolate_info->added_gc_cb = true;
1521
+ }
1522
+ }
1523
+
1265
1524
  Isolate::Scope isolate_scope(isolate);
1266
1525
  EscapableHandleScope handle_scope(isolate);
1267
1526
  TryCatch trycatch(isolate);
@@ -1287,8 +1546,7 @@ static void unblock_function(void *args) {
1287
1546
  call->context_info->isolate_info->interrupted = true;
1288
1547
  }
1289
1548
 
1290
- static VALUE
1291
- rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1549
+ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1292
1550
  ContextInfo* context_info;
1293
1551
  FunctionCall call;
1294
1552
  VALUE *call_argv = NULL;
@@ -1320,8 +1578,14 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1320
1578
  call_argv = argv + 1;
1321
1579
  }
1322
1580
 
1323
- bool missingFunction = false;
1581
+ call.max_memory = 0;
1582
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1583
+ if (mem_softlimit != Qnil) {
1584
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1585
+ call.max_memory = (size_t)sl_int;
1586
+ }
1324
1587
 
1588
+ bool missingFunction = false;
1325
1589
  {
1326
1590
  Locker lock(isolate);
1327
1591
  Isolate::Scope isolate_scope(isolate);
@@ -1332,35 +1596,37 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1332
1596
 
1333
1597
  // examples of such usage can be found in
1334
1598
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1335
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1336
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1337
-
1338
- if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1339
- missingFunction = true;
1340
- } else {
1341
-
1342
- Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
1343
- call.fun = fun;
1344
- int fun_argc = call.argc;
1345
-
1346
- if (fun_argc > 0) {
1347
- call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
1348
- if (!call.argv) {
1349
- return Qnil;
1350
- }
1351
- for(int i=0; i < fun_argc; i++) {
1352
- call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1353
- }
1354
- }
1355
-
1356
- rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
1357
- free(call.argv);
1599
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1600
+ MaybeLocal<v8::Value> val;
1601
+ if (!fname.IsEmpty()) {
1602
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1603
+ }
1358
1604
 
1359
- }
1605
+ if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1606
+ missingFunction = true;
1607
+ } else {
1608
+
1609
+ Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
1610
+ call.fun = fun;
1611
+ int fun_argc = call.argc;
1612
+
1613
+ if (fun_argc > 0) {
1614
+ call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
1615
+ if (!call.argv) {
1616
+ return Qnil;
1617
+ }
1618
+ for(int i=0; i < fun_argc; i++) {
1619
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1620
+ }
1621
+ }
1622
+ rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
1623
+ free(call.argv);
1624
+
1625
+ }
1360
1626
  }
1361
1627
 
1362
1628
  if (missingFunction) {
1363
- rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1629
+ rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1364
1630
  }
1365
1631
 
1366
1632
  return convert_result_to_ruby(self, call.result);
@@ -1379,57 +1645,84 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1379
1645
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1380
1646
  }
1381
1647
 
1382
- extern "C" {
1383
-
1384
- void Init_mini_racer_extension ( void )
1385
- {
1386
- VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1387
- rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1388
- rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1389
- rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1390
- VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
1391
-
1392
- VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
1393
-
1394
- VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
1395
- rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
1396
- rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
1397
- rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
1398
- rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
1648
+ static void set_ruby_exiting(VALUE value) {
1649
+ (void)value;
1399
1650
 
1400
- rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1401
- rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
1402
- rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
1403
- rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
1404
- rb_mJSON = rb_define_module("JSON");
1651
+ int res = pthread_rwlock_wrlock(&exit_lock);
1405
1652
 
1406
- VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
1407
-
1408
- rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1409
- rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1410
- rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1411
- rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1412
- rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1413
- rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
1414
- rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
1415
- rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
1416
-
1417
- rb_define_alloc_func(rb_cContext, allocate);
1418
- rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1419
- rb_define_alloc_func(rb_cIsolate, allocate_isolate);
1420
-
1421
- rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
1422
- rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1423
-
1424
- rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1425
- rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
1426
- rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
1427
- rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1653
+ ruby_exiting = true;
1654
+ if (res == 0) {
1655
+ pthread_rwlock_unlock(&exit_lock);
1656
+ }
1657
+ }
1428
1658
 
1429
- rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1430
- rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1659
+ extern "C" {
1431
1660
 
1432
- rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1661
+ __attribute__((visibility("default"))) void Init_mini_racer_extension ( void )
1662
+ {
1663
+ VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1664
+ rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1665
+ rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1666
+ rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1667
+ VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
1668
+
1669
+ VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
1670
+
1671
+ VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
1672
+ rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
1673
+ rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
1674
+ rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
1675
+ rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
1676
+
1677
+ rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1678
+ rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
1679
+ rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
1680
+ rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
1681
+ rb_mJSON = rb_define_module("JSON");
1682
+
1683
+ VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
1684
+
1685
+ rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1686
+ rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1687
+ rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1688
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1689
+
1690
+ rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1691
+ rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1692
+ rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
1693
+ rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
1694
+ rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
1695
+
1696
+ rb_define_alloc_func(rb_cContext, allocate);
1697
+ rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1698
+ rb_define_alloc_func(rb_cIsolate, allocate_isolate);
1699
+
1700
+ rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
1701
+ rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1702
+
1703
+ rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1704
+ rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
1705
+ rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
1706
+ rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1707
+
1708
+ rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1709
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1710
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1711
+ rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1712
+
1713
+ rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1714
+
1715
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1716
+
1717
+ static pthread_attr_t attr;
1718
+ if (pthread_attr_init(&attr) == 0) {
1719
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1720
+ thread_attr_p = &attr;
1721
+ }
1722
+ }
1723
+ auto on_fork_for_child = []() {
1724
+ exit_lock = PTHREAD_RWLOCK_INITIALIZER;
1725
+ };
1726
+ pthread_atfork(nullptr, nullptr, on_fork_for_child);
1433
1727
  }
1434
-
1435
1728
  }