Bug 1120655 - Suppress zone/compartment collection while iterating, r=terrence, a=abillings
authorSteve Fink <sfink@mozilla.com>
Thu, 19 Mar 2015 22:03:15 -0700
changeset 257864 525829e43c332c57cad50f71e570db176c0bb999
parent 257863 16babf5d700c31100392ce17c0132aad1d38ac9c
child 257865 e9d5b9c89b595c5b2876f58ce1c92e406e5227aa
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence, abillings
bugs1120655
milestone38.0a2
Bug 1120655 - Suppress zone/compartment collection while iterating, r=terrence, a=abillings
js/src/gc/GCRuntime.h
js/src/gc/Zone.h
js/src/jsgc.cpp
js/src/jsgc.h
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -995,16 +995,18 @@ class GCRuntime
     void *verifyPreData;
     void *verifyPostData;
     bool chunkAllocationSinceLastGC;
     int64_t nextFullGCTime;
     int64_t lastGCTime;
 
     JSGCMode mode;
 
+    mozilla::Atomic<size_t, mozilla::ReleaseAcquire> numActiveZoneIters;
+
     uint64_t decommitThreshold;
 
     /* During shutdown, the GC needs to clean up every possible object. */
     bool cleanUpEverything;
 
     /*
      * The gray bits can become invalid if UnmarkGray overflows the stack. A
      * full GC will reset this bit, since it fills in all the gray bits.
@@ -1251,16 +1253,32 @@ class GCRuntime
      * During incremental sweeping, this field temporarily holds the arenas of
      * the current AllocKind being swept in order of increasing free space.
      */
     SortedArenaList incrementalSweepList;
 
     friend class js::GCHelperState;
     friend class js::gc::MarkingValidator;
     friend class js::gc::AutoTraceSession;
+    friend class AutoEnterIteration;
+};
+
+/* Prevent compartments and zones from being collected during iteration. */
+class AutoEnterIteration {
+    GCRuntime *gc;
+
+  public:
+    explicit AutoEnterIteration(GCRuntime *gc_) : gc(gc_) {
+        ++gc->numActiveZoneIters;
+    }
+
+    ~AutoEnterIteration() {
+        MOZ_ASSERT(gc->numActiveZoneIters);
+        --gc->numActiveZoneIters;
+    }
 };
 
 #ifdef JS_GC_ZEAL
 inline int
 GCRuntime::zeal() {
     return zealMode;
 }
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -319,20 +319,21 @@ namespace js {
 // iterate over the atoms zone, consider taking the exclusive access lock first.
 enum ZoneSelector {
     WithAtoms,
     SkipAtoms
 };
 
 class ZonesIter
 {
+    gc::AutoEnterIteration iterMarker;
     JS::Zone **it, **end;
 
   public:
-    ZonesIter(JSRuntime *rt, ZoneSelector selector) {
+    ZonesIter(JSRuntime *rt, ZoneSelector selector) : iterMarker(&rt->gc) {
         it = rt->gc.zones.begin();
         end = rt->gc.zones.end();
 
         if (selector == SkipAtoms) {
             MOZ_ASSERT(atAtomsZone(rt));
             it++;
         }
     }
@@ -393,31 +394,32 @@ struct CompartmentsInZoneIter
     friend class mozilla::Maybe<CompartmentsInZoneIter>;
 };
 
 // This iterator iterates over all the compartments in a given set of zones. The
 // set of zones is determined by iterating ZoneIterT.
 template<class ZonesIterT>
 class CompartmentsIterT
 {
+    gc::AutoEnterIteration iterMarker;
     ZonesIterT zone;
     mozilla::Maybe<CompartmentsInZoneIter> comp;
 
   public:
     explicit CompartmentsIterT(JSRuntime *rt)
-      : zone(rt)
+      : iterMarker(&rt->gc), zone(rt)
     {
         if (zone.done())
             comp.emplace();
         else
             comp.emplace(zone);
     }
 
     CompartmentsIterT(JSRuntime *rt, ZoneSelector selector)
-      : zone(rt, selector)
+      : iterMarker(&rt->gc), zone(rt, selector)
     {
         if (zone.done())
             comp.emplace();
         else
             comp.emplace(zone);
     }
 
     bool done() const { return zone.done(); }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1075,16 +1075,17 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
     maxMallocBytes(0),
     numArenasFreeCommitted(0),
     verifyPreData(nullptr),
     verifyPostData(nullptr),
     chunkAllocationSinceLastGC(false),
     nextFullGCTime(0),
     lastGCTime(PRMJ_Now()),
     mode(JSGC_MODE_INCREMENTAL),
+    numActiveZoneIters(0),
     decommitThreshold(32 * 1024 * 1024),
     cleanUpEverything(false),
     grayBitsValid(false),
     majorGCTriggerReason(JS::gcreason::NO_REASON),
     minorGCTriggerReason(JS::gcreason::NO_REASON),
     fullGCForAtomsRequested_(false),
     majorGCNumber(0),
     jitReleaseNumber(0),
@@ -3660,16 +3661,20 @@ Zone::sweepCompartments(FreeOp *fop, boo
     }
     compartments.resize(write - compartments.begin());
     MOZ_ASSERT_IF(keepAtleastOne, !compartments.empty());
 }
 
 void
 GCRuntime::sweepZones(FreeOp *fop, bool destroyingRuntime)
 {
+    MOZ_ASSERT_IF(destroyingRuntime, rt->gc.numActiveZoneIters == 0);
+    if (rt->gc.numActiveZoneIters)
+        return;
+
     AutoLockGC lock(rt); // Avoid race with background sweeping.
 
     JSZoneCallback callback = rt->destroyZoneCallback;
 
     /* Skip the atomsCompartment zone. */
     Zone **read = zones.begin() + 1;
     Zone **end = zones.end();
     Zone **write = read;
@@ -5421,17 +5426,17 @@ GCRuntime::endSweepPhase(bool destroying
             if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
                 AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject());
         }
     }
 #endif
 }
 
 GCRuntime::IncrementalProgress
