mirror of
https://github.com/rubyjs/mini_racer
synced 2023-03-27 23:21:28 -04:00
FEATURE: add support for Isolate#low_memory_notification
This is another mechanism we can use for releasing as much memory as possible Note: Isolate#idle_notification can be used as well, but it is much slower to act. low_memory_notification forces a full GC and will clear up large amounts of space from the V8 heap. This also adds support for `ensure_gc_after_idle` options for MiniRacer::Context this allows you to automatically conserve memory on contexts, and only runs the GC when the context was idle for a certain amount of time.
This commit is contained in:
parent
2d4a760418
commit
5f29361ae9
6 changed files with 101 additions and 3 deletions
|
@ -1,3 +1,10 @@
|
|||
- 15-05-2020
|
||||
|
||||
- 0.2.12
|
||||
|
||||
- FEATURE: isolate.low_memory_notification which can force a full GC
|
||||
- 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
|
||||
|
||||
- 14-05-2020
|
||||
|
||||
- 0.2.11
|
||||
|
|
|
@ -230,12 +230,17 @@ context = MiniRacer::Context.new(isolate: isolate)
|
|||
# give up to 100ms for V8 garbage collection
|
||||
isolate.idle_notification(100)
|
||||
|
||||
# force V8 to perform a full GC
|
||||
isolate.low_memory_notification
|
||||
|
||||
```
|
||||
|
||||
This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
|
||||
|
||||
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.`
|
||||
|
||||
Additionally you may automate this process on a context by defining it with `MiniRacer::Content.new(ensure_gc_after_idle: 1)`. 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.
|
||||
|
||||
### V8 Runtime flags
|
||||
|
||||
It is possible to set V8 Runtime flags:
|
||||
|
|
|
@ -769,6 +769,16 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
|
|||
return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
static VALUE rb_isolate_low_memory_notification(VALUE self) {
|
||||
IsolateInfo* isolate_info;
|
||||
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
||||
|
||||
if (current_platform == NULL) return Qfalse;
|
||||
|
||||
isolate_info->isolate->LowMemoryNotification();
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
|
||||
ContextInfo* context_info;
|
||||
Data_Get_Struct(self, ContextInfo, context_info);
|
||||
|
@ -1657,6 +1667,8 @@ extern "C" {
|
|||
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
|
||||
|
||||
rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
|
||||
rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
|
||||
|
||||
rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
|
||||
|
||||
rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
|
||||
|
|
|
@ -145,6 +145,15 @@ module MiniRacer
|
|||
end
|
||||
# false signals it should be fetched if requested
|
||||
@isolate = options[:isolate] || false
|
||||
|
||||
@ensure_gc_after_idle = options[:ensure_gc_after_idle]
|
||||
|
||||
if @ensure_gc_after_idle
|
||||
@last_eval = nil
|
||||
@ensure_gc_thread = nil
|
||||
@ensure_gc_mutex = Mutex.new
|
||||
end
|
||||
|
||||
@disposed = false
|
||||
|
||||
@callback_mutex = Mutex.new
|
||||
|
@ -203,6 +212,7 @@ module MiniRacer
|
|||
end
|
||||
ensure
|
||||
@eval_thread = nil
|
||||
ensure_gc_thread if @ensure_gc_after_idle
|
||||
end
|
||||
|
||||
def call(function_name, *arguments)
|
||||
|
@ -216,15 +226,17 @@ module MiniRacer
|
|||
end
|
||||
ensure
|
||||
@eval_thread = nil
|
||||
ensure_gc_thread if @ensure_gc_after_idle
|
||||
end
|
||||
|
||||
def dispose
|
||||
return if @disposed
|
||||
isolate_mutex.synchronize do
|
||||
return if @disposed
|
||||
dispose_unsafe
|
||||
@disposed = true
|
||||
@isolate = nil # allow it to be garbage collected, if set
|
||||
end
|
||||
@disposed = true
|
||||
@isolate = nil # allow it to be garbage collected, if set
|
||||
end
|
||||
|
||||
|
||||
|
@ -273,6 +285,36 @@ module MiniRacer
|
|||
|
||||
private
|
||||
|
||||
def ensure_gc_thread
|
||||
@last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
@ensure_gc_mutex.synchronize do
|
||||
@ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
|
||||
@ensure_gc_thread ||= Thread.new do
|
||||
done = false
|
||||
while !done
|
||||
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
|
||||
if @disposed
|
||||
@ensure_gc_thread = nil
|
||||
break
|
||||
end
|
||||
|
||||
if @ensure_gc_after_idle < now - @last_eval
|
||||
@ensure_gc_mutex.synchronize do
|
||||
isolate_mutex.synchronize do
|
||||
# extra 50ms to make sure that we really have enough time
|
||||
isolate.low_memory_notification if !@disposed
|
||||
@ensure_gc_thread = nil
|
||||
done = true
|
||||
end
|
||||
end
|
||||
end
|
||||
sleep @ensure_gc_after_idle if !done
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stop_attached
|
||||
@callback_mutex.synchronize{
|
||||
if @callback_running
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module MiniRacer
|
||||
VERSION = "0.2.11"
|
||||
VERSION = "0.2.12"
|
||||
end
|
||||
|
|
|
@ -549,6 +549,7 @@ raise FooError, "I like foos"
|
|||
assert(isolate.idle_notification(1000))
|
||||
end
|
||||
|
||||
|
||||
def test_concurrent_access_over_the_same_isolate_1
|
||||
isolate = MiniRacer::Isolate.new
|
||||
context = MiniRacer::Context.new(isolate: isolate)
|
||||
|
@ -705,6 +706,37 @@ raise FooError, "I like foos"
|
|||
assert(stats.values.all?{|v| v > 0}, "expecting the isolate to have values for all the vals")
|
||||
end
|
||||
|
||||
def test_releasing_memory
|
||||
context = MiniRacer::Context.new
|
||||
|
||||
context.isolate.low_memory_notification
|
||||
|
||||
start_heap = context.heap_stats[:used_heap_size]
|
||||
|
||||
context.eval("'#{"x" * 1_000_000}'")
|
||||
|
||||
context.isolate.low_memory_notification
|
||||
|
||||
end_heap = context.heap_stats[:used_heap_size]
|
||||
|
||||
assert((end_heap - start_heap).abs < 1000, "expecting most of the 1_000_000 long string to be freed")
|
||||
end
|
||||
|
||||
def test_ensure_gc
|
||||
context = MiniRacer::Context.new(ensure_gc_after_idle: 0.001)
|
||||
context.isolate.low_memory_notification
|
||||
|
||||
start_heap = context.heap_stats[:used_heap_size]
|
||||
|
||||
context.eval("'#{"x" * 10_000_000}'")
|
||||
|
||||
sleep 0.005
|
||||
|
||||
end_heap = context.heap_stats[:used_heap_size]
|
||||
|
||||
assert((end_heap - start_heap).abs < 1000, "expecting most of the 1_000_000 long string to be freed")
|
||||
end
|
||||
|
||||
def test_eval_with_filename
|
||||
context = MiniRacer::Context.new()
|
||||
context.eval("var foo = function(){baz();}", filename: 'b/c/foo1.js')
|
||||
|
|
Loading…
Reference in a new issue