sq_mini_racer 0.2.5.0.2 → 0.3.1.0.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.
@@ -36,6 +36,8 @@
36
36
  #include "compat.hpp"
37
37
  #include "simdutf8check.h"
38
38
 
39
+ #include <time.h>
40
+
39
41
  using namespace v8;
40
42
 
41
43
  typedef struct {
@@ -49,6 +51,7 @@ public:
49
51
  ArrayBuffer::Allocator* allocator;
50
52
  StartupData* startup_data;
51
53
  bool interrupted;
54
+ bool added_gc_cb;
52
55
  pid_t pid;
53
56
  VALUE mutex;
54
57
 
@@ -66,15 +69,12 @@ public:
66
69
 
67
70
 
68
71
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
69
- interrupted(false), pid(getpid()), refs_count(0) {
72
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
70
73
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
71
74
  mutex = rb_class_new_instance(0, nullptr, cMutex);
72
75
  }
73
76
 
74
- ~IsolateInfo() {
75
- void free_isolate(IsolateInfo*);
76
- free_isolate(this);
77
- }
77
+ ~IsolateInfo();
78
78
 
79
79
  void init(SnapshotInfo* snapshot_info = nullptr);
80
80
 
@@ -151,6 +151,7 @@ typedef struct {
151
151
  Local<Function> fun;
152
152
  Local<Value> *argv;
153
153
  EvalResult result;
154
+ size_t max_memory;
154
155
  } FunctionCall;
155
156
 
156
157
  enum IsolateFlags {
@@ -179,6 +180,11 @@ static VALUE rb_cDateTime = Qnil;
179
180
  static std::unique_ptr<Platform> current_platform = NULL;
180
181
  static std::mutex platform_lock;
181
182
 
183
+ static pthread_attr_t *thread_attr_p;
184
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
185
+ static bool ruby_exiting = false; // guarded by exit_lock
186
+ static bool single_threaded = false;
187
+
182
188
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
183
189
  bool platform_already_initialized = false;
184
190
 
@@ -190,6 +196,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
190
196
  platform_lock.lock();
191
197
 
192
198
  if (current_platform == NULL) {
199
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
200
+ single_threaded = true;
201
+ }
193
202
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
194
203
  } else {
195
204
  platform_already_initialized = true;
@@ -256,11 +265,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
256
265
  Local<Value> local_value = v8res.ToLocalChecked();
257
266
  if ((local_value->IsObject() || local_value->IsArray()) &&
258
267
  !local_value->IsDate() && !local_value->IsFunction()) {
259
- Local<Object> JSON = context->Global()->Get(String::NewFromUtf8(isolate, "JSON"))
260
- ->ToObject(context).ToLocalChecked();
268
+ Local<Object> JSON = context->Global()->Get(
269
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
270
+ .ToLocalChecked().As<Object>();
261
271
 
262
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
263
- .As<Function>();
272
+ Local<Function> stringify = JSON->Get(
273
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
274
+ .ToLocalChecked().As<Function>();
264
275
 
265
276
  Local<Object> object = local_value->ToObject(context).ToLocalChecked();
266
277
  const unsigned argc = 1;
@@ -314,7 +325,7 @@ static void prepare_result(MaybeLocal<Value> v8res,
314
325
  } else if(trycatch.HasTerminated()) {
315
326
  evalRes.terminated = true;
316
327
  evalRes.message = new Persistent<Value>();
317
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
328
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
318
329
  evalRes.message->Reset(isolate, tmp);
319
330
  }
320
331
  if (!trycatch.StackTrace(context).IsEmpty()) {
@@ -335,7 +346,8 @@ nogvl_context_eval(void* arg) {
335
346
 
336
347
  EvalParams* eval_params = (EvalParams*)arg;
337
348
  EvalResult* result = eval_params->result;
338
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
349
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
350
+ Isolate* isolate = isolate_info->isolate;
339
351
 
340
352
  Isolate::Scope isolate_scope(isolate);
341
353
  HandleScope handle_scope(isolate);
@@ -379,7 +391,10 @@ nogvl_context_eval(void* arg) {
379
391
  // parsing successful
380
392
  if (eval_params->max_memory > 0) {
381
393
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
394
+ if (!isolate_info->added_gc_cb) {
382
395
  isolate->AddGCEpilogueCallback(gc_callback);
396
+ isolate_info->added_gc_cb = true;
397
+ }
383
398
  }
384
399
 
385
400
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -392,6 +407,12 @@ nogvl_context_eval(void* arg) {
392
407
  return 0;
393
408
  }
394
409
 
410
+ static VALUE new_empty_failed_conv_obj() {
411
+ // TODO isolate code that translates execption to ruby
412
+ // exception so we can properly return it
413
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
414
+ }
415
+
395
416
  // assumes isolate locking is in place
396
417
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
397
418
  Local<Value> value) {
@@ -423,8 +444,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
423
444
  VALUE rb_array = rb_ary_new();
424
445
  Local<Array> arr = Local<Array>::Cast(value);
425
446
  for(uint32_t i=0; i < arr->Length(); i++) {
426
- Local<Value> element = arr->Get(i);
427
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
447
+ MaybeLocal<Value> element = arr->Get(context, i);
448
+ if (element.IsEmpty()) {
449
+ continue;
450
+ }
451
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
428
452
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
429
453
  return rb_elem;
430
454
  }
@@ -454,26 +478,47 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
454
478
  if (!maybe_props.IsEmpty()) {
455
479
  Local<Array> props = maybe_props.ToLocalChecked();
456
480
  for(uint32_t i=0; i < props->Length(); i++) {
457
- Local<Value> key = props->Get(i);
458
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
459
- Local<Value> prop_value = object->Get(key);
460
- // this may have failed due to Get raising
481
+ MaybeLocal<Value> key = props->Get(context, i);
482
+ if (key.IsEmpty()) {
483
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
484
+ }
485
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
461
486
 
462
- if (trycatch.HasCaught()) {
463
- // TODO isolate code that translates execption to ruby
464
- // exception so we can properly return it
465
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
487
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
488
+ // this may have failed due to Get raising
489
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
490
+ return new_empty_failed_conv_obj();
466
491
  }
467
492
 
468
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
493
+ VALUE rb_value = convert_v8_to_ruby(
494
+ isolate, context, prop_value.ToLocalChecked());
469
495
  rb_hash_aset(rb_hash, rb_key, rb_value);
470
496
  }
471
497
  }
472
498
  return rb_hash;
473
499
  }
474
500
 
475
- Local<String> rstr = value->ToString(context).ToLocalChecked();
476
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
501
+ if (value->IsSymbol()) {
502
+ v8::String::Utf8Value symbol_name(isolate,
503
+ Local<Symbol>::Cast(value)->Name());
504
+
505
+ VALUE str_symbol = rb_enc_str_new(
506
+ *symbol_name,
507
+ symbol_name.length(),
508
+ rb_enc_find("utf-8")
509
+ );
510
+
511
+ return ID2SYM(rb_intern_str(str_symbol));
512
+ }
513
+
514
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
515
+
516
+ if (rstr_maybe.IsEmpty()) {
517
+ return Qnil;
518
+ } else {
519
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
520
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
521
+ }
477
522
  }
478
523
 
479
524
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -608,7 +653,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
608
653
  length = RARRAY_LEN(value);
609
654
  array = Array::New(isolate, (int)length);
610
655
  for(i=0; i<length; i++) {
611
- array->Set(i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
656
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
612
657
  }
613
658
  return scope.Escape(array);
614
659
  }
@@ -619,7 +664,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
619
664
  length = RARRAY_LEN(hash_as_array);
620
665
  for(i=0; i<length; i++) {
621
666
  pair = rb_ary_entry(hash_as_array, i);
622
- object->Set(convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
667
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
623
668
  convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
624
669
  }
625
670
  return scope.Escape(object);
@@ -661,8 +706,9 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
661
706
  value = rb_funcall(value, rb_intern("to_s"), 0);
662
707
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
663
708
  }
664
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
665
- }
709
+ return scope.Escape(
710
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
711
+ }
666
712
  }
667
713
  }
668
714
 
@@ -675,53 +721,43 @@ static void unblock_eval(void *ptr) {
675
721
  * The implementations of the run_extra_code(), create_snapshot_data_blob() and
676
722
  * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
677
723
  */
678
- bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
724
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
679
725
  const char *utf8_source, const char *name) {
680
726
  Context::Scope context_scope(context);
681
727
  TryCatch try_catch(isolate);
682
728
  Local<String> source_string;
683
- if (!String::NewFromUtf8(isolate, utf8_source,
684
- NewStringType::kNormal)
685
- .ToLocal(&source_string)) {
729
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
686
730
  return false;
687
731
  }
688
- Local<v8::String> resource_name =
689
- String::NewFromUtf8(isolate, name, NewStringType::kNormal)
690
- .ToLocalChecked();
732
+ Local<String> resource_name =
733
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
691
734
  ScriptOrigin origin(resource_name);
692
735
  ScriptCompiler::Source source(source_string, origin);
693
736
  Local<Script> script;
694
737
  if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
695
738
  return false;
696
- if (script->Run(context).IsEmpty())
697
- return false;
698
- // CHECK(!try_catch.HasCaught());
739
+ if (script->Run(context).IsEmpty()) return false;
699
740
  return true;
700
741
  }
701
742
 
702
- StartupData
743
+ static StartupData
703
744
  create_snapshot_data_blob(const char *embedded_source = nullptr) {
704
- // Create a new isolate and a new context from scratch, optionally run
705
- // a script to embed, and serialize to create a snapshot blob.
706
- StartupData result = {nullptr, 0};
745
+ Isolate *isolate = Isolate::Allocate();
746
+
747
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
748
+ SnapshotCreator snapshot_creator(isolate);
707
749
  {
708
- SnapshotCreator snapshot_creator;
709
- Isolate *isolate = snapshot_creator.GetIsolate();
710
- {
711
750
  HandleScope scope(isolate);
712
- Local<Context> context = Context::New(isolate);
751
+ Local<v8::Context> context = v8::Context::New(isolate);
713
752
  if (embedded_source != nullptr &&
714
- !run_extra_code(isolate, context, embedded_source,
715
- "<embedded>")) {
716
- return result;
753
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
754
+ return {};
717
755
  }
718
756
  snapshot_creator.SetDefaultContext(context);
719
757
  }
720
- result = snapshot_creator.CreateBlob(
758
+ return snapshot_creator.CreateBlob(
721
759
  SnapshotCreator::FunctionCodeHandling::kClear);
722
760
  }
723
- return result;
724
- }
725
761
 
726
762
  StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
727
763
  const char *warmup_source) {
@@ -868,6 +904,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
868
904
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
869
905
  }
870
906
 
907
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
908
+ IsolateInfo* isolate_info;
909
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
910
+
911
+ if (current_platform == NULL) return Qfalse;
912
+
913
+ isolate_info->isolate->LowMemoryNotification();
914
+ return Qnil;
915
+ }
916
+
917
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
918
+ IsolateInfo* isolate_info;
919
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
920
+
921
+ if (current_platform == NULL) return Qfalse;
922
+
923
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
924
+ return Qtrue;
925
+ } else {
926
+ return Qfalse;
927
+ }
928
+ }
929
+
871
930
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
872
931
  ContextInfo* context_info;
