Bug 341821: Running close hooks of generator objects outside GC locks. r=brendan sr=mrbkap

This commit is contained in:
igor.bukanov%gmail.com 2006-08-06 09:23:26 +00:00
parent a36f9ead4e
commit 765d801e0e
12 changed files with 391 additions and 275 deletions

View File

@ -1926,12 +1926,25 @@ JS_MarkGCThing(JSContext *cx, void *thing, const char *name, void *arg)
JS_PUBLIC_API(void)
JS_GC(JSContext *cx)
{
#if JS_HAS_GENERATORS
/* Run previously scheduled but delayed close hooks. */
js_RunCloseHooks(cx);
#endif
/* Don't nuke active arenas if executing or compiling. */
if (cx->stackPool.current == &cx->stackPool.first)
JS_FinishArenaPool(&cx->stackPool);
if (cx->tempPool.current == &cx->tempPool.first)
JS_FinishArenaPool(&cx->tempPool);
js_ForceGC(cx, 0);
js_GC(cx, GC_NORMAL);
#if JS_HAS_GENERATORS
/*
* Run close hooks for objects that became unreachable after the last GC.
*/
js_RunCloseHooks(cx);
#endif
JS_ArenaFinish();
}
JS_PUBLIC_API(void)
@ -1998,6 +2011,12 @@ JS_MaybeGC(JSContext *cx)
rt->gcMallocBytes >= rt->gcMaxMallocBytes) {
JS_GC(cx);
}
#if JS_HAS_GENERATORS
else {
/* Run scheduled but not yet executed close hooks. */
js_RunCloseHooks(cx);
}
#endif
#endif
}

View File

