-
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
Changes from 2 commits
d966715
b4658fc
c0b5265
839e2ae
9b94dbb
631973f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
use std::ops::{Deref, DerefMut}; | ||
|
||
use crate::isolate::Isolate; | ||
use crate::scope::data::ScopeData; | ||
use crate::support::Opaque; | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
struct LockerHandle(Opaque); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unused There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: Remove the unused |
||
|
||
/// 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: &'a mut Isolate) -> Self { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think this is UB, the locker should be acquired before a mutable reference is created. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: set parameter to |
||
let s = Self { | ||
_lock: raw::Locker::new(isolate), | ||
locked: 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([MaybeUninit<usize>; 2]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: Use binding to set Locker size. (When build in my local machine, there's an error report cannot find the |
||
|
||
impl Locker { | ||
pub fn new(isolate: &Isolate) -> Self { | ||
unsafe { | ||
let mut s = Self(MaybeUninit::uninit().assume_init()); | ||
v8__Locker__CONSTRUCT(&mut s, isolate); | ||
// v8-locker.h disallows copying and assigning, but it does not disallow moving so this is hopefully safe. | ||
s | ||
} | ||
} | ||
|
||
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; | ||
} | ||
} |
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); | ||
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(); | ||
} |
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.
it might be better of SharedIsolate just contains an OwnedIsolate, since the drop logic needs to stay in sync anyway.
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.
Update:
Drop
trait for SharedIsolate, because I think SharedIsolate can automatically/implicitly implement it when it only contains aUnsafeCell<OwnedIsolate>
.NOTE: Actually I found this PR is out of my capabilities, i.e. I'm not 100% sure about what some code is doing. So please be careful about it.