bug 472702 - using watchdog thread in js shell to trigger operation callback

This commit is contained in:
Igor Bukanov 2009-01-14 18:23:51 +01:00
parent 873ec9adae
commit c032867e33
8 changed files with 456 additions and 189 deletions

View File

@ -107,6 +107,16 @@ static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
static jsdouble gOperationTimeout = -1.0;
/*
* Watchdog thread state.
*/
#ifdef JS_THREADSAFE
static PRCondVar *gWatchdogWakeup;
static PRThread *gWatchdogThread;
static PRIntervalTime gWatchdogSleepDuration = 0;
static PRIntervalTime gLastWatchdogWakeup;
#endif
int gExitCode = 0;
JSBool gQuitting = JS_FALSE;
FILE *gErrFile = NULL;
@ -129,12 +139,6 @@ my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber);
static JSObject *
split_setup(JSContext *cx);
static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t);
static void
RescheduleOperationCallback(JSContext *cx);
#ifdef EDITLINE
JS_BEGIN_EXTERN_C
extern char *readline(const char *prompt);
@ -204,40 +208,66 @@ GetLine(FILE *file, const char * prompt)
return NULL;
}
/* Time-related portability helpers. */
typedef int64 OperationTime;
const OperationTime TIME_INFINITY = -1LL;
static inline OperationTime
TicksPerSecond()
{
return 1000LL * 1000LL;
}
static inline OperationTime
MaybeGCPeriod()
{
return TicksPerSecond() / 10;
}
static inline OperationTime
YieldRequestPeriod()
{
return TicksPerSecond() / 50;
}
/*
* State to store as JSContext private.
*
* In the JS_THREADSAFE case, when the watchdog thread triggers the operation
* callback, we use PR_IntervalNow(), not JS_Now() as the latter could be
* expensive and is not suitable for calls when a GC lock is held. This forces
* us to use PRIntervalTime as a time type and deal with potential time-wraps
* over uint32 limit. In particular, we must use time relative to some recent
* timestamp when checking for expiration, not absolute time values, as in the
* !JS_THREADSAFE case, when time is int64 and no time-wraps are feasible.
*
* We declare such timestamps as volatile as they are updated in the operation
* callback without taking any locks. Any possible race can only lead to more
* frequent callback calls. This is safe as the callback does everything based
* on timing.
*/
struct JSShellContextData {
OperationTime lastCallbackTime; /* the last operation callback call
time */
OperationTime operationTimeout; /* time left for script execution */
OperationTime lastMaybeGCTime; /* the last JS_MaybeGC call time */
OperationTime maybeGCPeriod; /* period of JS_MaybeGC calls */
#ifdef JS_THREADSAFE
OperationTime lastYieldTime; /* the last JS_YieldRequest call time */
OperationTime yieldPeriod; /* period of JS_YieldRequest calls */
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
JS_YieldRequest() */
#else
int64 stopTime; /* time when script must be
stopped */
int64 nextMaybeGCTime;/* time to call JS_MaybeGC */
#endif
};
static JSBool
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
* successful exit the lock will be held, on failure the lock is released and
* the error is reported.
*/
static JSBool
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;
#endif
static JSShellContextData *
NewContextData()
{
@ -245,14 +275,20 @@ NewContextData()
malloc(sizeof(JSShellContextData));
if (!data)
return NULL;
data->lastCallbackTime = 0;
data->operationTimeout = TIME_INFINITY;
data->lastMaybeGCTime = 0;
data->maybeGCPeriod = TIME_INFINITY;
#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;
data->yieldPeriod = TIME_INFINITY;
# endif
#else /* !JS_THREADSAFE */
data->stopTime = MAX_TIME_VALUE;
data->nextMaybeGCTime = MAX_TIME_VALUE;
#endif
return data;
}
@ -269,30 +305,45 @@ static JSBool
ShellOperationCallback(JSContext *cx)
{
JSShellContextData *data = GetContextData(cx);
OperationTime now = JS_Now();
OperationTime interval = now - data->lastCallbackTime;
data->lastCallbackTime = now;
if (data->operationTimeout != TIME_INFINITY) {
if (data->operationTimeout < interval) {
fprintf(stderr, "Error: Script is running too long\n");
return JS_FALSE;
}
data->operationTimeout -= interval;
}
if (data->maybeGCPeriod != TIME_INFINITY) {
if (now - data->lastMaybeGCTime >= data->maybeGCPeriod) {
JS_MaybeGC(cx);
data->lastMaybeGCTime = now;
}
}
JSBool doStop;
JSBool doMaybeGC;
#ifdef JS_THREADSAFE
if (data->yieldPeriod != TIME_INFINITY) {
if (now - data->lastYieldTime >= data->yieldPeriod) {
JS_YieldRequest(cx);
data->lastYieldTime = now;
}
JSBool doYield;
PRIntervalTime now = PR_IntervalNow();
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)
data->lastYieldTime = now;
#else /* !JS_THREADSAFE */
int64 now = JS_Now();
doStop = (now >= data->stopTime);
doMaybeGC = (now >= data->nextMaybeGCTime);
if (doMaybeGC)
data->nextMaybeGCTime = now + DEFAULT_MAYBEGC_PERIOD();
#endif
if (doStop) {
fputs("Error: script is running for too long\n", stderr);
return JS_FALSE;
}
if (doMaybeGC)
JS_MaybeGC(cx);
#ifdef JS_THREADSAFE
if (doYield)
JS_YieldRequest(cx);
#endif
return JS_TRUE;
@ -318,6 +369,7 @@ SetContextOptions(JSContext *cx)
JS_SetThreadStackLimit(cx, stackLimit);
JS_SetScriptStackQuota(cx, gScriptStackQuota);
SetTimeoutValue(cx, gOperationTimeout);
JS_SetOperationCallbackFunction(cx, ShellOperationCallback);
}
static void
@ -2825,19 +2877,40 @@ DoScatteredWork(JSContext *cx, ScatterThreadData *td)
{
jsval *rval = &td->shared->results[td->index];
JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx);
OperationTime oldPeriod = data->yieldPeriod;
if (oldPeriod == TIME_INFINITY)
data->lastYieldTime = JS_Now();
data->yieldPeriod = YieldRequestPeriod();
RescheduleOperationCallback(cx);
if (!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
JSShellContextData *data = GetContextData(cx);
PRIntervalTime oldYieldPeriod = data->yieldPeriod;
PRIntervalTime newYieldPeriod = DEFAULT_YIELD_PERIOD();
JSBool scheduleOk = JS_TRUE;
/*
* Here oldYieldPeriod is DEFAULT_YIELD_PERIOD() when the scatter reuses
* a context used by a previous scatter call.
*/
if (oldYieldPeriod != newYieldPeriod) {
JS_LOCK_GC(cx->runtime);
PRIntervalTime now = PR_IntervalNow();
JS_ASSERT(oldYieldPeriod == PR_INTERVAL_NO_TIMEOUT);
data->lastYieldTime = now;
data->yieldPeriod = newYieldPeriod;
scheduleOk = RescheduleWatchdog(cx, data, now);
if (scheduleOk)
JS_UNLOCK_GC(cx->runtime);
}
if (!scheduleOk ||
!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
*rval = JSVAL_VOID;
JS_GetPendingException(cx, rval);
JS_ClearPendingException(cx);
}
data->yieldPeriod = oldPeriod;
RescheduleOperationCallback(cx);
/*
* We do not need to lock or call RescheduleWatchdog. Here yieldPeriod
* can only stay at DEFAULT_YIELD_PERIOD or go to PR_INTERVAL_NO_TIMEOUT.
* Thus we never need to wake up the watchdog thread earlier.
*/
JS_ASSERT(oldYieldPeriod == data->yieldPeriod ||
oldYieldPeriod == PR_INTERVAL_NO_TIMEOUT);
data->yieldPeriod = oldYieldPeriod;
}
static void
@ -3031,55 +3104,189 @@ fail:
ok = JS_FALSE;
goto out;
}
#endif /* JS_THREADSAFE */
/*
* Find duration between now and base + period, set it to sleepDuration if the
* latter value is greater and set expired to true if base + period comes
* before now. This function correctly deals with a possible time wrap between
* base and now.
*/
static void
UpdateSleepDuration(PRIntervalTime now, PRIntervalTime base,
PRIntervalTime period, PRIntervalTime &sleepDuration,
JSBool &expired)
{
if (period == PR_INTERVAL_NO_TIMEOUT)
return;
PRIntervalTime t;
PRIntervalTime diff = now - base;
if (diff >= period) {
expired = JS_TRUE;
t = period;
} else {
t = period - diff;
}
if (sleepDuration == PR_INTERVAL_NO_TIMEOUT || sleepDuration > t)
sleepDuration = t;
}
static void
RescheduleOperationCallback(JSContext *cx)
CheckCallbackTime(JSContext *cx, JSShellContextData *data, PRIntervalTime now,
PRIntervalTime &sleepDuration)
{
JSShellContextData *data = GetContextData(cx);
JSBool expired = JS_FALSE;
if (data->operationTimeout == TIME_INFINITY &&
#ifdef JS_THREADSAFE
data->yieldPeriod == TIME_INFINITY &&
#endif
data->maybeGCPeriod == TIME_INFINITY) {
JS_ClearOperationCallback(cx);
} else if (!JS_GetOperationCallback(cx)) {
/*
* Call the callback infrequently enough to avoid the overhead of time
* calculations there.
*/
JS_SetOperationCallback(cx, ShellOperationCallback,
1000 * JS_OPERATION_WEIGHT_BASE);
data->lastCallbackTime = JS_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) {
JS_ASSERT(sleepDuration != PR_INTERVAL_NO_TIMEOUT);
JS_TriggerOperationCallback(cx);
}
}
static void
WatchdogMain(void *arg)
{
JSRuntime *rt = (JSRuntime *) arg;
PRBool isRunning = JS_TRUE;
JS_LOCK_GC(rt);
while (gWatchdogThread) {
PRIntervalTime now = PR_IntervalNow();
PRIntervalTime sleepDuration = PR_INTERVAL_NO_TIMEOUT;
JSContext *iter = NULL;
JSContext *acx;
while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) {
if (acx->requestDepth > 0) {
JSShellContextData *data = (JSShellContextData *)
JS_GetContextPrivate(acx);
/*
* For the last context inside JS_DestroyContext the engine
* starts a new request to shutdown the runtime. For such
* context data is null.
*/
if (data)
CheckCallbackTime(acx, data, now, sleepDuration);
}
}
gLastWatchdogWakeup = now;
gWatchdogSleepDuration = sleepDuration;
#ifdef DEBUG
PRStatus status =
#endif
PR_WaitCondVar(gWatchdogWakeup, sleepDuration);
JS_ASSERT(status == PR_SUCCESS);
}
/* Wake up the main thread waiting for the watchdog to terminate. */
PR_NotifyCondVar(gWatchdogWakeup);
JS_UNLOCK_GC(rt);
}
static JSBool
RescheduleWatchdog(JSContext *cx, JSShellContextData *data, PRIntervalTime now)
{
JS_ASSERT(data == GetContextData(cx));
PRIntervalTime nextCallbackTime = PR_INTERVAL_NO_TIMEOUT;
CheckCallbackTime(cx, data, now, nextCallbackTime);
if (nextCallbackTime == PR_INTERVAL_NO_TIMEOUT)
return JS_TRUE;
if (gWatchdogThread) {
/*
* Notify the watchdog if it would wake up after data->watchdogLimit
* expires. PRIntervalTime is unsigned so the subtraction in the
* following check gives the correct interval even when time wraps
* around between gLastWatchdogWakeup and now.
*/
if (gWatchdogSleepDuration == PR_INTERVAL_NO_TIMEOUT ||
PRInt32(now - gLastWatchdogWakeup) <
PRInt32(gWatchdogSleepDuration) - PRInt32(nextCallbackTime)) {
PR_NotifyCondVar(gWatchdogWakeup);
}
} else {
gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
WatchdogMain,
cx->runtime,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_UNJOINABLE_THREAD,
0);
if (!gWatchdogThread) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportError(cx, "failed to create the watchdog thread");
return JS_FALSE;
}
/* The watchdog thread does not sleep on creation. */
JS_ASSERT(gWatchdogSleepDuration == 0);
gLastWatchdogWakeup = now;
}
return JS_TRUE;
}
#endif /* JS_THREADSAFE */
static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t)
{
/* NB: The next condition also filter out NaNs. */
if (!(t <= 3600.0)) {
JS_ReportError(cx, "Excessive argument value");
JS_ReportError(cx, "Excessive timeout value");
return JS_FALSE;
}
JSShellContextData *data = GetContextData(cx);
/*
* 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->operationTimeout = TIME_INFINITY;
data->maybeGCPeriod = TIME_INFINITY;
data->timeout = PR_INTERVAL_NO_TIMEOUT;
data->maybeGCPeriod = PR_INTERVAL_NO_TIMEOUT;
} else {
data->operationTimeout = OperationTime(t * TicksPerSecond());
if (data->maybeGCPeriod == TIME_INFINITY)
data->lastMaybeGCTime = JS_Now();
data->maybeGCPeriod = MaybeGCPeriod();
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;
}
}
RescheduleOperationCallback(cx);
JS_UNLOCK_GC(cx->runtime);
#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
* time calculations there.
*/
JS_SetOperationLimit(cx, 1000 * JS_OPERATION_WEIGHT_BASE);
}
#endif
return JS_TRUE;
}
@ -3088,11 +3295,28 @@ Timeout(JSContext *cx, uintN argc, jsval *vp)
{
if (argc == 0) {
JSShellContextData *data = GetContextData(cx);
jsdouble t;
t = (data->operationTimeout == TIME_INFINITY)
? -1
: jsdouble(data->operationTimeout) / TicksPerSecond();
return JS_NewDoubleValue(cx, t, vp);
jsdouble t; /* remaining time to run */
#ifdef JS_THREADSAFE
if (data->timeout == PR_INTERVAL_NO_TIMEOUT) {
t = -1.0;
} else {
PRIntervalTime expiredTime = PR_IntervalNow() - data->startTime;
t = (expiredTime >= data->timeout)
? 0.0
: jsdouble(data->timeout - expiredTime) / PR_TicksPerSecond();
}
#else
if (data->stopTime == MAX_TIME_VALUE) {
t = -1.0;
} else {
int64 remainingTime = data->stopTime - JS_Now();
t = (remainingTime <= 0)
? 0.0
: jsdouble(remainingTime) / MICROSECONDS_PER_SECOND;
}
#endif
return JS_NewNumberValue(cx, t, vp);
}
if (argc > 1) {
@ -4225,6 +4449,13 @@ main(int argc, char **argv, char **envp)
rt = JS_NewRuntime(64L * 1024L * 1024L);
if (!rt)
return 1;
#ifdef JS_THREADSAFE
gWatchdogWakeup = JS_NEW_CONDVAR(rt->gcLock);
if (!gWatchdogWakeup)
return 1;
#endif
JS_SetContextCallback(rt, ContextCallback);
cx = JS_NewContext(rt, gStackChunkSize);
@ -4335,6 +4566,22 @@ main(int argc, char **argv, char **envp)
#endif
JS_DestroyContext(cx);
#ifdef JS_THREADSAFE
JS_LOCK_GC(rt);
if (gWatchdogThread) {
/*
* The watchdog thread is running, tell it to terminate waking it up
* if necessary and wait until it signals that it done.
*/
gWatchdogThread = NULL;
PR_NotifyCondVar(gWatchdogWakeup);
PR_WaitCondVar(gWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
}
JS_UNLOCK_GC(rt);
JS_DESTROY_CONDVAR(gWatchdogWakeup);
#endif
JS_DestroyRuntime(rt);
JS_ShutDown();
return result;

View File

@ -5253,49 +5253,34 @@ JS_PUBLIC_API(void)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
uint32 operationLimit)
{
JS_ASSERT(callback);
JS_ASSERT(operationLimit <= JS_MAX_OPERATION_LIMIT);
JS_ASSERT(operationLimit > 0);
cx->operationCount = (int32) operationLimit;
cx->operationLimit = operationLimit;
cx->operationCallbackIsSet = 1;
cx->operationCallback = callback;
JS_SetOperationCallbackFunction(cx, callback);
JS_SetOperationLimit(cx, operationLimit);
}
JS_PUBLIC_API(void)
JS_ClearOperationCallback(JSContext *cx)
{
cx->operationCount = (int32) JS_MAX_OPERATION_LIMIT;
cx->operationLimit = JS_MAX_OPERATION_LIMIT;
cx->operationCallbackIsSet = 0;
cx->operationCallback = NULL;
}
JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx)
{
JS_ASSERT(cx->operationCallbackIsSet || !cx->operationCallback);
return cx->operationCallback;
}
JS_PUBLIC_API(uint32)
JS_GetOperationLimit(JSContext *cx)
{
JS_ASSERT(cx->operationCallbackIsSet);
return cx->operationLimit;
JS_SetOperationCallbackFunction(cx, NULL);
JS_SetOperationLimit(cx, JS_MAX_OPERATION_LIMIT);
}
JS_PUBLIC_API(void)
JS_SetOperationLimit(JSContext *cx, uint32 operationLimit)
{
/* Mixed operation and branch callbacks are not supported. */
JS_ASSERT(!cx->branchCallbackWasSet);
JS_ASSERT(operationLimit <= JS_MAX_OPERATION_LIMIT);
JS_ASSERT(operationLimit > 0);
JS_ASSERT(cx->operationCallbackIsSet);
cx->operationCount = (int32) operationLimit;
cx->operationLimit = operationLimit;
if (cx->operationCount > (int32) operationLimit)
cx->operationCount = (int32) operationLimit;
}
JS_PUBLIC_API(uint32)
JS_GetOperationLimit(JSContext *cx)
{
JS_ASSERT(!cx->branchCallbackWasSet);
return cx->operationLimit;
}
JS_PUBLIC_API(JSBranchCallback)
@ -5303,14 +5288,16 @@ JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb)
{
JSBranchCallback oldcb;
if (cx->operationCallbackIsSet) {
if (!cx->branchCallbackWasSet) {
#ifdef DEBUG
fprintf(stderr,
if (cx->operationCallback) {
fprintf(stderr,
"JS API usage error: call to JS_SetOperationCallback is followed by\n"
"invocation of deprecated JS_SetBranchCallback\n");
JS_ASSERT(0);
JS_ASSERT(0);
}
#endif
cx->operationCallbackIsSet = 0;
cx->branchCallbackWasSet = 1;
oldcb = NULL;
} else {
oldcb = (JSBranchCallback) cx->operationCallback;
@ -5320,11 +5307,32 @@ JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb)
cx->operationLimit = JSOW_SCRIPT_JUMP;
cx->operationCallback = (JSOperationCallback) cb;
} else {
JS_ClearOperationCallback(cx);
cx->operationCallback = NULL;
}
return oldcb;
}
JS_PUBLIC_API(void)
JS_SetOperationCallbackFunction(JSContext *cx, JSOperationCallback callback)
{
/* Mixed operation and branch callbacks are not supported. */
JS_ASSERT(!cx->branchCallbackWasSet);
cx->operationCallback = callback;
}
JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx)
{
JS_ASSERT(!cx->branchCallbackWasSet);
return cx->operationCallback;
}
JS_PUBLIC_API(void)
JS_TriggerOperationCallback(JSContext *cx)
{
cx->operationCount = 0;
}
JS_PUBLIC_API(JSBool)
JS_IsRunning(JSContext *cx)
{

View File

@ -2174,13 +2174,26 @@ JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc,
* The maximum value of the operation limit to pass to JS_SetOperationCallback
* and JS_SetOperationLimit.
*/
#define JS_MAX_OPERATION_LIMIT ((uint32) 0x7FFFFFFF)
#define JS_MAX_OPERATION_LIMIT ((uint32) 0x7FFFFFFF - (uint32) 1)
#define JS_OPERATION_WEIGHT_BASE 4096
extern JS_PUBLIC_API(void)
JS_SetOperationCallbackFunction(JSContext *cx, JSOperationCallback callback);
extern JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx);
/*
* Set the operation callback that the engine calls periodically after
* the internal operation count reaches the specified limit.
* Force a call to operation callback at some later moment. The function can be
* called from an arbitrary thread for any context.
*/
extern JS_PUBLIC_API(void)
JS_TriggerOperationCallback(JSContext *cx);
/*
* Set the limit for the internal operation counter. The engine calls the
* operation callback When the limit is reached.
*
* When operationLimit is JS_OPERATION_WEIGHT_BASE, the callback will be
* called at least after each backward jump in the interpreter. To minimize
@ -2191,14 +2204,7 @@ JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc,
* as a value for operationLimit.
*/
extern JS_PUBLIC_API(void)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
uint32 operationLimit);
extern JS_PUBLIC_API(void)
JS_ClearOperationCallback(JSContext *cx);
extern JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx);
JS_SetOperationLimit(JSContext *cx, uint32 operationLimit);
/*
* Get the operation limit associated with the operation callback. This API
@ -2208,13 +2214,12 @@ JS_GetOperationCallback(JSContext *cx);
extern JS_PUBLIC_API(uint32)
JS_GetOperationLimit(JSContext *cx);
/*
* Change the operation limit associated with the operation callback. This API
* function may be called only when the result of JS_GetOperationCallback(cx)
* is not null.
*/
extern JS_PUBLIC_API(void)
JS_SetOperationLimit(JSContext *cx, uint32 operationLimit);
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
uint32 operationLimit);
extern JS_PUBLIC_API(void)
JS_ClearOperationCallback(JSContext *cx);
/*
* Note well: JS_SetBranchCallback is deprecated. It is similar to

View File

@ -78,6 +78,9 @@
static PRUintn threadTPIndex;
static JSBool tpIndexInited = JS_FALSE;
static void
InitOperationLimit(JSContext *cx);
JS_BEGIN_EXTERN_C
JSBool
js_InitThreadPrivateIndex(void (*ptr)(void *))
@ -251,7 +254,7 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
memset(cx, 0, sizeof *cx);
cx->runtime = rt;
JS_ClearOperationCallback(cx);
js_InitOperationLimit(cx);
cx->debugHooks = &rt->globalDebugHooks;
#if JS_STACK_GROWTH_DIRECTION > 0
cx->stackLimit = (jsuword)-1;
@ -1355,10 +1358,11 @@ js_ResetOperationCount(JSContext *cx)
JS_ASSERT(cx->operationLimit > 0);
cx->operationCount = (int32) cx->operationLimit;
if (cx->operationCallbackIsSet)
return cx->operationCallback(cx);
JSOperationCallback cb = cx->operationCallback;
if (cb) {
if (!cx->branchCallbackWasSet)
return cb(cx);
if (cx->operationCallback) {
/*
* Invoke the deprecated branch callback. It may be called only when
* the top-most frame is scripted or JSOPTION_NATIVE_BRANCH_CALLBACK
@ -1367,7 +1371,7 @@ js_ResetOperationCount(JSContext *cx)
fp = js_GetTopStackFrame(cx);
script = fp ? fp->script : NULL;
if (script || JS_HAS_OPTION(cx, JSOPTION_NATIVE_BRANCH_CALLBACK))
return ((JSBranchCallback) cx->operationCallback)(cx, script);
return ((JSBranchCallback) cb)(cx, script);
}
return JS_TRUE;
}

View File

@ -750,7 +750,7 @@ struct JSContext {
* Operation count. It is declared as the first field in the struct to
* ensure the fastest possible access.
*/
int32 operationCount;
volatile int32 operationCount;
/* JSRuntime contextList linkage. */
JSCList link;
@ -860,11 +860,10 @@ struct JSContext {
JSErrorReporter errorReporter;
/*
* Flag indicating that the operation callback is set. When the flag is 0
* but operationCallback is not null, operationCallback stores the branch
* Flag indicating that operationCallback stores the deprecated branch
* callback.
*/
uint32 operationCallbackIsSet : 1;
uint32 branchCallbackWasSet : 1;
uint32 operationLimit : 31;
JSOperationCallback operationCallback;
@ -1274,6 +1273,23 @@ extern JSErrorFormatString js_ErrorFormatString[JSErr_Limit];
extern JSBool
js_ResetOperationCount(JSContext *cx);
static JS_INLINE void
js_InitOperationLimit(JSContext *cx)
{
/*
* Set the limit to 1 + max to detect if JS_SetOperationLimit() was ever
* called.
*/
cx->operationCount = (int32) JS_MAX_OPERATION_LIMIT + 1;
cx->operationLimit = JS_MAX_OPERATION_LIMIT + 1;
}
static JS_INLINE JSBool
js_HasOperationLimit(JSContext *cx)
{
return cx->operationLimit <= JS_MAX_OPERATION_LIMIT;
}
/*
* Get the current cx->fp, first lazily instantiating stack frames if needed.
* (Do not access cx->fp directly except in JS_REQUIRES_STACK code.)

View File

@ -2669,10 +2669,8 @@ js_Interpret(JSContext *cx)
*/
#define CHECK_BRANCH() \
JS_BEGIN_MACRO \
if ((cx->operationCount -= JSOW_SCRIPT_JUMP) <= 0) { \
if (!js_ResetOperationCount(cx)) \
goto error; \
} \
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_SCRIPT_JUMP)) \
goto error; \
JS_END_MACRO
#define BRANCH(n) \