-GCRuntime::compactPhase(bool lastGC)
+GCRuntime::compactPhase(bool destroyingRuntime)
 {
     gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
 
     if (isIncremental) {
         // Poll for end of background sweeping
         AutoLockGC lock(rt);
         if (isBackgroundSweeping())
             return NotFinished;
@@ -5459,17 +5464,17 @@ GCRuntime::compactPhase(bool lastGC)
 
     // Release the relocated arenas, or in debug builds queue them to be
     // released until the start of the next GC unless this is the last GC.
 #ifndef DEBUG
     releaseRelocatedArenas(relocatedList);
 #else
     protectRelocatedArenas(relocatedList);
     MOZ_ASSERT(!relocatedArenasToRelease);
-    if (!lastGC)
+    if (!destroyingRuntime)
         relocatedArenasToRelease = relocatedList;
     else
         releaseRelocatedArenas(relocatedList);
 #endif
 
     // Ensure execess chunks are returns to the system and free arenas
     // decommitted.
     shrinkBuffers();
@@ -5879,17 +5884,17 @@ GCRuntime::incrementalCollectSlice(Slice
 
         incrementalState = COMPACT;
 
         /* Yield before compacting since it is not incremental. */
         if (isCompacting && isIncremental)
             break;
 
       case COMPACT:
-        if (isCompacting && compactPhase(lastGC) == NotFinished)
+        if (isCompacting && compactPhase(destroyingRuntime) == NotFinished)
             break;
         finishCollection();
         incrementalState = NO_INCREMENTAL;
         break;
 
       default:
         MOZ_ASSERT(false);
     }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1306,17 +1306,17 @@ MaybeVerifyBarriers(JSContext *cx, bool 
 {
 }
 
 #endif
 
 /*
  * Instances of this class set the |JSRuntime::suppressGC| flag for the duration
  * that they are live. Use of this class is highly discouraged. Please carefully
- * read the comment in jscntxt.h above |suppressGC| and take all appropriate
+ * read the comment in vm/Runtime.h above |suppressGC| and take all appropriate
  * precautions before instantiating this class.
  */
 class AutoSuppressGC
 {
     int32_t &suppressGC_;
 
   public:
     explicit AutoSuppressGC(ExclusiveContext *cx);