@ -417,7 +417,7 @@ js_FinishAtomState(JSAtomState *state)
}
typedef struct MarkArgs {
uintN gcflags;
JSBool keepAtoms;
JSGCThingMarker mark;
void *data;
} MarkArgs;
@ -431,8 +431,7 @@ js_atom_marker(JSHashEntry *he, intN i, void *arg)
atom = (JSAtom *)he;
args = (MarkArgs *)arg;
if ((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) ||
(args->gcflags & GC_KEEP_ATOMS)) {
if ((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) || args->keepAtoms) {
atom->flags |= ATOM_MARK;
key = ATOM_KEY(atom);
if (JSVAL_IS_GCTHING(key))
@ -442,14 +441,14 @@ js_atom_marker(JSHashEntry *he, intN i, void *arg)
}
void
js_MarkAtomState(JSAtomState *state, uintN gcflags, JSGCThingMarker mark,
js_MarkAtomState(JSAtomState *state, JSBool keepAtoms, JSGCThingMarker mark,
void *data)
{
MarkArgs args;
if (!state->table)
return;
args.gcflags = gcflags;
args.keepAtoms = keepAtoms;
args.mark = mark;
args.data = data;
JS_HashTableEnumerateEntries(state->table, js_atom_marker, &args);

View File

@ -343,7 +343,7 @@ typedef void
(*JSGCThingMarker)(void *thing, void *data);
extern void
js_MarkAtomState(JSAtomState *state, uintN gcflags, JSGCThingMarker mark,
js_MarkAtomState(JSAtomState *state, JSBool keepAtoms, JSGCThingMarker mark,
void *data);
extern void

View File

@ -358,12 +358,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
#endif
if (last) {
/* Always force, so we wait for any racing GC to finish. */
js_ForceGC(cx, GC_LAST_CONTEXT);
/* Iterate until no JSGC_END-status callback creates more garbage. */
while (rt->gcPoke)
js_GC(cx, GC_LAST_CONTEXT);
js_GC(cx, GC_LAST_CONTEXT);
/* Try to free atom state, now that no unrooted scripts survive. */
if (rt->atomState.liveAtoms == 0)
@ -386,14 +381,18 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
JS_UNLOCK_GC(rt);
} else {
if (mode == JSDCM_FORCE_GC)
js_ForceGC(cx, 0);
js_GC(cx, GC_NORMAL);
else if (mode == JSDCM_MAYBE_GC)
JS_MaybeGC(cx);
}
if (last || mode == JSDCM_FORCE_GC)
JS_ArenaFinish();
/* Free the stuff hanging off of cx. */
JS_FinishArenaPool(&cx->stackPool);
JS_FinishArenaPool(&cx->tempPool);
if (cx->lastMessage)
free(cx->lastMessage);

View File

@ -79,6 +79,11 @@ struct JSThread {
* locks on each JS_malloc.
*/
uint32 gcMallocBytes;
#if JS_HAS_GENERATORS
/* Flag indicating that the current thread is excuting close hooks. */
JSBool gcRunningCloseHooks;
#endif
};
extern void JS_DLL_CALLBACK
@ -146,8 +151,7 @@ struct JSRuntime {
*/
JSPackedBool gcPoke;
JSPackedBool gcRunning;
JSPackedBool gcClosePhase;
uint8 gcPadding;
uint16 gcPadding;
JSGCCallback gcCallback;
uint32 gcMallocBytes;
@ -169,18 +173,17 @@ struct JSRuntime {
*/
uint32 gcPrivateBytes;
/*
* Table for tracking objects of extended classes that have non-null close
* hooks, and need the GC to perform two-phase finalization.
*/
JSPtrTable gcCloseTable;
/*
* Table for tracking iterators to ensure that we close iterator's state
* before finalizing the iterable object.
*/
JSPtrTable gcIteratorTable;
#if JS_HAS_GENERATORS
/* Runtime state to support close hooks. */
JSGCCloseState gcCloseState;
#endif
#ifdef JS_GCMETER
JSGCStats gcStats;
#endif

View File

@ -61,6 +61,7 @@
#include "jscntxt.h"
#include "jsconfig.h"
#include "jsdbgapi.h"
#include "jsexn.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsinterp.h"
@ -248,16 +249,9 @@ typedef struct JSPtrTableInfo {
uint16 linearGrowthThreshold;
} JSPtrTableInfo;
#define GC_CLOSE_TABLE_MIN 4
#define GC_CLOSE_TABLE_LINEAR 1024
#define GC_ITERATOR_TABLE_MIN 4
#define GC_ITERATOR_TABLE_LINEAR 1024
static const JSPtrTableInfo closeTableInfo = {
GC_CLOSE_TABLE_MIN,
GC_CLOSE_TABLE_LINEAR
};
static const JSPtrTableInfo iteratorTableInfo = {
GC_ITERATOR_TABLE_MIN,
GC_ITERATOR_TABLE_LINEAR
@ -486,9 +480,6 @@ FinishGCArenaLists(JSRuntime *rt)
DestroyGCArena(rt, arenaList, &arenaList->last);
arenaList->freeList = NULL;
}
FreePtrTable(&rt->gcCloseTable, &closeTableInfo);
FreePtrTable(&rt->gcIteratorTable, &iteratorTableInfo);
}
uint8 *
@ -620,6 +611,10 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes)
* for default backward API compatibility.
*/
rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes;
#if JS_HAS_GENERATORS
rt->gcCloseState.todoTail = &rt->gcCloseState.todoHead;
#endif
return JS_TRUE;
}
@ -699,6 +694,11 @@ js_DumpGCStats(JSRuntime *rt, FILE *fp)
fprintf(fp, " thing arenas freed so far: %lu\n", ULSTAT(afree));
fprintf(fp, " stack segments scanned: %lu\n", ULSTAT(stackseg));
fprintf(fp, "stack segment slots scanned: %lu\n", ULSTAT(segslots));
fprintf(fp, "reachable closeable objects: %lu\n", ULSTAT(nclose));
fprintf(fp, " max reachable closeable: %lu\n", ULSTAT(maxnclose));
fprintf(fp, " scheduled close hooks: %lu\n",
UL(rt->gcCloseState.todoCount));
fprintf(fp, " max scheduled close hooks: %lu\n", ULSTAT(maxcloselater));
#undef UL
#undef US
@ -733,6 +733,15 @@ js_FinishGC(JSRuntime *rt)
#ifdef JS_GCMETER
js_DumpGCStats(rt, stdout);
#endif
FreePtrTable(&rt->gcIteratorTable, &iteratorTableInfo);
#if JS_HAS_GENERATORS
rt->gcCloseState.reachableList = NULL;
METER(rt->gcStats.nclose = 0);
rt->gcCloseState.todoHead = NULL;
rt->gcCloseState.todoTail = &rt->gcCloseState.todoHead;
rt->gcCloseState.todoCount = 0;
#endif
FinishGCArenaLists(rt);
if (rt->gcRootsHash.ops) {
@ -846,7 +855,7 @@ js_RegisterCloseableIterator(JSContext *cx, JSObject *obj)
JSBool ok;
rt = cx->runtime;
JS_ASSERT(!rt->gcRunning || rt->gcClosePhase);
JS_ASSERT(!rt->gcRunning);
JS_LOCK_GC(rt);
ok = AddToPtrTable(cx, &rt->gcIteratorTable, &iteratorTableInfo, obj);
@ -877,157 +886,207 @@ CloseIteratorStates(JSContext *cx)
ShrinkPtrTable(&rt->gcIteratorTable, &iteratorTableInfo, newCount);
}
JSBool
js_RegisterGeneratorObject(JSContext *cx, JSObject *obj)
#if JS_HAS_GENERATORS
void
js_RegisterGeneratorObject(JSContext *cx, JSGenerator *gen)
{
JSRuntime *rt;
JSBool ok;
JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_GeneratorClass);
/*
* Return early without doing anything if shutting down, to prevent a bad
* close hook from ilooping the GC. This could result in shutdown leaks,
* so printf in DEBUG builds.
*/
rt = cx->runtime;
JS_ASSERT(!rt->gcRunning || rt->gcClosePhase);
if (rt->state == JSRTS_LANDING)
return JS_TRUE;
JS_ASSERT(!rt->gcRunning);
JS_ASSERT(rt->state != JSRTS_LANDING);
JS_LOCK_GC(rt);
ok = AddToPtrTable(cx, &rt->gcCloseTable, &closeTableInfo, obj);
gen->next = rt->gcCloseState.reachableList;
rt->gcCloseState.reachableList = gen;
METER(rt->gcStats.nclose++);
METER(rt->gcStats.maxnclose = JS_MAX(rt->gcStats.maxnclose,
rt->gcStats.nclose));
JS_UNLOCK_GC(rt);
return ok;
}
static void
MarkCloseList(JSContext *cx, JSGenerator *gen)
{
for (; gen; gen = gen->next)
GC_MARK(cx, gen->obj, "close list generator");
}
/*
* Struct to define object to close after the finalization phase of GC:
* GC executes the close hooks for elements of JSRuntime.gcCloseTable.array
* with indexes from the [startIndex, startIndex + count) range.
* Find all unreachable generators and move them to the todo queue from
* rt->gcCloseState.reachableList to execute thier close hooks after the GC
* cycle completes. To ensure liveness during the sweep phase we mark all
* generators we are going to close later.
*/
typedef struct JSObjectsToClose {
size_t count;
size_t startIndex;
#ifdef DEBUG
JSPtrTable tableSnapshot;
#endif
} JSObjectsToClose;
static void
ScanDelayedChildren(JSContext *cx);
static void
FindAndMarkObjectsToClose(JSContext *cx, JSObjectsToClose *toClose)
FindAndMarkObjectsToClose(JSContext *cx, JSGCInvocationKind gckind)
{
JSRuntime *rt;
void **array;
JSObject *obj;
size_t count, index, pivot;
JSGenerator *todo, **genp, *gen;
/*
* Find unmarked objects reachable from rt->gcCloseTable and set them
* aside at the end of the table. These are the objects to close.
*/
rt = cx->runtime;
array = rt->gcCloseTable.array;
count = pivot = rt->gcCloseTable.count;
index = 0;
while (index != pivot) {
obj = (JSObject *)array[index];
if (*js_GetGCThingFlags(obj) & GCF_MARK) {
++index;
todo = NULL;
genp = &rt->gcCloseState.reachableList;
while ((gen = *genp) != NULL) {
if (*js_GetGCThingFlags(gen->obj) & GCF_MARK) {
genp = &gen->next;
} else {
array[index] = array[--pivot];
array[pivot] = obj;
*genp = gen->next;
gen->next = NULL;
*rt->gcCloseState.todoTail = gen;
rt->gcCloseState.todoTail = &gen->next;
rt->gcCloseState.todoCount++;
if (!todo)
todo = gen;
METER(JS_ASSERT(rt->gcStats.nclose));
METER(rt->gcStats.nclose--);
METER(rt->gcStats.maxcloselater
= JS_MAX(rt->gcStats.maxcloselater,
rt->gcCloseState.todoCount));
}
}
if (pivot == count) {
/* Skip the close phase when threre are no objects to close. */
toClose->count = 0;
return;
if (gckind == GC_LAST_CONTEXT) {
/*
* Remove scheduled hooks on shutdown as it is too late to run them:
* we do not allow execution of arbitrary scripts at this point.
*/
if (rt->gcCloseState.todoCount != 0) {
JS_ASSERT(rt->gcCloseState.todoHead);
JS_ASSERT(rt->gcCloseState.todoTail != &rt->gcCloseState.todoHead);
rt->gcCloseState.todoHead = NULL;
rt->gcCloseState.todoTail = &rt->gcCloseState.todoHead;
rt->gcCloseState.todoCount = 0;
}
} else {
/*
* Mark just found unreachable generators *after* we scan the global
* list so a generator that refers to other unreachable generators
* cannot keep them on gcCloseState.reachableList.
*/
MarkCloseList(cx, todo);
}
}
toClose->count = count - pivot;
toClose->startIndex = pivot;
#ifdef DEBUG
toClose->tableSnapshot = rt->gcCloseTable;
#ifdef JS_THREADSAFE
# define GC_RUNNING_CLOSE_HOOKS_PTR(cx) \
(&(cx)->thread->gcRunningCloseHooks)
#else
# define GC_RUNNING_CLOSE_HOOKS_PTR(cx) \
(&(cx)->runtime->gcCloseState.runningCloseHook)
#endif
/*
* First half of the close phase: loop over the objects that we set aside
* in the close table and mark them to protect them against finalization
* during sweeping phase.
*/
index = pivot;
do {
GC_MARK(cx, array[index], "close-phase object");
} while (++index != count);
/*
* Mark children of things that caused too deep recursion during the
* just-completed marking half of the close phase.
*/
ScanDelayedChildren(cx);
}
static void
ExecuteCloseHooks(JSContext *cx, const JSObjectsToClose *toClose)
JSBool
js_RunCloseHooks(JSContext *cx)
{
JSRuntime *rt;
JSTempValueRooter tvr;
JSStackFrame *fp;
uint32 index, endIndex;
JSObject *obj;
void **array;
size_t todoLimit = 0; /* initialized to quell GCC warnings */
JSGenerator *gen;
JSBool ok;
rt = cx->runtime;
JS_ASSERT(toClose->count > 0);
/* Close table manupulations are not allowed during the marking phase. */
JS_ASSERT(memcmp(&toClose->tableSnapshot, &rt->gcCloseTable,
sizeof toClose->tableSnapshot) == 0);
/*
* Execute the close hooks. Temporarily set aside cx->fp here to prevent
* the close hooks from running on the GC's interpreter stack.
* It is OK to access todoCount outside the lock here. When many threads
* update the todo list, accessing some older value of todoCount in the
* worst case just delays the excution of close hooks.
*/
if (rt->gcCloseState.todoCount == 0)
return JS_TRUE;
/*
* To prevent an infinite loop when a close hook creats more objects with
* close hooks and then triggers GC we ignore recursive invocations of
* js_RunCloseHooks and limit number of hooks to execute to the initial
* size of the list.
*/
if (*GC_RUNNING_CLOSE_HOOKS_PTR(cx))
return JS_TRUE;
*GC_RUNNING_CLOSE_HOOKS_PTR(cx) = JS_TRUE;
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
/*
* Set aside cx->fp since we do not want a close hook using caller or
* other means to backtrace into whatever stack might be active when
* running the hook. We store the current frame on the dormant list to
* protect against GC that the hook can trigger.
*/
rt->gcClosePhase = JS_TRUE;
fp = cx->fp;
if (fp) {
JS_ASSERT(!fp->dormantNext);
fp->dormantNext = cx->dormantFrameChain;
cx->dormantFrameChain = fp;
}
cx->fp = NULL;
index = toClose->startIndex;
endIndex = index + toClose->count;
do {
/*
* Reload rt->gcCloseTable.array because close hooks may create
* new objects that have close hooks and reallocate the table.
*/
obj = (JSObject *)rt->gcCloseTable.array[index];
gen = NULL;
ok = JS_TRUE;
for (;;) {
JS_LOCK_GC(rt);
JS_ASSERT(rt->gcCloseState.todoCount > 0 || !rt->gcCloseState.todoHead);
JS_ASSERT(rt->gcCloseState.todoCount == 0 || rt->gcCloseState.todoHead);
if (!gen) {
/* First iteration, init the limit from inside the lock. */
todoLimit = rt->gcCloseState.todoCount;
}
if (todoLimit == 0) {
gen = NULL;
} else if ((gen = rt->gcCloseState.todoHead) != NULL) {
JS_ASSERT(gen->obj);
/*
* Ignore errors until after we call the close method, then force
* prompt error reporting, since GC is infallible.
*/
js_CloseGeneratorObject(cx, obj);
if (cx->throwing && !js_ReportUncaughtException(cx))
JS_ClearPendingException(cx);
} while (++index != endIndex);
/* Root obj before unlinking the gen and executing the hook. */
tvr.u.value = OBJECT_TO_JSVAL(gen->obj);
if (!gen->next)
rt->gcCloseState.todoTail = &rt->gcCloseState.todoHead;
rt->gcCloseState.todoHead = gen->next;
rt->gcCloseState.todoCount--;
todoLimit--;
rt->gcPoke = JS_TRUE;
#ifdef DEBUG
gen->next = NULL;
#endif
}
JS_UNLOCK_GC(rt);
if (!gen)
break;
ok = js_CloseGeneratorObject(cx, gen);
if (cx->throwing) {
/*
* Report the exception thrown by the close hook and continue to
* execute the rest of the hooks.
*/
if (!js_ReportUncaughtException(cx))
JS_ClearPendingException(cx);
ok = JS_TRUE;
} else if (!ok) {
/*
* Assume this is a stop signal from the branch callback or other
* quit ASAP condition. Break execution until the next invocation
* of js_RunCloseHooks.
*/
break;
}
}
rt->gcClosePhase = JS_FALSE;
cx->fp = fp;
if (fp) {
JS_ASSERT(cx->dormantFrameChain == fp);
cx->dormantFrameChain = fp->dormantNext;
fp->dormantNext = NULL;
}
JS_POP_TEMP_ROOT(cx, &tvr);
*GC_RUNNING_CLOSE_HOOKS_PTR(cx) = JS_FALSE;
/*
* Move any added object pointers down over the span just
* processed, and update the table's count.
*/
array = rt->gcCloseTable.array;
memmove(array + toClose->startIndex, array + endIndex,
(rt->gcCloseTable.count - endIndex) * sizeof array[0]);
ShrinkPtrTable(&rt->gcCloseTable, &closeTableInfo,
rt->gcCloseTable.count - toClose->count);
return ok;
}
#endif /* JS_HAS_GENERATORS */
#if defined(DEBUG_brendan) || defined(DEBUG_timeless)
#define DEBUG_gchist
#endif
@ -1096,8 +1155,8 @@ js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
rt->gcMallocBytes += localMallocBytes;
}
#endif
JS_ASSERT(!rt->gcRunning || rt->gcClosePhase);
if (rt->gcRunning && !rt->gcClosePhase) {
JS_ASSERT(!rt->gcRunning);
if (rt->gcRunning) {
METER(rt->gcStats.finalfail++);
JS_UNLOCK_GC(rt);
return NULL;
@ -1114,7 +1173,7 @@ js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
arenaList = &rt->gcArenaList[flindex];
for (;;) {
if (doGC && !rt->gcClosePhase) {
if (doGC) {
/*
* Keep rt->gcLock across the call into js_GC so we don't starve
* and lose to racing threads who deplete the heap just after
@ -1123,7 +1182,7 @@ js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
* can happen on certain operating systems. For the gory details,
* see bug 162779 at https://bugzilla.mozilla.org/.
*/
if (!js_GC(cx, GC_KEEP_ATOMS | GC_LAST_DITCH)) {
if (!js_GC(cx, GC_LAST_DITCH)) {
/*
* js_GC ensures that GC is unlocked when the branch callback
* wants to stop execution, and in this case we do not report
@ -2235,20 +2294,6 @@ gc_lock_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg)
return JS_DHASH_NEXT;
}
void
js_ForceGC(JSContext *cx, uintN gcflags)
{
uintN i;
JS_ASSERT((gcflags & ~(GC_KEEP_ATOMS | GC_LAST_CONTEXT)) == 0);
for (i = 0; i < GCX_NTYPES; i++)
cx->newborn[i] = NULL;
cx->lastAtom = NULL;
cx->runtime->gcPoke = JS_TRUE;
js_GC(cx, gcflags);
JS_ArenaFinish();
}
#define GC_MARK_JSVALS(cx, len, vec, name) \
JS_BEGIN_MACRO \
jsval _v, *_vp, *_end; \
@ -2345,30 +2390,30 @@ js_MarkStackFrame(JSContext *cx, JSStackFrame *fp)
* Return false when the branch callback wants to stop exeution ASAP and true
* otherwise.
*
* When gcflags contains GC_LAST_DITCH, it indicates a call from js_NewGCThing
* with rt->gcLock already held. On return to js_NewGCThing the lock is kept
* when js_GC returns false and released when it returns true. This asymmetry
* helps avoid re-taking the lock just to release it immediately in
* js_NewGCThing when the branch callback called outside the lock cancels the
* allocation and js_GC returns false. See bug 341896.
* When gckind is GC_LAST_DITCH, it indicates a call from js_NewGCThing with
* rt->gcLock already held. On return to js_NewGCThing the lock is kept when
* js_GC returns false and released when it returns true. This asymmetry helps
* avoid re-taking the lock just to release it immediately in js_NewGCThing
* when the branch callback called outside the lock cancels the allocation and
* js_GC returns false. See bug 341896.
*/
JSBool
js_GC(JSContext *cx, uintN gcflags)
js_GC(JSContext *cx, JSGCInvocationKind gckind)
{
JSRuntime *rt;
JSBool keepAtoms;
uintN i, type;
JSContext *iter, *acx;
JSStackFrame *fp, *chain;
uintN i, type;
JSStackHeader *sh;
JSTempValueRooter *tvr;
JSObjectsToClose objectsToClose;
size_t nbytes, limit, offset;
JSGCArena *a, **ap;
uint8 flags, *flagp, *firstPage;
JSGCThing *thing, *freeList;
JSGCArenaList *arenaList;
GCFinalizeOp finalizer;
JSBool allClear, shouldRestart, checkBranchCallback;
JSBool allClear, checkBranchCallback;
#ifdef JS_THREADSAFE
uint32 requestDebit;
#endif
@ -2379,33 +2424,47 @@ js_GC(JSContext *cx, uintN gcflags)
JS_ASSERT(!JS_IS_RUNTIME_LOCKED(rt));
#endif
if (gckind == GC_LAST_DITCH) {
/* The last ditch GC preserves all atoms and cx->newborn. */
keepAtoms = JS_TRUE;
} else {
for (i = 0; i < GCX_NTYPES; i++)
cx->newborn[i] = NULL;
cx->lastAtom = NULL;
rt->gcPoke = JS_TRUE;
/* Keep atoms when a suspended compile is running on another context. */
keepAtoms = (rt->gcKeepAtoms != 0);
}
/*
* Don't collect garbage if the runtime isn't up, and cx is not the last
* context in the runtime. The last context must force a GC, and nothing
* should suppress that final collection or there may be shutdown leaks,
* or runtime bloat until the next context is created.
*/
if (rt->state != JSRTS_UP && !(gcflags & GC_LAST_CONTEXT))
if (rt->state != JSRTS_UP && gckind != GC_LAST_CONTEXT)
return JS_TRUE;
restart_after_callback:
/*
* Let the API user decide to defer a GC if it wants to (unless this
* is the last context). Invoke the callback regardless.
*/
if (rt->gcCallback &&
!rt->gcCallback(cx, JSGC_BEGIN) &&
!(gcflags & GC_LAST_CONTEXT)) {
gckind != GC_LAST_CONTEXT) {
return JS_TRUE;
}
/* Lock out other GC allocator and collector invocations. */
if (!(gcflags & GC_LAST_DITCH))
if (gckind != GC_LAST_DITCH)
JS_LOCK_GC(rt);
/* Do nothing if no mutator has executed since the last GC. */
if (!rt->gcPoke) {
METER(rt->gcStats.nopoke++);
if (!(gcflags & GC_LAST_DITCH))
if (gckind != GC_LAST_DITCH)
JS_UNLOCK_GC(rt);
return JS_TRUE;
}
@ -2419,7 +2478,7 @@ js_GC(JSContext *cx, uintN gcflags)
rt->gcLevel++;
METER(if (rt->gcLevel > rt->gcStats.maxlevel)
rt->gcStats.maxlevel = rt->gcLevel);
if (!(gcflags & GC_LAST_DITCH))
if (gckind != GC_LAST_DITCH)
JS_UNLOCK_GC(rt);
return JS_TRUE;
}
@ -2476,7 +2535,7 @@ js_GC(JSContext *cx, uintN gcflags)
JS_AWAIT_GC_DONE(rt);
if (requestDebit)
rt->requestCount += requestDebit;
if (!(gcflags & GC_LAST_DITCH))
if (gckind != GC_LAST_DITCH)
JS_UNLOCK_GC(rt);
return JS_TRUE;
}
@ -2512,10 +2571,6 @@ js_GC(JSContext *cx, uintN gcflags)
rt->gcRunning = JS_TRUE;
JS_UNLOCK_GC(rt);
/* If a suspended compile is running on another context, keep atoms. */
if (rt->gcKeepAtoms)
gcflags |= GC_KEEP_ATOMS;
/* Reset malloc counter. */
rt->gcMallocBytes = 0;
@ -2554,11 +2609,16 @@ restart:
JS_DHashTableEnumerate(&rt->gcRootsHash, gc_root_marker, cx);
if (rt->gcLocksHash)
JS_DHashTableEnumerate(rt->gcLocksHash, gc_lock_marker, cx);
js_MarkAtomState(&rt->atomState, gcflags, gc_mark_atom_key_thing, cx);
js_MarkAtomState(&rt->atomState, keepAtoms, gc_mark_atom_key_thing, cx);
js_MarkWatchPoints(rt);
js_MarkScriptFilenames(rt, gcflags);
js_MarkScriptFilenames(rt, keepAtoms);
js_MarkNativeIteratorStates(cx);
#if JS_HAS_GENERATORS
/* Mark generators whose close hooks were already scheduled to run. */
MarkCloseList(cx, rt->gcCloseState.todoHead);
#endif
iter = NULL;
while ((acx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL) {
/*
@ -2639,18 +2699,19 @@ restart:
*/
ScanDelayedChildren(cx);
#if JS_HAS_GENERATORS
/*
* Close phase: search and mark part.
*
* We find all unreachable objects with close hooks and move them to a
* private work-list to execute the close hooks after the sweep phase.
* To ensure liveness during the sweep phase we mark all objects we are
* about to close.
* Close phase: search and mark part. See comments in
* FindAndMarkObjectsToClose for details.
*/
#ifdef __GNUC__ /* suppress a bogus gcc warning */
objectsToClose.startIndex = 0;
FindAndMarkObjectsToClose(cx, gckind);
/*
* Mark children of things that caused too deep recursion during the
* just-completed marking part of the close phase.
*/
ScanDelayedChildren(cx);
#endif
FindAndMarkObjectsToClose(cx, &objectsToClose);
JS_ASSERT(!cx->insideGCMarkCallback);
if (rt->gcCallback) {
@ -2797,36 +2858,13 @@ restart:
}
#endif
/*
* We want to restart GC if any of the finalizers called js_RemoveRoot or
* js_UnlockGCThingRT.
*/
shouldRestart = rt->gcPoke;
/*
* Close phase: execution part.
*
* The GC allocator works when called during execution of a close hook
* but if it exhausts the malloc heap, it will fail without trying a
* last-ditch GC.
*/
if (objectsToClose.count != 0) {
ExecuteCloseHooks(cx, &objectsToClose);
/*
* On the last destroy context restart GC to collect just closed
* objects. This does not cause infinite loops with close hooks
* creating more closeable objects since we do not allow installing
* close hooks during the shutdown of runtime.
*
* See bug 340889 and bug 341675.
*/
if (gcflags & GC_LAST_CONTEXT)
shouldRestart = JS_TRUE;
}
JS_LOCK_GC(rt);
if (rt->gcLevel > 1 || shouldRestart) {
/*
* We want to restart GC if js_GC was called recursively or if any of the
* finalizers called js_RemoveRoot or js_UnlockGCThingRT.
*/
if (rt->gcLevel > 1 || rt->gcPoke) {
rt->gcLevel = 1;
rt->gcPoke = JS_FALSE;
JS_UNLOCK_GC(rt);
@ -2848,21 +2886,28 @@ restart:
* Unlock unless we have GC_LAST_DITCH which requires locked GC on return
* after a successful GC cycle.
*/
if (!(gcflags & GC_LAST_DITCH))
if (gckind != GC_LAST_DITCH)
JS_UNLOCK_GC(rt);
#endif
checkBranchCallback = (gcflags & GC_LAST_DITCH) &&
JS_HAS_NATIVE_BRANCH_CALLBACK_OPTION(cx) &&
cx->branchCallback;
checkBranchCallback = (gckind == GC_LAST_DITCH &&
JS_HAS_NATIVE_BRANCH_CALLBACK_OPTION(cx) &&
cx->branchCallback);
/* Execute JSGC_END callback and branch callback outside the lock. */
if (rt->gcCallback || checkBranchCallback) {
/* Execute JSGC_END and branch callbacks outside the lock. */
if (gcflags & GC_LAST_DITCH)
if (gckind == GC_LAST_DITCH)
JS_UNLOCK_GC(rt);
if (rt->gcCallback)
if (rt->gcCallback) {
(void) rt->gcCallback(cx, JSGC_END);
/*
* On shutdown iterate until no JSGC_END-status callback creates
* more garbage.
*/
if (gckind == GC_LAST_CONTEXT && rt->gcPoke)
goto restart_after_callback;
}
if (checkBranchCallback &&
cx->branchCallback && /* gcCallback can reset the branchCallback */
!cx->branchCallback(cx, NULL)) {
@ -2871,7 +2916,7 @@ restart:
/* Keep GC unlocked when canceled. */
return JS_FALSE;
}
if (gcflags & GC_LAST_DITCH)
if (gckind == GC_LAST_DITCH)
JS_LOCK_GC(rt);
}

View File

@ -140,8 +140,42 @@ typedef struct JSPtrTable {
extern JSBool
js_RegisterCloseableIterator(JSContext *cx, JSObject *obj);
extern JSBool
js_RegisterGeneratorObject(JSContext *cx, JSObject *obj);
#if JS_HAS_GENERATORS
/*
* Runtime state to support generators' close hooks.
*/
typedef struct JSGCCloseState {
/*
* Singly linked list of generators that are reachable from GC roots or
* were created after the last GC.
*/
JSGenerator *reachableList;
/*
* Head, pointer to tail and length of the queue of generators that have
* already become unreachable but whose close hooks are not yet run.
*/
JSGenerator *todoHead;
JSGenerator **todoTail;
size_t todoCount;
#ifndef JS_THREADSAFE
/*
* Flag indicating that the current thread is excuting a close hook for
* single thread case.
*/
JSBool runningCloseHook;
#endif
} JSGCCloseState;
extern void
js_RegisterGeneratorObject(JSContext *cx, JSGenerator *gen);
JSBool
js_RunCloseHooks(JSContext *cx);
#endif
/*
* The private JSGCThing struct, which describes a gcFreeList element.
@ -206,29 +240,30 @@ extern void
js_MarkStackFrame(JSContext *cx, JSStackFrame *fp);
/*
* Flags to modify how a GC marks and sweeps:
* GC_KEEP_ATOMS Don't sweep unmarked atoms, they may be in use by the
* compiler, or by an API function that calls js_Atomize,
* when the GC is called from js_NewGCThing, due to a
* malloc failure or the runtime GC-thing limit.
* GC_LAST_CONTEXT Called from js_DestroyContext for last JSContext in a
* JSRuntime, when it is imperative that rt->gcPoke gets
* cleared early in js_GC, if it is set.
* GC_LAST_DITCH Called from js_NewGCThing as a last-ditch GC attempt.
* See comments before js_GC definition for details.
* Kinds of js_GC invocation.
*/
#define GC_KEEP_ATOMS 0x1
#define GC_LAST_CONTEXT 0x2
#define GC_LAST_DITCH 0x4
typedef enum JSGCInvocationKind {
/* Normal invocation. */
GC_NORMAL,
extern void
js_ForceGC(JSContext *cx, uintN gcflags);
/*
* Called from js_DestroyContext for last JSContext in a JSRuntime, when
* it is imperative that rt->gcPoke gets cleared early in js_GC.
*/
GC_LAST_CONTEXT,
/*
* Called from js_NewGCThing as a last-ditch GC attempt. See comments
* before js_GC definition for details.
*/
GC_LAST_DITCH
} JSGCInvocationKind;
/*
* Return false when the branch callback cancels GC and true otherwise.
*/
extern JSBool
js_GC(JSContext *cx, uintN gcflags);
js_GC(JSContext *cx, JSGCInvocationKind gckind);
/* Call this after succesful malloc of memory for GC-related things. */
extern void
@ -267,6 +302,9 @@ typedef struct JSGCStats {
uint32 afree; /* thing arenas freed so far */
uint32 stackseg; /* total extraordinary stack segments scanned */
uint32 segslots; /* total stack segment jsval slots scanned */
uint32 nclose; /* number of objects with close hooks */
uint32 maxnclose; /* max number of objects with close hooks */
uint32 maxcloselater; /* max number of close hooks scheduled to run */
} JSGCStats;
extern JS_FRIEND_API(void)

View File

@ -594,28 +594,23 @@ js_ThrowStopIteration(JSContext *cx, JSObject *obj)
#if JS_HAS_GENERATORS
typedef enum JSGeneratorState {
JSGEN_NEWBORN,
JSGEN_RUNNING,
JSGEN_CLOSED
} JSGeneratorState;
typedef struct JSGenerator {
JSGeneratorState state;
JSStackFrame frame;
JSArena arena;
jsval stack[1];
} JSGenerator;
/*
* Execute generator's close hook after GC detects that the object has become
* unreachable.
*/
JSBool
js_CloseGeneratorObject(JSContext *cx, JSObject *obj)
js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen)
{
JSObject *obj;
jsval fval, rval;
const jsid id = ATOM_TO_JSID(cx->runtime->atomState.closeAtom);
JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_GeneratorClass);
JS_ASSERT(JS_GetPrivate(cx, obj));
/* JSGenerator.closeLink must be already unlinked from all lists. */
JS_ASSERT(!gen->next);
JS_ASSERT(gen != cx->runtime->gcCloseState.reachableList);
JS_ASSERT(gen != cx->runtime->gcCloseState.todoHead);
obj = gen->obj;
if (!JS_GetMethodById(cx, obj, id, &obj, &fval))
return JS_FALSE;
@ -687,6 +682,8 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp)
if (!gen)
goto bad;
gen->obj = obj;
/* Copy call-invariant object and function references. */
gen->frame.callobj = fp->callobj;
gen->frame.argsobj = fp->argsobj;
@ -743,13 +740,9 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp)
goto bad;
}
if (!js_RegisterGeneratorObject(cx, obj)) {
/*
* Do not free gen here, as the finalizer will do that since we
* called JS_SetPrivate.
*/
goto bad;
}
/* Register after we have properly initialized the private slot. */
js_RegisterGeneratorObject(cx, gen);
return obj;
bad:

View File

@ -92,11 +92,30 @@ js_CallIteratorNext(JSContext *cx, JSObject *iterobj, uintN flags,
extern JSBool
js_ThrowStopIteration(JSContext *cx, JSObject *obj);
#if JS_HAS_GENERATORS
typedef enum JSGeneratorState {
JSGEN_NEWBORN,
JSGEN_RUNNING,
JSGEN_CLOSED
} JSGeneratorState;
struct JSGenerator {
JSGenerator *next;
JSObject *obj;
JSGeneratorState state;
JSStackFrame frame;
JSArena arena;
jsval stack[1];
};
extern JSObject *
js_NewGenerator(JSContext *cx, JSStackFrame *fp);
extern JSBool
js_CloseGeneratorObject(JSContext *cx, JSObject *obj);
js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen);
#endif
extern JSClass js_GeneratorClass;
extern JSClass js_IteratorClass;

View File

@ -94,6 +94,7 @@ typedef struct JSDependentString JSDependentString;
typedef struct JSGCLockHashEntry JSGCLockHashEntry;
typedef struct JSGCRootHashEntry JSGCRootHashEntry;
typedef struct JSGCThing JSGCThing;
typedef struct JSGenerator JSGenerator;
typedef struct JSParseNode JSParseNode;
typedef struct JSSharpObjectMap JSSharpObjectMap;
typedef struct JSThread JSThread;

View File

@ -1212,7 +1212,7 @@ js_script_filename_marker(JSHashEntry *he, intN i, void *arg)
}
void
js_MarkScriptFilenames(JSRuntime *rt, uintN gcflags)
js_MarkScriptFilenames(JSRuntime *rt, JSBool keepAtoms)
{
JSCList *head, *link;
ScriptFilenamePrefix *sfp;
@ -1220,7 +1220,7 @@ js_MarkScriptFilenames(JSRuntime *rt, uintN gcflags)
if (!rt->scriptFilenameTable)
return;
if (gcflags & GC_KEEP_ATOMS) {
if (keepAtoms) {
JS_HashTableEnumerateEntries(rt->scriptFilenameTable,
js_script_filename_marker,
rt);

View File

@ -139,7 +139,7 @@ extern void
js_MarkScriptFilename(const char *filename);
extern void
js_MarkScriptFilenames(JSRuntime *rt, uintN gcflags);
js_MarkScriptFilenames(JSRuntime *rt, JSBool keepAtoms);
extern void
js_SweepScriptFilenames(JSRuntime *rt);