mini_racer 0.2.11 → 0.3.0

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