mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-12-23 15:50:11 -05:00
feat: v8::Isolate::{add,remove}_gc_prologue_callback (#1142)
This commit is contained in:
parent
3783a5c725
commit
caa2ef4510
5 changed files with 226 additions and 0 deletions
|
@ -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
73
src/gc.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue