denoland / rusty_v8

Rust bindings for the V8 JavaScript engine
https://crates.io/crates/v8
MIT License
3.09k stars 300 forks source link

Unsoundness when starting an isolate per thread #1467

Open dev-ardi opened 3 months ago

dev-ardi commented 3 months ago

Is it supposed to be sound to instantiate one isolate per thread? If it's not the API shouldn't allow it.

If it is, you have data races as reported by ThreadSanitizer at least when creating and dropping the isolates.

There are more data races reported by it.

use std::sync::Once;

fn main() {
    fn init_isolate() {
        // Init isolate
        let isolate = &mut v8::Isolate::new(Default::default());
        let scope = &mut v8::HandleScope::new(isolate);
        let context = v8::Context::new(scope);
        let scope = &mut v8::ContextScope::new(scope, context);
    }

    static START: Once = Once::new();

    START.call_once(|| {
        let platform = v8::new_default_platform(0, false).make_shared();
        v8::V8::initialize_platform(platform);
        v8::V8::initialize();
    });

    for _ in 0..16 {
        std::thread::spawn(move || {
            init_isolate();
        });
    }
}

This code can be ran with RUSTFLAGS=-Zsanitizer=thread cargo +nightly run -r -Zbuild-std --target x86_64-unknown-linux-gnu

mmastrac commented 3 months ago

Do you mind providing the output of the data races? v8 may have some undocumented ordering requirements that we may need to encode via mutexes.

I assume these don't happen when you serialize them on different threads via mutex?

dev-ardi commented 3 months ago

I assume these don't happen when you serialize them on different threads via mutex?

What do you mean by this? Serialize what? Lock what?

The output of TSan:

==================
WARNING: ThreadSanitizer: data race (pid=182953)
  Atomic read of size 1 at 0x5644b1136a18 by thread T16:
    #0 pthread_mutex_lock <null> (tests+0x117f1a)
    #1 v8::base::OS::GetRandomMmapAddr() <null> (tests+0x1a339a)
    #2 tests::main::init_isolate::hdb6a8a0e21dd9715 <null> (tests+0x19b574)
    #3 std::sys_common::backtrace::__rust_begin_short_backtrace::ha9ac64f218ace66d (.llvm.4541231759099217939) <null> (tests+0x19bdbf)
    #4 std::panicking::try::do_call::h443b026a56148bf6 (.llvm.4541231759099217939) <null> (tests+0x19c00f)
    #5 __rust_try.llvm.4541231759099217939 <null> (tests+0x19c561)
    #6 std::panicking::try::h0d28ae24534cbb2c <null> (tests+0x19be48)
    #7 core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::hfb10caf8482df762 tests.c69f0d4a92970f3-cgu.2 (tests+0x19ce40)
    #8 _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::h6f76cea8842f91a2 <null> (tests+0x1577b2d)
    #9 std::sys::pal::unix::thread::Thread::new::thread_start::hc367969c21712737 std.207e8f97ea6a6eb5-cgu.05 (tests+0x1552e3c)

  Previous write of size 1 at 0x5644b1136a18 by thread T22:
    #0 pthread_mutex_init <null> (tests+0x117c0f)
    #1 v8::base::CallOnceImpl(std::Cr::atomic<unsigned char>*, std::Cr::function<void ()>) <null> (tests+0xa8c7ff)
    #2 tests::main::init_isolate::hdb6a8a0e21dd9715 <null> (tests+0x19b574)
    #3 std::sys_common::backtrace::__rust_begin_short_backtrace::ha9ac64f218ace66d (.llvm.4541231759099217939) <null> (tests+0x19bdbf)
    #4 std::panicking::try::do_call::h443b026a56148bf6 (.llvm.4541231759099217939) <null> (tests+0x19c00f)
    #5 __rust_try.llvm.4541231759099217939 <null> (tests+0x19c561)
    #6 std::panicking::try::h0d28ae24534cbb2c <null> (tests+0x19be48)
    #7 core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::hfb10caf8482df762 tests.c69f0d4a92970f3-cgu.2 (tests+0x19ce40)
    #8 _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::h6f76cea8842f91a2 <null> (tests+0x1577b2d)
    #9 std::sys::pal::unix::thread::Thread::new::thread_start::hc367969c21712737 std.207e8f97ea6a6eb5-cgu.05 (tests+0x1552e3c)

  Location is global 'v8::base::(anonymous namespace)::rng_mutex' of size 48 at 0x5644b1136a10 (tests+0x29fea18)

  Thread T16 (tid=183014, running) created by main thread at:
    #0 pthread_create <null> (tests+0x11641b)
    #1 std::sys::pal::unix::thread::Thread::new::ha89e7bb49291c670 <null> (tests+0x1552c51)
    #2 std::thread::spawn::h625baca1ceeb02e1 <null> (tests+0x19c94b)
    #3 tests::main::h20db97e3753fa6e0 tests.c69f0d4a92970f3-cgu.0 (tests+0x19b3df)
    #4 std::sys_common::backtrace::__rust_begin_short_backtrace::h556a55e1ea49f791 tests.c69f0d4a92970f3-cgu.1 (tests+0x19bd8f)
    #5 std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h873a0e41b53764be (.llvm.4541231759099217939) <null> (tests+0x19bdfd)
    #6 std::panicking::try::do_call::h9deba7566458d303 std.207e8f97ea6a6eb5-cgu.07 (tests+0x155da49)
    #7 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #8 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #9 std::panicking::try::do_call::he5dfd3bef74c0515 (.llvm.6907422618784771728) <null> (tests+0x155dc1b)
    #10 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #11 std::panicking::try::h536db1b3ee4a40d5 <null> (tests+0x155d968)
    #12 std::rt::lang_start_internal::h6189102e9d939b31 <null> (tests+0x15578e6)
    #13 main <null> (tests+0x19bc32)
    #14 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)
    #15 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)

  Thread T22 (tid=183020, running) created by main thread at:
    #0 pthread_create <null> (tests+0x11641b)
    #1 std::sys::pal::unix::thread::Thread::new::ha89e7bb49291c670 <null> (tests+0x1552c51)
    #2 std::thread::spawn::h625baca1ceeb02e1 <null> (tests+0x19c94b)
    #3 tests::main::h20db97e3753fa6e0 tests.c69f0d4a92970f3-cgu.0 (tests+0x19b3df)
    #4 std::sys_common::backtrace::__rust_begin_short_backtrace::h556a55e1ea49f791 tests.c69f0d4a92970f3-cgu.1 (tests+0x19bd8f)
    #5 std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h873a0e41b53764be (.llvm.4541231759099217939) <null> (tests+0x19bdfd)
    #6 std::panicking::try::do_call::h9deba7566458d303 std.207e8f97ea6a6eb5-cgu.07 (tests+0x155da49)
    #7 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #8 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #9 std::panicking::try::do_call::he5dfd3bef74c0515 (.llvm.6907422618784771728) <null> (tests+0x155dc1b)
    #10 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #11 std::panicking::try::h536db1b3ee4a40d5 <null> (tests+0x155d968)
    #12 std::rt::lang_start_internal::h6189102e9d939b31 <null> (tests+0x15578e6)
    #13 main <null> (tests+0x19bc32)
    #14 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)
    #15 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)

