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 +4 -4
- data/.travis.yml +0 -7
- data/CHANGELOG +32 -0
- data/README.md +15 -0
- data/ext/mini_racer_extension/extconf.rb +1 -0
- data/ext/mini_racer_extension/mini_racer_extension.cc +126 -85
- data/lib/mini_racer.rb +71 -14
- data/lib/mini_racer/version.rb +3 -1
- data/mini_racer.gemspec +3 -2
- metadata +28 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8218ee18399c771370bf599a4d4cd46707e9f463502e058c98fb38de7505c36
|
4
|
+
data.tar.gz: 8fb4a94f36528343c71b05d2b25473a86c02ff0caf4b651acad4be460ea5f016
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6765304fc32d76d8e7050eeed958130e0e2075d7d2826edc6e855eeb447799b5f009d29b87e448f20c1c1cebba583d9d16569d5f0f83d5f1a64e2530333a5cee
|
7
|
+
data.tar.gz: b224d6b448e1bc064b051adace85bf7ead7334b7682fe132897e87e028de6506afe5fc1921612ec9b8da3b09232f49cf9a6ecba4e594739792479e6e2fbfa54f
|
data/.travis.yml
CHANGED
@@ -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
|
|
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(
|
241
|
-
|
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(
|
244
|
-
|
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
|
|
295
295
|
} else if(trycatch.HasTerminated()) {
|
296
296
|
evalRes.terminated = true;
|
297
297
|
evalRes.message = new Persistent<Value>();
|
298
|
-
Local<String> tmp = String::
|
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
|
-
|
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
|
|
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
|
-
|
404
|
-
|
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
|
|
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
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
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
|
-
|
440
|
-
//
|
441
|
-
|
442
|
-
|
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(
|
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
|
|
524
539
|
length = RARRAY_LEN(value);
|
525
540
|
array = Array::New(isolate, (int)length);
|
526
541
|
for(i=0; i<length; i++) {
|
527
|
-
|
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
|
|
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
|
-
|
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
|
|
563
578
|
case T_UNDEF:
|
564
579
|
case T_NODE:
|
565
580
|
default:
|
566
|
-
return scope.Escape(String::
|
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<
|
590
|
-
|
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
|
-
|
606
|
-
|
607
|
-
|
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
|
-
|
621
|
+
Local<v8::Context> context = v8::Context::New(isolate);
|
614
622
|
if (embedded_source != nullptr &&
|
615
|
-
!run_extra_code(isolate, context, embedded_source,
|
616
|
-
|
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
|
-
|
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(
|
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::
|
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
|
-
|
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
|
-
|
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
|
-
|
1151
|
-
|
1182
|
+
context,
|
1183
|
+
v8_str,
|
1184
|
+
FunctionTemplate::New(isolate, ruby_callback, external)
|
1152
1185
|
->GetFunction(context)
|
1153
1186
|
.ToLocalChecked());
|
1154
|
-
|
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
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
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
|
-
|
1196
|
-
|
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
|
-
|
1230
|
+
isolate->Dispose();
|
1199
1231
|
}
|
1200
1232
|
}
|
1201
|
-
|
1233
|
+
isolate = nullptr;
|
1202
1234
|
}
|
1203
1235
|
|
1204
|
-
if (
|
1205
|
-
delete[]
|
1206
|
-
delete
|
1236
|
+
if (startup_data) {
|
1237
|
+
delete[] startup_data->data;
|
1238
|
+
delete startup_data;
|
1207
1239
|
}
|
1208
1240
|
|
1209
|
-
delete
|
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
|
-
|
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
|
-
|
1555
|
-
MaybeLocal<v8::Value> val
|
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);
|
data/lib/mini_racer.rb
CHANGED
@@ -130,21 +130,29 @@ module MiniRacer
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
-
def initialize(
|
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!(
|
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 =
|
143
|
-
|
144
|
-
|
145
|
-
end
|
142
|
+
@timeout = timeout
|
143
|
+
@max_memory = max_memory
|
144
|
+
|
146
145
|
# false signals it should be fetched if requested
|
147
|
-
@isolate =
|
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(
|
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!(
|
330
|
-
assert_option_is_nil_or_a('isolate',
|
331
|
-
assert_option_is_nil_or_a('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
|
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}"
|
data/lib/mini_racer/version.rb
CHANGED
data/mini_racer.gemspec
CHANGED
@@ -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", "
|
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', '>
|
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.
|
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-
|
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:
|
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:
|
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: '
|
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: '
|
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.
|
112
|
-
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.
|
113
|
-
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.
|
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: []
|