873
932
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -1142,7 +1201,9 @@ gvl_ruby_callback(void* data) {
1142
1201
  callback_data.failed = false;
1143
1202
 
1144
1203
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1145
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
1204
+ args->GetIsolate()->ThrowException(
1205
+ String::NewFromUtf8Literal(args->GetIsolate(),
1206
+ "Terminated execution during transition from Ruby to JS"));
1146
1207
  args->GetIsolate()->TerminateExecution();
1147
1208
  if (length > 0) {
1148
1209
  rb_ary_clear(ruby_args);
@@ -1156,7 +1217,7 @@ gvl_ruby_callback(void* data) {
1156
1217
 
1157
1218
  if(callback_data.failed) {
1158
1219
  rb_iv_set(parent, "@current_exception", result);
1159
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1220
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1160
1221
  }
1161
1222
  else {
1162
1223
  HandleScope scope(args->GetIsolate());
@@ -1231,7 +1292,9 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1231
1292
 
1232
1293
  if (parent_object == Qnil) {
1233
1294
  context->Global()->Set(
1234
- v8_str, FunctionTemplate::New(isolate, ruby_callback, external)
1295
+ context,
1296
+ v8_str,
1297
+ FunctionTemplate::New(isolate, ruby_callback, external)
1235
1298
  ->GetFunction(context)
1236
1299
  .ToLocalChecked());
1237
1300
 
@@ -1244,7 +1307,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1244
1307
 
1245
1308
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1246
1309
  if (parsed_script.IsEmpty()) {
1247
- parse_error = true;
1310
+ parse_error = true;
1248
1311
  } else {
1249
1312
  MaybeLocal<Value> maybe_value =
1250
1313
  parsed_script.ToLocalChecked()->Run(context);
@@ -1254,11 +1317,12 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1254
1317
  Local<Value> value = maybe_value.ToLocalChecked();
1255
1318
  if (value->IsObject()) {
1256
1319
  value.As<Object>()->Set(
1257
- v8_str, FunctionTemplate::New(
1258
- isolate, ruby_callback, external)
1320
+ context,
1321
+ v8_str,
1322
+ FunctionTemplate::New(isolate, ruby_callback, external)
1259
1323
  ->GetFunction(context)
1260
1324
  .ToLocalChecked());
1261
- attach_error = false;
1325
+ attach_error = false;
1262
1326
  }
1263
1327
  }
1264
1328
  }
@@ -1288,35 +1352,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1288
1352
  return context_info->isolate_info->mutex;
1289
1353
  }
1290
1354
 
1291
- void free_isolate(IsolateInfo* isolate_info) {
1292
-
1293
- if (isolate_info->isolate) {
1294
- Locker lock(isolate_info->isolate);
1295
- }
1296
-
1297
- if (isolate_info->isolate) {
1298
- if (isolate_info->interrupted) {
1299
- 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");
1355
+ IsolateInfo::~IsolateInfo() {
1356
+ if (isolate) {
1357
+ if (this->interrupted) {
1358
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1359
+ "it can not be disposed and memory will not be "
1360
+ "reclaimed till the Ruby process exits.\n");
1300
1361
  } else {
1301
-
1302
- if (isolate_info->pid != getpid()) {
1303
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1362
+ if (this->pid != getpid() && !single_threaded) {
1363
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1364
+ "it can not be disposed and "
1365
+ "memory will not be reclaimed "
1366
+ "till the Ruby process exits.\n"
1367
+ "It is VERY likely your process will hang.\n"
1368
+ "If you wish to use v8 in forked environment "
1369
+ "please ensure the platform is initialized with:\n"
1370
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1371
+ );
1304
1372
  } else {
1305
- isolate_info->isolate->Dispose();
1373
+ isolate->Dispose();
1306
1374
  }
1307
1375
  }
1308
- isolate_info->isolate = NULL;
1376
+ isolate = nullptr;
1309
1377
  }
1310
1378
 
1311
- if (isolate_info->startup_data) {
1312
- delete[] isolate_info->startup_data->data;
1313
- delete isolate_info->startup_data;
1379
+ if (startup_data) {
1380
+ delete[] startup_data->data;
1381
+ delete startup_data;
1314
1382
  }
1315
1383
 
1316
- delete isolate_info->allocator;
1384
+ delete allocator;
1317
1385
  }
1318
1386
 
1319
- static void *free_context_raw(void* arg) {
1387
+ static void free_context_raw(void *arg) {
1320
1388
  ContextInfo* context_info = (ContextInfo*)arg;
1321
1389
  IsolateInfo* isolate_info = context_info->isolate_info;
1322
1390
  Persistent<Context>* context = context_info->context;
@@ -1333,6 +1401,20 @@ static void *free_context_raw(void* arg) {
1333
1401
  }
1334
1402
 
1335
1403
  xfree(context_info);
1404
+ }
1405
+
1406
+ static void *free_context_thr(void* arg) {
1407
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1408
+ return NULL;
1409
+ }
1410
+ if (ruby_exiting) {
1411
+ return NULL;
1412
+ }
1413
+
1414
+ free_context_raw(arg);
1415
+
1416
+ pthread_rwlock_unlock(&exit_lock);
1417
+
1336
1418
  return NULL;
1337
1419
  }
1338
1420
 
@@ -1347,22 +1429,17 @@ static void free_context(ContextInfo* context_info) {
1347
1429
 
1348
1430
  if (isolate_info && isolate_info->refs() > 1) {
1349
1431
  pthread_t free_context_thread;
1350
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1432
+ if (pthread_create(&free_context_thread, thread_attr_p,
1433
+ free_context_thr, (void*)context_info_copy)) {
1351
1434
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1352
1435
  }
1353
-
1354
1436
  } else {
1355
1437
  free_context_raw(context_info_copy);
1356
1438
  }
1357
1439
 
1358
- if (context_info->context && isolate_info && isolate_info->isolate) {
1359
1440
  context_info->context = NULL;
1360
- }
1361
-
1362
- if (isolate_info) {
1363
1441
  context_info->isolate_info = NULL;
1364
1442
  }
1365
- }
1366
1443
 
1367
1444
  static void deallocate_isolate(void* data) {
1368
1445
 
@@ -1376,7 +1453,7 @@ static void mark_isolate(void* data) {
1376
1453
  isolate_info->mark();
1377
1454
  }
1378
1455
 
1379
- void deallocate(void* data) {
1456
+ static void deallocate(void* data) {
1380
1457
  ContextInfo* context_info = (ContextInfo*)data;
1381
1458
 
1382
1459
  free_context(context_info);
@@ -1391,22 +1468,22 @@ static void mark_context(void* data) {
1391
1468
  }
1392
1469
  }
1393
1470
 
1394
- void deallocate_external_function(void * data) {
1471
+ static void deallocate_external_function(void * data) {
1395
1472
  xfree(data);
1396
1473
  }
1397
1474
 
1398
- void deallocate_snapshot(void * data) {
1475
+ static void deallocate_snapshot(void * data) {
1399
1476
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1400
1477
  delete[] snapshot_info->data;
1401
1478
  xfree(snapshot_info);
1402
1479
  }
1403
1480
 
1404
- VALUE allocate_external_function(VALUE klass) {
1481
+ static VALUE allocate_external_function(VALUE klass) {
1405
1482
  VALUE* self = ALLOC(VALUE);
1406
1483
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1407
1484
  }
1408
1485
 
1409
- VALUE allocate(VALUE klass) {
1486
+ static VALUE allocate(VALUE klass) {
1410
1487
  ContextInfo* context_info = ALLOC(ContextInfo);
1411
1488
  context_info->isolate_info = NULL;
1412
1489
  context_info->context = NULL;
@@ -1414,7 +1491,7 @@ VALUE allocate(VALUE klass) {
1414
1491
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1415
1492
  }
1416
1493
 
1417
- VALUE allocate_snapshot(VALUE klass) {
1494
+ static VALUE allocate_snapshot(VALUE klass) {
1418
1495
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1419
1496
  snapshot_info->data = NULL;
1420
1497
  snapshot_info->raw_size = 0;
@@ -1422,7 +1499,7 @@ VALUE allocate_snapshot(VALUE klass) {
1422
1499
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1423
1500
  }
1424
1501
 
1425
- VALUE allocate_isolate(VALUE klass) {
1502
+ static VALUE allocate_isolate(VALUE klass) {
1426
1503
  IsolateInfo* isolate_info = new IsolateInfo();
1427
1504
 
1428
1505
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1519,6 +1596,8 @@ rb_heap_snapshot(VALUE self, VALUE file) {
1519
1596
  FileOutputStream stream(fp);
1520
1597
  snap->Serialize(&stream, HeapSnapshot::kJSON);
1521
1598
 
1599
+ fflush(fp);
1600
+
1522
1601
  const_cast<HeapSnapshot*>(snap)->Delete();
1523
1602
 
1524
1603
  return Qtrue;
@@ -1576,13 +1655,23 @@ nogvl_context_call(void *args) {
1576
1655
  if (!call) {
1577
1656
  return 0;
1578
1657
  }
1579
- Isolate* isolate = call->context_info->isolate_info->isolate;
1658
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1659
+ Isolate* isolate = isolate_info->isolate;
1580
1660
 
1581
1661
  // in gvl flag
1582
1662
  isolate->SetData(IN_GVL, (void*)false);
1583
1663
  // terminate ASAP
1584
1664
  isolate->SetData(DO_TERMINATE, (void*)false);
1585
1665
 
1666
+ if (call->max_memory > 0) {
1667
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1668
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1669
+ if (!isolate_info->added_gc_cb) {
1670
+ isolate->AddGCEpilogueCallback(gc_callback);
1671
+ isolate_info->added_gc_cb = true;
1672
+ }
1673
+ }
1674
+
1586
1675
  Isolate::Scope isolate_scope(isolate);
1587
1676
  EscapableHandleScope handle_scope(isolate);
1588
1677
  TryCatch trycatch(isolate);
@@ -1640,6 +1729,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1640
1729
  call_argv = argv + 1;
1641
1730
  }
1642
1731
 
1732
+ call.max_memory = 0;
1733
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1734
+ if (mem_softlimit != Qnil) {
1735
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1736
+ call.max_memory = (size_t)sl_int;
1737
+ }
1738
+
1643
1739
  bool missingFunction = false;
1644
1740
  {
1645
1741
  Locker lock(isolate);
@@ -1651,8 +1747,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1651
1747
 
1652
1748
  // examples of such usage can be found in
1653
1749
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1654
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1655
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1750
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1751
+ MaybeLocal<v8::Value> val;
1752
+ if (!fname.IsEmpty()) {
1753
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1754
+ }
1656
1755
 
1657
1756
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1658
1757
  missingFunction = true;
@@ -1701,12 +1800,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1701
1800
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1702
1801
  }
1703
1802
 
1803
+ static void set_ruby_exiting(VALUE value) {
1804
+ (void)value;
1805
+
1806
+ int res = pthread_rwlock_wrlock(&exit_lock);
1807
+
1808
+ ruby_exiting = true;
1809
+ if (res == 0) {
1810
+ pthread_rwlock_unlock(&exit_lock);
1811
+ }
1812
+ }
1813
+
1814
+ static VALUE rb_monotime(VALUE self) {
1815
+ struct timespec ts;
1816
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
1817
+ return INT2FIX(-1);
1818
+ }
1819
+
1820
+ return DBL2NUM(
1821
+ (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0);
1822
+ }
1823
+
1704
1824
  extern "C" {
1705
1825
 
1706
- void Init_sq_mini_racer_extension ( void )
1826
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1707
1827
  {
1708
- VALUE rb_mSqreen = rb_define_module("Sqreen");
1828
+ ID sqreen_id = rb_intern("Sqreen");
1829
+ VALUE rb_mSqreen;
1830
+ if (rb_const_defined(rb_cObject, sqreen_id)) {
1831
+ rb_mSqreen = rb_const_get(rb_cObject, sqreen_id);
1832
+ if (TYPE(rb_mSqreen) != T_MODULE) {
1833
+ rb_raise(rb_eTypeError, "Sqreen is not a module");
1834
+ return;
1835
+ }
1836
+ } else {
1837
+ rb_mSqreen = rb_define_module("Sqreen");
1838
+ }
1709
1839
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1840
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1710
1841
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1711
1842
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1712
1843
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1753,8 +1884,19 @@ extern "C" {
1753
1884
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1754
1885
 
1755
1886
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1887
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1888
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1756
1889
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1757
1890
 
1758
1891
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1892
+
1893
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1894
+
1895
+ static pthread_attr_t attr;
1896
+ if (pthread_attr_init(&attr) == 0) {
1897
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1898
+ thread_attr_p = &attr;
1899
+ }
1900
+ }
1759
1901
  }
1760
1902
  }