mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 472702 - using watchdog thread in js shell to trigger operation callback
This commit is contained in:
parent
873ec9adae
commit
c032867e33
453
js/src/js.cpp
453
js/src/js.cpp
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.)
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user