View File

@ -372,7 +372,6 @@ hash_accum(uintptr_t& h, uintptr_t i)
h = ((h << 5) + h + (ORACLE_MASK & i)) & ORACLE_MASK;
}
JS_REQUIRES_STACK static inline int
stackSlotHash(JSContext* cx, unsigned slot)
{
@ -1065,9 +1064,14 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag
if (fragment == fragment->root) {
LIns* counter = lir->insLoadi(cx_ins,
offsetof(JSContext, operationCount));
LIns* updated = lir->ins2i(LIR_sub, counter, JSOW_SCRIPT_JUMP);
lir->insStorei(updated, cx_ins, offsetof(JSContext, operationCount));
guard(false, lir->ins2i(LIR_le, updated, 0), snapshot(TIMEOUT_EXIT));
if (js_HasOperationLimit(cx)) {
/* Add code to decrease the operationCount if the embedding relies
on its auto-updating. */
counter = lir->ins2i(LIR_sub, counter, JSOW_SCRIPT_JUMP);
lir->insStorei(counter, cx_ins,
offsetof(JSContext, operationCount));
}
guard(false, lir->ins2i(LIR_le, counter, 0), snapshot(TIMEOUT_EXIT));
}
/* If we are attached to a tree call guard, make sure the guard the inner tree exited from

View File

@ -3404,12 +3404,8 @@ ContextHolder::ContextHolder(JSContext *aOuterCx, JSObject *aSandbox)
JSOPTION_PRIVATE_IS_NSISUPPORTS);
JS_SetGlobalObject(mJSContext, aSandbox);
JS_SetContextPrivate(mJSContext, this);
if(JS_GetOperationCallback(aOuterCx))
{
JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback,
JS_GetOperationLimit(aOuterCx));
}
JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback,
JS_GetOperationLimit(aOuterCx));
}
}
@ -3424,19 +3420,8 @@ ContextHolder::ContextHolderOperationCallback(JSContext *cx)
JSOperationCallback callback = JS_GetOperationCallback(origCx);
JSBool ok = JS_TRUE;
if(callback)
{
ok = callback(origCx);
callback = JS_GetOperationCallback(origCx);
if(callback)
{
// If the callback is still set in the original context, reflect
// a possibly updated operation limit into cx.
JS_SetOperationLimit(cx, JS_GetOperationLimit(origCx));
return ok;
}
}
JS_ClearOperationCallback(cx);
JS_SetOperationLimit(cx, JS_GetOperationLimit(origCx));
return ok;
}