Bug 999158 - Keep a spare chunk around to mitigate GGC OOM crashes on tenuring. r=terrence, a=lmandel
authorJon Coppeard <jcoppeard@mozilla.com>
Fri, 11 Jul 2014 08:27:00 -0700
changeset 208311 97fd0156fdc2
parent 208310 ef94af3dd0ad
child 208312 76f7c4f771f5
push id3817
push userryanvm@gmail.com
push date2014-08-15 15:17 +0000
treeherdermozilla-beta@97fd0156fdc2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence, lmandel
bugs999158
milestone32.0
Bug 999158 - Keep a spare chunk around to mitigate GGC OOM crashes on tenuring. r=terrence, a=lmandel
js/src/jsgc.cpp
js/src/jsgc.h
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -233,20 +233,26 @@ using mozilla::Swap;
 using JS::AutoGCRooter;
 
 /* Perform a Full GC every 20 seconds if MaybeGC is called */
 static const uint64_t GC_IDLE_FULL_SPAN = 20 * 1000 * 1000;
 
 /* Increase the IGC marking slice time if we are in highFrequencyGC mode. */
 static const int IGC_MARK_SLICE_MULTIPLIER = 2;
 
+#ifdef JSGC_GENERATIONAL
+static const unsigned MIN_EMPTY_CHUNK_COUNT = 1;
+#else
+static const unsigned MIN_EMPTY_CHUNK_COUNT = 0;
+#endif
+
 #if defined(ANDROID) || defined(MOZ_B2G)
-static const int MAX_EMPTY_CHUNK_COUNT = 2;
+static const unsigned MAX_EMPTY_CHUNK_COUNT = 2;
 #else
-static const int MAX_EMPTY_CHUNK_COUNT = 30;
+static const unsigned MAX_EMPTY_CHUNK_COUNT = 30;
 #endif
 
 /* This array should be const, but that doesn't link right under GCC. */
 const AllocKind gc::slotsToThingKind[] = {
     /* 0 */  FINALIZE_OBJECT0,  FINALIZE_OBJECT2,  FINALIZE_OBJECT2,  FINALIZE_OBJECT4,
     /* 4 */  FINALIZE_OBJECT4,  FINALIZE_OBJECT8,  FINALIZE_OBJECT8,  FINALIZE_OBJECT8,
     /* 8 */  FINALIZE_OBJECT8,  FINALIZE_OBJECT12, FINALIZE_OBJECT12, FINALIZE_OBJECT12,
     /* 12 */ FINALIZE_OBJECT12, FINALIZE_OBJECT16, FINALIZE_OBJECT16, FINALIZE_OBJECT16,
@@ -647,48 +653,50 @@ ChunkPool::put(Chunk *chunk)
     chunk->info.age = 0;
     chunk->info.next = emptyChunkListHead;
     emptyChunkListHead = chunk;
     emptyCount++;
 }
 
 /* Must be called either during the GC or with the GC lock taken. */
 Chunk *
