Bug 1360372 - Acquire cooperative lock when entering system zone group (r=bhackett)
authorBill McCloskey <billm@mozilla.com>
Thu, 27 Apr 2017 15:25:21 -0700
changeset 355957 11a43a7edbae7de43559146b476965eef689b7de
parent 355956 915489b79bbfac6cb173f952d08f04290d70179d
child 355958 7292d50807c9fe323996743c825b7b763b908879
push id31752
push usercbook@mozilla.com
push dateTue, 02 May 2017 09:05:11 +0000
treeherdermozilla-central@48c0fd9c9ec5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1360372
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1360372 - Acquire cooperative lock when entering system zone group (r=bhackett) MozReview-Commit-ID: 92SgTKMD6xt
js/src/gc/ZoneGroup.cpp
js/src/gc/ZoneGroup.h
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsgc.cpp
--- a/js/src/gc/ZoneGroup.cpp
+++ b/js/src/gc/ZoneGroup.cpp
@@ -52,22 +52,27 @@ ZoneGroup::~ZoneGroup()
 
     js_delete(jitZoneGroup.ref());
 
     if (this == runtime->gc.systemZoneGroup)
         runtime->gc.systemZoneGroup = nullptr;
 }
 
 void
-ZoneGroup::enter()
+ZoneGroup::enter(JSContext* cx)
 {
-    JSContext* cx = TlsContext.get();
     if (ownerContext().context() == cx) {
         MOZ_ASSERT(enterCount);
     } else {
+        if (useExclusiveLocking) {
+            MOZ_ASSERT(!usedByHelperThread);
+            while (ownerContext().context() != nullptr) {
+                cx->yieldToEmbedding();
+            }
+        }
         MOZ_RELEASE_ASSERT(ownerContext().context() == nullptr);
         MOZ_ASSERT(enterCount == 0);
         ownerContext_ = CooperatingContext(cx);
         if (cx->generationalDisabled)
             nursery().disable();
 
         // Finish any Ion compilations in this zone group, in case compilation
         // finished for some script in this group while no thread was in this
--- a/js/src/gc/ZoneGroup.h
+++ b/js/src/gc/ZoneGroup.h
@@ -36,21 +36,25 @@ class ZoneGroup
 
   private:
     // The context with exclusive access to this zone group.
     UnprotectedData<CooperatingContext> ownerContext_;
 
     // The number of times the context has entered this zone group.
     UnprotectedData<size_t> enterCount;
 
+    // If this flag is true, then we may need to block before entering this zone
+    // group. Blocking happens using JSContext::yieldToEmbedding.
+    UnprotectedData<bool> useExclusiveLocking;
+
   public:
     CooperatingContext& ownerContext() { return ownerContext_.ref(); }
     void* addressOfOwnerContext() { return &ownerContext_.ref().cx; }
 
-    void enter();
+    void enter(JSContext* cx);
     void leave();
     bool ownedByCurrentThread();
 
     // All zones in the group.
   private:
     ZoneGroupOrGCTaskData<ZoneVector> zones_;
   public:
     ZoneVector& zones() { return zones_.ref(); }
@@ -67,16 +71,19 @@ class ZoneGroup
     inline gc::StoreBuffer& storeBuffer();
 
     // Queue a thunk to run after the next minor GC.
     inline void callAfterMinorGC(void (*thunk)(void* data), void* data);
 
     inline bool isCollecting();
     inline bool isGCScheduled();
 
+    // See the useExclusiveLocking field above.
+    void setUseExclusiveLocking() { useExclusiveLocking = true; }
+
 #ifdef DEBUG
   private:
     // The number of possible bailing places encounters before forcefully bailing
     // in that place. Zero means inactive.
     ZoneGroupData<uint32_t> ionBailAfter_;
 
   public:
     void* addressOfIonBailAfter() { return &ionBailAfter_; }
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -298,20 +298,36 @@ struct JSContext : public JS::RootingCon
     static size_t offsetOfCompartment() {
         return offsetof(JSContext, compartment_);
     }
 
     friend class JS::AutoSaveExceptionState;
     friend class js::jit::DebugModeOSRVolatileJitFrameIterator;
     friend void js::ReportOverRecursed(JSContext*, unsigned errorNumber);
 
+    // Returns to the embedding to allow other cooperative threads to run. We
+    // may do this if we need access to a ZoneGroup that is in use by another
+    // thread.
+    void yieldToEmbedding() {
+        (*yieldCallback_)(this);
+    }
+
+    void setYieldCallback(js::YieldCallback callback) {
+        yieldCallback_ = callback;
+    }
+
   private:
     static JS::Error reportedError;
     static JS::OOM reportedOOM;
 
+    // This callback is used to ask the embedding to allow other cooperative
+    // threads to run. We may do this if we need access to a ZoneGroup that is
+    // in use by another thread.
+    js::ThreadLocalData<js::YieldCallback> yieldCallback_;
+
   public:
     inline JS::Result<> boolToResult(bool ok);
 
     /**
      * Intentionally awkward signpost method that is stationed on the
      * boundary between Result-using and non-Result-using code.
      */
     template <typename V, typename E>
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -524,17 +524,17 @@ JSContext::setCompartment(JSCompartment*
     zone_ = comp ? comp->zone() : nullptr;
     arenas_ = zone_ ? &zone_->arenas : nullptr;
 }
 
 inline void
 JSContext::enterZoneGroup(js::ZoneGroup* group)
 {
     MOZ_ASSERT(this == js::TlsContext.get());
-    group->enter();
+    group->enter(this);
 }
 
 inline void
 JSContext::leaveZoneGroup(js::ZoneGroup* group)
 {
     MOZ_ASSERT(this == js::TlsContext.get());
     group->leave();
 }
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1477,8 +1477,21 @@ js::EnableAccessValidation(JSContext* cx
     cx->enableAccessValidation = enabled;
 }
 
 JS_FRIEND_API(void)
 js::SetCompartmentValidAccessPtr(JSContext* cx, JS::HandleObject global, bool* accessp)
 {
     global->compartment()->setValidAccessPtr(accessp);
 }
+
+JS_FRIEND_API(void)
+js::SetCooperativeYieldCallback(JSContext* cx, YieldCallback callback)
+{
+    cx->setYieldCallback(callback);
+}
+
+JS_FRIEND_API(bool)
+js::SystemZoneAvailable(JSContext* cx)
+{
+    CooperatingContext& owner = cx->runtime()->gc.systemZoneGroup->ownerContext();
+    return owner.context() == cx || owner.context() == nullptr;
+}
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2958,16 +2958,30 @@ EnableAccessValidation(JSContext* cx, bo
 
 // See EnableAccessValidation above. The caller must guarantee that accessp will
 // live at least as long as |global| is alive. The JS engine reads accessp from
 // threads that are allowed to run code on |global|, so all changes to *accessp
 // should be made from whichever thread owns |global| at a given time.
 extern JS_FRIEND_API(void)
 SetCompartmentValidAccessPtr(JSContext* cx, JS::HandleObject global, bool* accessp);
 
+// If the JS engine wants to block so that other cooperative threads can run, it
+// will call the yield callback. It may do this if it needs to access a ZoneGroup
+// that is held by another thread (such as the system zone group).
+typedef void
+(* YieldCallback)(JSContext* cx);
+
+extern JS_FRIEND_API(void)
+SetCooperativeYieldCallback(JSContext* cx, YieldCallback callback);
+
+// Returns true if the system zone is available (i.e., if no cooperative contexts
+// are using it now).
+extern JS_FRIEND_API(bool)
+SystemZoneAvailable(JSContext* cx);
+
 } /* namespace js */
 
 class NativeProfiler
 {
   public:
     virtual ~NativeProfiler() {};
     virtual void sampleNative(void* addr, uint32_t size) = 0;
     virtual void removeNative(void* addr) = 0;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6966,17 +6966,17 @@ js::NewCompartment(JSContext* cx, JSPrin
       case JS::NewZoneInExistingZoneGroup:
         group = static_cast<ZoneGroup*>(options.creationOptions().zonePointer());
         MOZ_ASSERT(group);
         break;
     }
 
     if (group) {
         // Take over ownership of the group while we create the compartment/zone.
-        group->enter();
+        group->enter(cx);
     } else {
         MOZ_ASSERT(!zone);
         group = cx->new_<ZoneGroup>(rt);
         if (!group)
             return nullptr;
 
         groupHolder.reset(group);
 
@@ -7037,16 +7037,17 @@ js::NewCompartment(JSContext* cx, JSPrin
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         // Lazily set the runtime's system zone group.
         if (zoneSpec == JS::SystemZone || zoneSpec == JS::NewZoneInSystemZoneGroup) {
             MOZ_RELEASE_ASSERT(!rt->gc.systemZoneGroup);
             rt->gc.systemZoneGroup = group;
+            group->setUseExclusiveLocking();
         }
     }
 
     zoneHolder.forget();
     groupHolder.forget();
     group->leave();
     return compartment.forget();
 }