44
55//! Rust wrappers around the raw JS apis
66
7-
87use libc:: { size_t, c_uint} ;
98
109use mozjs_sys:: jsgc:: CustomAutoRooterVFTable ;
@@ -23,7 +22,7 @@ use std::ops::{Deref, DerefMut};
2322use std:: os:: raw:: c_void;
2423use std:: cell:: Cell ;
2524use std:: marker:: PhantomData ;
26- use std:: sync:: atomic :: { AtomicBool , AtomicPtr , AtomicUsize , Ordering } ;
25+ use std:: sync:: { Arc , Mutex } ;
2726
2827use consts:: { JSCLASS_RESERVED_SLOTS_MASK , JSCLASS_GLOBAL_SLOT_COUNT } ;
2928use consts:: { JSCLASS_IS_DOMJSCLASS , JSCLASS_IS_GLOBAL } ;
@@ -124,16 +123,94 @@ impl ToResult for bool {
124123
125124thread_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+
127134lazy_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.
135198pub 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
139216impl 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
259345impl 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}
0 commit comments