Skip to content

Global::into_raw() and Global::from_raw() leaks #1841

@alshdavid

Description

@alshdavid

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions