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 +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: []
|