diff --git a/src/binding.cc b/src/binding.cc index c05c530a..f886472f 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -178,6 +178,18 @@ bool v8__Isolate__AddMessageListener(v8::Isolate* isolate, return isolate->AddMessageListener(callback); } +void v8__Isolate__AddNearHeapLimitCallback(v8::Isolate* isolate, + v8::NearHeapLimitCallback callback, + void* data) { + isolate->AddNearHeapLimitCallback(callback, data); +} + +void v8__Isolate__RemoveNearHeapLimitCallback( + v8::Isolate* isolate, v8::NearHeapLimitCallback callback, + size_t heap_limit) { + isolate->RemoveNearHeapLimitCallback(callback, heap_limit); +} + const v8::Value* v8__Isolate__ThrowException(v8::Isolate* isolate, const v8::Value& exception) { return local_to_ptr(isolate->ThrowException(ptr_to_local(&exception))); @@ -204,6 +216,13 @@ size_t v8__Isolate__CreateParams__SIZEOF() { return sizeof(v8::Isolate::CreateParams); } +void v8__ResourceConstraints__ConfigureDefaultsFromHeapSize( + v8::ResourceConstraints* constraints, size_t initial_heap_size_in_bytes, + size_t maximum_heap_size_in_bytes) { + constraints->ConfigureDefaultsFromHeapSize(initial_heap_size_in_bytes, + maximum_heap_size_in_bytes); +} + void v8__HandleScope__CONSTRUCT(uninit_t* buf, v8::Isolate* isolate) { construct_in_place(buf, isolate); @@ -743,11 +762,10 @@ MaybeBool v8__Object__SetAccessor(const v8::Object& self, ptr_to_local(&context), ptr_to_local(&key), getter)); } -MaybeBool v8__Object__SetAccessorWithSetter(const v8::Object& self, - const v8::Context& context, - const v8::Name& key, - v8::AccessorNameGetterCallback getter, - v8::AccessorNameSetterCallback setter) { +MaybeBool v8__Object__SetAccessorWithSetter( + const v8::Object& self, const v8::Context& context, const v8::Name& key, + v8::AccessorNameGetterCallback getter, + v8::AccessorNameSetterCallback setter) { return maybe_to_maybe_bool(ptr_to_local(&self)->SetAccessor( ptr_to_local(&context), ptr_to_local(&key), getter, setter)); } diff --git a/src/isolate.rs b/src/isolate.rs index c2d6041f..6365a606 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -69,6 +69,12 @@ pub type HostImportModuleDynamicallyCallback = extern "C" fn( pub type InterruptCallback = extern "C" fn(isolate: &mut Isolate, data: *mut c_void); +pub type NearHeapLimitCallback = extern "C" fn( + data: *mut c_void, + current_heap_limit: usize, + initial_heap_limit: usize, +) -> usize; + extern "C" { fn v8__Isolate__New(params: *const raw::CreateParams) -> *mut Isolate; fn v8__Isolate__Dispose(this: *mut Isolate); @@ -86,6 +92,16 @@ extern "C" { isolate: *mut Isolate, callback: MessageCallback, ) -> bool; + fn v8__Isolate__AddNearHeapLimitCallback( + isolate: *mut Isolate, + callback: NearHeapLimitCallback, + data: *mut c_void, + ); + fn v8__Isolate__RemoveNearHeapLimitCallback( + isolate: *mut Isolate, + callback: NearHeapLimitCallback, + heap_limit: usize, + ); fn v8__Isolate__SetPromiseRejectCallback( isolate: *mut Isolate, callback: PromiseRejectCallback, @@ -345,6 +361,32 @@ impl Isolate { } } + /// Add a callback to invoke in case the heap size is close to the heap limit. + /// If multiple callbacks are added, only the most recently added callback is + /// invoked. + #[allow(clippy::not_unsafe_ptr_arg_deref)] // False positive. + pub fn add_near_heap_limit_callback( + &mut self, + callback: NearHeapLimitCallback, + data: *mut c_void, + ) { + unsafe { v8__Isolate__AddNearHeapLimitCallback(self, callback, data) }; + } + + /// Remove the given callback and restore the heap limit to the given limit. + /// If the given limit is zero, then it is ignored. If the current heap size + /// is greater than the given limit, then the heap limit is restored to the + /// minimal limit that is possible for the current heap size. + pub fn remove_near_heap_limit_callback( + &mut self, + callback: NearHeapLimitCallback, + heap_limit: usize, + ) { + unsafe { + v8__Isolate__RemoveNearHeapLimitCallback(self, callback, heap_limit) + }; + } + /// Runs the default MicrotaskQueue until it gets empty. /// Any exceptions thrown by microtask callbacks are swallowed. pub fn run_microtasks(&mut self) { diff --git a/src/isolate_create_params.rs b/src/isolate_create_params.rs index a9f015d1..2c9ef302 100644 --- a/src/isolate_create_params.rs +++ b/src/isolate_create_params.rs @@ -114,6 +114,35 @@ impl CreateParams { self } + /// Configures the constraints with reasonable default values based on the + /// provided lower and upper bounds. + /// + /// By default V8 starts with a small heap and dynamically grows it to match + /// the set of live objects. This may lead to ineffective garbage collections + /// at startup if the live set is large. Setting the initial heap size avoids + /// such garbage collections. Note that this does not affect young generation + /// garbage collections. + /// + /// When the heap size approaches `max`, V8 will perform series of + /// garbage collections and invoke the + /// [NearHeapLimitCallback](struct.Isolate.html#method.add_near_heap_limit_callback). + /// If the garbage collections do not help and the callback does not + /// increase the limit, then V8 will crash with V8::FatalProcessOutOfMemory. + /// + /// The heap size includes both the young and the old generation. + /// + /// # Arguments + /// + /// * `initial` - The initial heap size or zero in bytes + /// * `max` - The hard limit for the heap size in bytes + pub fn heap_limits(mut self, initial: usize, max: usize) -> Self { + self + .raw + .constraints + .configure_defaults_from_heap_size(initial, max); + self + } + fn set_fallback_defaults(mut self) -> Self { if self.raw.array_buffer_allocator_shared.is_null() { self = self.array_buffer_allocator(array_buffer::new_default_allocator()); @@ -201,4 +230,28 @@ pub(crate) mod raw { initial_young_generation_size_: usize, stack_limit_: *mut u32, } + + extern "C" { + fn v8__ResourceConstraints__ConfigureDefaultsFromHeapSize( + constraints: *mut ResourceConstraints, + initial_heap_size_in_bytes: usize, + maximum_heap_size_in_bytes: usize, + ); + } + + impl ResourceConstraints { + pub fn configure_defaults_from_heap_size( + &mut self, + initial_heap_size_in_bytes: usize, + maximum_heap_size_in_bytes: usize, + ) { + unsafe { + v8__ResourceConstraints__ConfigureDefaultsFromHeapSize( + self, + initial_heap_size_in_bytes, + maximum_heap_size_in_bytes, + ) + }; + } + } } diff --git a/src/lib.rs b/src/lib.rs index bfe418be..075a34df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ pub use isolate::HostInitializeImportMetaObjectCallback; pub use isolate::Isolate; pub use isolate::IsolateHandle; pub use isolate::MessageCallback; +pub use isolate::NearHeapLimitCallback; pub use isolate::OwnedIsolate; pub use isolate::PromiseRejectCallback; pub use isolate_create_params::CreateParams; diff --git a/tests/test_api.rs b/tests/test_api.rs index d4117f6a..d3e61510 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -4,6 +4,7 @@ extern crate lazy_static; use std::convert::{Into, TryFrom, TryInto}; +use std::ffi::c_void; use std::ptr::NonNull; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; @@ -3313,3 +3314,41 @@ fn module_snapshot() { } } } + +#[derive(Default)] +struct TestHeapLimitState { + near_heap_limit_callback_calls: u64, +} + +extern "C" fn heap_limit_callback( + data: *mut c_void, + current_heap_limit: usize, + _initial_heap_limit: usize, +) -> usize { + let state = unsafe { &mut *(data as *mut TestHeapLimitState) }; + state.near_heap_limit_callback_calls += 1; + current_heap_limit * 2 // Avoid V8 OOM. +} + +#[test] +fn heap_limits() { + let _setup_guard = setup(); + + let params = v8::CreateParams::default().heap_limits(0, 20 * 1024 * 1024); // 20 MB + let isolate = &mut v8::Isolate::new(params); + + let mut test_state = TestHeapLimitState::default(); + let state_ptr = &mut test_state as *mut _ as *mut c_void; + isolate.add_near_heap_limit_callback(heap_limit_callback, state_ptr); + + let scope = &mut v8::HandleScope::new(isolate); + + // Allocate some strings; 20 MB is reached at about 800k iterations. + for _ in 0..1_000_000 { + v8::String::new(scope, "HelloWorld").unwrap(); + if test_state.near_heap_limit_callback_calls > 0 { + break; + } + } + assert_eq!(1, test_state.near_heap_limit_callback_calls); +}