66#include " JSCRuntime.h"
77
88#include < JavaScriptCore/JavaScript.h>
9+ #include < atomic>
910#include < condition_variable>
1011#include < cstdlib>
1112#include < mutex>
1213#include < queue>
1314#include < sstream>
1415#include < thread>
1516
16- #ifndef NDEBUG
17- #include < atomic>
18- #endif
19-
2017namespace facebook {
2118namespace jsc {
2219
2320namespace detail {
2421class ArgsConverter ;
25- class ProtectionQueue ;
2622} // namespace detail
2723
2824class JSCRuntime ;
@@ -65,7 +61,6 @@ class JSCRuntime : public jsi::Runtime {
6561
6662 protected:
6763 friend class detail ::ArgsConverter;
68- friend class detail ::ProtectionQueue;
6964 class JSCStringValue final : public PointerValue {
7065#ifndef NDEBUG
7166 JSCStringValue (JSStringRef str, std::atomic<intptr_t >& counter);
@@ -83,31 +78,26 @@ class JSCRuntime : public jsi::Runtime {
8378 };
8479
8580 class JSCObjectValue final : public PointerValue {
86- #ifndef NDEBUG
8781 JSCObjectValue (
8882 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
9788#endif
89+ );
9890
9991 void invalidate () override ;
100- void unprotect ();
10192
10293 JSGlobalContextRef ctx_;
94+ const std::atomic<bool >& ctxInvalid_;
10395 JSObjectRef obj_;
104- detail::ProtectionQueue& protectionQueue_;
10596#ifndef NDEBUG
10697 std::atomic<intptr_t >& counter_;
10798#endif
10899 protected:
109100 friend class JSCRuntime ;
110- friend class detail ::ProtectionQueue;
111101 };
112102
113103 PointerValue* cloneString (const Runtime::PointerValue* pv) override ;
@@ -202,10 +192,8 @@ class JSCRuntime : public jsi::Runtime {
202192 void checkException (JSValueRef res, JSValueRef exc, const char * msg);
203193
204194 JSGlobalContextRef ctx_;
195+ std::atomic<bool > ctxInvalid_;
205196 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_;
209197#ifndef NDEBUG
210198 mutable std::atomic<intptr_t > objectCounter_;
211199 mutable std::atomic<intptr_t > stringCounter_;
@@ -293,106 +281,14 @@ std::string to_string(void* value) {
293281}
294282} // namespace
295283
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-
388284JSCRuntime::JSCRuntime ()
389285 : JSCRuntime(JSGlobalContextCreateInGroup(nullptr , nullptr )) {
390286 JSGlobalContextRelease (ctx_);
391287}
392288
393289JSCRuntime::JSCRuntime (JSGlobalContextRef ctx)
394290 : ctx_(JSGlobalContextRetain(ctx)),
395- protectionQueue_ (std::make_unique<detail::ProtectionQueue>() )
291+ ctxInvalid_ ( false )
396292#ifndef NDEBUG
397293 ,
398294 objectCounter_ (0 ),
@@ -405,15 +301,11 @@ JSCRuntime::~JSCRuntime() {
405301 // On shutting down and cleaning up: when JSC is actually torn down,
406302 // it calls JSC::Heap::lastChanceToFinalize internally which
407303 // 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 ;
415308 JSGlobalContextRelease (ctx_);
416- protectionQueue_->shutdown ();
417309#ifndef NDEBUG
418310 assert (
419311 objectCounter_ == 0 && " JSCRuntime destroyed with a dangling API object" );
@@ -473,15 +365,9 @@ JSCRuntime::JSCStringValue::JSCStringValue(JSStringRef str)
473365#endif
474366
475367void 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- //
480368 // These JSC{String,Object}Value objects are implicitly owned by the
481369 // {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.
485371#ifndef NDEBUG
486372 counter_ -= 1 ;
487373#endif
@@ -492,16 +378,16 @@ void JSCRuntime::JSCStringValue::invalidate() {
492378
493379JSCRuntime::JSCObjectValue::JSCObjectValue (
494380 JSGlobalContextRef ctx,
495- detail::ProtectionQueue& pq ,
381+ const std::atomic< bool >& ctxInvalid ,
496382 JSObjectRef obj
497383#ifndef NDEBUG
498384 ,
499385 std::atomic<intptr_t >& counter
500386#endif
501387 )
502388 : ctx_(ctx),
503- obj_ (obj ),
504- protectionQueue_(pq )
389+ ctxInvalid_ (ctxInvalid ),
390+ obj_(obj )
505391#ifndef NDEBUG
506392 ,
507393 counter_ (counter)
@@ -514,16 +400,32 @@ JSCRuntime::JSCObjectValue::JSCObjectValue(
514400}
515401
516402void 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 () {
523403#ifndef NDEBUG
524404 counter_ -= 1 ;
525405#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_) {
527429 JSValueUnprotect (ctx_, obj_);
528430 }
529431 delete this ;
@@ -1222,9 +1124,9 @@ jsi::Runtime::PointerValue* JSCRuntime::makeObjectValue(
12221124 objectRef = JSObjectMake (ctx_, nullptr , nullptr );
12231125 }
12241126#ifndef NDEBUG
1225- return new JSCObjectValue (ctx_, *protectionQueue_ , objectRef, objectCounter_);
1127+ return new JSCObjectValue (ctx_, ctxInvalid_ , objectRef, objectCounter_);
12261128#else
1227- return new JSCObjectValue (ctx_, *protectionQueue_ , objectRef);
1129+ return new JSCObjectValue (ctx_, ctxInvalid_ , objectRef);
12281130#endif
12291131}
12301132
0 commit comments