-
Notifications
You must be signed in to change notification settings - Fork 320
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
(Again) Attempt to implement Locker
for Isolate
#1620
Closed
Closed
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d966715
(Resolved) Attempt to implement `Locker` for `Isolate`
linrongbin16 b4658fc
chore
linrongbin16 c0b5265
SharedIsolate contains an OwnedIsolate
linrongbin16 839e2ae
Remove LockerHandle
linrongbin16 9b94dbb
Update Locker size with binding
linrongbin16 631973f
Pass immutable ref casting to mutable
linrongbin16 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use std::ops::{Deref, DerefMut}; | ||
|
||
use crate::isolate::Isolate; | ||
use crate::scope::data::ScopeData; | ||
|
||
/// A handle to a shared isolate, allowing access to the isolate in a thread safe way. | ||
/// | ||
/// Unlike V8 isolates, these do not currently support re-entrancy. | ||
/// Do not create multiple lockers to the same isolate in the same thread. | ||
#[derive(Debug)] | ||
pub struct Locker<'a> { | ||
_lock: raw::Locker, | ||
// We maintain a mut reference to ensure we have exclusive ownership of the isolate during the lock. | ||
locked: &'a mut Isolate, | ||
} | ||
|
||
impl<'a> Locker<'a> { | ||
/// Claims the isolate, this should only be used from a shared isolate. | ||
pub(crate) fn new(isolate: &Isolate) -> Self { | ||
let const_isolate = isolate as *const Isolate; | ||
let mut_isolate = const_isolate as *mut Isolate; | ||
let s = unsafe { | ||
Self { | ||
_lock: raw::Locker::new(isolate), | ||
locked: &mut *mut_isolate, | ||
} | ||
}; | ||
ScopeData::new_root(s.locked); | ||
unsafe { s.locked.enter() }; | ||
s | ||
} | ||
|
||
/// Returns a reference to the locked isolate. | ||
pub fn isolate(&self) -> &Isolate { | ||
self.locked | ||
} | ||
|
||
/// Returns a mutable reference to the locked isolate. | ||
pub fn isolate_mut(&mut self) -> &mut Isolate { | ||
self.locked | ||
} | ||
|
||
/// Returns if the isolate is locked by the current thread. | ||
pub fn is_locked(isolate: &Isolate) -> bool { | ||
raw::Locker::is_locked(isolate) | ||
} | ||
} | ||
|
||
impl<'a> Drop for Locker<'a> { | ||
fn drop(&mut self) { | ||
// A new locker automatically enters the isolate, so be sure to exit the isolate when the locker is exited. | ||
unsafe { self.exit() }; | ||
ScopeData::drop_root(self); | ||
} | ||
} | ||
|
||
impl<'a> Deref for Locker<'a> { | ||
type Target = Isolate; | ||
fn deref(&self) -> &Self::Target { | ||
self.isolate() | ||
} | ||
} | ||
|
||
impl<'a> DerefMut for Locker<'a> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
self.isolate_mut() | ||
} | ||
} | ||
|
||
impl<'a> AsMut<Isolate> for Locker<'a> { | ||
fn as_mut(&mut self) -> &mut Isolate { | ||
self | ||
} | ||
} | ||
|
||
mod raw { | ||
use std::mem::MaybeUninit; | ||
|
||
use crate::Isolate; | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
pub(super) struct Locker([u8; crate::binding::v8__Locker__SIZE]); | ||
|
||
impl Locker { | ||
pub fn new(isolate: &Isolate) -> Self { | ||
unsafe { | ||
let mut this = MaybeUninit::<Self>::uninit(); | ||
v8__Locker__CONSTRUCT(this.as_mut_ptr(), isolate); | ||
// v8-locker.h disallows copying and assigning, but it does not disallow moving so this is hopefully safe. | ||
this.assume_init() | ||
} | ||
} | ||
|
||
pub fn is_locked(isolate: &Isolate) -> bool { | ||
unsafe { v8__Locker__IsLocked(isolate) } | ||
} | ||
} | ||
|
||
impl Drop for Locker { | ||
fn drop(&mut self) { | ||
unsafe { v8__Locker__DESTRUCT(self) } | ||
} | ||
} | ||
|
||
extern "C" { | ||
fn v8__Locker__CONSTRUCT(locker: *mut Locker, isolate: *const Isolate); | ||
fn v8__Locker__DESTRUCT(locker: *mut Locker); | ||
fn v8__Locker__IsLocked(isolate: *const Isolate) -> bool; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
use std::{ | ||
sync::{Arc, Once}, | ||
thread::{self, JoinHandle}, | ||
}; | ||
|
||
static INIT: Once = Once::new(); | ||
|
||
fn initialize_test() { | ||
INIT.call_once(|| { | ||
v8::V8::initialize_platform( | ||
v8::new_default_platform(0, false).make_shared(), | ||
); | ||
v8::V8::initialize(); | ||
}); | ||
} | ||
|
||
fn spawn_thread_locked<F, R>( | ||
isolate: &Arc<v8::SharedIsolate>, | ||
f: F, | ||
) -> JoinHandle<R> | ||
where | ||
F: FnOnce(&mut v8::Locker) -> R + Send + Sync + 'static, | ||
R: Send + 'static, | ||
{ | ||
let isolate = isolate.clone(); | ||
thread::spawn(move || { | ||
let mut locker = isolate.lock(); | ||
f(&mut locker) | ||
}) | ||
} | ||
|
||
fn spawn_thread_with_scope<F, R>( | ||
isolate: &Arc<v8::SharedIsolate>, | ||
f: F, | ||
) -> JoinHandle<R> | ||
where | ||
F: FnOnce(&mut v8::HandleScope<v8::Context>) -> R + Send + Sync + 'static, | ||
R: Send + 'static, | ||
{ | ||
spawn_thread_locked(isolate, |locker| { | ||
let scope = &mut v8::HandleScope::new(locker.isolate_mut()); | ||
let context = v8::Context::new(scope, Default::default()); | ||
let scope = &mut v8::ContextScope::new(scope, context); | ||
f(scope) | ||
}) | ||
} | ||
|
||
#[test] | ||
fn isolate_passed_between_threads_with_locker() { | ||
initialize_test(); | ||
let isolate = Arc::new(v8::Isolate::new_shared(Default::default())); | ||
|
||
let global = spawn_thread_with_scope(&isolate, move |scope| { | ||
let name = v8::String::new(scope, "Thread 1 value").unwrap(); | ||
v8::Global::new(scope, name) | ||
}) | ||
.join() | ||
.unwrap(); | ||
|
||
let found = spawn_thread_with_scope(&isolate, move |scope| { | ||
let name = v8::Local::new(scope, global); | ||
name.to_rust_string_lossy(scope) | ||
}) | ||
.join() | ||
.unwrap(); | ||
|
||
assert_eq!(found, "Thread 1 value"); | ||
} | ||
|
||
fn single_isolate_cross_thread_operation_spam(isolate: Arc<v8::SharedIsolate>) { | ||
let global_handles = (0..100) | ||
.map(|i| { | ||
let val = i; | ||
spawn_thread_with_scope(&isolate, move |scope| { | ||
let name = v8::Number::new(scope, val as f64); | ||
v8::Global::new(scope, name) | ||
}) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let globals = global_handles | ||
.into_iter() | ||
.map(|h| h.join().unwrap()) | ||
.collect::<Vec<_>>(); | ||
|
||
let number_handles = globals | ||
.into_iter() | ||
.map(|global| { | ||
spawn_thread_with_scope(&isolate, move |scope| { | ||
let local = v8::Local::new(scope, global); | ||
local.number_value(scope).unwrap() | ||
}) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let numbers = number_handles | ||
.into_iter() | ||
.map(|h| h.join().unwrap()) | ||
.collect::<Vec<_>>(); | ||
|
||
for (val, item) in numbers.iter().enumerate() { | ||
assert_eq!(val as f64, *item); | ||
} | ||
} | ||
|
||
#[test] | ||
fn mass_spam_isolate() { | ||
initialize_test(); | ||
|
||
// This is done multiple times to verify that disposal of an isolate doesn't raise errors. | ||
let t1 = thread::spawn(|| { | ||
single_isolate_cross_thread_operation_spam(Arc::new( | ||
v8::Isolate::new_shared(Default::default()), | ||
)); | ||
}); | ||
let t2 = thread::spawn(|| { | ||
single_isolate_cross_thread_operation_spam(Arc::new( | ||
v8::Isolate::new_shared(Default::default()), | ||
)); | ||
}); | ||
t1.join().unwrap(); | ||
t2.join().unwrap(); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In @Earthmark 's previous PR #1411 , there is a comment saying, simply add
Send
andSync
trait toGlobal
can be risk.Could you also please give comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found there is a mutex with
isolate
inside theIsolateAnnex
, see:rusty_v8/src/isolate.rs
Line 1490 in b04640d
This code looks it wants to keep the data race on the isolate, makes me feel like using the
Locker
C++ api is the correct way to implement this part. Once this done, seems aIsolate
can natively supportSend
andSync
traits, i.e. safely be sent between threads.Correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I completely forgot I had this change out here, thank you for picking it back up.
Let me know if you have questions about what I was trying to do.
I'm pretty sure the comment on the original pr about globals stands.
Globals moving between threads is a bit scary if lockers are not used, although it's kind of a landmine either way because the global needs to be made into a local with the same isolate that made it.
If lockers are not used, the global can only be used on the isolate that made it, which will be single threaded.
This usage constraint kind of enforces that the global is single threaded, but a global being send means it could be moved to another thread accidently (like if it's bundled in an async method), then dropped, which I think I vaguely recall could be a problem. (I haven't looked at this in months, and I'm writing this on my phone).
I think there are two paths with globals:
verify that dropping a global on the wrong thread won't explode (even in the lockers-not-used case)
OR
Make some other thread safe wrapper for globals that locked isolates can produce, that has whatever mechanisms are needed to not explode.
That being said, haven't looked at this in months.
I'm not sure if it's acceptable to make every isolate internally use lockers, they are re-entrant, but adding lockers to every API may add an unacceptable performance cost for users who are not using lockers.
I'm pretty sure lockers block as well, which is a bit nasty if someone wasn't expecting a hard block from an API call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, too many API (in rusty_v8) are designed to run in a single thread at the beginning. And JavaScript language itself doesn't support such concepts like multi-thread and mutex.
So just keep it
!Send
and!Sync
is more compatible with V8 and JavaScript language.The best practice of using v8 along with multi-thread or async runtime should be: put v8 engine inside a single thread, and communicate with outer world with channels.
I close this PR now.