0
0
Fork 0
mirror of https://github.com/denoland/rusty_v8.git synced 2024-12-25 08:39:15 -05:00

Add support for setting V8 heap limits and using a near-heap-limit callback (#427)

This commit is contained in:
Marcus Weiner 2020-07-30 18:40:18 +02:00 committed by GitHub
parent 31036d8130
commit b0dc42f900
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 5 deletions

View file

@ -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<v8::HandleScope>* buf,
v8::Isolate* isolate) {
construct_in_place<v8::HandleScope>(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));
}

View file

@ -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) {

View file

@ -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,
)
};
}
}
}

View file

@ -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;

View file

@ -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);
}