6
6
#include " JSCRuntime.h"
7
7
8
8
#include < JavaScriptCore/JavaScript.h>
9
+ #include < atomic>
9
10
#include < condition_variable>
10
11
#include < cstdlib>
11
12
#include < mutex>
12
13
#include < queue>
13
14
#include < sstream>
14
15
#include < thread>
15
16
16
- #ifndef NDEBUG
17
- #include < atomic>
18
- #endif
19
-
20
17
namespace facebook {
21
18
namespace jsc {
22
19
23
20
namespace detail {
24
21
class ArgsConverter ;
25
- class ProtectionQueue ;
26
22
} // namespace detail
27
23
28
24
class JSCRuntime ;
@@ -65,7 +61,6 @@ class JSCRuntime : public jsi::Runtime {
65
61
66
62
protected:
67
63
friend class detail ::ArgsConverter;
68
- friend class detail ::ProtectionQueue;
69
64
class JSCStringValue final : public PointerValue {
70
65
#ifndef NDEBUG
71
66
JSCStringValue (JSStringRef str, std::atomic<intptr_t >& counter);
@@ -83,31 +78,26 @@ class JSCRuntime : public jsi::Runtime {
83
78
};
84
79
85
80
class JSCObjectValue final : public PointerValue {
86
- #ifndef NDEBUG
87
81
JSCObjectValue (
88
82
JSGlobalContextRef ctx,
89
- detail::ProtectionQueue& pq,
90
- JSObjectRef obj,
91
- std::atomic<intptr_t >& counter);
92
- #else
93
- JSCObjectValue (
94
- JSGlobalContextRef context,
95
- detail::ProtectionQueue& pq,
96
- JSObjectRef obj);
83
+ const std::atomic<bool >& ctxInvalid,
84
+ JSObjectRef obj
85
+ #ifndef NDEBUG
86
+ ,
87
+ std::atomic<intptr_t >& counter
97
88
#endif
89
+ );
98
90
99
91
void invalidate () override ;
100
- void unprotect ();
101
92
102
93
JSGlobalContextRef ctx_;
94
+ const std::atomic<bool >& ctxInvalid_;
103
95
JSObjectRef obj_;
104
- detail::ProtectionQueue& protectionQueue_;
105
96
#ifndef NDEBUG
106
97
std::atomic<intptr_t >& counter_;
107
98
#endif
108
99
protected:
109
100
friend class JSCRuntime ;
110
- friend class detail ::ProtectionQueue;
111
101
};
112
102
113
103
PointerValue* cloneString (const Runtime::PointerValue* pv) override ;
@@ -202,10 +192,8 @@ class JSCRuntime : public jsi::Runtime {
202
192
void checkException (JSValueRef res, JSValueRef exc, const char * msg);
203
193
204
194
JSGlobalContextRef ctx_;
195
+ std::atomic<bool > ctxInvalid_;
205
196
std::string desc_;
206
- // We make this a pointer so that we can control explicitly when it's deleted
207
- // namely before the context is released.
208
- mutable std::unique_ptr<detail::ProtectionQueue> protectionQueue_;
209
197
#ifndef NDEBUG
210
198
mutable std::atomic<intptr_t > objectCounter_;
211
199
mutable std::atomic<intptr_t > stringCounter_;
@@ -293,106 +281,14 @@ std::string to_string(void* value) {
293
281
}
294
282
} // namespace
295
283
296
- // UnprotectQueue
297
- namespace detail {
298
- class ProtectionQueue {
299
- public:
300
- ProtectionQueue ()
301
- : ctxInvalid_(false )
302
- , shuttingDown_(false )
303
- #ifndef NDEBUG
304
- ,
305
- didShutdown_ {
306
- false
307
- }
308
- #endif
309
- , unprotectorThread_(&ProtectionQueue::unprotectThread, this ) {}
310
-
311
- void setContextInvalid () {
312
- std::lock_guard<std::mutex> locker (mutex_);
313
- ctxInvalid_ = true ;
314
- }
315
-
316
- void shutdown () {
317
- {
318
- std::lock_guard<std::mutex> locker (mutex_);
319
- assert (ctxInvalid_);
320
- shuttingDown_ = true ;
321
- notEmpty_.notify_one ();
322
- }
323
- unprotectorThread_.join ();
324
- }
325
-
326
- void push (JSCRuntime::JSCObjectValue* value) {
327
- std::lock_guard<std::mutex> locker (mutex_);
328
- assert (!didShutdown_);
329
- queue_.push (value);
330
- notEmpty_.notify_one ();
331
- }
332
-
333
- private:
334
- // This this the function that runs in the background deleting (and thus
335
- // unprotecting JSObjectRefs as need be). This needs to be explicitly on a
336
- // separate thread so that we don't have the API lock when `JSValueUnprotect`
337
- // is called already (i.e. if we did this on the same thread that calls
338
- // invalidate() on an Object then we might be in the middle of a GC pass, and
339
- // already have the API lock).
340
- void unprotectThread () {
341
- #if defined(__APPLE__)
342
- pthread_setname_np (" jsc-protectionqueue-unprotectthread" );
343
- #endif
344
-
345
- std::unique_lock<std::mutex> locker (mutex_);
346
- while (!shuttingDown_ || !queue_.empty ()) {
347
- if (queue_.empty ()) {
348
- // This will wake up when shuttingDown_ becomes true
349
- notEmpty_.wait (locker);
350
- } else {
351
- JSCRuntime::JSCObjectValue* value = queue_.front ();
352
- queue_.pop ();
353
- // We need to drop the lock here since this calls JSValueUnprotect and
354
- // that may make another GC pass, which could call another finalizer
355
- // and thus attempt to push to this queue then, and deadlock.
356
- locker.unlock ();
357
- if (ctxInvalid_) {
358
- value->ctx_ = nullptr ;
359
- }
360
- value->unprotect ();
361
- locker.lock ();
362
- }
363
- }
364
- #ifndef NDEBUG
365
- didShutdown_ = true ;
366
- #endif
367
- }
368
- // Used to lock the queue_/shuttingDown_ ivars
369
- std::mutex mutex_;
370
- // Used to signal queue_ empty status changing
371
- std::condition_variable notEmpty_;
372
- // The actual underlying queue
373
- std::queue<JSCRuntime::JSCObjectValue*> queue_;
374
- // A flag which is set before shutting down JSC
375
- bool ctxInvalid_;
376
- // A flag dictating whether or not we need to stop all execution
377
- bool shuttingDown_;
378
- #ifndef NDEBUG
379
- bool didShutdown_;
380
- #endif
381
- // The thread that dequeues and processes the queue. Note this is the last
382
- // member on purpose so the thread starts up after all state has been
383
- // properly initialized
384
- std::thread unprotectorThread_;
385
- };
386
- } // namespace detail
387
-
388
284
JSCRuntime::JSCRuntime ()
389
285
: JSCRuntime(JSGlobalContextCreateInGroup(nullptr , nullptr )) {
390
286
JSGlobalContextRelease (ctx_);
391
287
}
392
288
393
289
JSCRuntime::JSCRuntime (JSGlobalContextRef ctx)
394
290
: ctx_(JSGlobalContextRetain(ctx)),
395
- protectionQueue_ (std::make_unique<detail::ProtectionQueue>() )
291
+ ctxInvalid_ ( false )
396
292
#ifndef NDEBUG
397
293
,
398
294
objectCounter_ (0 ),
@@ -405,15 +301,11 @@ JSCRuntime::~JSCRuntime() {
405
301
// On shutting down and cleaning up: when JSC is actually torn down,
406
302
// it calls JSC::Heap::lastChanceToFinalize internally which
407
303
// finalizes anything left over. But at this point,
408
- // JSUnprotectValue() can no longer be called. So there's a
409
- // multiphase shutdown here. We tell the protection queue that the
410
- // VM is invalid, which causes it not to call JSUnprotectValue. but
411
- // it will decrement its counters, if !NDEBUG. Then we shut down
412
- // the VM, which will clean everything up. Finally, we shut down
413
- // the queue itself.
414
- protectionQueue_->setContextInvalid ();
304
+ // JSValueUnprotect() can no longer be called. We use an
305
+ // atomic<bool> to avoid unsafe unprotects happening after shutdown
306
+ // has started.
307
+ ctxInvalid_ = true ;
415
308
JSGlobalContextRelease (ctx_);
416
- protectionQueue_->shutdown ();
417
309
#ifndef NDEBUG
418
310
assert (
419
311
objectCounter_ == 0 && " JSCRuntime destroyed with a dangling API object" );
@@ -473,15 +365,9 @@ JSCRuntime::JSCStringValue::JSCStringValue(JSStringRef str)
473
365
#endif
474
366
475
367
void JSCRuntime::JSCStringValue::invalidate () {
476
- // We want to immediately JSStringRelease once a String is released,
477
- // and queue a JSObjectRef to unprotected (see comment on
478
- // ProtectionQueue::unprotectThread above).
479
- //
480
368
// These JSC{String,Object}Value objects are implicitly owned by the
481
369
// {String,Object} objects, thus when a String/Object is destructed
482
- // the JSC{String,Object}Value should be released (again this has
483
- // the caveat that objects must be unprotected on a separate
484
- // thread).
370
+ // the JSC{String,Object}Value should be released.
485
371
#ifndef NDEBUG
486
372
counter_ -= 1 ;
487
373
#endif
@@ -492,16 +378,16 @@ void JSCRuntime::JSCStringValue::invalidate() {
492
378
493
379
JSCRuntime::JSCObjectValue::JSCObjectValue (
494
380
JSGlobalContextRef ctx,
495
- detail::ProtectionQueue& pq ,
381
+ const std::atomic< bool >& ctxInvalid ,
496
382
JSObjectRef obj
497
383
#ifndef NDEBUG
498
384
,
499
385
std::atomic<intptr_t >& counter
500
386
#endif
501
387
)
502
388
: ctx_(ctx),
503
- obj_ (obj ),
504
- protectionQueue_(pq )
389
+ ctxInvalid_ (ctxInvalid ),
390
+ obj_(obj )
505
391
#ifndef NDEBUG
506
392
,
507
393
counter_ (counter)
@@ -514,16 +400,32 @@ JSCRuntime::JSCObjectValue::JSCObjectValue(
514
400
}
515
401
516
402
void JSCRuntime::JSCObjectValue::invalidate () {
517
- // See comment in JSCRuntime::JSCStringValue::invalidate as well as
518
- // on ProtectionQueue::unprotectThread.
519
- protectionQueue_.push (this );
520
- }
521
-
522
- void JSCRuntime::JSCObjectValue::unprotect () {
523
403
#ifndef NDEBUG
524
404
counter_ -= 1 ;
525
405
#endif
526
- if (ctx_) {
406
+ // When shutting down the VM, if there is a HostObject which
407
+ // contains or otherwise owns a jsi::Object, then the final GC will
408
+ // finalize the HostObject, leading to a call to invalidate(). But
409
+ // at that point, making calls to JSValueUnprotect will crash.
410
+ // It is up to the application to make sure that any other calls to
411
+ // invalidate() happen before VM destruction; see the comment on
412
+ // jsi::Runtime.
413
+ //
414
+ // Another potential concern here is that in the non-shutdown case,
415
+ // if a HostObject is GCd, JSValueUnprotect will be called from the
416
+ // JSC finalizer. The documentation warns against this: "You must
417
+ // not call any function that may cause a garbage collection or an
418
+ // allocation of a garbage collected object from within a
419
+ // JSObjectFinalizeCallback. This includes all functions that have a
420
+ // JSContextRef parameter." However, an audit of the source code for
421
+ // JSValueUnprotect in late 2018 shows that it cannot cause
422
+ // allocation or a GC, and further, this code has not changed in
423
+ // about two years. In the future, we may choose to reintroduce the
424
+ // mechanism previously used here which uses a separate thread for
425
+ // JSValueUnprotect, in order to conform to the documented API, but
426
+ // use the "unsafe" synchronous version on iOS 11 and earlier.
427
+
428
+ if (!ctxInvalid_) {
527
429
JSValueUnprotect (ctx_, obj_);
528
430
}
529
431
delete this ;
@@ -1222,9 +1124,9 @@ jsi::Runtime::PointerValue* JSCRuntime::makeObjectValue(
1222
1124
objectRef = JSObjectMake (ctx_, nullptr , nullptr );
1223
1125
}
1224
1126
#ifndef NDEBUG
1225
- return new JSCObjectValue (ctx_, *protectionQueue_ , objectRef, objectCounter_);
1127
+ return new JSCObjectValue (ctx_, ctxInvalid_ , objectRef, objectCounter_);
1226
1128
#else
1227
- return new JSCObjectValue (ctx_, *protectionQueue_ , objectRef);
1129
+ return new JSCObjectValue (ctx_, ctxInvalid_ , objectRef);
1228
1130
#endif
1229
1131
}
1230
1132
0 commit comments