When idle the GC holds on to unused chunks indefinitely (bug 631733, r=brendan, a=blocker).
authorAndreas Gal <gal@mozilla.com>
Sat, 19 Feb 2011 22:59:49 -0800
changeset 62955 f2fa1da62fe3ca7613c02a23d19e760abc2e1ac7
parent 62954 3a7bba27f6c3e4253af081845586b1384f7db3d0
child 62956 8ba032375d53eda14e67efd8e86aeef5309ec25d
push idunknown
push userunknown
push dateunknown
reviewersbrendan, blocker
bugs631733
milestone2.0b12pre
When idle the GC holds on to unused chunks indefinitely (bug 631733, r=brendan, a=blocker).
dom/base/nsJSEnvironment.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsgc.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -3479,16 +3479,22 @@ DOMGCCallback(JSContext *cx, JSGCStatus 
         nsJSContext::KillCCTimer();
       }
     } else {
       // If this was a full GC, poke the CC to run soon.
       if (!cx->runtime->gcTriggerCompartment) {
         nsJSContext::PokeCC();
       }
     }
+
+    // If we didn't end up scheduling a GC, and there are unused
+    // chunks waiting to expire, make sure we will GC again soon.
+    if (!sGCTimer && JS_GetGCParameter(cx->runtime, JSGC_UNUSED_CHUNKS) > 0) {
+      nsJSContext::PokeGC();
+    }
   }
 
   JSBool result = gOldJSGCCallback ? gOldJSGCCallback(cx, status) : JS_TRUE;
 
   if (status == JSGC_BEGIN && !NS_IsMainThread())
     return JS_FALSE;
 
   return result;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2639,16 +2639,18 @@ JS_GetGCParameter(JSRuntime *rt, JSGCPar
       case JSGC_STACKPOOL_LIFESPAN:
         return rt->gcEmptyArenaPoolLifespan;
       case JSGC_TRIGGER_FACTOR:
         return rt->gcTriggerFactor;
       case JSGC_BYTES:
         return rt->gcBytes;
       case JSGC_MODE:
         return uint32(rt->gcMode);
+      case JSGC_UNUSED_CHUNKS:
+        return uint32(rt->gcChunksWaitingToExpire);
       default:
         JS_ASSERT(key == JSGC_NUMBER);
         return rt->gcNumber;
     }
 }
 
 JS_PUBLIC_API(void)
 JS_SetGCParameterForThread(JSContext *cx, JSGCParamKey key, uint32 value)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1780,17 +1780,20 @@ typedef enum JSGCParamKey {
 
     /* Number of times when GC was invoked. */
     JSGC_NUMBER = 5,
 
     /* Max size of the code cache in bytes. */
     JSGC_MAX_CODE_CACHE_BYTES = 6,
 
     /* Select GC mode. */
-    JSGC_MODE = 7
+    JSGC_MODE = 7,
+
+    /* Number of GC chunks waiting to expire. */
+    JSGC_UNUSED_CHUNKS = 8
 } JSGCParamKey;
 
 typedef enum JSGCMode {
     /* Perform only global GCs. */
     JSGC_MODE_GLOBAL = 0,
 
     /* Perform per-compartment GCs until too much garbage has accumulated. */
     JSGC_MODE_COMPARTMENT = 1
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1042,16 +1042,17 @@ struct JSRuntime {
     js::RootedValueMap  gcRootsHash;
     js::GCLocks         gcLocksHash;
     jsrefcount          gcKeepAtoms;
     size_t              gcBytes;
     size_t              gcTriggerBytes;
     size_t              gcLastBytes;
     size_t              gcMaxBytes;
     size_t              gcMaxMallocBytes;
+    size_t              gcChunksWaitingToExpire;
     uint32              gcEmptyArenaPoolLifespan;
     uint32              gcNumber;
     js::GCMarker        *gcMarkingTracer;
     uint32              gcTriggerFactor;
     int64               gcJitReleaseTime;
     JSGCMode            gcMode;
     volatile bool       gcIsNeeded;
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -355,24 +355,16 @@ Chunk::releaseArena(Arena<T> *arena)
     comp->gcBytes -= sizeof(Arena<T>);
     info.emptyArenaLists.insert((Arena<Cell> *)arena);
     arena->header()->isUsed = false;
     ++info.numFree;
     if (unused())
         info.age = 0;
 }
 
-bool
-Chunk::expire()
-{
-    if (!unused())
-        return false;
-    return info.age++ > MaxAge;
-}
-
 JSRuntime *
 Chunk::getRuntime()
 {
     return info.runtime;
 }
 
 inline jsuword
 GetGCChunk(JSRuntime *rt)
@@ -451,26 +443,32 @@ PickChunk(JSRuntime *rt)
     chunk->init(rt);
 
     return chunk;
 }
 
 static void
 ExpireGCChunks(JSRuntime *rt)
 {
+    static const size_t MaxAge = 3;
+
     /* Remove unused chunks. */
     AutoLockGC lock(rt);
 
+    rt->gcChunksWaitingToExpire = 0;
     for (GCChunkSet::Enum e(rt->gcChunkSet); !e.empty(); e.popFront()) {
         Chunk *chunk = e.front();
         JS_ASSERT(chunk->info.runtime == rt);
-        if (chunk->expire()) {
-            e.removeFront();
-            ReleaseGCChunk(rt, chunk);
-            continue;
+        if (chunk->unused()) {
+            if (chunk->info.age++ > MaxAge) {
+                e.removeFront();
+                ReleaseGCChunk(rt, chunk);
+                continue;
+            }
+            rt->gcChunksWaitingToExpire++;
         }
     }
 }
 
 template <typename T>
 static Arena<T> *
 AllocateArena(JSContext *cx, unsigned thingKind)
 {
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -335,17 +335,16 @@ struct ChunkInfo {
 
 /* Chunks contain arenas and associated data structures (mark bitmap, delayed marking state). */
 struct Chunk {
     static const size_t BytesPerArena = sizeof(Arena<FreeCell>) +
                                         sizeof(ArenaBitmap) +
                                         sizeof(MarkingDelay);
 
     static const size_t ArenasPerChunk = (GC_CHUNK_SIZE - sizeof(ChunkInfo)) / BytesPerArena;
-    static const size_t MaxAge = 3;
 
     Arena<FreeCell> arenas[ArenasPerChunk];
     ArenaBitmap     bitmaps[ArenasPerChunk];
     MarkingDelay    markingDelay[ArenasPerChunk];
 
     ChunkInfo       info;
 
     void clearMarkBitmap();
@@ -357,17 +356,16 @@ struct Chunk {
 
     template <typename T>
     Arena<T> *allocateArena(JSCompartment *comp, unsigned thingKind);
 
     template <typename T>
     void releaseArena(Arena<T> *a);
 
     JSRuntime *getRuntime();
-    bool expire();
 };
 JS_STATIC_ASSERT(sizeof(Chunk) <= GC_CHUNK_SIZE);
 JS_STATIC_ASSERT(sizeof(Chunk) + Chunk::BytesPerArena > GC_CHUNK_SIZE);
 
 Arena<Cell> *
 Cell::arena() const
 {
     uintptr_t addr = uintptr_t(this);