mini_racer 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 276af6ac1a39ed9f00576b34e1944c0c27c9ee745871fc50de8010f06c642abf
4
- data.tar.gz: 911e1bf55677699df6623afec3430be7eccba88664c60e085562fb37e5a62847
3
+ metadata.gz: f8218ee18399c771370bf599a4d4cd46707e9f463502e058c98fb38de7505c36
4
+ data.tar.gz: 8fb4a94f36528343c71b05d2b25473a86c02ff0caf4b651acad4be460ea5f016
5
5
  SHA512:
6
- metadata.gz: 17494a1abe347480cd62671155bcde45e60c8d47fee115338606a2b793308d9fe06f756da795f2b4fc84bbf8fded92ec6f6c3a656164ba195298efc4f27144e3
7
- data.tar.gz: 0c9cc0bad0cf4963582c00d4a6ac8df547fb1b655464f01ae115ec286c864c364521bba5d70ed2081c589d007328151af34e001ad48280f18d9afeca0af879c4
6
+ metadata.gz: 6765304fc32d76d8e7050eeed958130e0e2075d7d2826edc6e855eeb447799b5f009d29b87e448f20c1c1cebba583d9d16569d5f0f83d5f1a64e2530333a5cee
7
+ data.tar.gz: b224d6b448e1bc064b051adace85bf7ead7334b7682fe132897e87e028de6506afe5fc1921612ec9b8da3b09232f49cf9a6ecba4e594739792479e6e2fbfa54f
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4
4
3
  - 2.5
5
4
  - 2.6
6
5
  - 2.7
@@ -10,12 +9,6 @@ matrix:
10
9
  - rvm: 2.5.1
11
10
  os: osx
12
11
  osx_image: xcode9.4
13
- - rvm: 2.4.0
14
- os: osx
15
- osx_image: xcode8.3
16
- - rvm: 2.4.0
17
- os: osx
18
- osx_image: xcode7.3
19
12
  dist: trusty
20
13
  sudo: true
21
14
  before_install:
data/CHANGELOG CHANGED
@@ -1,3 +1,35 @@
1
+ - 22-07-2020
2
+
3
+ - 0.3.0
4
+
5
+ - FEATURE: upgraded to libv8 version 8.4.255.0
6
+
7
+ - 29-06-2020
8
+
9
+ - 0.2.15
10
+
11
+ - FEATURE: basic wasm support via pump_message_loop
12
+
13
+ - 15-05-2020
14
+
15
+ - 0.2.14
16
+
17
+ - FIX: ensure_gc_after_idle should take in milliseconds like the rest of the APIs not seconds
18
+ - FEATURE: strict params on MiniRacer::Context.new
19
+
20
+ - 15-05-2020
21
+
22
+ - 0.2.13
23
+
24
+ - FIX: edge case around ensure_gc_after_idle possibly firing when context is not idle
25
+
26
+ - 15-05-2020
27
+
28
+ - 0.2.12
29
+
30
+ - FEATURE: isolate.low_memory_notification which can force a full GC
31
+ - FEATURE: MiniRacer::Context.new(ensure_gc_after_idle: 2) - to force full GC 2 seconds after context is idle, this allows you to conserve memory on isolates
32
+
1
33
  - 14-05-2020
2
34
 
3
35
  - 0.2.11
data/README.md CHANGED
@@ -230,12 +230,17 @@ context = MiniRacer::Context.new(isolate: isolate)
230
230
  # give up to 100ms for V8 garbage collection
231
231
  isolate.idle_notification(100)
232
232
 
233
+ # force V8 to perform a full GC
234
+ isolate.low_memory_notification
235
+
233
236
  ```
234
237
 
235
238
  This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
236
239
 
237
240
  Note that this method maps directly to [`v8::Isolate::IdleNotification`](http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#aea16cbb2e351de9a3ae7be2b7cb48297), and that in particular its return value is the same (true if there is no further garbage to collect, false otherwise) and the same caveats apply, in particular that `there is no guarantee that the [call will return] within the time limit.`
238
241
 
242
+ Additionally you may automate this process on a context by defining it with `MiniRacer::Content.new(ensure_gc_after_idle: 1000)`. Using this will ensure V8 will run a full GC using `context.isolate.low_memory_notification` 1 second after the last eval on the context. Low memory notification is both slower and more aggressive than an idle_notification and will ensure long living isolates use minimal amounts of memory.
243
+
239
244
  ### V8 Runtime flags
240
245
 
241
246
  It is possible to set V8 Runtime flags:
@@ -310,6 +315,16 @@ context.eval("a = 2")
310
315
  # nothing works on the context from now on, its a shell waiting to be disposed
311
316
  ```