SUMMARY: ThreadSanitizer: data race (/home/ardi/repos/tests/target/x86_64-unknown-linux-gnu/release/tests+0x117f1a) in pthread_mutex_lock
==================
==================
WARNING: ThreadSanitizer: data race (pid=182953)
  Atomic read of size 1 at 0x5644b1139f78 by thread T16:
    #0 pthread_mutex_lock <null> (tests+0x117f1a)
    #1 v8::internal::CodeRangeAddressHint::GetAddressHint(unsigned long, unsigned long) <null> (tests+0x28e736)
    #2 tests::main::init_isolate::hdb6a8a0e21dd9715 <null> (tests+0x19b574)
    #3 std::sys_common::backtrace::__rust_begin_short_backtrace::ha9ac64f218ace66d (.llvm.4541231759099217939) <null> (tests+0x19bdbf)
    #4 std::panicking::try::do_call::h443b026a56148bf6 (.llvm.4541231759099217939) <null> (tests+0x19c00f)
    #5 __rust_try.llvm.4541231759099217939 <null> (tests+0x19c561)
    #6 std::panicking::try::h0d28ae24534cbb2c <null> (tests+0x19be48)
    #7 core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::hfb10caf8482df762 tests.c69f0d4a92970f3-cgu.2 (tests+0x19ce40)
    #8 _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::h6f76cea8842f91a2 <null> (tests+0x1577b2d)
    #9 std::sys::pal::unix::thread::Thread::new::thread_start::hc367969c21712737 std.207e8f97ea6a6eb5-cgu.05 (tests+0x1552e3c)

  Previous write of size 1 at 0x5644b1139f78 by thread T22:
    #0 pthread_mutex_init <null> (tests+0x117c0f)
    #1 v8::internal::CodeRange::InitReservation(v8::PageAllocator*, unsigned long) <null> (tests+0x28eff5)
    #2 tests::main::init_isolate::hdb6a8a0e21dd9715 <null> (tests+0x19b574)
    #3 std::sys_common::backtrace::__rust_begin_short_backtrace::ha9ac64f218ace66d (.llvm.4541231759099217939) <null> (tests+0x19bdbf)
    #4 std::panicking::try::do_call::h443b026a56148bf6 (.llvm.4541231759099217939) <null> (tests+0x19c00f)
    #5 __rust_try.llvm.4541231759099217939 <null> (tests+0x19c561)
    #6 std::panicking::try::h0d28ae24534cbb2c <null> (tests+0x19be48)
    #7 core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::hfb10caf8482df762 tests.c69f0d4a92970f3-cgu.2 (tests+0x19ce40)
    #8 _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::h6f76cea8842f91a2 <null> (tests+0x1577b2d)
    #9 std::sys::pal::unix::thread::Thread::new::thread_start::hc367969c21712737 std.207e8f97ea6a6eb5-cgu.05 (tests+0x1552e3c)

  Location is global 'v8::internal::(anonymous namespace)::GetCodeRangeAddressHint()::object' of size 80 at 0x5644b1139f78 (tests+0x2a01f78)

  Thread T16 (tid=183014, running) created by main thread at:
    #0 pthread_create <null> (tests+0x11641b)
    #1 std::sys::pal::unix::thread::Thread::new::ha89e7bb49291c670 <null> (tests+0x1552c51)
    #2 std::thread::spawn::h625baca1ceeb02e1 <null> (tests+0x19c94b)
    #3 tests::main::h20db97e3753fa6e0 tests.c69f0d4a92970f3-cgu.0 (tests+0x19b3df)
    #4 std::sys_common::backtrace::__rust_begin_short_backtrace::h556a55e1ea49f791 tests.c69f0d4a92970f3-cgu.1 (tests+0x19bd8f)
    #5 std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h873a0e41b53764be (.llvm.4541231759099217939) <null> (tests+0x19bdfd)
    #6 std::panicking::try::do_call::h9deba7566458d303 std.207e8f97ea6a6eb5-cgu.07 (tests+0x155da49)
    #7 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #8 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #9 std::panicking::try::do_call::he5dfd3bef74c0515 (.llvm.6907422618784771728) <null> (tests+0x155dc1b)
    #10 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #11 std::panicking::try::h536db1b3ee4a40d5 <null> (tests+0x155d968)
    #12 std::rt::lang_start_internal::h6189102e9d939b31 <null> (tests+0x15578e6)
    #13 main <null> (tests+0x19bc32)
    #14 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)
    #15 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)

  Thread T22 (tid=183020, running) created by main thread at:
    #0 pthread_create <null> (tests+0x11641b)
    #1 std::sys::pal::unix::thread::Thread::new::ha89e7bb49291c670 <null> (tests+0x1552c51)
    #2 std::thread::spawn::h625baca1ceeb02e1 <null> (tests+0x19c94b)
    #3 tests::main::h20db97e3753fa6e0 tests.c69f0d4a92970f3-cgu.0 (tests+0x19b3df)
    #4 std::sys_common::backtrace::__rust_begin_short_backtrace::h556a55e1ea49f791 tests.c69f0d4a92970f3-cgu.1 (tests+0x19bd8f)
    #5 std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h873a0e41b53764be (.llvm.4541231759099217939) <null> (tests+0x19bdfd)
    #6 std::panicking::try::do_call::h9deba7566458d303 std.207e8f97ea6a6eb5-cgu.07 (tests+0x155da49)
    #7 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #8 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #9 std::panicking::try::do_call::he5dfd3bef74c0515 (.llvm.6907422618784771728) <null> (tests+0x155dc1b)
    #10 __rust_try.llvm.6907422618784771728 <null> (tests+0x155ed61)
    #11 std::panicking::try::h536db1b3ee4a40d5 <null> (tests+0x155d968)
    #12 std::rt::lang_start_internal::h6189102e9d939b31 <null> (tests+0x15578e6)
    #13 main <null> (tests+0x19bc32)
    #14 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)
    #15 __libc_start_call_main <null> (libc.so.6+0x2a10d) (BuildId: 224c8d53cec4f2afe0c167f14dc41e087c39dfba)

SUMMARY: ThreadSanitizer: data race (/home/ardi/repos/tests/target/x86_64-unknown-linux-gnu/release/tests+0x117f1a) in pthread_mutex_lock
==================
ThreadSanitizer: reported 2 warnings

I got even more data races in my real code, when I actually used the isolates. This lead to a segfault.

mmastrac commented 3 months ago

I assume these don't happen when you serialize them on different threads via mutex? What do you mean by this? Serialize what? Lock what?

Serialization and locking refer to mutual exclusion, ie: std::sync::Mutex.

We can certainly fix this or accept a PR, but we would need to understand if this means:

  1. Only one isolate can be instantiated at any given time, or
  2. The first isolate instantiation must happen on its own, while further instantiations may be in parallel.
dev-ardi commented 3 months ago

Serialization and locking refer to mutual exclusion, ie: std::sync::Mutex.

Oh serializing as in running serially, I thought you meant serializing as in marshalling :P

Indeed it seems like only the first isolate must be started on its own

dev-ardi commented 3 months ago

This is also a good moment to revise the API. I'll expand on another issue though

dev-ardi commented 3 months ago

I'll work on this