-
Notifications
You must be signed in to change notification settings - Fork 352
Open
Description
Just running into this and was a bit surprised. Is there guidance on how to properly use v8::Global:into_raw()
and v8::Global::from_raw()
to avoid a leak?
// Run this 10_000 times
fn run_in_loop() -> anyhow::Result<()> {
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
{
let handle_scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(handle_scope, Default::default());
let context_scope = &mut v8::ContextScope::new(handle_scope, context);
let global_raw = {
let scope = &mut v8::HandleScope::new(context_scope);
let local_str = v8::String::new(scope, "Hello world").unwrap();
let global_str = v8::Global::new(scope, local_str);
let global_raw = global_str.into_raw(); // Leaks value
global_raw
};
{
let scope = &mut v8::HandleScope::new(context_scope);
let global_handle = unsafe { v8::Global::from_raw(scope, global_raw) };
drop(global_handle) // previously allocated value is never GC'd
}
}
isolate.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
Ok(())
}
Running this code in a loop, memory usage continuously increases as the value within into_raw()
is never cleaned up by GC
Run 0, 4mb resident (+2368kb)
Run 1, 6mb resident (+2256kb)
Run 2, 9mb resident (+2160kb)
Run 3, 11mb resident (+2256kb)
Run 4, 13mb resident (+2160kb)
Run 5, 15mb resident (+2256kb)
Run 6, 17mb resident (+2160kb)
Run 7, 19mb resident (+2240kb)
Run 8, 22mb resident (+2176kb)
Run 9, 24mb resident (+2256kb)
Run 10, 26mb resident (+2160kb)
Run 11, 28mb resident (+2256kb)
Run 12, 30mb resident (+2160kb)
Run 13, 32mb resident (+2256kb)
Run 14, 34mb resident (+2160kb)
Run 15, 37mb resident (+2240kb)
However, never calling into_raw()
and it is cleaned up with GC
// Run this 10_000 times
fn run_in_loop() -> anyhow::Result<()> {
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
{
let handle_scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(handle_scope, Default::default());
let context_scope = &mut v8::ContextScope::new(handle_scope, context);
let global_str = {
let scope = &mut v8::HandleScope::new(context_scope);
let local_str = v8::String::new(scope, "Hello world").unwrap();
let global_str = v8::Global::new(scope, local_str);
// let global_raw = global_str.into_raw(); // Leaks value
// global_raw
global_str
};
// {
// let scope = &mut v8::HandleScope::new(context_scope);
// let global_handle = unsafe { v8::Global::from_raw(scope, global_raw) };
// drop(global_handle) // previously allocated value is never GC'd
// }
drop(global_str)
}
isolate.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
Ok(())
}
Run 0, 2704kb resident (+192kb)
Run 1, 2720kb resident (+16kb)
Run 2, 2720kb resident (+0 bytes)
Run 3, 2720kb resident (+0 bytes)
Run 4, 2720kb resident (+0 bytes)
Run 5, 2720kb resident (+0 bytes)
Run 6, 2720kb resident (+0 bytes)
Run 7, 2720kb resident (+0 bytes)
Relevant: #902 (comment)
EDIT:
Managing the pointer myself seems to work. Is this okay to do?
fn run_in_loop() -> anyhow::Result<()> {
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
{
let handle_scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(handle_scope, Default::default());
let context_scope = &mut v8::ContextScope::new(handle_scope, context);
let global_raw = {
let scope = &mut v8::HandleScope::new(context_scope);
let local_str = v8::String::new(scope, "Hello world").unwrap();
let global_str = v8::Global::new(scope, local_str);
let global_raw = Box::into_raw(Box::new(global_str));
global_raw
};
{
let scope = &mut v8::HandleScope::new(context_scope);
let global_handle = unsafe { *Box::from_raw(global_raw) };
let handle = v8::Local::new(scope, global_handle);
drop(handle)
}
}
isolate.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
Ok(())
}
Metadata
Metadata
Assignees
Labels
No labels