312
317
 
318
+ A MiniRacer context can also be dumped in a heapsnapshot file using `#write_heap_snapshot(file_or_io)`
319
+
320
+ ```ruby
321
+ context = MiniRacer::Context.new(timeout: 5)
322
+ context.eval("let a='testing';")
323
+ context.write_heap_snapshot("test.heapsnapshot")
324
+ ```
325
+
326
+ This file can then be loaded in the memory tab of the chrome dev console.
327
+
313
328
  ### Function call
314
329
 
315
330
  This calls the function passed as first argument:
@@ -11,6 +11,7 @@ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
11
11
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
12
12
  $CPPFLAGS += " -std=c++0x"
13
13
  $CPPFLAGS += " -fpermissive"
14
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
14
15
 
15
16
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
16
17
 
@@ -25,6 +25,7 @@ public:
25
25
  ArrayBuffer::Allocator* allocator;
26
26
  StartupData* startup_data;
27
27
  bool interrupted;
28
+ bool added_gc_cb;
28
29
  pid_t pid;
29
30
  VALUE mutex;
30
31
 
@@ -42,15 +43,12 @@ public:
42
43
 
43
44
 
44
45
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
45
- interrupted(false), pid(getpid()), refs_count(0) {
46
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
46
47
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
47
48
  mutex = rb_class_new_instance(0, nullptr, cMutex);
48
49
  }
49
50
 
50
- ~IsolateInfo() {
51
- void free_isolate(IsolateInfo*);
52
- free_isolate(this);
53
- }
51
+ ~IsolateInfo();
54
52
 
55
53
  void init(SnapshotInfo* snapshot_info = nullptr);
56
54
 
