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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions