mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-27 12:15:33 +00:00
bug 474801 - Checking for MaybeGC conditions when allocating GC things in JS shell
This commit is contained in:
parent
623b0edce3
commit
0d5ff88ef3
@ -854,9 +854,6 @@ PrintWinCodebase(nsGlobalWindow *win)
|
||||
}
|
||||
#endif
|
||||
|
||||
// The accumulated operation weight before we call MaybeGC
|
||||
const PRUint32 MAYBE_GC_OPERATION_WEIGHT = 5000 * JS_OPERATION_WEIGHT_BASE;
|
||||
|
||||
static void
|
||||
MaybeGC(JSContext *cx)
|
||||
{
|
||||
@ -868,11 +865,13 @@ MaybeGC(JSContext *cx)
|
||||
|| cx->runtime->gcZeal > 0
|
||||
#endif
|
||||
) {
|
||||
++sGCCount;
|
||||
JS_GC(cx);
|
||||
}
|
||||
}
|
||||
|
||||
// The accumulated operation weight for DOM callback.
|
||||
const PRUint32 DOM_CALLBACK_OPERATION_WEIGHT = 5000 * JS_OPERATION_WEIGHT_BASE;
|
||||
|
||||
static already_AddRefed<nsIPrompt>
|
||||
GetPromptFromContext(nsJSContext* ctx)
|
||||
{
|
||||
@ -1251,7 +1250,7 @@ nsJSContext::nsJSContext(JSRuntime *aRuntime) : mGCOnDestruction(PR_TRUE)
|
||||
this);
|
||||
|
||||
::JS_SetOperationCallback(mContext, DOMOperationCallback,
|
||||
MAYBE_GC_OPERATION_WEIGHT);
|
||||
DOM_CALLBACK_OPERATION_WEIGHT);
|
||||
|
||||
static JSLocaleCallbacks localeCallbacks =
|
||||
{
|
||||
@ -3409,7 +3408,7 @@ nsJSContext::CC()
|
||||
#endif
|
||||
sPreviousCCTime = PR_Now();
|
||||
sDelayedCCollectCount = 0;
|
||||
sGCCount = 0;
|
||||
sGCCount = JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER);
|
||||
sCCSuspectChanges = 0;
|
||||
// nsCycleCollector_collect() will run a ::JS_GC() indirectly, so
|
||||
// we do not explicitly call ::JS_GC() here.
|
||||
@ -3422,6 +3421,23 @@ nsJSContext::CC()
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32
|
||||
GetGCRunsCount()
|
||||
{
|
||||
/*
|
||||
* The result value may overflow if sGCCount is close to the uint32
|
||||
* maximum. It may cause additional invocations of the CC, which may
|
||||
* reduce performance but cannot breach security.
|
||||
*/
|
||||
|
||||
// To avoid crash if nsJSRuntime is not properly initialized.
|
||||
// See the bug 474586
|
||||
if (!nsJSRuntime::sRuntime)
|
||||
return 0;
|
||||
|
||||
return JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER) - sGCCount;
|
||||
}
|
||||
|
||||
//static
|
||||
PRBool
|
||||
nsJSContext::MaybeCC(PRBool aHigherProbability)
|
||||
@ -3430,7 +3446,7 @@ nsJSContext::MaybeCC(PRBool aHigherProbability)
|
||||
|
||||
// Don't check suspected count if CC will be called anyway.
|
||||
if (sCCSuspectChanges <= NS_MIN_SUSPECT_CHANGES ||
|
||||
sGCCount <= NS_MAX_GC_COUNT) {
|
||||
GetGCRunsCount() <= NS_MAX_GC_COUNT) {
|
||||
#ifdef DEBUG_smaug
|
||||
PRTime now = PR_Now();
|
||||
#endif
|
||||
@ -3448,7 +3464,7 @@ nsJSContext::MaybeCC(PRBool aHigherProbability)
|
||||
}
|
||||
#ifdef DEBUG_smaug
|
||||
printf("sCCSuspectChanges %u, sGCCount %u\n",
|
||||
sCCSuspectChanges, sGCCount);
|
||||
sCCSuspectChanges, GetGCRunsCount());
|
||||
#endif
|
||||
|
||||
// Increase the probability also if the previous call to cycle collector
|
||||
@ -3461,7 +3477,7 @@ nsJSContext::MaybeCC(PRBool aHigherProbability)
|
||||
if (!sGCTimer &&
|
||||
(sDelayedCCollectCount > NS_MAX_DELAYED_CCOLLECT) &&
|
||||
((sCCSuspectChanges > NS_MIN_SUSPECT_CHANGES &&
|
||||
sGCCount > NS_MAX_GC_COUNT) ||
|
||||
GetGCRunsCount() > NS_MAX_GC_COUNT) ||
|
||||
(sCCSuspectChanges > NS_MAX_SUSPECT_CHANGES))) {
|
||||
if ((PR_Now() - sPreviousCCTime) >=
|
||||
PRTime(NS_MIN_CC_INTERVAL * PR_USEC_PER_MSEC)) {
|
||||
|
@ -2595,6 +2595,31 @@ JS_SetGCParameter(JSRuntime *rt, JSGCParamKey key, uint32 value)
|
||||
case JSGC_STACKPOOL_LIFESPAN:
|
||||
rt->gcEmptyArenaPoolLifespan = value;
|
||||
break;
|
||||
default:
|
||||
JS_ASSERT(key == JSGC_TRIGGER_FACTOR);
|
||||
JS_ASSERT(value >= 100);
|
||||
rt->gcTriggerFactor = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint32)
|
||||
JS_GetGCParameter(JSRuntime *rt, JSGCParamKey key)
|
||||
{
|
||||
switch (key) {
|
||||
case JSGC_MAX_BYTES:
|
||||
return rt->gcMaxBytes;
|
||||
case JSGC_MAX_MALLOC_BYTES:
|
||||
return rt->gcMaxMallocBytes;
|
||||
case JSGC_STACKPOOL_LIFESPAN:
|
||||
return rt->gcEmptyArenaPoolLifespan;
|
||||
case JSGC_TRIGGER_FACTOR:
|
||||
return rt->gcTriggerFactor;
|
||||
case JSGC_BYTES:
|
||||
return rt->gcBytes;
|
||||
default:
|
||||
JS_ASSERT(key == JSGC_NUMBER);
|
||||
return rt->gcNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3812,7 +3837,7 @@ JS_HasUCProperty(JSContext *cx, JSObject *obj,
|
||||
JSProperty *prop;
|
||||
|
||||
CHECK_REQUEST(cx);
|
||||
ok = LookupUCProperty(cx, obj, name, namelen,
|
||||
ok = LookupUCProperty(cx, obj, name, namelen,
|
||||
JSRESOLVE_QUALIFIED | JSRESOLVE_DETECTING,
|
||||
&obj2, &prop);
|
||||
if (ok) {
|
||||
|
@ -1137,12 +1137,31 @@ typedef enum JSGCParamKey {
|
||||
JSGC_MAX_MALLOC_BYTES = 1,
|
||||
|
||||
/* Hoard stackPools for this long, in ms, default is 30 seconds. */
|
||||
JSGC_STACKPOOL_LIFESPAN = 2
|
||||
JSGC_STACKPOOL_LIFESPAN = 2,
|
||||
|
||||
/*
|
||||
* The factor that defines when the GC is invoked. The factor is a
|
||||
* percent of the memory allocated by the GC after the last run of
|
||||
* the GC. When the current memory allocated by the GC is more than
|
||||
* this percent then the GC is invoked. The factor cannot be less
|
||||
* than 100 since the current memory allocated by the GC cannot be less
|
||||
* than the memory allocated after the last run of the GC.
|
||||
*/
|
||||
JSGC_TRIGGER_FACTOR = 3,
|
||||
|
||||
/* Amount of bytes allocated by the GC. */
|
||||
JSGC_BYTES = 4,
|
||||
|
||||
/* Number of times when GC was invoked. */
|
||||
JSGC_NUMBER = 5
|
||||
} JSGCParamKey;
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetGCParameter(JSRuntime *rt, JSGCParamKey key, uint32 value);
|
||||
|
||||
extern JS_PUBLIC_API(uint32)
|
||||
JS_GetGCParameter(JSRuntime *rt, JSGCParamKey key);
|
||||
|
||||
/*
|
||||
* Add a finalizer for external strings created by JS_NewExternalString (see
|
||||
* below) using a type-code returned from this function, and that understands
|
||||
|
@ -265,6 +265,7 @@ struct JSRuntime {
|
||||
uint32 gcLevel;
|
||||
uint32 gcNumber;
|
||||
JSTracer *gcMarkingTracer;
|
||||
uint32 gcTriggerFactor;
|
||||
|
||||
/*
|
||||
* NB: do not pack another flag here by claiming gcPadding unless the new
|
||||
|
@ -1253,6 +1253,18 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes)
|
||||
rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes;
|
||||
rt->gcEmptyArenaPoolLifespan = 30000;
|
||||
|
||||
/*
|
||||
* By default the trigger factor gets maximum possible value. This
|
||||
* means that GC will not be triggered by growth of GC memory (gcBytes).
|
||||
*/
|
||||
rt->gcTriggerFactor = (uint32) -1;
|
||||
|
||||
/*
|
||||
* The assigned value prevents GC from running when GC memory is too low
|
||||
* (during JS engine start).
|
||||
*/
|
||||
rt->gcLastBytes = 8192;
|
||||
|
||||
METER(memset(&rt->gcStats, 0, sizeof rt->gcStats));
|
||||
return JS_TRUE;
|
||||
}
|
||||
@ -1757,6 +1769,17 @@ EnsureLocalFreeList(JSContext *cx)
|
||||
|
||||
#endif
|
||||
|
||||
static JS_INLINE JSBool
|
||||
IsGCThresholdReached(JSRuntime *rt)
|
||||
{
|
||||
/*
|
||||
* Since the initial value of the gcLastBytes parameter is not equal to
|
||||
* zero (see the js_InitGC function) the return value is false when
|
||||
* the gcBytes value is close to zero at the JS engine start.
|
||||
*/
|
||||
return rt->gcBytes / rt->gcTriggerFactor >= rt->gcLastBytes / 100;
|
||||
}
|
||||
|
||||
void *
|
||||
js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
|
||||
{
|
||||
@ -1823,7 +1846,8 @@ js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
doGC = (rt->gcMallocBytes >= rt->gcMaxMallocBytes && rt->gcPoke);
|
||||
doGC = (rt->gcMallocBytes >= rt->gcMaxMallocBytes && rt->gcPoke) ||
|
||||
IsGCThresholdReached(rt);
|
||||
#ifdef JS_GC_ZEAL
|
||||
doGC = doGC || rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke);
|
||||
# ifdef JS_TRACER
|
||||
@ -2056,9 +2080,10 @@ RefillDoubleFreeList(JSContext *cx)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (rt->gcMallocBytes >= rt->gcMaxMallocBytes && rt->gcPoke
|
||||
if ((rt->gcMallocBytes >= rt->gcMaxMallocBytes && rt->gcPoke) ||
|
||||
IsGCThresholdReached(rt)
|
||||
#ifdef JS_GC_ZEAL
|
||||
&& (rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke))
|
||||
|| (rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke))
|
||||
#endif
|
||||
) {
|
||||
goto do_gc;
|
||||
@ -2257,7 +2282,8 @@ js_AddAsGCBytes(JSContext *cx, size_t sz)
|
||||
|
||||
rt = cx->runtime;
|
||||
if (rt->gcBytes >= rt->gcMaxBytes ||
|
||||
sz > (size_t) (rt->gcMaxBytes - rt->gcBytes)
|
||||
sz > (size_t) (rt->gcMaxBytes - rt->gcBytes) ||
|
||||
IsGCThresholdReached(rt)
|
||||
#ifdef JS_GC_ZEAL
|
||||
|| rt->gcZeal >= 2 || (rt->gcZeal >= 1 && rt->gcPoke)
|
||||
#endif
|
||||
|
@ -229,9 +229,6 @@ struct JSShellContextData {
|
||||
PRIntervalTime timeout;
|
||||
volatile PRIntervalTime startTime; /* startTime + timeout is time when
|
||||
script must be stopped */
|
||||
PRIntervalTime maybeGCPeriod;
|
||||
volatile PRIntervalTime lastMaybeGCTime;/* lastMaybeGCTime + maybeGCPeriod
|
||||
is the time to call MaybeGC */
|
||||
PRIntervalTime yieldPeriod;
|
||||
volatile PRIntervalTime lastYieldTime; /* lastYieldTime + yieldPeriod is
|
||||
the time to call
|
||||
@ -239,7 +236,6 @@ struct JSShellContextData {
|
||||
#else
|
||||
int64 stopTime; /* time when script must be
|
||||
stopped */
|
||||
int64 nextMaybeGCTime;/* time to call JS_MaybeGC */
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -249,7 +245,6 @@ SetTimeoutValue(JSContext *cx, jsdouble t);
|
||||
#ifdef JS_THREADSAFE
|
||||
|
||||
# define DEFAULT_YIELD_PERIOD() (PR_TicksPerSecond() / 50)
|
||||
# define DEFAULT_MAYBEGC_PERIOD() (PR_TicksPerSecond() / 10)
|
||||
|
||||
/*
|
||||
* The function assumes that the GC lock is already held on entry. On a
|
||||
@ -261,8 +256,6 @@ RescheduleWatchdog(JSContext *cx, JSShellContextData *data, PRIntervalTime now);
|
||||
|
||||
#else
|
||||
|
||||
# define DEFAULT_MAYBEGC_PERIOD() (MICROSECONDS_PER_SECOND / 10)
|
||||
|
||||
const int64 MICROSECONDS_PER_SECOND = 1000000LL;
|
||||
const int64 MAX_TIME_VALUE = 0x7FFFFFFFFFFFFFFFLL;
|
||||
|
||||
@ -277,16 +270,13 @@ NewContextData()
|
||||
return NULL;
|
||||
#ifdef JS_THREADSAFE
|
||||
data->timeout = PR_INTERVAL_NO_TIMEOUT;
|
||||
data->maybeGCPeriod = PR_INTERVAL_NO_TIMEOUT;
|
||||
data->yieldPeriod = PR_INTERVAL_NO_TIMEOUT;
|
||||
# ifdef DEBUG
|
||||
data->startTime = 0;
|
||||
data->lastMaybeGCTime = 0;
|
||||
data->lastYieldTime = 0;
|
||||
# endif
|
||||
#else /* !JS_THREADSAFE */
|
||||
data->stopTime = MAX_TIME_VALUE;
|
||||
data->nextMaybeGCTime = MAX_TIME_VALUE;
|
||||
#endif
|
||||
|
||||
return data;
|
||||
@ -306,7 +296,6 @@ ShellOperationCallback(JSContext *cx)
|
||||
{
|
||||
JSShellContextData *data = GetContextData(cx);
|
||||
JSBool doStop;
|
||||
JSBool doMaybeGC;
|
||||
#ifdef JS_THREADSAFE
|
||||
JSBool doYield;
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
@ -314,11 +303,6 @@ ShellOperationCallback(JSContext *cx)
|
||||
doStop = (data->timeout != PR_INTERVAL_NO_TIMEOUT &&
|
||||
now - data->startTime >= data->timeout);
|
||||
|
||||
doMaybeGC = (data->maybeGCPeriod != PR_INTERVAL_NO_TIMEOUT &&
|
||||
now - data->lastMaybeGCTime >= data->maybeGCPeriod);
|
||||
if (doMaybeGC)
|
||||
data->lastMaybeGCTime = now;
|
||||
|
||||
doYield = (data->yieldPeriod != PR_INTERVAL_NO_TIMEOUT &&
|
||||
now - data->lastYieldTime >= data->yieldPeriod);
|
||||
if (doYield)
|
||||
@ -328,9 +312,6 @@ ShellOperationCallback(JSContext *cx)
|
||||
int64 now = JS_Now();
|
||||
|
||||
doStop = (now >= data->stopTime);
|
||||
doMaybeGC = (now >= data->nextMaybeGCTime);
|
||||
if (doMaybeGC)
|
||||
data->nextMaybeGCTime = now + DEFAULT_MAYBEGC_PERIOD();
|
||||
#endif
|
||||
|
||||
if (doStop) {
|
||||
@ -338,9 +319,6 @@ ShellOperationCallback(JSContext *cx)
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
if (doMaybeGC)
|
||||
JS_MaybeGC(cx);
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
if (doYield)
|
||||
JS_YieldRequest(cx);
|
||||
@ -1090,24 +1068,49 @@ GCParameter(JSContext *cx, uintN argc, jsval *vp)
|
||||
param = JSGC_MAX_BYTES;
|
||||
} else if (strcmp(paramName, "maxMallocBytes") == 0) {
|
||||
param = JSGC_MAX_MALLOC_BYTES;
|
||||
} else if (strcmp(paramName, "gcStackpoolLifespan") == 0) {
|
||||
param = JSGC_STACKPOOL_LIFESPAN;
|
||||
} else if (strcmp(paramName, "gcBytes") == 0) {
|
||||
param = JSGC_BYTES;
|
||||
} else if (strcmp(paramName, "gcNumber") == 0) {
|
||||
param = JSGC_NUMBER;
|
||||
} else if (strcmp(paramName, "gcTriggerFactor") == 0) {
|
||||
param = JSGC_TRIGGER_FACTOR;
|
||||
} else {
|
||||
JS_ReportError(cx,
|
||||
"the first argument argument must be either maxBytes "
|
||||
"or maxMallocBytes");
|
||||
"the first argument argument must be maxBytes, "
|
||||
"maxMallocBytes, gcStackpoolLifespan, gcBytes, "
|
||||
"gcNumber or gcTriggerFactor");
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
if (!JS_ValueToECMAUint32(cx, argc < 2 ? JSVAL_VOID : vp[3], &value))
|
||||
if (argc == 1) {
|
||||
value = JS_GetGCParameter(cx->runtime, param);
|
||||
return JS_NewNumberValue(cx, value, &vp[0]);
|
||||
}
|
||||
|
||||
if (param == JSGC_NUMBER ||
|
||||
param == JSGC_BYTES) {
|
||||
JS_ReportError(cx, "Attempt to change read-only parameter %s",
|
||||
paramName);
|
||||
return JS_FALSE;
|
||||
if (value == 0) {
|
||||
}
|
||||
|
||||
if (!JS_ValueToECMAUint32(cx, vp[3], &value)) {
|
||||
JS_ReportError(cx,
|
||||
"the second argument must be convertable to uint32 with "
|
||||
"non-zero value");
|
||||
"the second argument must be convertable to uint32 "
|
||||
"with non-zero value");
|
||||
return JS_FALSE;
|
||||
}
|
||||
if (param == JSGC_TRIGGER_FACTOR && value < 100) {
|
||||
JS_ReportError(cx,
|
||||
"the gcTriggerFactor value must be >= 100");
|
||||
return JS_FALSE;
|
||||
}
|
||||
JS_SetGCParameter(cx->runtime, param, value);
|
||||
*vp = JSVAL_VOID;
|
||||
return JS_TRUE;
|
||||
|
||||
}
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
@ -3142,8 +3145,6 @@ CheckCallbackTime(JSContext *cx, JSShellContextData *data, PRIntervalTime now,
|
||||
|
||||
UpdateSleepDuration(now, data->startTime, data->timeout,
|
||||
sleepDuration, expired);
|
||||
UpdateSleepDuration(now, data->lastMaybeGCTime, data->maybeGCPeriod,
|
||||
sleepDuration, expired);
|
||||
UpdateSleepDuration(now, data->lastYieldTime, data->yieldPeriod,
|
||||
sleepDuration, expired);
|
||||
if (expired) {
|
||||
@ -3247,24 +3248,15 @@ SetTimeoutValue(JSContext *cx, jsdouble t)
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* For compatibility periodic MaybeGC calls are enabled only when the
|
||||
* execution time is bounded.
|
||||
*/
|
||||
JSShellContextData *data = GetContextData(cx);
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_LOCK_GC(cx->runtime);
|
||||
if (t < 0) {
|
||||
data->timeout = PR_INTERVAL_NO_TIMEOUT;
|
||||
data->maybeGCPeriod = PR_INTERVAL_NO_TIMEOUT;
|
||||
} else {
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
data->timeout = PRIntervalTime(t * PR_TicksPerSecond());
|
||||
data->startTime = now;
|
||||
if (data->maybeGCPeriod == PR_INTERVAL_NO_TIMEOUT) {
|
||||
data->maybeGCPeriod = DEFAULT_MAYBEGC_PERIOD();
|
||||
data->lastMaybeGCTime = now;
|
||||
}
|
||||
if (!RescheduleWatchdog(cx, data, now)) {
|
||||
/* The GC lock is already released here. */
|
||||
return JS_FALSE;
|
||||
@ -3275,13 +3267,10 @@ SetTimeoutValue(JSContext *cx, jsdouble t)
|
||||
#else /* !JS_THREADSAFE */
|
||||
if (t < 0) {
|
||||
data->stopTime = MAX_TIME_VALUE;
|
||||
data->nextMaybeGCTime = MAX_TIME_VALUE;
|
||||
JS_SetOperationLimit(cx, JS_MAX_OPERATION_LIMIT);
|
||||
} else {
|
||||
int64 now = JS_Now();
|
||||
data->stopTime = now + int64(t * MICROSECONDS_PER_SECOND);
|
||||
if (data->nextMaybeGCTime == MAX_TIME_VALUE)
|
||||
data->nextMaybeGCTime = now + DEFAULT_MAYBEGC_PERIOD();
|
||||
|
||||
/*
|
||||
* Call the callback infrequently enough to avoid the overhead of
|
||||
|
Loading…
Reference in New Issue
Block a user