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

Commit 7811094

Browse files
author
bors-servo
authored
Auto merge of #450 - jdm:parent-refactor, r=asajeffrey
Make runtime creation safe The fundamental problem exposed in servo/servo#22342 is that our concept of a parent runtime did not match reality. Using the first JSContext's runtime as the global parent for all subsequent contexts only makes sense if that JSContext outlives every other context. This is not guaranteed, leading to crashes when trying to use those contexts if the first context (and therefore its runtime) was destroyed. The new design incorporates several changes for safer, more clear context and runtime management: * in order to create a new context, either a handle to an initialized JS engine is required or a handle to an existing runtime * all runtimes track outstanding handles that have been created, and assert if a runtime is destroyed before all of its child runtimes * overall initialization and shutdown of the engine is controlled by the lifetime of a JSEngine value, so creating a Runtime value is now infallible <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-mozjs/450) <!-- Reviewable:end -->
2 parents e2d07dc + 981e266 commit 7811094

16 files changed

+226
-100
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "mozjs"
33
description = "Rust bindings to the Mozilla SpiderMonkey JavaScript engine."
44
repository = "https://github.com/servo/rust-mozjs"
5-
version = "0.9.5"
5+
version = "0.10.0"
66
authors = ["The Servo Project Developers"]
77
build = "build.rs"
88
license = "MPL-2.0"
@@ -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()]);

0 commit comments

Comments
 (0)