-ChunkPool::expire(JSRuntime *rt, bool releaseAll)
+ChunkPool::expire(JSRuntime *rt, bool shrinkBuffers, bool releaseAll)
 {
     JS_ASSERT(this == &rt->gc.chunkPool);
 
     /*
      * Return old empty chunks to the system while preserving the order of
      * other chunks in the list. This way, if the GC runs several times
      * without emptying the list, the older chunks will stay at the tail
      * and are more likely to reach the max age.
      */
     Chunk *freeList = nullptr;
-    int freeChunkCount = 0;
+    unsigned freeChunkCount = 0;
     for (Chunk **chunkp = &emptyChunkListHead; *chunkp; ) {
         JS_ASSERT(emptyCount);
         Chunk *chunk = *chunkp;
         JS_ASSERT(chunk->unused());
         JS_ASSERT(!rt->gc.chunkSet.has(chunk));
-        JS_ASSERT(chunk->info.age <= MAX_EMPTY_CHUNK_AGE);
-        if (releaseAll || chunk->info.age == MAX_EMPTY_CHUNK_AGE ||
-            freeChunkCount++ > MAX_EMPTY_CHUNK_COUNT)
+        if (releaseAll || freeChunkCount >= MAX_EMPTY_CHUNK_COUNT ||
+            (freeChunkCount >= MIN_EMPTY_CHUNK_COUNT &&
+             (shrinkBuffers || chunk->info.age == MAX_EMPTY_CHUNK_AGE)))
         {
             *chunkp = chunk->info.next;
             --emptyCount;
             chunk->prepareToBeFreed(rt);
             chunk->info.next = freeList;
             freeList = chunk;
         } else {
             /* Keep the chunk but increase its age. */
+            ++freeChunkCount;
             ++chunk->info.age;
             chunkp = &chunk->info.next;
         }
     }
+    JS_ASSERT_IF(shrinkBuffers, emptyCount <= MIN_EMPTY_CHUNK_COUNT);
     JS_ASSERT_IF(releaseAll, !emptyCount);
     return freeList;
 }
 
 static void
 FreeChunkList(JSRuntime *rt, Chunk *chunkListHead)
 {
     while (Chunk *chunk = chunkListHead) {
@@ -696,17 +704,17 @@ FreeChunkList(JSRuntime *rt, Chunk *chun
         chunkListHead = chunk->info.next;
         FreeChunk(rt, chunk);
     }
 }
 
 void
 ChunkPool::expireAndFree(JSRuntime *rt, bool releaseAll)
 {
-    FreeChunkList(rt, expire(rt, releaseAll));
+    FreeChunkList(rt, expire(rt, true, releaseAll));
 }
 
 /* static */ Chunk *
 Chunk::allocate(JSRuntime *rt)
 {
     Chunk *chunk = AllocChunk(rt);
     if (!chunk)
         return nullptr;
@@ -959,17 +967,17 @@ inline bool
 GCRuntime::wantBackgroundAllocation() const
 {
     /*
      * To minimize memory waste we do not want to run the background chunk
      * allocation if we have empty chunks or when the runtime needs just few
      * of them.
      */
     return helperState.canBackgroundAllocate() &&
-           chunkPool.getEmptyCount() == 0 &&
+           chunkPool.getEmptyCount() < MIN_EMPTY_CHUNK_COUNT &&
            chunkSet.count() >= 4;
 }
 
 class js::gc::AutoMaybeStartBackgroundAllocation
 {
   private:
     JSRuntime *runtime;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
@@ -2375,17 +2383,17 @@ DecommitArenas(JSRuntime *rt)
     DecommitArenasFromAvailableList(rt, &rt->gc.systemAvailableChunkListHead);
     DecommitArenasFromAvailableList(rt, &rt->gc.userAvailableChunkListHead);
 }
 
 /* Must be called with the GC lock taken. */
 static void
 ExpireChunksAndArenas(JSRuntime *rt, bool shouldShrink)
 {
-    if (Chunk *toFree = rt->gc.chunkPool.expire(rt, shouldShrink)) {
+    if (Chunk *toFree = rt->gc.chunkPool.expire(rt, shouldShrink, false)) {
         AutoUnlockGC unlock(rt);
         FreeChunkList(rt, toFree);
     }
 
     if (shouldShrink)
         DecommitArenas(rt);
 }
 
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -82,17 +82,17 @@ class ChunkPool {
 
     /* Must be called either during the GC or with the GC lock taken. */
     inline void put(Chunk *chunk);
 
     /*
      * Return the list of chunks that can be released outside the GC lock.
      * Must be called either during the GC or with the GC lock taken.
      */
-    Chunk *expire(JSRuntime *rt, bool releaseAll);
+    Chunk *expire(JSRuntime *rt, bool shrinkBuffers, bool releaseAll);
 
     /* Must be called with the GC lock taken. */
     void expireAndFree(JSRuntime *rt, bool releaseAll);
 };
 
 static inline JSGCTraceKind
 MapAllocToTraceKind(AllocKind kind)
 {