@@ -237,11 +235,13 @@ static void prepare_result(MaybeLocal v8res,
237
235
  Local<Value> local_value = v8res.ToLocalChecked();
238
236
  if ((local_value->IsObject() || local_value->IsArray()) &&
239
237
  !local_value->IsDate() && !local_value->IsFunction()) {
240
- Local<Object> JSON = context->Global()->Get(String::NewFromUtf8(isolate, "JSON"))
241
- ->ToObject(context).ToLocalChecked();
238
+ Local<Object> JSON = context->Global()->Get(
239
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
240
+ .ToLocalChecked().As<Object>();
242
241
 
243
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
244
- .As<Function>();
242
+ Local<Function> stringify = JSON->Get(
243
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
244
+ .ToLocalChecked().As<Function>();
245
245
 
246
246
  Local<Object> object = local_value->ToObject(context).ToLocalChecked();
247
247
  const unsigned argc = 1;
@@ -295,7 +295,7 @@ static void prepare_result(MaybeLocal v8res,
295
295
  } else if(trycatch.HasTerminated()) {
296
296
  evalRes.terminated = true;
297
297
  evalRes.message = new Persistent<Value>();
298
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
298
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
299
299
  evalRes.message->Reset(isolate, tmp);
300
300
  }
301
301
  if (!trycatch.StackTrace(context).IsEmpty()) {
@@ -312,7 +312,8 @@ nogvl_context_eval(void* arg) {
312
312
 
313
313
  EvalParams* eval_params = (EvalParams*)arg;
314
314
  EvalResult* result = eval_params->result;
315
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
315
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
316
+ Isolate* isolate = isolate_info->isolate;
316
317
 
317
318
  Isolate::Scope isolate_scope(isolate);
318
319
  HandleScope handle_scope(isolate);
@@ -356,7 +357,10 @@ nogvl_context_eval(void* arg) {
356
357
  // parsing successful
357
358
  if (eval_params->max_memory > 0) {
358
359
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
360
+ if (!isolate_info->added_gc_cb) {
359
361
  isolate->AddGCEpilogueCallback(gc_callback);
362
+ isolate_info->added_gc_cb = true;
363
+ }
360
364
  }
361
365
 
362
366
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -369,6 +373,12 @@ nogvl_context_eval(void* arg) {
369
373
  return NULL;
370
374
  }
371
375
 
376
+ static VALUE new_empty_failed_conv_obj() {
377
+ // TODO isolate code that translates execption to ruby
378
+ // exception so we can properly return it
379
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
380
+ }
381
+
372
382
  // assumes isolate locking is in place
373
383
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
374
384
  Local<Value> value) {
@@ -400,8 +410,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local context,
400
410
  VALUE rb_array = rb_ary_new();
401
411
  Local<Array> arr = Local<Array>::Cast(value);
402
412
  for(uint32_t i=0; i < arr->Length(); i++) {
403
- Local<Value> element = arr->Get(i);
404
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
413
+ MaybeLocal<Value> element = arr->Get(context, i);
414
+ if (element.IsEmpty()) {
415
+ continue;
416
+ }
417
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
405
418
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
406
419
  return rb_elem;
407
420
  }
@@ -431,18 +444,20 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local context,
431
444
  if (!maybe_props.IsEmpty()) {
432
445
  Local<Array> props = maybe_props.ToLocalChecked();
433
446
  for(uint32_t i=0; i < props->Length(); i++) {
434
- Local<Value> key = props->Get(i);
435
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
436
- Local<Value> prop_value = object->Get(key);
437
- // this may have failed due to Get raising
447
+ MaybeLocal<Value> key = props->Get(context, i);
448
+ if (key.IsEmpty()) {
449
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
450
+ }
451
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
438
452
 
439
- if (trycatch.HasCaught()) {
440
- // TODO isolate code that translates execption to ruby
441
- // exception so we can properly return it
442
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
453
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
454
+ // this may have failed due to Get raising
455
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
456
+ return new_empty_failed_conv_obj();
443
457
  }
444
458
 
445
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
459
+ VALUE rb_value = convert_v8_to_ruby(
460
+ isolate, context, prop_value.ToLocalChecked());
446
461
  rb_hash_aset(rb_hash, rb_key, rb_value);
447
462
  }
448
463
  }
@@ -524,7 +539,7 @@ static Local convert_ruby_to_v8(Isolate* isolate, Local context,
524
539
  length = RARRAY_LEN(value);
525
540
  array = Array::New(isolate, (int)length);
526
541
  for(i=0; i<length; i++) {
527
- array->Set(i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
542
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
528
543
  }
529
544
  return scope.Escape(array);
530
545
  case T_HASH:
@@ -533,7 +548,7 @@ static Local convert_ruby_to_v8(Isolate* isolate, Local context,
533
548
  length = RARRAY_LEN(hash_as_array);
534
549
  for(i=0; i<length; i++) {
535
550
  pair = rb_ary_entry(hash_as_array, i);
536
- object->Set(convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
551
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
537
552
  convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
538
553
  }
539
554
  return scope.Escape(object);
@@ -563,9 +578,9 @@ static Local convert_ruby_to_v8(Isolate* isolate, Local context,
563
578
  case T_UNDEF:
564
579
  case T_NODE:
565
580
  default:
566
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
581
+ return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
582
+ }
567
583
  }
568
- }
569
584
 
570
585
  static void unblock_eval(void *ptr) {
571
586
  EvalParams* eval = (EvalParams*)ptr;
@@ -576,53 +591,43 @@ static void unblock_eval(void *ptr) {
576
591
  * The implementations of the run_extra_code(), create_snapshot_data_blob() and
577
592
  * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
578
593
  */
579
- bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
594
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
580
595
  const char *utf8_source, const char *name) {
581
596
  Context::Scope context_scope(context);
582
597
  TryCatch try_catch(isolate);
583
598
  Local<String> source_string;
584
- if (!String::NewFromUtf8(isolate, utf8_source,
585
- NewStringType::kNormal)
586
- .ToLocal(&source_string)) {
599
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
587
600
  return false;
588
601
  }
589
- Local<v8::String> resource_name =
590
- String::NewFromUtf8(isolate, name, NewStringType::kNormal)
591
- .ToLocalChecked();
602
+ Local<String> resource_name =
603
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
592
604
  ScriptOrigin origin(resource_name);
593
605
  ScriptCompiler::Source source(source_string, origin);
594
606
  Local<Script> script;
595
607
  if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
596
608
  return false;
597
- if (script->Run(context).IsEmpty())
598
- return false;
599
- // CHECK(!try_catch.HasCaught());
609
+ if (script->Run(context).IsEmpty()) return false;
600
610
  return true;
601
611
  }
602
612
 
603
- StartupData
613
+ static StartupData
604
614
  create_snapshot_data_blob(const char *embedded_source = nullptr) {
605
- // Create a new isolate and a new context from scratch, optionally run
606
- // a script to embed, and serialize to create a snapshot blob.
607
- StartupData result = {nullptr, 0};
608
- {
609
- SnapshotCreator snapshot_creator;
610
- Isolate *isolate = snapshot_creator.GetIsolate();
615
+ Isolate *isolate = Isolate::Allocate();
616
+
617
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
618
+ SnapshotCreator snapshot_creator(isolate);
611
619
  {
612
620
  HandleScope scope(isolate);
613
- Local<Context> context = Context::New(isolate);
621
+ Local<v8::Context> context = v8::Context::New(isolate);
614
622
  if (embedded_source != nullptr &&
615
- !run_extra_code(isolate, context, embedded_source,
616
- "<embedded>")) {
617
- return result;
623
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
624
+ return {};
618
625
  }
619
626
  snapshot_creator.SetDefaultContext(context);
620
627
  }
621
- result = snapshot_creator.CreateBlob(
628
+ return snapshot_creator.CreateBlob(
622
629
  SnapshotCreator::FunctionCodeHandling::kClear);
623
630
  }
624
- return result;
625
- }
626
631
 
627
632
  StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
628
633
  const char *warmup_source) {
@@ -769,6 +774,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
769
774
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
770
775
  }
771
776
 
777
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
778
+ IsolateInfo* isolate_info;
779
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
780
+
781
+ if (current_platform == NULL) return Qfalse;
782
+
783
+ isolate_info->isolate->LowMemoryNotification();
784
+ return Qnil;
785
+ }
786
+
787
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
788
+ IsolateInfo* isolate_info;
789
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
790
+
791
+ if (current_platform == NULL) return Qfalse;
792
+
793
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
794
+ return Qtrue;
795
+ } else {
796
+ return Qfalse;
797
+ }
798
+ }
799
+
772
800
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
773
801
  ContextInfo* context_info;
774
802
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -1039,7 +1067,9 @@ gvl_ruby_callback(void* data) {
1039
1067
  callback_data.failed = false;
1040
1068
 
1041
1069
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1042
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
1070
+ args->GetIsolate()->ThrowException(
1071
+ String::NewFromUtf8Literal(args->GetIsolate(),
1072
+ "Terminated execution during transition from Ruby to JS"));
1043
1073
  args->GetIsolate()->TerminateExecution();
1044
1074
  if (length > 0) {
1045
1075
  rb_ary_clear(ruby_args);
@@ -1053,7 +1083,7 @@ gvl_ruby_callback(void* data) {
1053
1083
 
1054
1084
  if(callback_data.failed) {
1055
1085
  rb_iv_set(parent, "@current_exception", result);
1056
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1086
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1057
1087
  }
1058
1088
  else {
1059
1089
  HandleScope scope(args->GetIsolate());
@@ -1124,7 +1154,9 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1124
1154
 
1125
1155
  if (parent_object == Qnil) {
1126
1156
  context->Global()->Set(
1127
- v8_str, FunctionTemplate::New(isolate, ruby_callback, external)
1157
+ context,
1158
+ v8_str,
1159
+ FunctionTemplate::New(isolate, ruby_callback, external)
1128
1160
  ->GetFunction(context)
1129
1161
  .ToLocalChecked());
1130
1162
 
@@ -1137,7 +1169,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1137
1169
 
1138
1170
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1139
1171
  if (parsed_script.IsEmpty()) {
1140
- parse_error = true;
1172
+ parse_error = true;
1141
1173
  } else {
1142
1174
  MaybeLocal<Value> maybe_value =
1143
1175
  parsed_script.ToLocalChecked()->Run(context);
@@ -1147,11 +1179,12 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1147
1179
  Local<Value> value = maybe_value.ToLocalChecked();
1148
1180
  if (value->IsObject()) {
1149
1181
  value.As<Object>()->Set(
1150
- v8_str, FunctionTemplate::New(
1151
- isolate, ruby_callback, external)
1182
+ context,
1183
+ v8_str,
1184
+ FunctionTemplate::New(isolate, ruby_callback, external)
1152
1185
  ->GetFunction(context)
1153
1186
  .ToLocalChecked());
1154
- attach_error = false;
1187
+ attach_error = false;
1155
1188
  }
1156
1189
  }
1157
1190
  }
@@ -1181,32 +1214,31 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1181
1214
  return context_info->isolate_info->mutex;
1182
1215
  }
1183
1216
 
1184
- void free_isolate(IsolateInfo* isolate_info) {
1185
-
1186
- if (isolate_info->isolate) {
1187
- Locker lock(isolate_info->isolate);
1188
- }
1189
-
1190
- if (isolate_info->isolate) {
1191
- if (isolate_info->interrupted) {
1192
- 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");
1217
+ IsolateInfo::~IsolateInfo() {
1218
+ if (isolate) {
1219
+ if (this->interrupted) {
1220
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1221
+ "it can not be disposed and memory will not be "
1222
+ "reclaimed till the Ruby process exits.\n");
1193
1223
  } else {
1194
-
1195
- if (isolate_info->pid != getpid()) {
1196
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1224
+ if (this->pid != getpid()) {
1225
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1226
+ "it can not be disposed and "
1227
+ "memory will not be reclaimed "
1228
+ "till the Ruby process exits.\n");
1197
1229
  } else {
1198
- isolate_info->isolate->Dispose();
1230
+ isolate->Dispose();
1199
1231
  }
1200
1232
  }
1201
- isolate_info->isolate = NULL;
1233
+ isolate = nullptr;
1202
1234
  }
1203
1235
 
1204
- if (isolate_info->startup_data) {
1205
- delete[] isolate_info->startup_data->data;
1206
- delete isolate_info->startup_data;
1236
+ if (startup_data) {
1237
+ delete[] startup_data->data;
1238
+ delete startup_data;
1207
1239
  }
1208
1240
 
1209
- delete isolate_info->allocator;
1241
+ delete allocator;
1210
1242
  }
1211
1243
 
1212
1244
  static void free_context_raw(void *arg) {
@@ -1278,7 +1310,7 @@ static void mark_isolate(void* data) {
1278
1310
  isolate_info->mark();
1279
1311
  }
1280
1312
 
1281
- void deallocate(void* data) {
1313
+ static void deallocate(void* data) {
1282
1314
  ContextInfo* context_info = (ContextInfo*)data;
1283
1315
 
1284
1316
  free_context(context_info);
@@ -1293,22 +1325,22 @@ static void mark_context(void* data) {
1293
1325
  }
1294
1326
  }
1295
1327
 
1296
- void deallocate_external_function(void * data) {
1328
+ static void deallocate_external_function(void * data) {
1297
1329
  xfree(data);
1298
1330
  }
1299
1331
 
1300
- void deallocate_snapshot(void * data) {
1332
+ static void deallocate_snapshot(void * data) {
1301
1333
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1302
1334
  delete[] snapshot_info->data;
1303
1335
  xfree(snapshot_info);
1304
1336
  }
1305
1337
 
1306
- VALUE allocate_external_function(VALUE klass) {
1338
+ static VALUE allocate_external_function(VALUE klass) {
1307
1339
  VALUE* self = ALLOC(VALUE);
1308
1340
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1309
1341
  }
1310
1342
 
1311
- VALUE allocate(VALUE klass) {
1343
+ static VALUE allocate(VALUE klass) {
1312
1344
  ContextInfo* context_info = ALLOC(ContextInfo);
1313
1345
  context_info->isolate_info = NULL;
1314
1346
  context_info->context = NULL;
@@ -1316,7 +1348,7 @@ VALUE allocate(VALUE klass) {
1316
1348
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1317
1349
  }
1318
1350
 
1319
- VALUE allocate_snapshot(VALUE klass) {
1351
+ static VALUE allocate_snapshot(VALUE klass) {
1320
1352
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1321
1353
  snapshot_info->data = NULL;
1322
1354
  snapshot_info->raw_size = 0;
@@ -1324,7 +1356,7 @@ VALUE allocate_snapshot(VALUE klass) {
1324
1356
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1325
1357
  }
1326
1358
 
1327
- VALUE allocate_isolate(VALUE klass) {
1359
+ static VALUE allocate_isolate(VALUE klass) {
1328
1360
  IsolateInfo* isolate_info = new IsolateInfo();
1329
1361
 
1330
1362
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1463,7 +1495,8 @@ nogvl_context_call(void *args) {
1463
1495
  if (!call) {
1464
1496
  return NULL;
1465
1497
  }
1466
- Isolate* isolate = call->context_info->isolate_info->isolate;
1498
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1499
+ Isolate* isolate = isolate_info->isolate;
1467
1500
 
1468
1501
  // in gvl flag
1469
1502
  isolate->SetData(IN_GVL, (void*)false);
@@ -1473,7 +1506,10 @@ nogvl_context_call(void *args) {
1473
1506
  if (call->max_memory > 0) {
1474
1507
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1475
1508
  isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1509
+ if (!isolate_info->added_gc_cb) {
1476
1510
  isolate->AddGCEpilogueCallback(gc_callback);
1511
+ isolate_info->added_gc_cb = true;
1512
+ }
1477
1513
  }
1478
1514
 
1479
1515
  Isolate::Scope isolate_scope(isolate);
@@ -1551,8 +1587,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1551
1587
 
1552
1588
  // examples of such usage can be found in
1553
1589
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1554
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1555
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1590
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1591
+ MaybeLocal<v8::Value> val;
1592
+ if (!fname.IsEmpty()) {
1593
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1594
+ }
1556
1595
 
1557
1596
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1558
1597
  missingFunction = true;
@@ -1657,6 +1696,8 @@ extern "C" {
1657
1696
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1658
1697
 
1659
1698
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1699
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1700
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1660
1701
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1661
1702
 
1662
1703
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
@@ -130,21 +130,29 @@ module MiniRacer
130
130
  end
131
131
  end
132
132
 
133
- def initialize(options = nil)
133
+ def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil)
134
134
  options ||= {}
135
135
 
136
- check_init_options!(options)
136
+ check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
137
137
 
138
138
  @functions = {}
139
139
  @timeout = nil
140
140
  @max_memory = nil
141
141
  @current_exception = nil
142
- @timeout = options[:timeout]
143
- if options[:max_memory].is_a?(Numeric) && options[:max_memory] > 0
144
- @max_memory = options[:max_memory]
145
- end
142
+ @timeout = timeout
143
+ @max_memory = max_memory
144
+
146
145
  # false signals it should be fetched if requested
147
- @isolate = options[:isolate] || false
146
+ @isolate = isolate || false
147
+
148
+ @ensure_gc_after_idle = ensure_gc_after_idle
149
+
150
+ if @ensure_gc_after_idle
151
+ @last_eval = nil
152
+ @ensure_gc_thread = nil
153
+ @ensure_gc_mutex = Mutex.new
154
+ end
155
+
148
156
  @disposed = false
149
157
 
150
158
  @callback_mutex = Mutex.new
@@ -153,7 +161,7 @@ module MiniRacer
153
161
  @eval_thread = nil
154
162
 
155
163
  # defined in the C class
156
- init_unsafe(options[:isolate], options[:snapshot])
164
+ init_unsafe(isolate, snapshot)
157
165
  end
158
166
 
159
167
  def isolate
@@ -203,6 +211,7 @@ module MiniRacer
203
211
  end
204
212
  ensure
205
213
  @eval_thread = nil
214
+ ensure_gc_thread if @ensure_gc_after_idle
206
215
  end
207
216
 
208
217
  def call(function_name, *arguments)
@@ -216,15 +225,17 @@ module MiniRacer
216
225
  end
217
226
  ensure
218
227
  @eval_thread = nil
228
+ ensure_gc_thread if @ensure_gc_after_idle
219
229
  end
220
230
 
221
231
  def dispose
222
232
  return if @disposed
223
233
  isolate_mutex.synchronize do
234
+ return if @disposed
224
235
  dispose_unsafe
236
+ @disposed = true
237
+ @isolate = nil # allow it to be garbage collected, if set
225
238
  end
226
- @disposed = true
227
- @isolate = nil # allow it to be garbage collected, if set
228
239
  end
229
240
 
230
241
 
@@ -273,6 +284,38 @@ module MiniRacer
273
284
 
274
285
  private
275
286
 
287
+ def ensure_gc_thread
288
+ @last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
289
+ @ensure_gc_mutex.synchronize do
290
+ @ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
291
+ @ensure_gc_thread ||= Thread.new do
292
+ ensure_gc_after_idle_seconds = @ensure_gc_after_idle / 1000.0
293
+ done = false
294
+ while !done
295
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
296
+
297
+ if @disposed
298
+ @ensure_gc_thread = nil
299
+ break
300
+ end
301
+
302
+ if !@eval_thread && ensure_gc_after_idle_seconds < now - @last_eval
303
+ @ensure_gc_mutex.synchronize do
304
+ isolate_mutex.synchronize do
305
+ if !@eval_thread
306
+ isolate.low_memory_notification if !@disposed
307
+ @ensure_gc_thread = nil
308
+ done = true
309
+ end
310
+ end
311
+ end
312
+ end
313
+ sleep ensure_gc_after_idle_seconds if !done
314
+ end
315
+ end
316
+ end
317
+ end
318
+
276
319
  def stop_attached
277
320
  @callback_mutex.synchronize{
278
321
  if @callback_running
@@ -326,15 +369,29 @@ module MiniRacer
326
369
  rp.close if rp
327
370
  end
328
371
 
329
- def check_init_options!(options)
330
- assert_option_is_nil_or_a('isolate', options[:isolate], Isolate)
331
- assert_option_is_nil_or_a('snapshot', options[:snapshot], Snapshot)
372
+ def check_init_options!(isolate:, snapshot:, max_memory:, ensure_gc_after_idle:, timeout:)
373
+ assert_option_is_nil_or_a('isolate', isolate, Isolate)
374
+ assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
375
+
376
+ assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000)
377
+ assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
378
+ assert_numeric_or_nil('timeout', timeout, min_value: 1)
332
379
 
333
- if options[:isolate] && options[:snapshot]
380
+ if isolate && snapshot
334
381
  raise ArgumentError, 'can only pass one of isolate and snapshot options'
335
382
  end
336
383
  end
337
384
 
385
+ def assert_numeric_or_nil(option_name, object, min_value:)
386
+ if object.is_a?(Numeric) && object < min_value
387
+ raise ArgumentError, "#{option_name} must be larger than #{min_value}"
388
+ end
389
+
390
+ if !object.nil? && !object.is_a?(Numeric)
391
+ raise ArgumentError, "#{option_name} must be a number, passed a #{object.inspect}"
392
+ end
393
+ end
394
+
338
395
  def assert_option_is_nil_or_a(option_name, object, klass)
339
396
  unless object.nil? || object.is_a?(klass)
340
397
  raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniRacer
2
- VERSION = "0.2.11"
4
+ VERSION = "0.3.0"
3
5
  end
@@ -27,11 +27,12 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_development_dependency "bundler"
30
- spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rake", ">= 12.3.3"
31
31
  spec.add_development_dependency "minitest", "~> 5.0"
32
32
  spec.add_development_dependency "rake-compiler"
33
+ spec.add_development_dependency "m"
33
34
 
34
- spec.add_dependency 'libv8', '> 7.3'
35
+ spec.add_dependency 'libv8', '> 8.4'
35
36
  spec.require_paths = ["lib", "ext"]
36
37
 
37
38
  spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.11
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-14 00:00:00.000000000 Z
11
+ date: 2020-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 12.3.3
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +66,34 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: m
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: libv8
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">"
74
88
  - !ruby/object:Gem::Version
75
- version: '7.3'
89
+ version: '8.4'
76
90
  type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - ">"
81
95
  - !ruby/object:Gem::Version
82
- version: '7.3'
96
+ version: '8.4'
83
97
  description: Minimal embedded v8 engine for Ruby
84
98
  email:
85
99
  - sam.saffron@gmail.com
@@ -108,10 +122,10 @@ licenses:
108
122
  - MIT
109
123
  metadata:
110
124
  bug_tracker_uri: https://github.com/discourse/mini_racer/issues
111
- changelog_uri: https://github.com/discourse/mini_racer/blob/v0.2.11/CHANGELOG
112
- documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.2.11
113
- source_code_uri: https://github.com/discourse/mini_racer/tree/v0.2.11
114
- post_install_message:
125
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.3.0/CHANGELOG
126
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.3.0
127
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.3.0
128
+ post_install_message:
115
129
  rdoc_options: []
116
130
  require_paths:
117
131
  - lib
@@ -128,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
142
  version: '0'
129
143
  requirements: []
130
144
  rubygems_version: 3.0.3
131
- signing_key:
145
+ signing_key:
132
146
  specification_version: 4
133
147
  summary: Minimal embedded v8 for Ruby
134
148
  test_files: []