4
4
5
5
//! Rust wrappers around the raw JS apis
6
6
7
-
8
7
use libc:: { size_t, c_uint} ;
9
8
10
9
use mozjs_sys:: jsgc:: CustomAutoRooterVFTable ;
@@ -23,7 +22,7 @@ use std::ops::{Deref, DerefMut};
23
22
use std:: os:: raw:: c_void;
24
23
use std:: cell:: Cell ;
25
24
use std:: marker:: PhantomData ;
26
- use std:: sync:: atomic :: { AtomicBool , AtomicPtr , AtomicUsize , Ordering } ;
25
+ use std:: sync:: { Arc , Mutex } ;
27
26
28
27
use consts:: { JSCLASS_RESERVED_SLOTS_MASK , JSCLASS_GLOBAL_SLOT_COUNT } ;
29
28
use consts:: { JSCLASS_IS_DOMJSCLASS , JSCLASS_IS_GLOBAL } ;
@@ -124,16 +123,94 @@ impl ToResult for bool {
124
123
125
124
thread_local ! ( static CONTEXT : Cell <* mut JSContext > = Cell :: new( ptr:: null_mut( ) ) ) ;
126
125
126
+ #[ derive( PartialEq ) ]
127
+ enum EngineState {
128
+ Uninitialized ,
129
+ InitFailed ,
130
+ Initialized ,
131
+ ShutDown ,
132
+ }
133
+
127
134
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 < ( ) > ,
132
194
}
195
+ unsafe impl Send for ParentRuntime { }
133
196
134
197
/// A wrapper for the `JSContext` structure in SpiderMonkey.
135
198
pub struct Runtime {
199
+ /// Raw pointer to the underlying SpiderMonkey context.
136
200
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 < ( ) > ,
137
214
}
138
215
139
216
impl Runtime {
@@ -147,76 +224,85 @@ impl Runtime {
147
224
}
148
225
149
226
/// 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
+ }
194
243
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
+ } ) ;
199
281
200
- InitSelfHostedCode ( js_context) ;
282
+ InitSelfHostedCode ( js_context) ;
201
283
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 ) ;
206
288
207
- SetWarningReporter ( js_context, Some ( report_warning) ) ;
289
+ SetWarningReporter ( js_context, Some ( report_warning) ) ;
208
290
209
- JS_BeginRequest ( js_context) ;
291
+ JS_BeginRequest ( js_context) ;
210
292
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 ( ( ) ) ,
214
298
}
215
299
}
216
300
217
301
/// Returns the `JSRuntime` object.
218
302
pub fn rt ( & self ) -> * mut JSRuntime {
219
- PARENT . load ( Ordering :: SeqCst )
303
+ unsafe {
304
+ JS_GetRuntime ( self . cx )
305
+ }
220
306
}
221
307
222
308
/// Returns the `JSContext` object.
@@ -258,6 +344,9 @@ impl Runtime {
258
344
259
345
impl Drop for Runtime {
260
346
fn drop ( & mut self ) {
347
+ assert_eq ! ( Arc :: strong_count( & self . outstanding_children) ,
348
+ 1 ,
349
+ "This runtime still has live children." ) ;
261
350
unsafe {
262
351
JS_EndRequest ( self . cx ) ;
263
352
JS_DestroyContext ( self . cx ) ;
@@ -266,14 +355,6 @@ impl Drop for Runtime {
266
355
assert_eq ! ( context. get( ) , self . cx) ;
267
356
context. set ( ptr:: null_mut ( ) ) ;
268
357
} ) ;
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
- }
277
358
}
278
359
}
279
360
}
0 commit comments