Skip to content

Commit

Permalink
feat: Add v8::Isolate::request_garbage_collection_for_testing (denola…
Browse files Browse the repository at this point in the history
  • Loading branch information
bartlomieju committed Dec 2, 2022
1 parent 0f43fc0 commit 3e8a572
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 38 deletions.
5 changes: 5 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,11 @@ bool v8__Isolate__HasPendingBackgroundTasks(v8::Isolate* isolate) {
return isolate->HasPendingBackgroundTasks();
}

void v8__Isolate__RequestGarbageCollectionForTesting(
v8::Isolate* isolate, v8::Isolate::GarbageCollectionType type) {
isolate->RequestGarbageCollectionForTesting(type);
}

void v8__Isolate__CreateParams__CONSTRUCT(
uninit_t<v8::Isolate::CreateParams>* buf) {
construct_in_place<v8::Isolate::CreateParams>(buf);
Expand Down
38 changes: 38 additions & 0 deletions src/isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ pub enum PromiseHookType {
After,
}

/// Types of garbage collections that can be requested via
/// [`Isolate::request_garbage_collection_for_testing`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum GarbageCollectionType {
Full,
Minor,
}

pub type MessageCallback = extern "C" fn(Local<Message>, Local<Value>);

pub type PromiseHook =
Expand Down Expand Up @@ -471,6 +480,10 @@ extern "C" {
callback: extern "C" fn(*const FunctionCallbackInfo),
);
fn v8__Isolate__HasPendingBackgroundTasks(isolate: *const Isolate) -> bool;
fn v8__Isolate__RequestGarbageCollectionForTesting(
isolate: *mut Isolate,
r#type: usize,
);

fn v8__HeapProfiler__TakeHeapSnapshot(
isolate: *mut Isolate,
Expand Down Expand Up @@ -1140,6 +1153,31 @@ impl Isolate {
unsafe { v8__Isolate__HasPendingBackgroundTasks(self) }
}

/// Request garbage collection with a specific embedderstack state in this
/// Isolate. It is only valid to call this function if --expose_gc was
/// specified.
///
/// This should only be used for testing purposes and not to enforce a garbage
/// collection schedule. It has strong negative impact on the garbage
/// collection performance. Use IdleNotificationDeadline() or
/// LowMemoryNotification() instead to influence the garbage collection
/// schedule.
#[inline(always)]
pub fn request_garbage_collection_for_testing(
&mut self,
r#type: GarbageCollectionType,
) {
unsafe {
v8__Isolate__RequestGarbageCollectionForTesting(
self,
match r#type {
GarbageCollectionType::Full => 0,
GarbageCollectionType::Minor => 1,
},
)
}
}

unsafe fn clear_scope_and_annex(&mut self) {
// Drop the scope stack.
ScopeData::drop_root(self);
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub use handle::Global;
pub use handle::Handle;
pub use handle::Local;
pub use handle::Weak;
pub use isolate::GarbageCollectionType;
pub use isolate::HeapStatistics;
pub use isolate::HostCreateShadowRealmContextCallback;
pub use isolate::HostImportModuleDynamicallyCallback;
Expand Down
69 changes: 31 additions & 38 deletions tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,7 @@ fn global_from_into_raw() {
(raw, weak)
};

// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(!weak.is_empty());

{
Expand All @@ -172,8 +171,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();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(weak.is_empty());
}

Expand Down Expand Up @@ -6379,23 +6377,24 @@ 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 }));
gc();
"#;
let step2 = r#"
if (weakrefs.some(w => !w.deref())) throw "fail";
"#;

// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
let step2 = r#"
gc();
let step3 = r#"
if (weakrefs.every(w => w.deref())) throw "fail";
"#;

eval(scope, step1).unwrap();
scope.clear_kept_objects();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
eval(scope, step2).unwrap();
scope.clear_kept_objects();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
eval(scope, step3).unwrap();
}

#[test]
Expand Down Expand Up @@ -7420,8 +7419,7 @@ fn weak_handle() {

let scope = &mut v8::HandleScope::new(scope);

// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);

assert!(weak.is_empty());
assert_eq!(weak.to_local(scope), None);
Expand Down Expand Up @@ -7450,8 +7448,8 @@ fn finalizers() {
}

let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
}

let finalizer_called = Rc::new(Cell::new(false));
Expand Down Expand Up @@ -7486,8 +7484,7 @@ fn finalizers() {
};

let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(weak.is_empty());
assert!(finalizer_called.get());
}
Expand Down Expand Up @@ -7521,8 +7518,8 @@ fn guaranteed_finalizers() {
}

let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
}

let finalizer_called = Rc::new(Cell::new(false));
Expand Down Expand Up @@ -7557,8 +7554,7 @@ fn guaranteed_finalizers() {
};

let scope = &mut v8::HandleScope::new(scope);
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(weak.is_empty());
assert!(finalizer_called.get());
}
Expand All @@ -7583,8 +7579,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();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(weak.is_empty());
}

Expand Down Expand Up @@ -7633,8 +7628,8 @@ fn weak_from_into_raw() {
assert!(!finalizer_called.get());
(weak1, weak2)
};
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(weak1.is_empty());
assert!(weak2.is_empty());
assert!(finalizer_called.get());
Expand All @@ -7648,8 +7643,8 @@ 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();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(weak.is_empty());
assert_eq!(weak.into_raw(), None);
}
Expand Down Expand Up @@ -7680,8 +7675,8 @@ 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();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(finalizer_called.get());
let weak1 = unsafe { v8::Weak::from_raw(scope, raw1) };
let weak2 = unsafe { v8::Weak::from_raw(scope, raw2) };
Expand All @@ -7695,11 +7690,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();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
}
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
}

#[test]
Expand Down Expand Up @@ -7739,8 +7733,7 @@ fn drop_weak_from_raw_in_finalizer() {
}

assert!(!finalized.get());
// TODO use binding to Isolate::RequestGarbageCollectionForTesting instead of gc()
eval(scope, "gc()").unwrap();
scope.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert!(finalized.get());
}

Expand Down Expand Up @@ -8747,8 +8740,8 @@ fn gc_callbacks() {
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();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
assert_eq!(state.mark_sweep_calls, 1);
assert_eq!(state.incremental_marking_calls, 0);
}
Expand All @@ -8760,8 +8753,8 @@ fn gc_callbacks() {
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();
scope
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
// Assert callback was removed and not called again.
assert_eq!(state.mark_sweep_calls, 1);
assert_eq!(state.incremental_marking_calls, 0);
Expand Down

0 comments on commit 3e8a572

Please sign in to comment.