bug 702251 - free GC chunks in background outside the GC lock. r=wmccloskey

--HG--
extra : rebase_source : 40aaadef1af1a2cefcfeefd178096e5c51e32873
This commit is contained in:
Igor Bukanov 2011-12-25 02:45:22 +01:00
Родитель a6373ebfc3
Коммит 1375512ed1
2 изменённых файлов: 83 добавлений и 78 удалений

Просмотреть файл

@ -474,42 +474,16 @@ ChunkPool::get(JSRuntime *rt)
/* Must be called either during the GC or with the GC lock taken. */
inline void
ChunkPool::put(JSRuntime *rt, Chunk *chunk)
ChunkPool::put(Chunk *chunk)
{
JS_ASSERT(this == &rt->gcChunkPool);
size_t initialAge = 0;
#ifdef JS_THREADSAFE
/*
* When we have not yet started the background finalization, we must keep
* empty chunks until we are done with all the sweeping and finalization
* that cannot be done in the background even if shouldShrink() is true.
* This way we can safely call IsAboutToBeFinalized and Cell::isMarked for
* finalized GC things in empty chunks. So we only release the chunk if we
* are called from the background thread.
*/
if (rt->gcHelperThread.sweeping()) {
if (rt->gcHelperThread.shouldShrink()) {
Chunk::release(rt, chunk);
return;
}
/*
* Set the age to one as we expire chunks early during the background
* sweep so this chunk already survived one GC cycle.
*/
initialAge = 1;
}
#endif
chunk->info.age = initialAge;
chunk->info.age = 0;
chunk->info.next = emptyChunkListHead;
emptyChunkListHead = chunk;
emptyCount++;
}
/* Must be called either during the GC or with the GC lock taken. */
void
Chunk *
ChunkPool::expire(JSRuntime *rt, bool releaseAll)
{
JS_ASSERT(this == &rt->gcChunkPool);
@ -520,6 +494,7 @@ ChunkPool::expire(JSRuntime *rt, bool releaseAll)
* without emptying the list, the older chunks will stay at the tail
* and are more likely to reach the max age.
*/
Chunk *freeList = NULL;
for (Chunk **chunkp = &emptyChunkListHead; *chunkp; ) {
JS_ASSERT(emptyCount);
Chunk *chunk = *chunkp;
@ -529,7 +504,9 @@ ChunkPool::expire(JSRuntime *rt, bool releaseAll)
if (releaseAll || chunk->info.age == MAX_EMPTY_CHUNK_AGE) {
*chunkp = chunk->info.next;
--emptyCount;
Chunk::release(rt, chunk);
chunk->prepareToBeFreed(rt);
chunk->info.next = freeList;
freeList = chunk;
} else {
/* Keep the chunk but increase its age. */
++chunk->info.age;
@ -537,6 +514,7 @@ ChunkPool::expire(JSRuntime *rt, bool releaseAll)
}
}
JS_ASSERT_IF(releaseAll, !emptyCount);
return freeList;
}
JS_FRIEND_API(int64_t)
@ -571,12 +549,36 @@ Chunk::allocate(JSRuntime *rt)
Chunk::release(JSRuntime *rt, Chunk *chunk)
{
JS_ASSERT(chunk);
JS_ASSERT(rt->gcNumArenasFreeCommitted >= chunk->info.numArenasFreeCommitted);
rt->gcNumArenasFreeCommitted -= chunk->info.numArenasFreeCommitted;
rt->gcStats.count(gcstats::STAT_DESTROY_CHUNK);
chunk->prepareToBeFreed(rt);
FreeChunk(chunk);
}
static void
FreeChunkList(Chunk *chunkListHead)
{
while (Chunk *chunk = chunkListHead) {
JS_ASSERT(!chunk->info.numArenasFreeCommitted);
chunkListHead = chunk->info.next;
FreeChunk(chunk);
}
}
inline void
Chunk::prepareToBeFreed(JSRuntime *rt)
{
JS_ASSERT(rt->gcNumArenasFreeCommitted >= info.numArenasFreeCommitted);
rt->gcNumArenasFreeCommitted -= info.numArenasFreeCommitted;
rt->gcStats.count(gcstats::STAT_DESTROY_CHUNK);
#ifdef DEBUG
/*
* Let FreeChunkList detect a missing prepareToBeFreed call before it
* frees chunk.
*/
info.numArenasFreeCommitted = 0;
#endif
}
void
Chunk::init()
{
@ -765,7 +767,7 @@ Chunk::releaseArena(ArenaHeader *aheader)
} else {
rt->gcChunkSet.remove(this);
removeFromAvailableList();
rt->gcChunkPool.put(rt, this);
rt->gcChunkPool.put(this);
}
}
@ -1173,7 +1175,7 @@ js_FinishGC(JSRuntime *rt)
* Finish the pool after the background thread stops in case it was doing
* the background sweeping.
*/
rt->gcChunkPool.expire(rt, true);
FreeChunkList(rt->gcChunkPool.expire(rt, true));
#ifdef DEBUG
if (!rt->gcRootsHash.empty())
@ -2196,12 +2198,8 @@ MaybeGC(JSContext *cx)
}
}
} /* namespace js */
#ifdef JS_THREADSAFE
namespace js {
bool
GCHelperThread::init()
{
@ -2287,7 +2285,7 @@ GCHelperThread::threadLoop()
break;
JS_ASSERT(chunk->info.numArenasFreeCommitted == ArenasPerChunk);
rt->gcNumArenasFreeCommitted += ArenasPerChunk;
rt->gcChunkPool.put(rt, chunk);
rt->gcChunkPool.put(chunk);
} while (state == ALLOCATING && rt->gcChunkPool.wantBackgroundAllocation(rt));
if (state == ALLOCATING)
state = IDLE;
@ -2376,42 +2374,43 @@ GCHelperThread::doSweep()
{
JS_ASSERT(context);
/*
* Expire the chunks released during the GC so they will be available to
* the rest of the system immediately.
*/
rt->gcChunkPool.expire(rt, shouldShrink());
{
AutoUnlockGC unlock(rt);
AutoUnlockGC unlock(rt);
/*
* We must finalize in the insert order, see comments in
* finalizeObjects.
*/
for (ArenaHeader **i = finalizeVector.begin(); i != finalizeVector.end(); ++i)
ArenaLists::backgroundFinalize(context, *i);
finalizeVector.resize(0);
/*
* We must finalize in the insert order, see comments in
* finalizeObjects.
*/
for (ArenaHeader **i = finalizeVector.begin(); i != finalizeVector.end(); ++i)
ArenaLists::backgroundFinalize(context, *i);
finalizeVector.resize(0);
context = NULL;
context = NULL;
if (freeCursor) {
void **array = freeCursorEnd - FREE_ARRAY_LENGTH;
freeElementsAndArray(array, freeCursor);
freeCursor = freeCursorEnd = NULL;
} else {
JS_ASSERT(!freeCursorEnd);
if (freeCursor) {
void **array = freeCursorEnd - FREE_ARRAY_LENGTH;
freeElementsAndArray(array, freeCursor);
freeCursor = freeCursorEnd = NULL;
} else {
JS_ASSERT(!freeCursorEnd);
}
for (void ***iter = freeVector.begin(); iter != freeVector.end(); ++iter) {
void **array = *iter;
freeElementsAndArray(array, array + FREE_ARRAY_LENGTH);
}
freeVector.resize(0);
}
for (void ***iter = freeVector.begin(); iter != freeVector.end(); ++iter) {
void **array = *iter;
freeElementsAndArray(array, array + FREE_ARRAY_LENGTH);
if (Chunk *toFree = rt->gcChunkPool.expire(rt, shouldShrink())) {
AutoUnlockGC unlock(rt);
FreeChunkList(toFree);
}
freeVector.resize(0);
}
} /* namespace js */
#endif /* JS_THREADSAFE */
} /* namespace js */
static bool
ReleaseObservedTypes(JSContext *cx)
{
@ -2657,7 +2656,7 @@ SweepPhase(JSContext *cx, GCMarker *gcmarker, JSGCInvocationKind gckind)
* use IsAboutToBeFinalized().
* This is done on the GCHelperThread if JS_THREADSAFE is defined.
*/
rt->gcChunkPool.expire(rt, gckind == GC_SHRINK);
FreeChunkList(rt->gcChunkPool.expire(rt, gckind == GC_SHRINK));
#endif
if (gckind == GC_SHRINK)

Просмотреть файл

@ -598,23 +598,23 @@ struct ChunkInfo {
*
* For the mark bitmap, we know that each arena will use a fixed number of full
* bytes: ArenaBitmapBytes. The full size of the header data is this number
* multiplied by the eventual number of arenas we have in the header. We,
* multiplied by the eventual number of arenas we have in the header. We,
* conceptually, distribute this header data among the individual arenas and do
* not include it in the header. This way we do not have to worry about its
* not include it in the header. This way we do not have to worry about its
* variable size: it gets attached to the variable number we are computing.
*
* For the decommitted arena bitmap, we only have 1 bit per arena, so this
* technique will not work. Instead, we observe that we do not have enough
* header info to fill 8 full arenas: it is currently 4 on 64bit, less on
* header info to fill 8 full arenas: it is currently 4 on 64bit, less on
* 32bit. Thus, with current numbers, we need 64 bytes for decommittedArenas.
* This will not become 63 bytes unless we double the data required in the
* header. Therefore, we just compute the number of bytes required to track
* This will not become 63 bytes unless we double the data required in the
* header. Therefore, we just compute the number of bytes required to track
* every possible arena and do not worry about slop bits, since there are too
* few to usefully allocate.
*
* To actually compute the number of arenas we can allocate in a chunk, we
* divide the amount of available space less the header info (not including
* the mark bitmap which is distributed into the arena size) by the size of
* the mark bitmap which is distributed into the arena size) by the size of
* the arena (with the mark bitmap bytes it uses).
*/
const size_t BytesPerArenaWithHeader = ArenaSize + ArenaBitmapBytes;
@ -750,6 +750,9 @@ struct Chunk {
/* Must be called with the GC lock taken. */
static inline void release(JSRuntime *rt, Chunk *chunk);
/* Must be called with the GC lock taken. */
inline void prepareToBeFreed(JSRuntime *rt);
private:
inline void init();
@ -782,10 +785,13 @@ class ChunkPool {
inline Chunk *get(JSRuntime *rt);
/* Must be called either during the GC or with the GC lock taken. */
inline void put(JSRuntime *rt, Chunk *chunk);
inline void put(Chunk *chunk);
/* Must be called either during the GC or with the GC lock taken. */
void expire(JSRuntime *rt, bool releaseAll);
/*
* 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);
/* Must be called either during the GC or with the GC lock taken. */
JS_FRIEND_API(int64_t) countCleanDecommittedArenas(JSRuntime *rt);
@ -1720,7 +1726,7 @@ struct GCMarker : public JSTracer {
void drainMarkStack();
inline void processMarkStackTop();
void pushObject(JSObject *obj) {
pushTaggedPtr(ObjectTag, obj);
}