Skip to content

Commit 5673622

Browse files
committed
Add v8::Locker and v8::UnenteredIsolate
1 parent 4d9f927 commit 5673622

File tree

6 files changed

+263
-4
lines changed

6 files changed

+263
-4
lines changed

src/binding.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ void v8__Isolate__Enter(v8::Isolate* isolate) { isolate->Enter(); }
170170

171171
void v8__Isolate__Exit(v8::Isolate* isolate) { isolate->Exit(); }
172172

173+
void v8__Locker__CONSTRUCT(uninit_t<v8::Locker>* buf, v8::Isolate* isolate) {
174+
construct_in_place<v8::Locker>(buf, isolate);
175+
}
176+
177+
void v8__Locker__DESTRUCT(v8::Locker* self) { self->~Locker(); }
178+
179+
bool v8__Locker__IsLocked(v8::Isolate* isolate) {
180+
return v8::Locker::IsLocked(isolate);
181+
}
182+
173183
v8::Isolate* v8__Isolate__GetCurrent() { return v8::Isolate::GetCurrent(); }
174184

175185
const v8::Data* v8__Isolate__GetCurrentHostDefinedOptions(

src/isolate.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,14 @@ impl Isolate {
914914
OwnedIsolate::new(Self::new_impl(params))
915915
}
916916

917+
/// Creates an isolate for use with `v8::Locker` in multi-threaded scenarios.
918+
///
919+
/// Unlike `Isolate::new()`, this does not automatically enter the isolate.
920+
#[allow(clippy::new_ret_no_self)]
921+
pub fn new_unentered(params: CreateParams) -> UnenteredIsolate {
922+
UnenteredIsolate::new(Self::new_impl(params))
923+
}
924+
917925
#[allow(clippy::new_ret_no_self)]
918926
pub fn snapshot_creator(
919927
external_references: Option<Cow<'static, [ExternalReference]>>,
@@ -2118,6 +2126,40 @@ impl AsMut<Isolate> for Isolate {
21182126
}
21192127
}
21202128

2129+
/// An isolate that must be accessed via `Locker`. Does not auto-enter.
2130+
#[derive(Debug)]
2131+
pub struct UnenteredIsolate {
2132+
cxx_isolate: NonNull<RealIsolate>,
2133+
}
2134+
2135+
impl UnenteredIsolate {
2136+
pub(crate) fn new(cxx_isolate: *mut RealIsolate) -> Self {
2137+
Self {
2138+
cxx_isolate: NonNull::new(cxx_isolate).unwrap(),
2139+
}
2140+
}
2141+
}
2142+
2143+
impl Drop for UnenteredIsolate {
2144+
fn drop(&mut self) {
2145+
unsafe {
2146+
let isolate = Isolate::from_raw_ref_mut(&mut self.cxx_isolate);
2147+
let snapshot_creator =
2148+
isolate.get_annex_mut().maybe_snapshot_creator.take();
2149+
assert!(
2150+
snapshot_creator.is_none(),
2151+
"v8::UnenteredIsolate::create_blob must be called before dropping"
2152+
);
2153+
isolate.dispose_annex();
2154+
Platform::notify_isolate_shutdown(&get_current_platform(), isolate);
2155+
isolate.dispose();
2156+
}
2157+
}
2158+
}
2159+
2160+
// Thread safety ensured by Locker.
2161+
unsafe impl Send for UnenteredIsolate {}
2162+
21212163
/// Collection of V8 heap information.
21222164
///
21232165
/// Instances of this class can be passed to v8::Isolate::GetHeapStatistics to
@@ -2417,3 +2459,50 @@ impl AsRef<Isolate> for Isolate {
24172459
self
24182460
}
24192461
}
2462+
2463+
/// Locks an isolate and enters it for the current thread.
2464+
pub struct Locker<'a> {
2465+
raw: std::mem::ManuallyDrop<crate::scope::raw::Locker>,
2466+
isolate: &'a mut UnenteredIsolate,
2467+
}
2468+
2469+
impl<'a> Locker<'a> {
2470+
pub fn new(isolate: &'a mut UnenteredIsolate) -> Self {
2471+
let isolate_ptr = isolate.cxx_isolate;
2472+
unsafe {
2473+
v8__Isolate__Enter(isolate_ptr.as_ptr());
2474+
}
2475+
let mut raw = unsafe { crate::scope::raw::Locker::uninit() };
2476+
unsafe { raw.init(isolate_ptr) };
2477+
Self {
2478+
raw: std::mem::ManuallyDrop::new(raw),
2479+
isolate,
2480+
}
2481+
}
2482+
2483+
pub fn is_locked(isolate: &UnenteredIsolate) -> bool {
2484+
crate::scope::raw::Locker::is_locked(isolate.cxx_isolate)
2485+
}
2486+
}
2487+
2488+
impl Drop for Locker<'_> {
2489+
fn drop(&mut self) {
2490+
unsafe {
2491+
std::mem::ManuallyDrop::drop(&mut self.raw);
2492+
v8__Isolate__Exit(self.isolate.cxx_isolate.as_ptr());
2493+
}
2494+
}
2495+
}
2496+
2497+
impl Deref for Locker<'_> {
2498+
type Target = Isolate;
2499+
fn deref(&self) -> &Self::Target {
2500+
unsafe { Isolate::from_raw_ref(&self.isolate.cxx_isolate) }
2501+
}
2502+
}
2503+
2504+
impl DerefMut for Locker<'_> {
2505+
fn deref_mut(&mut self) -> &mut Self::Target {
2506+
unsafe { Isolate::from_raw_ref_mut(&mut self.isolate.cxx_isolate) }
2507+
}
2508+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub use isolate::HostImportModuleWithPhaseDynamicallyCallback;
114114
pub use isolate::HostInitializeImportMetaObjectCallback;
115115
pub use isolate::Isolate;
116116
pub use isolate::IsolateHandle;
117+
pub use isolate::Locker;
117118
pub use isolate::MemoryPressureLevel;
118119
pub use isolate::MessageCallback;
119120
pub use isolate::MessageErrorLevel;
@@ -128,6 +129,7 @@ pub use isolate::PromiseHookType;
128129
pub use isolate::PromiseRejectCallback;
129130
pub use isolate::RealIsolate;
130131
pub use isolate::TimeZoneDetection;
132+
pub use isolate::UnenteredIsolate;
131133
pub use isolate::UseCounterCallback;
132134
pub use isolate::UseCounterFeature;
133135
pub use isolate::WasmAsyncSuccess;

