mirror of
https://github.com/rubyjs/mini_racer
synced 2023-03-27 23:21:28 -04:00
FEATURE: filename support for #eval
also fixes heap_stats so it returns all 0s when context is disposed
This commit is contained in:
parent
f7ec907547
commit
2f484fe1b9
6 changed files with 99 additions and 25 deletions
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
- 0.1.10
|
- 0.1.10
|
||||||
|
|
||||||
- Fix leak: minor memory leak when disposing a context (20 bytes per context)
|
- Fix leak: memory leak when disposing a context (20 bytes per context)
|
||||||
- Feature: added #heap_stats so you can get visibility from context to actual memory usage of isolate
|
- Feature: added #heap_stats so you can get visibility from context to actual memory usage of isolate
|
||||||
- Feature: added #dispose so you reclaim all v8 memory right away as opposed to waiting for GC
|
- Feature: added #dispose so you reclaim all v8 memory right away as opposed to waiting for GC
|
||||||
|
- Feature: you can now specify filename in an eval eg: eval('a = 1', filename: 'my_awesome.js')
|
||||||
|
|
||||||
09-03-2017
|
09-03-2017
|
||||||
|
|
||||||
|
|
19
README.md
19
README.md
|
@ -68,6 +68,19 @@ context.eval 'while(true){}'
|
||||||
# => exception is raised
|
# => exception is raised
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Rich debugging with "filename" support
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
|
||||||
|
context = MiniRacer::Context.new
|
||||||
|
context.eval('var foo = function() {bar();}', filename: 'a/foo.js')
|
||||||
|
context.eval('bar()', filename: 'a/bar.js')
|
||||||
|
|
||||||
|
# MiniRacer::RuntimeError is raised containing the filenames you specified for evals in backtrace
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Threadsafe
|
### Threadsafe
|
||||||
|
|
||||||
Context usage is threadsafe
|
Context usage is threadsafe
|
||||||
|
@ -226,6 +239,12 @@ A list of all V8 runtime flags can be found using `node --v8-options`, or else b
|
||||||
|
|
||||||
Note that runtime flags must be set before any other operation (e.g. creating a context, a snapshot or an isolate), otherwise an exception will be thrown.
|
Note that runtime flags must be set before any other operation (e.g. creating a context, a snapshot or an isolate), otherwise an exception will be thrown.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
|
||||||
|
- :expose_gc : Will expose `gc()` which you can run in JavaScript to issue a gc
|
||||||
|
- :max_old_space_size : defaults to 1400 (megs) on 64 bit, you can restric memory usage by limiting this.
|
||||||
|
- **NOTE TO READER** our documentation could be awesome we could be properly documenting all the flags, they are hugely useful, if you feel like documenting a few more, PLEASE DO, PRs are welcome.
|
||||||
|
|
||||||
## Controlling memory
|
## Controlling memory
|
||||||
|
|
||||||
When hosting v8 you may want to keep track of memory usage, use #heap_stats to get memory usage:
|
When hosting v8 you may want to keep track of memory usage, use #heap_stats to get memory usage:
|
||||||
|
|
|
@ -64,6 +64,7 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ContextInfo* context_info;
|
ContextInfo* context_info;
|
||||||
Local<String>* eval;
|
Local<String>* eval;
|
||||||
|
Local<String>* filename;
|
||||||
useconds_t timeout;
|
useconds_t timeout;
|
||||||
EvalResult* result;
|
EvalResult* result;
|
||||||
} EvalParams;
|
} EvalParams;
|
||||||
|
@ -131,13 +132,25 @@ nogvl_context_eval(void* arg) {
|
||||||
TryCatch trycatch(isolate);
|
TryCatch trycatch(isolate);
|
||||||
Local<Context> context = eval_params->context_info->context->Get(isolate);
|
Local<Context> context = eval_params->context_info->context->Get(isolate);
|
||||||
Context::Scope context_scope(context);
|
Context::Scope context_scope(context);
|
||||||
|
v8::ScriptOrigin *origin = NULL;
|
||||||
|
|
||||||
// in gvl flag
|
// in gvl flag
|
||||||
isolate->SetData(0, (void*)false);
|
isolate->SetData(0, (void*)false);
|
||||||
// terminate ASAP
|
// terminate ASAP
|
||||||
isolate->SetData(1, (void*)false);
|
isolate->SetData(1, (void*)false);
|
||||||
|
|
||||||
MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
|
MaybeLocal<Script> parsed_script;
|
||||||
|
|
||||||
|
if (eval_params->filename) {
|
||||||
|
origin = new v8::ScriptOrigin(*eval_params->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed_script = Script::Compile(context, *eval_params->eval, origin);
|
||||||
|
|
||||||
|
if (origin) {
|
||||||
|
delete origin;
|
||||||
|
}
|
||||||
|
|
||||||
result->parsed = !parsed_script.IsEmpty();
|
result->parsed = !parsed_script.IsEmpty();
|
||||||
result->executed = false;
|
result->executed = false;
|
||||||
result->terminated = false;
|
result->terminated = false;
|
||||||
|
@ -507,7 +520,7 @@ static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
|
||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
||||||
|
|
||||||
EvalParams eval_params;
|
EvalParams eval_params;
|
||||||
EvalResult eval_result;
|
EvalResult eval_result;
|
||||||
|
@ -528,6 +541,16 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
||||||
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
|
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
|
||||||
NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
|
NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
|
||||||
|
|
||||||
|
Local<String> local_filename;
|
||||||
|
|
||||||
|
if (filename != Qnil) {
|
||||||
|
local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
|
||||||
|
NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
|
||||||
|
eval_params.filename = &local_filename;
|
||||||
|
} else {
|
||||||
|
eval_params.filename = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
eval_params.context_info = context_info;
|
eval_params.context_info = context_info;
|
||||||
eval_params.eval = &eval;
|
eval_params.eval = &eval;
|
||||||
eval_params.result = &eval_result;
|
eval_params.result = &eval_result;
|
||||||
|
@ -937,28 +960,30 @@ rb_heap_stats(VALUE self) {
|
||||||
|
|
||||||
ContextInfo* context_info;
|
ContextInfo* context_info;
|
||||||
Data_Get_Struct(self, ContextInfo, context_info);
|
Data_Get_Struct(self, ContextInfo, context_info);
|
||||||
|
Isolate* isolate;
|
||||||
if (!context_info->isolate_info) {
|
|
||||||
return Qnil;
|
|
||||||
}
|
|
||||||
|
|
||||||
Isolate* isolate = context_info->isolate_info->isolate;
|
|
||||||
|
|
||||||
if (!isolate) {
|
|
||||||
return Qnil;
|
|
||||||
}
|
|
||||||
|
|
||||||
v8::HeapStatistics stats;
|
v8::HeapStatistics stats;
|
||||||
|
|
||||||
isolate->GetHeapStatistics(&stats);
|
isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
|
||||||
|
|
||||||
VALUE rval = rb_hash_new();
|
VALUE rval = rb_hash_new();
|
||||||
|
|
||||||
|
if (!isolate) {
|
||||||
|
|
||||||
|
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
|
||||||
|
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
|
||||||
|
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
|
||||||
|
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
|
||||||
|
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
isolate->GetHeapStatistics(&stats);
|
||||||
|
|
||||||
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
|
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
|
||||||
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
|
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
|
||||||
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
|
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
|
||||||
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
|
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
|
||||||
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
|
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
|
||||||
|
}
|
||||||
|
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
@ -1029,7 +1054,7 @@ extern "C" {
|
||||||
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
||||||
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
||||||
|
|
||||||
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
|
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
||||||
rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
|
rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
|
||||||
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
||||||
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
||||||
|
|
|
@ -164,14 +164,16 @@ module MiniRacer
|
||||||
eval(File.read(filename))
|
eval(File.read(filename))
|
||||||
end
|
end
|
||||||
|
|
||||||
def eval(str)
|
def eval(str, options=nil)
|
||||||
raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
|
raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
|
||||||
|
|
||||||
|
filename = options && options[:filename].to_s
|
||||||
|
|
||||||
@eval_thread = Thread.current
|
@eval_thread = Thread.current
|
||||||
isolate.with_lock do
|
isolate.with_lock do
|
||||||
@current_exception = nil
|
@current_exception = nil
|
||||||
timeout do
|
timeout do
|
||||||
eval_unsafe(str)
|
eval_unsafe(str, filename)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module MiniRacer
|
module MiniRacer
|
||||||
VERSION = "0.1.9"
|
VERSION = "0.1.10"
|
||||||
end
|
end
|
||||||
|
|
|
@ -637,6 +637,33 @@ raise FooError, "I like foos"
|
||||||
assert(stats.values.all?{|v| v > 0}, "expecting the isolate to have values for all the vals")
|
assert(stats.values.all?{|v| v > 0}, "expecting the isolate to have values for all the vals")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_eval_with_filename
|
||||||
|
context = MiniRacer::Context.new()
|
||||||
|
context.eval("var foo = function(){baz();}", filename: 'b/c/foo1.js')
|
||||||
|
|
||||||
|
got_error = false
|
||||||
|
begin
|
||||||
|
context.eval("foo()", filename: 'baz1.js')
|
||||||
|
rescue MiniRacer::RuntimeError => e
|
||||||
|
assert_match(/foo1.js/, e.backtrace[0])
|
||||||
|
assert_match(/baz1.js/, e.backtrace[1])
|
||||||
|
got_error = true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(got_error, "should raise")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_estimated_size_when_disposed
|
||||||
|
|
||||||
|
context = MiniRacer::Context.new(timeout: 5)
|
||||||
|
context.eval("let a='testing';")
|
||||||
|
context.dispose
|
||||||
|
|
||||||
|
stats = context.heap_stats
|
||||||
|
assert(stats.values.all?{|v| v==0}, "should have 0 values once disposed")
|
||||||
|
end
|
||||||
|
|
||||||
def test_can_dispose
|
def test_can_dispose
|
||||||
skip "takes too long"
|
skip "takes too long"
|
||||||
#
|
#
|
||||||
|
|
Loading…
Add table
Reference in a new issue