Skip to content
This repository was archived by the owner on Nov 12, 2022. It is now read-only.

Commit 59fce55

Browse files
committed
Separate parent runtime, engine initialization, engine shutdown, and runtime creation concepts.
1 parent e2d07dc commit 59fce55

16 files changed

+225
-99
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ name = "rooting"
2929
[[test]]
3030
name = "runtime"
3131
[[test]]
32+
name = "runtime_no_outlive"
33+
[[test]]
3234
name = "typedarray"
3335
[[test]]
3436
name = "typedarray_panic"
@@ -42,7 +44,6 @@ doctest = false
4244

4345
[features]
4446
debugmozjs = ['mozjs_sys/debugmozjs']
45-
init_once = []
4647

4748
[dependencies]
4849
lazy_static = "1"

src/rust.rs

Lines changed: 154 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
//! Rust wrappers around the raw JS apis
66
7-
87
use libc::{size_t, c_uint};
98

109
use mozjs_sys::jsgc::CustomAutoRooterVFTable;
@@ -23,7 +22,7 @@ use std::ops::{Deref, DerefMut};
2322
use std::os::raw::c_void;
2423
use std::cell::Cell;
2524
use std::marker::PhantomData;
26-
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
25+
use std::sync::{Arc, Mutex};
2726

2827
use consts::{JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_GLOBAL_SLOT_COUNT};
2928
use consts::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
@@ -124,16 +123,94 @@ impl ToResult for bool {
124123

125124
thread_local!(static CONTEXT: Cell<*mut JSContext> = Cell::new(ptr::null_mut()));
126125

126+
#[derive(PartialEq)]
127+
enum EngineState {
128+
Uninitialized,
129+
InitFailed,
130+
Initialized,
131+
ShutDown,
132+
}
133+
127134
lazy_static! {
128-
static ref PARENT: AtomicPtr<JSRuntime> = AtomicPtr::new(ptr::null_mut());
129-
static ref OUTSTANDING_RUNTIMES: AtomicUsize = AtomicUsize::new(0);
130-
static ref SHUT_DOWN: AtomicBool = AtomicBool::new(false);
131-
static ref JS_INIT_CALLED: AtomicBool = AtomicBool::new(false);
135+
static ref ENGINE_STATE: Mutex<EngineState> = Mutex::new(EngineState::Uninitialized);
136+
}
137+
138+
#[derive(Debug)]
139+
pub enum JSEngineError {
140+
AlreadyInitialized,
141+
AlreadyShutDown,
142+
InitFailed,
143+
}
144+
145+
/// A handle that must be kept alive in order to create new Runtimes.
146+
/// When this handle is dropped, the engine is shut down and cannot
147+
/// be reinitialized.
148+
pub struct JSEngine(());
149+
150+
impl JSEngine {
151+
/// Initialize the JS engine to prepare for creating new JS runtimes.
152+
pub fn init() -> Result<Arc<JSEngine>, JSEngineError> {
153+
let mut state = ENGINE_STATE.lock().unwrap();
154+
match *state {
155+
EngineState::Initialized => return Err(JSEngineError::AlreadyInitialized),
156+
EngineState::InitFailed => return Err(JSEngineError::InitFailed),
157+
EngineState::ShutDown => return Err(JSEngineError::AlreadyShutDown),
158+
EngineState::Uninitialized => (),
159+
}
160+
if unsafe { !JS_Init() } {
161+
*state = EngineState::InitFailed;
162+
Err(JSEngineError::InitFailed)
163+
} else {
164+
*state = EngineState::Initialized;
165+
Ok(Arc::new(JSEngine(())))
166+
}
167+
}
168+
}
169+
170+
/// Shut down the JS engine, invalidating any existing runtimes and preventing
171+
/// any new ones from being created.
172+
impl Drop for JSEngine {
173+
fn drop(&mut self) {
174+
let mut state = ENGINE_STATE.lock().unwrap();
175+
if *state == EngineState::Initialized {
176+
*state = EngineState::ShutDown;
177+
unsafe {
178+
JS_ShutDown();
179+
}
180+
}
181+
}
182+
}
183+
184+
/// A handle to a Runtime that will be used to create a new runtime in another
185+
/// thread. This handle and the new runtime must be destroyed before the original
186+
/// runtime can be dropped.
187+
pub struct ParentRuntime {
188+
/// Raw pointer to the underlying SpiderMonkey runtime.
189+
parent: *mut JSRuntime,
190+
/// Handle to ensure the JS engine remains running while this handle exists.
191+
engine: Arc<JSEngine>,
192+
/// The number of children of the runtime that created this ParentRuntime value.
193+
children_of_parent: Arc<()>,
132194
}
195+
unsafe impl Send for ParentRuntime {}
133196

134197
/// A wrapper for the `JSContext` structure in SpiderMonkey.
135198
pub struct Runtime {
199+
/// Raw pointer to the underlying SpiderMonkey context.
136200
cx: *mut JSContext,
201+
/// The engine that this runtime is associated with.
202+
engine: Arc<JSEngine>,
203+
/// If this Runtime was created with a parent, this member exists to ensure
204+
/// that that parent's count of outstanding children (see [outstanding_children])
205+
/// remains accurate and will be automatically decreased when this Runtime value
206+
/// is dropped.
207+
_parent_child_count: Option<Arc<()>>,
208+
/// The strong references to this value represent the number of child runtimes
209+
/// that have been created using this Runtime as a parent. Since Runtime values
210+
/// must be associated with a particular thread, we cannot simply use Arc<Runtime>
211+
/// to represent the resulting ownership graph and risk destroying a Runtime on
212+
/// the wrong thread.
213+
outstanding_children: Arc<()>,
137214
}
138215

139216
impl Runtime {
@@ -147,76 +224,85 @@ impl Runtime {
147224
}
148225

149226
/// Creates a new `JSContext`.
150-
pub fn new() -> Result<Runtime, ()> {
151-
unsafe {
152-
if SHUT_DOWN.load(Ordering::SeqCst) {
153-
return Err(());
154-
}
155-
156-
let outstanding = OUTSTANDING_RUNTIMES.fetch_add(1, Ordering::SeqCst);
157-
158-
let js_context = if outstanding == 0 {
159-
// We are creating the first JSContext, so we need to initialize
160-
// the runtime.
161-
if cfg!(not(feature = "init_once")) || !JS_INIT_CALLED.load(Ordering::SeqCst) {
162-
assert!(JS_Init());
163-
JS_INIT_CALLED.store(true, Ordering::SeqCst);
164-
}
165-
let js_context = JS_NewContext(default_heapsize, ChunkSize as u32, ptr::null_mut());
166-
let parent_runtime = JS_GetRuntime(js_context);
167-
assert!(!parent_runtime.is_null());
168-
let old_runtime = PARENT.compare_and_swap(ptr::null_mut(), parent_runtime, Ordering::SeqCst);
169-
assert!(old_runtime.is_null());
170-
// TODO: should we use the internal job queues or not?
171-
// assert!(UseInternalJobQueues(js_context, false));
172-
js_context
173-
} else {
174-
let parent_runtime = PARENT.load(Ordering::SeqCst);
175-
assert!(!parent_runtime.is_null());
176-
JS_NewContext(default_heapsize, ChunkSize as u32, parent_runtime)
177-
};
178-
179-
assert!(!js_context.is_null());
180-
181-
// Unconstrain the runtime's threshold on nominal heap size, to avoid
182-
// triggering GC too often if operating continuously near an arbitrary
183-
// finite threshold. This leaves the maximum-JS_malloc-bytes threshold
184-
// still in effect to cause periodical, and we hope hygienic,
185-
// last-ditch GCs from within the GC's allocator.
186-
JS_SetGCParameter(
187-
js_context, JSGCParamKey::JSGC_MAX_BYTES, u32::MAX);
188-
189-
JS_SetNativeStackQuota(
190-
js_context,
191-
STACK_QUOTA,
192-
STACK_QUOTA - SYSTEM_CODE_BUFFER,
193-
STACK_QUOTA - SYSTEM_CODE_BUFFER - TRUSTED_SCRIPT_BUFFER);
227+
pub fn new(engine: Arc<JSEngine>) -> Runtime {
228+
unsafe { Self::create(engine, None) }
229+
}
230+
231+
/// Signal that a new child runtime will be created in the future, and ensure
232+
/// that this runtime will not allow itself to be destroyed before the new
233+
/// child runtime. Returns a handle that can be passed to `create_with_parent`
234+
/// in order to create a new runtime on another thread that is associated with
235+
/// this runtime.
236+
pub fn prepare_for_new_child(&self) -> ParentRuntime {
237+
ParentRuntime {
238+
parent: self.rt(),
239+
engine: self.engine.clone(),
240+
children_of_parent: self.outstanding_children.clone(),
241+
}
242+
}
194243

195-
CONTEXT.with(|context| {
196-
assert!(context.get().is_null());
197-
context.set(js_context);
198-
});
244+
/// Creates a new `JSContext` with a parent runtime. If the parent does not outlive
245+
/// the new runtime, its destructor will assert.
246+
///
247+
/// Unsafety:
248+
/// If panicking does not abort the program, any threads with child runtimes will
249+
/// continue executing after the thread with the parent runtime panics, but they
250+
/// will be in an invalid and undefined state.
251+
pub unsafe fn create_with_parent(parent: ParentRuntime) -> Runtime {
252+
Self::create(parent.engine.clone(), Some(parent))
253+
}
254+
255+
unsafe fn create(engine: Arc<JSEngine>, parent: Option<ParentRuntime>) -> Runtime {
256+
let parent_runtime = parent.as_ref().map_or(
257+
ptr::null_mut(),
258+
|r| r.parent,
259+
);
260+
let js_context = JS_NewContext(default_heapsize, ChunkSize as u32, parent_runtime);
261+
assert!(!js_context.is_null());
262+
263+
// Unconstrain the runtime's threshold on nominal heap size, to avoid
264+
// triggering GC too often if operating continuously near an arbitrary
265+
// finite threshold. This leaves the maximum-JS_malloc-bytes threshold
266+
// still in effect to cause periodical, and we hope hygienic,
267+
// last-ditch GCs from within the GC's allocator.
268+
JS_SetGCParameter(
269+
js_context, JSGCParamKey::JSGC_MAX_BYTES, u32::MAX);
270+
271+
JS_SetNativeStackQuota(
272+
js_context,
273+
STACK_QUOTA,
274+
STACK_QUOTA - SYSTEM_CODE_BUFFER,
275+
STACK_QUOTA - SYSTEM_CODE_BUFFER - TRUSTED_SCRIPT_BUFFER);
276+
277+
CONTEXT.with(|context| {
278+
assert!(context.get().is_null());
279+
context.set(js_context);
280+
});
199281

200-
InitSelfHostedCode(js_context);
282+
InitSelfHostedCode(js_context);
201283

202-
let contextopts = ContextOptionsRef(js_context);
203-
(*contextopts).set_baseline_(true);
204-
(*contextopts).set_ion_(true);
205-
(*contextopts).set_nativeRegExp_(true);
284+
let contextopts = ContextOptionsRef(js_context);
285+
(*contextopts).set_baseline_(true);
286+
(*contextopts).set_ion_(true);
287+
(*contextopts).set_nativeRegExp_(true);
206288

207-
SetWarningReporter(js_context, Some(report_warning));
289+
SetWarningReporter(js_context, Some(report_warning));
208290

209-
JS_BeginRequest(js_context);
291+
JS_BeginRequest(js_context);
210292

211-
Ok(Runtime {
212-
cx: js_context,
213-
})
293+
Runtime {
294+
engine,
295+
_parent_child_count: parent.map(|p| p.children_of_parent),
296+
cx: js_context,
297+
outstanding_children: Arc::new(()),
214298
}
215299
}
216300

217301
/// Returns the `JSRuntime` object.
218302
pub fn rt(&self) -> *mut JSRuntime {
219-
PARENT.load(Ordering::SeqCst)
303+
unsafe {
304+
JS_GetRuntime(self.cx)
305+
}
220306
}
221307

222308
/// Returns the `JSContext` object.
@@ -258,6 +344,9 @@ impl Runtime {
258344

259345
impl Drop for Runtime {
260346
fn drop(&mut self) {
347+
assert_eq!(Arc::strong_count(&self.outstanding_children),
348+
1,
349+
"This runtime still has live children.");
261350
unsafe {
262351
JS_EndRequest(self.cx);
263352
JS_DestroyContext(self.cx);
@@ -266,14 +355,6 @@ impl Drop for Runtime {
266355
assert_eq!(context.get(), self.cx);
267356
context.set(ptr::null_mut());
268357
});
269-
270-
if OUTSTANDING_RUNTIMES.fetch_sub(1, Ordering::SeqCst) == 1 {
271-
PARENT.store(ptr::null_mut(), Ordering::SeqCst);
272-
if cfg!(not(feature = "init_once")) {
273-
SHUT_DOWN.store(true, Ordering::SeqCst);
274-
JS_ShutDown();
275-
}
276-
}
277358
}
278359
}
279360
}

tests/callback.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ use mozjs::jsapi::JS_ReportErrorASCII;
1717
use mozjs::jsapi::OnNewGlobalHookOption;
1818
use mozjs::jsapi::Value;
1919
use mozjs::jsval::UndefinedValue;
20-
use mozjs::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
20+
use mozjs::rust::{JSEngine, Runtime, SIMPLE_GLOBAL_CLASS};
2121

2222
use std::ffi::CStr;
2323
use std::ptr;
2424
use std::str;
2525

2626
#[test]
2727
fn callback() {
28-
let runtime = Runtime::new().unwrap();
28+
let engine = JSEngine::init().unwrap();
29+
let runtime = Runtime::new(engine);
2930
let context = runtime.cx();
3031
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
3132
let c_option = CompartmentOptions::default();

tests/capture_stack.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ use mozjs::jsapi::OnNewGlobalHookOption;
1616
use mozjs::jsapi::StackFormat;
1717
use mozjs::jsapi::Value;
1818
use mozjs::jsval::UndefinedValue;
19-
use mozjs::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
19+
use mozjs::rust::{JSEngine, Runtime, SIMPLE_GLOBAL_CLASS};
2020

2121
use std::ptr;
2222

2323
#[test]
2424
fn capture_stack() {
25-
let runtime = Runtime::new().unwrap();
25+
let engine = JSEngine::init().unwrap();
26+
let runtime = Runtime::new(engine);
2627
let context = runtime.cx();
2728
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
2829
let c_option = CompartmentOptions::default();

tests/custom_auto_rooter.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
extern crate mozjs;
66
use mozjs::jsapi::JSTracer;
77
use mozjs::jsapi::JS_GC;
8+
use mozjs::rust::JSEngine;
89
use mozjs::rust::Runtime;
910
use mozjs::rust::CustomTrace;
1011
use mozjs::rust::CustomAutoRooter;
@@ -31,7 +32,8 @@ unsafe impl CustomTrace for TraceCheck {
3132
/// by checking if appropriate virtual trace function was called.
3233
#[test]
3334
fn virtual_trace_called() {
34-
let rt = Runtime::new().unwrap();
35+
let engine = JSEngine::init().unwrap();
36+
let rt = Runtime::new(engine);
3537
let cx = rt.cx();
3638

3739
let mut rooter = CustomAutoRooter::new(TraceCheck::new());

tests/custom_auto_rooter_macro.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
extern crate mozjs;
77
use mozjs::jsapi::JSTracer;
88
use mozjs::jsapi::JS_GC;
9+
use mozjs::rust::JSEngine;
910
use mozjs::rust::Runtime;
1011
use mozjs::rust::CustomTrace;
1112
use std::cell::Cell;
@@ -28,7 +29,8 @@ unsafe impl CustomTrace for TraceCheck {
2829

2930
#[test]
3031
fn custom_auto_rooter_macro() {
31-
let rt = Runtime::new().unwrap();
32+
let engine = JSEngine::init().unwrap();
33+
let rt = Runtime::new(engine);
3234
let cx = rt.cx();
3335

3436
auto_root!(in(cx) let vec = vec![TraceCheck::new(), TraceCheck::new()]);

tests/enumerate.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ use mozjs::jsapi::JS_StringEqualsAscii;
1515
use mozjs::jsapi::OnNewGlobalHookOption;
1616
use mozjs::jsval::UndefinedValue;
1717
use mozjs::rust::IdVector;
18+
use mozjs::rust::JSEngine;
1819
use mozjs::rust::Runtime;
1920
use mozjs::rust::SIMPLE_GLOBAL_CLASS;
2021
use std::ptr;
2122

2223
#[test]
2324
fn enumerate() {
24-
let rt = Runtime::new().unwrap();
25+
let engine = JSEngine::init().unwrap();
26+
let rt = Runtime::new(engine);
2527
let cx = rt.cx();
2628

2729
unsafe {

0 commit comments

Comments
 (0)