src/scope.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@
127127
//! So in `ContextScope<'b, 's>`, `'b` is the lifetime of the borrow of the inner scope, and `'s` is the lifetime of the inner scope (and therefore the handles).
128128
use crate::{
129129
Context, Data, DataError, Function, FunctionCallbackInfo, Isolate, Local,
130-
Message, Object, OwnedIsolate, PromiseRejectMessage, PropertyCallbackInfo,
131-
SealedLocal, Value, fast_api::FastApiCallbackOptions, isolate::RealIsolate,
132-
support::assert_layout_subset,
130+
Locker, Message, Object, OwnedIsolate, PromiseRejectMessage,
131+
PropertyCallbackInfo, SealedLocal, Value, fast_api::FastApiCallbackOptions,
132+
isolate::RealIsolate, support::assert_layout_subset,
133133
};
134134
use std::{
135135
any::type_name,
@@ -279,7 +279,7 @@ mod get_isolate {
279279
pub(crate) use get_isolate::GetIsolate;
280280

281281
mod get_isolate_impls {
282-
use crate::{Promise, PromiseRejectMessage};
282+
use crate::{Locker, Promise, PromiseRejectMessage};
283283

284284
use super::*;
285285
impl GetIsolate for Isolate {
@@ -294,6 +294,14 @@ mod get_isolate_impls {
294294
}
295295
}
296296

297+
impl GetIsolate for Locker<'_> {
298+
fn get_isolate_ptr(&self) -> *mut RealIsolate {
299+
// Locker derefs to Isolate, which has as_real_ptr()
300+
use std::ops::Deref;
301+
self.deref().as_real_ptr()
302+
}
303+
}
304+
297305
impl GetIsolate for FunctionCallbackInfo {
298306
fn get_isolate_ptr(&self) -> *mut RealIsolate {
299307
self.get_isolate_ptr()
@@ -442,6 +450,20 @@ impl<'s> NewHandleScope<'s> for OwnedIsolate {
442450
}
443451
}
444452

453+
impl<'s, 'a: 's> NewHandleScope<'s> for Locker<'a> {
454+
type NewScope = HandleScope<'s, ()>;
455+
456+
fn make_new_scope(me: &'s mut Self) -> Self::NewScope {
457+
HandleScope {
458+
raw_handle_scope: unsafe { raw::HandleScope::uninit() },
459+
isolate: unsafe { NonNull::new_unchecked(me.get_isolate_ptr()) },
460+
context: Cell::new(None),
461+
_phantom: PhantomData,
462+
_pinned: PhantomPinned,
463+
}
464+
}
465+
}
466+
445467
impl<'s, 'p: 's, 'i, C> NewHandleScope<'s>
446468
for PinnedRef<'p, CallbackScope<'i, C>>
447469
{

src/scope/raw.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,34 @@ impl Drop for AllowJavascriptExecutionScope {
219219
}
220220
}
221221

222+
#[repr(C)]
223+
#[derive(Debug)]
224+
pub(crate) struct Locker([MaybeUninit<usize>; 3]);
225+
226+
impl Locker {
227+
#[inline]
228+
pub unsafe fn uninit() -> Self {
229+
Self(unsafe { MaybeUninit::uninit().assume_init() })
230+
}
231+
232+
#[inline]
233+
pub unsafe fn init(&mut self, isolate: NonNull<RealIsolate>) {
234+
let buf = NonNull::from(self).cast();
235+
unsafe { v8__Locker__CONSTRUCT(buf.as_ptr(), isolate.as_ptr()) };
236+
}
237+
238+
pub fn is_locked(isolate: NonNull<RealIsolate>) -> bool {
239+
unsafe { v8__Locker__IsLocked(isolate.as_ptr()) }
240+
}
241+
}
242+
243+
impl Drop for Locker {
244+
#[inline(always)]
245+
fn drop(&mut self) {
246+
unsafe { v8__Locker__DESTRUCT(self) };
247+
}
248+
}
249+
222250
unsafe extern "C" {
223251
pub(super) fn v8__Isolate__GetCurrent() -> *mut RealIsolate;
224252
pub(super) fn v8__Isolate__GetCurrentContext(
@@ -311,4 +339,11 @@ unsafe extern "C" {
311339
pub(super) fn v8__AllowJavascriptExecutionScope__DESTRUCT(
312340
this: *mut AllowJavascriptExecutionScope,
313341
);
342+
343+
pub(super) fn v8__Locker__CONSTRUCT(
344+
buf: *mut MaybeUninit<Locker>,
345+
isolate: *mut RealIsolate,
346+
);
347+
pub(super) fn v8__Locker__DESTRUCT(this: *mut Locker);
348+
pub(super) fn v8__Locker__IsLocked(isolate: *mut RealIsolate) -> bool;
314349
}

tests/test_locker.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use std::pin::pin;
2+
3+
#[test]
4+
fn locker_basic() {
5+
let _setup_guard = setup();
6+
let mut isolate = v8::Isolate::new_unentered(Default::default());
7+
{
8+
let mut locker = v8::Locker::new(&mut isolate);
9+
let scope = pin!(v8::HandleScope::new(&mut *locker));
10+
let scope = &mut scope.init();
11+
let _context = v8::Context::new(scope, Default::default());
12+
}
13+
}
14+
15+
#[test]
16+
fn locker_with_script() {
17+
let _setup_guard = setup();
18+
let mut isolate = v8::Isolate::new_unentered(Default::default());
19+
{
20+
let mut locker = v8::Locker::new(&mut isolate);
21+
let scope = pin!(v8::HandleScope::new(&mut *locker));
22+
let scope = &mut scope.init();
23+
let context = v8::Context::new(scope, Default::default());
24+
let scope = &mut v8::ContextScope::new(scope, context);
25+
26+
let code = v8::String::new(scope, "40 + 2").unwrap();
27+
let script = v8::Script::compile(scope, code, None).unwrap();
28+
let result = script.run(scope).unwrap();
29+
assert_eq!(result.to_integer(scope).unwrap().value(), 42);
30+
}
31+
}
32+
33+
#[test]
34+
fn unentered_isolate_no_lifo_constraint() {
35+
let _setup_guard = setup();
36+
let isolate1 = v8::Isolate::new_unentered(Default::default());
37+
let isolate2 = v8::Isolate::new_unentered(Default::default());
38+
let isolate3 = v8::Isolate::new_unentered(Default::default());
39+
drop(isolate2);
40+
drop(isolate1);
41+
drop(isolate3);
42+
}
43+
44+
#[test]
45+
fn locker_multiple_lock_unlock() {
46+
let _setup_guard = setup();
47+
let mut isolate = v8::Isolate::new_unentered(Default::default());
48+
49+
{
50+
let mut locker = v8::Locker::new(&mut isolate);
51+
let scope = pin!(v8::HandleScope::new(&mut *locker));
52+
let scope = &mut scope.init();
53+
let context = v8::Context::new(scope, Default::default());
54+
let scope = &mut v8::ContextScope::new(scope, context);
55+
56+
let code = v8::String::new(scope, "1 + 1").unwrap();
57+
let script = v8::Script::compile(scope, code, None).unwrap();
58+
let result = script.run(scope).unwrap();
59+
assert_eq!(result.to_integer(scope).unwrap().value(), 2);
60+
}
61+
62+
{
63+
let mut locker = v8::Locker::new(&mut isolate);
64+
let scope = pin!(v8::HandleScope::new(&mut *locker));
65+
let scope = &mut scope.init();
66+
let context = v8::Context::new(scope, Default::default());
67+
let scope = &mut v8::ContextScope::new(scope, context);
68+
69+
let code = v8::String::new(scope, "2 + 2").unwrap();
70+
let script = v8::Script::compile(scope, code, None).unwrap();
71+
let result = script.run(scope).unwrap();
72+
assert_eq!(result.to_integer(scope).unwrap().value(), 4);
73+
}
74+
}
75+
76+
#[test]
77+
fn locker_is_locked() {
78+
let _setup_guard = setup();
79+
let mut isolate = v8::Isolate::new_unentered(Default::default());
80+
81+
assert!(!v8::Locker::is_locked(&isolate));
82+
{
83+
let _locker = v8::Locker::new(&mut isolate);
84+
}
85+
assert!(!v8::Locker::is_locked(&isolate));
86+
}
87+
88+
fn setup() -> impl Drop {
89+
use std::sync::Once;
90+
static INIT: Once = Once::new();
91+
INIT.call_once(|| {
92+
let platform = v8::new_default_platform(0, false).make_shared();
93+
v8::V8::initialize_platform(platform);
94+
v8::V8::initialize();
95+
});
96+
struct Guard;
97+
impl Drop for Guard {
98+
fn drop(&mut self) {}
99+
}
100+
Guard
101+
}

0 commit comments

Comments
 (0)