0
0
Fork 0
mirror of https://github.com/denoland/rusty_v8.git synced 2024-11-21 15:04:33 -05:00

feat: v8::Isolate::{add,remove}_gc_prologue_callback (#1142)

This commit is contained in:
Bartek Iwańczuk 2022-11-30 19:10:42 +01:00 committed by GitHub
parent 3783a5c725
commit caa2ef4510
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 226 additions and 0 deletions

View file

@ -252,6 +252,19 @@ bool v8__Isolate__AddMessageListener(v8::Isolate* isolate,
return isolate->AddMessageListener(callback);
}
void v8__Isolate__AddGCPrologueCallback(v8::Isolate* isolate,
v8::Isolate::GCCallbackWithData callback,
void* data,
v8::GCType gc_type_filter) {
isolate->AddGCPrologueCallback(callback, data, gc_type_filter);
}
void v8__Isolate__RemoveGCPrologueCallback(v8::Isolate* isolate,
v8::Isolate::GCCallbackWithData callback,
void* data) {
isolate->RemoveGCPrologueCallback(callback, data);
}
void v8__Isolate__AddNearHeapLimitCallback(v8::Isolate* isolate,
v8::NearHeapLimitCallback callback,
void* data) {

73
src/gc.rs Normal file
View file

@ -0,0 +1,73 @@
/// Applications can register callback functions which will be called before and
/// after certain garbage collection operations. Allocations are not allowed in
/// the callback functions, you therefore cannot manipulate objects (set or
/// delete properties for example) since it is possible such operations will
/// result in the allocation of objects.
#[repr(C)]
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct GCType(u32);
pub const GC_TYPE_TYPE_SCAVENGE: GCType = GCType(1);
pub const GC_TYPE_MINOR_MARK_COMPACT: GCType = GCType(2);
pub const GC_TYPE_MARK_SWEEP_COMPACT: GCType = GCType(4);
pub const GC_TYPE_INCREMENTAL_MARKING: GCType = GCType(8);
pub const GC_TYPE_PROCESS_WEAK_CALLBACK: GCType = GCType(16);
pub const GC_TYPE_ALL: GCType = GCType(31);
impl std::ops::BitOr for GCType {
type Output = Self;
fn bitor(self, Self(rhs): Self) -> Self {
let Self(lhs) = self;
Self(lhs | rhs)
}
}
/// GCCallbackFlags is used to notify additional information about the GC
/// callback.
/// - GCCallbackFlagConstructRetainedObjectInfos: The GC callback is for
/// constructing retained object infos.
/// - GCCallbackFlagForced: The GC callback is for a forced GC for testing.
/// - GCCallbackFlagSynchronousPhantomCallbackProcessing: The GC callback
/// is called synchronously without getting posted to an idle task.
/// - GCCallbackFlagCollectAllAvailableGarbage: The GC callback is called
/// in a phase where V8 is trying to collect all available garbage
/// (e.g., handling a low memory notification).
/// - GCCallbackScheduleIdleGarbageCollection: The GC callback is called to
/// trigger an idle garbage collection.
#[repr(C)]
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct GCCallbackFlags(u32);
pub const GC_CALLBACK_FLAGS_NO_FLAGS: GCCallbackFlags = GCCallbackFlags(0);
pub const GC_CALLBACK_FLAGS_CONSTRUCT_RETAINED_OBJECT_INFOS: GCCallbackFlags =
GCCallbackFlags(2);
pub const GC_CALLBACK_FLAGS_FORCED: GCCallbackFlags = GCCallbackFlags(4);
pub const GC_CALLBACK_FLAGS_SYNCHRONOUS_PHANTOM_CALLBACK_PROCESSING:
GCCallbackFlags = GCCallbackFlags(8);
pub const GC_CALLBACK_FLAGS_COLLECT_ALL_AVAILABLE_GARBAGE: GCCallbackFlags =
GCCallbackFlags(16);
pub const GC_CALLBACK_FLAGS_COLLECT_ALL_EXTERNAL_MEMORY: GCCallbackFlags =
GCCallbackFlags(32);
pub const GC_CALLBACK_FLAGS_SCHEDULE_IDLE_GARBAGE_COLLECTION: GCCallbackFlags =
GCCallbackFlags(64);
impl std::ops::BitOr for GCCallbackFlags {
type Output = Self;
fn bitor(self, Self(rhs): Self) -> Self {
let Self(lhs) = self;
Self(lhs | rhs)
}
}

View file

@ -1,5 +1,7 @@
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
use crate::function::FunctionCallbackInfo;
use crate::gc::GCCallbackFlags;
use crate::gc::GCType;
use crate::handle::FinalizerCallback;
use crate::handle::FinalizerMap;
use crate::isolate_create_params::raw;
@ -302,6 +304,13 @@ where
pub type HostCreateShadowRealmContextCallback =
for<'s> fn(scope: &mut HandleScope<'s>) -> Option<Local<'s, Context>>;
pub type GcCallbackWithData = extern "C" fn(
isolate: *mut Isolate,
r#type: GCType,
flags: GCCallbackFlags,
data: *mut c_void,
);
pub type InterruptCallback =
extern "C" fn(isolate: &mut Isolate, data: *mut c_void);
@ -373,6 +382,17 @@ extern "C" {
isolate: *mut Isolate,
callback: MessageCallback,
) -> bool;
fn v8__Isolate__AddGCPrologueCallback(
isolate: *mut Isolate,
callback: GcCallbackWithData,
data: *mut c_void,
gc_type_filter: GCType,
);
fn v8__Isolate__RemoveGCPrologueCallback(
isolate: *mut Isolate,
callback: GcCallbackWithData,
data: *mut c_void,
);
fn v8__Isolate__AddNearHeapLimitCallback(
isolate: *mut Isolate,
callback: NearHeapLimitCallback,
@ -974,6 +994,38 @@ impl Isolate {
}
}
/// Enables the host application to receive a notification before a
/// garbage collection. Allocations are allowed in the callback function,
/// but the callback is not re-entrant: if the allocation inside it will
/// trigger the garbage collection, the callback won't be called again.
/// It is possible to specify the GCType filter for your callback. But it is
/// not possible to register the same callback function two times with
/// different GCType filters.
#[allow(clippy::not_unsafe_ptr_arg_deref)] // False positive.
#[inline(always)]
pub fn add_gc_prologue_callback(
&mut self,
callback: GcCallbackWithData,
data: *mut c_void,
gc_type_filter: GCType,
) {
unsafe {
v8__Isolate__AddGCPrologueCallback(self, callback, data, gc_type_filter)
}
}
/// This function removes callback which was installed by
/// AddGCPrologueCallback function.
#[allow(clippy::not_unsafe_ptr_arg_deref)] // False positive.
#[inline(always)]
pub fn remove_gc_prologue_callback(
&mut self,
callback: GcCallbackWithData,
data: *mut c_void,
) {
unsafe { v8__Isolate__RemoveGCPrologueCallback(self, callback, data) }
}
/// 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.

View file

@ -41,6 +41,7 @@ mod external_references;
pub mod fast_api;
mod fixed_array;
mod function;
mod gc;
mod get_property_names_args_builder;
mod handle;
pub mod icu;
@ -90,6 +91,7 @@ pub use exception::*;
pub use external_references::ExternalReference;
pub use external_references::ExternalReferences;
pub use function::*;
pub use gc::*;
pub use get_property_names_args_builder::*;
pub use handle::Global;
pub use handle::Handle;

View file

@ -161,6 +161,7 @@ fn global_from_into_raw() {
(raw, weak)
};
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(!weak.is_empty());
@ -171,6 +172,7 @@ fn global_from_into_raw() {
assert_eq!(global_from_weak, reconstructed);
}
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak.is_empty());
}
@ -6377,6 +6379,7 @@ fn clear_kept_objects() {
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
let step1 = r#"
var weakrefs = [];
for (let i = 0; i < 424242; i++) weakrefs.push(new WeakRef({ i }));
@ -6384,6 +6387,7 @@ fn clear_kept_objects() {
if (weakrefs.some(w => !w.deref())) throw "fail";
"#;
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
let step2 = r#"
gc();
if (weakrefs.every(w => w.deref())) throw "fail";
@ -7416,6 +7420,7 @@ fn weak_handle() {
let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak.is_empty());
@ -7445,6 +7450,7 @@ fn finalizers() {
}
let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
}
@ -7480,6 +7486,7 @@ fn finalizers() {
};
let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak.is_empty());
assert!(finalizer_called.get());
@ -7514,6 +7521,7 @@ fn guaranteed_finalizers() {
}
let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
}
@ -7549,6 +7557,7 @@ fn guaranteed_finalizers() {
};
let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak.is_empty());
assert!(finalizer_called.get());
@ -7574,6 +7583,7 @@ fn weak_from_global() {
assert_eq!(weak.to_global(scope).unwrap(), global);
drop(global);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak.is_empty());
}
@ -7623,6 +7633,7 @@ fn weak_from_into_raw() {
assert!(!finalizer_called.get());
(weak1, weak2)
};
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak1.is_empty());
assert!(weak2.is_empty());
@ -7637,6 +7648,7 @@ fn weak_from_into_raw() {
v8::Weak::new(scope, local)
};
assert!(!weak.is_empty());
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(weak.is_empty());
assert_eq!(weak.into_raw(), None);
@ -7668,6 +7680,7 @@ fn weak_from_into_raw() {
let raw2 = weak_with_finalizer.into_raw();
assert!(raw1.is_some());
assert!(raw2.is_some());
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(finalizer_called.get());
let weak1 = unsafe { v8::Weak::from_raw(scope, raw1) };
@ -7682,8 +7695,10 @@ fn weak_from_into_raw() {
let local = v8::Object::new(scope);
v8::Weak::new(scope, local).into_raw();
v8::Weak::with_finalizer(scope, local, Box::new(|_| {})).into_raw();
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
}
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
}
@ -7724,6 +7739,7 @@ fn drop_weak_from_raw_in_finalizer() {
}
assert!(!finalized.get());
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert!(finalized.get());
}
@ -8681,3 +8697,73 @@ fn test_fast_calls_onebytestring() {
eval(scope, source).unwrap();
assert_eq!("fast", unsafe { WHO });
}
#[test]
fn gc_callbacks() {
let _setup_guard = setup();
#[derive(Default)]
struct GCCallbackState {
mark_sweep_calls: u64,
incremental_marking_calls: u64,
}
extern "C" fn callback(
_isolate: *mut v8::Isolate,
r#type: v8::GCType,
_flags: v8::GCCallbackFlags,
data: *mut c_void,
) {
// We should get a mark-sweep GC here.
assert_eq!(r#type, v8::GC_TYPE_MARK_SWEEP_COMPACT);
let state = unsafe { &mut *(data as *mut GCCallbackState) };
state.mark_sweep_calls += 1;
}
extern "C" fn callback2(
_isolate: *mut v8::Isolate,
r#type: v8::GCType,
_flags: v8::GCCallbackFlags,
data: *mut c_void,
) {
// We should get a mark-sweep GC here.
assert_eq!(r#type, v8::GC_TYPE_INCREMENTAL_MARKING);
let state = unsafe { &mut *(data as *mut GCCallbackState) };
state.incremental_marking_calls += 1;
}
let mut state = GCCallbackState::default();
let state_ptr = &mut state as *mut _ as *mut c_void;
let isolate = &mut v8::Isolate::new(Default::default());
isolate.add_gc_prologue_callback(callback, state_ptr, v8::GC_TYPE_ALL);
isolate.add_gc_prologue_callback(
callback2,
state_ptr,
v8::GC_TYPE_INCREMENTAL_MARKING | v8::GC_TYPE_PROCESS_WEAK_CALLBACK,
);
{
let scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
assert_eq!(state.mark_sweep_calls, 1);
assert_eq!(state.incremental_marking_calls, 0);
}
isolate.remove_gc_prologue_callback(callback, state_ptr);
isolate.remove_gc_prologue_callback(callback2, state_ptr);
{
let scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
// Assert callback was removed and not called again.
assert_eq!(state.mark_sweep_calls, 1);
assert_eq!(state.incremental_marking_calls, 0);
}
}