Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsoundness when starting an isolate per thread #1467

Open
dev-ardi opened this issue Apr 19, 2024 · 13 comments
Open

Unsoundness when starting an isolate per thread #1467

dev-ardi opened this issue Apr 19, 2024 · 13 comments

Comments

@dev-ardi
Copy link
Contributor

dev-ardi commented Apr 19, 2024

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
Copy link
Contributor

mmastrac commented Apr 19, 2024

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
Copy link
Contributor Author

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
Copy link
Contributor

mmastrac commented Apr 20, 2024

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
Copy link
Contributor Author

dev-ardi commented Apr 22, 2024

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
Copy link
Contributor Author

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

@dev-ardi
Copy link
Contributor Author

I'll work on this

@linrongbin16
Copy link

I think I also meet such issue (or requirement).

I want to use a tokio::spawn to run the js runtime in a non-main thread. But the compiler tells me the v8::Isolate doesn't implement the std::marker::Send so it cannot be sent between threads.

Without the Send trait, seems I cannot use it in either tokio::spawn, tokio::blocking_spawn or even std::thread::spawn.

image

Could you help me with that?

@devsnek
Copy link
Member

devsnek commented Sep 13, 2024

This is correct, v8::Isolate is not a thread safe type. Lockers (#643) would allow this.

@linrongbin16
Copy link

linrongbin16 commented Sep 13, 2024

I checked the PR #1411, seems there is some hard issue in the design of the Global, which cannot be easily solve in a short time.

@dev-ardi
Copy link
Contributor Author

What you can do in this case is instead of putting it in a task, you just put it in a plain old thread, and then communicate back and forth with channels.

It's wouldn't be a good idea to put them in a task in the first place since they would be blocking most of the time (well, depending on your workload).

@dev-ardi
Copy link
Contributor Author

For everyone else finding this unsoundness bug here's a work around, you just need to use a OnceLock:
https://github.com/denoland/rusty_v8/pull/1470/files#diff-5968ad1a2c8f5b1977d1dc3776ad078298f49e52a8a7bc137ecb46d6a45fc8a9R314

@linrongbin16
Copy link

linrongbin16 commented Sep 13, 2024

For everyonelse finding this unsoundness bug here's a work around, you just need to use a OnceLock: https://github.com/denoland/rusty_v8/pull/1470/files#diff-5968ad1a2c8f5b1977d1dc3776ad078298f49e52a8a7bc137ecb46d6a45fc8a9R314

Thanks for sharing! One more question: do you have any idea, does V8 natively support async/await keyword? or the runtime need to implement it? and how to implement?

Update: I searched async/await in ECMAScript https://ecma-international.org/publications-and-standards/standards/ecma-262/, seems it's already been mentioned several times. I guess it's already natively supported. But still wonder how does it work when rusty_v8 running in a single thread. Does the V8 Context switches between different async functions inside a single thread?

@devsnek
Copy link
Member

devsnek commented Sep 13, 2024

Please open a new issue or discussion for new topics 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants