bug 479252 - avoiding watchdog ticks when idle in jsshell. r=gal

This commit is contained in:
Igor Bukanov 2009-02-20 13:47:46 +01:00
parent 7fe73224c5
commit fe9a728583
3 changed files with 280 additions and 154 deletions

View File

@ -5336,6 +5336,17 @@ JS_TriggerOperationCallback(JSContext *cx)
JS_ATOMIC_SET(&cx->operationCallbackFlag, 1); JS_ATOMIC_SET(&cx->operationCallbackFlag, 1);
} }
JS_PUBLIC_API(void)
JS_TriggerAllOperationCallbacks(JSRuntime *rt)
{
JSContext *acx, *iter;
JS_LOCK_GC(rt);
iter = NULL;
while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)))
JS_TriggerOperationCallback(acx);
JS_UNLOCK_GC(rt);
}
JS_PUBLIC_API(JSBool) JS_PUBLIC_API(JSBool)
JS_IsRunning(JSContext *cx) JS_IsRunning(JSContext *cx)
{ {

View File

@ -2281,6 +2281,9 @@ JS_GetOperationCallback(JSContext *cx);
extern JS_PUBLIC_API(void) extern JS_PUBLIC_API(void)
JS_TriggerOperationCallback(JSContext *cx); JS_TriggerOperationCallback(JSContext *cx);
extern JS_PUBLIC_API(void)
JS_TriggerAllOperationCallbacks(JSRuntime *rt);
extern JS_PUBLIC_API(JSBool) extern JS_PUBLIC_API(JSBool)
JS_IsRunning(JSContext *cx); JS_IsRunning(JSContext *cx);

View File

@ -43,6 +43,7 @@
*/ */
#include "jsstddef.h" #include "jsstddef.h"
#include <errno.h> #include <errno.h>
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -98,7 +99,8 @@
typedef enum JSShellExitCode { typedef enum JSShellExitCode {
EXITCODE_RUNTIME_ERROR = 3, EXITCODE_RUNTIME_ERROR = 3,
EXITCODE_FILE_NOT_FOUND = 4, EXITCODE_FILE_NOT_FOUND = 4,
EXITCODE_OUT_OF_MEMORY = 5 EXITCODE_OUT_OF_MEMORY = 5,
EXITCODE_TIMEOUT = 6
} JSShellExitCode; } JSShellExitCode;
size_t gStackChunkSize = 8192; size_t gStackChunkSize = 8192;
@ -109,26 +111,41 @@ static jsuword gStackBase;
static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA; static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
static uint32 gOperationLimit = 0; /*
* Limit the timeout to 30 minutes to prevent an overflow on platfoms
* that represent the time internally in microseconds using 32-bit int.
*/
static jsdouble MAX_TIMEOUT_INTERVAL = 1800.0;
static jsdouble gTimeoutInterval = -1.0;
static volatile bool gCanceled = false;
static JSBool static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t); SetTimeoutValue(JSContext *cx, jsdouble t);
static double static bool
GetTimeoutValue(JSContext *cx); InitWatchdog(JSRuntime *rt);
static void static void
StopWatchdog(JSRuntime *rt); KillWatchdog();
static JSBool static bool
StartWatchdog(JSRuntime *rt); ScheduleWatchdog(JSRuntime *rt, jsdouble t);
static void
CancelExecution(JSRuntime *rt);
/* /*
* Watchdog thread state. * Watchdog thread state.
*/ */
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
static PRLock *gWatchdogLock = NULL;
static PRCondVar *gWatchdogWakeup = NULL; static PRCondVar *gWatchdogWakeup = NULL;
static PRThread *gWatchdogThread = NULL; static PRThread *gWatchdogThread = NULL;
static bool gWatchdogHasTimeout = false;
static PRIntervalTime gWatchdogTimeout = 0;
static PRCondVar *gSleepWakeup = NULL;
/* /*
* Holding the gcLock already guarantees that the context list is locked when * Holding the gcLock already guarantees that the context list is locked when
@ -141,20 +158,19 @@ static PRThread *gWatchdogThread = NULL;
JS_END_MACRO JS_END_MACRO
#else #else
static JSRuntime *gRuntime = NULL; static JSRuntime *gRuntime = NULL;
/* /*
* Since signal handlers can't block, we must disable them before manipulating * Since signal handlers can't block, we must disable them before manipulating
* the context list. * the context list.
*/ */
#define WITH_LOCKED_CONTEXT_LIST(x) \
#define WITH_LOCKED_CONTEXT_LIST(x) \ JS_BEGIN_MACRO \
JS_BEGIN_MACRO \ ScheduleWatchdog(gRuntime, -1); \
StopWatchdog(gRuntime); \ x; \
x; \ ScheduleWatchdog(gRuntime, gTimeoutInterval); \
StartWatchdog(gRuntime); \
JS_END_MACRO JS_END_MACRO
#endif #endif
int gExitCode = 0; int gExitCode = 0;
@ -263,6 +279,10 @@ struct JSShellContextData {
static JSShellContextData * static JSShellContextData *
NewContextData() NewContextData()
{ {
/* Prevent creation of new contexts after we have been canceled. */
if (gCanceled)
return NULL;
JSShellContextData *data = (JSShellContextData *) JSShellContextData *data = (JSShellContextData *)
calloc(sizeof(JSShellContextData), 1); calloc(sizeof(JSShellContextData), 1);
if (!data) if (!data)
@ -283,12 +303,7 @@ GetContextData(JSContext *cx)
static JSBool static JSBool
ShellOperationCallback(JSContext *cx) ShellOperationCallback(JSContext *cx)
{ {
JSShellContextData *data; return !gCanceled;
if ((data = GetContextData(cx)) != NULL) {
/* If we spent too much time in this script, abort it. */
return !gOperationLimit || (uint32(js_IntervalNow() - data->startTime) < gOperationLimit);
}
return JS_TRUE;
} }
static void static void
@ -383,8 +398,6 @@ Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY)
buffer = NULL; buffer = NULL;
size = 0; /* assign here to avoid warnings */ size = 0; /* assign here to avoid warnings */
do { do {
size_t len = 0; /* initialize to avoid warnings */
/* /*
* Accumulate lines until we get a 'compilable unit' - one that either * Accumulate lines until we get a 'compilable unit' - one that either
* generates an error (before running out of source) or that compiles * generates an error (before running out of source) or that compiles
@ -392,7 +405,13 @@ Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY)
* coincides with the end of a line. * coincides with the end of a line.
*/ */
startline = lineno; startline = lineno;
size_t len = 0; /* initialize to avoid warnings */
do { do {
ScheduleWatchdog(cx->runtime, -1);
#ifdef JS_THREADSAFE
jsrefcount rc = JS_SuspendRequest(cx);
#endif
gCanceled = false;
errno = 0; errno = 0;
char *line = GetLine(file, startline == lineno ? "js> " : ""); char *line = GetLine(file, startline == lineno ? "js> " : "");
if (!line) { if (!line) {
@ -432,6 +451,13 @@ Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY)
free(line); free(line);
} }
lineno++; lineno++;
#ifdef JS_THREADSAFE
JS_ResumeRequest(cx, rc);
#endif
if (!ScheduleWatchdog(cx->runtime, gTimeoutInterval)) {
hitEOF = JS_TRUE;
break;
}
} while (!JS_BufferIsCompilableUnit(cx, obj, buffer, len)); } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, len));
if (hitEOF && !buffer) if (hitEOF && !buffer)
@ -693,6 +719,8 @@ extern void js_InitJITStatsClass(JSContext *cx, JSObject *glob);
return usage(); return usage();
Process(cx, obj, argv[i], JS_FALSE); Process(cx, obj, argv[i], JS_FALSE);
if (gExitCode != 0)
return gExitCode;
/* /*
* XXX: js -f foo.js should interpret foo.js and then * XXX: js -f foo.js should interpret foo.js and then
@ -2808,27 +2836,59 @@ ShapeOf(JSContext *cx, uintN argc, jsval *vp)
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
/*
* Check that t1 comes strictly before t2. The function correctly deals with
* PRIntervalTime wrap-around between t2 and t1 assuming that t2 and t1 stays
* within INT32_MAX from each other. We use MAX_TIMEOUT_INTERVAL to enforce
* this restriction.
*/
static bool
IsBefore(PRIntervalTime t1, PRIntervalTime t2)
{
return int32(t1 - t2) < 0;
}
static JSBool static JSBool
Sleep_fn(JSContext *cx, uintN argc, jsval *vp) Sleep_fn(JSContext *cx, uintN argc, jsval *vp)
{ {
jsdouble t_secs; PRIntervalTime t_ticks;
PRUint32 t_ticks;
jsrefcount rc;
if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs)) if (argc == 0) {
return JS_FALSE; t_ticks = 0;
} else {
jsdouble t_secs;
if (t_secs < 0 || JSDOUBLE_IS_NaN(t_secs)) if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs))
t_secs = 0; return JS_FALSE;
rc = JS_SuspendRequest(cx); /* NB: The next condition also filter out NaNs. */
t_ticks = (PRUint32)(PR_TicksPerSecond() * t_secs); if (!(t_secs <= MAX_TIMEOUT_INTERVAL)) {
if (PR_Sleep(t_ticks) == PR_SUCCESS) JS_ReportError(cx, "Excessive sleep interval");
*vp = JSVAL_TRUE; return JS_FALSE;
else }
*vp = JSVAL_FALSE; t_ticks = (t_secs <= 0.0)
JS_ResumeRequest(cx, rc); ? 0
return JS_TRUE; : PRIntervalTime(PR_TicksPerSecond() * t_secs);
}
if (t_ticks == 0) {
JS_YieldRequest(cx);
} else {
jsrefcount rc = JS_SuspendRequest(cx);
PR_Lock(gWatchdogLock);
PRIntervalTime to_wakeup = PR_IntervalNow() + t_ticks;
for (;;) {
PR_WaitCondVar(gSleepWakeup, t_ticks);
if (gCanceled)
break;
PRIntervalTime now = PR_IntervalNow();
if (!IsBefore(now, to_wakeup))
break;
t_ticks = to_wakeup - now;
}
PR_Unlock(gWatchdogLock);
JS_ResumeRequest(cx, rc);
}
return !gCanceled;
} }
typedef struct ScatterThreadData ScatterThreadData; typedef struct ScatterThreadData ScatterThreadData;
@ -3065,165 +3125,222 @@ fail:
goto out; goto out;
} }
static bool
InitWatchdog(JSRuntime *rt)
{
JS_ASSERT(!gWatchdogThread);
gWatchdogLock = PR_NewLock();
if (gWatchdogLock) {
gWatchdogWakeup = PR_NewCondVar(gWatchdogLock);
if (gWatchdogWakeup) {
gSleepWakeup = PR_NewCondVar(gWatchdogLock);
if (gSleepWakeup)
return true;
PR_DestroyCondVar(gWatchdogWakeup);
}
PR_DestroyLock(gWatchdogLock);
}
return false;
}
static void
KillWatchdog()
{
PRThread *thread;
PR_Lock(gWatchdogLock);
thread = gWatchdogThread;
if (thread) {
/*
* The watchdog thread is running, tell it to terminate waking it up
* if necessary.
*/
gWatchdogThread = NULL;
PR_NotifyCondVar(gWatchdogWakeup);
}
PR_Unlock(gWatchdogLock);
if (thread)
PR_JoinThread(thread);
PR_DestroyCondVar(gSleepWakeup);
PR_DestroyCondVar(gWatchdogWakeup);
PR_DestroyLock(gWatchdogLock);
}
static void static void
WatchdogMain(void *arg) WatchdogMain(void *arg)
{ {
JSRuntime *rt = (JSRuntime *) arg; JSRuntime *rt = (JSRuntime *) arg;
JS_LOCK_GC(rt); PR_Lock(gWatchdogLock);
while (gWatchdogThread) { while (gWatchdogThread) {
JSContext *acx = NULL; PRIntervalTime now = PR_IntervalNow();
if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) {
while ((acx = js_NextActiveContext(rt, acx))) /*
JS_TriggerOperationCallback(acx); * The timeout has just expired. Trigger the operation callback
* outside the lock.
*/
gWatchdogHasTimeout = false;
PR_Unlock(gWatchdogLock);
CancelExecution(rt);
PR_Lock(gWatchdogLock);
/* Wake up any threads doing sleep. */
PR_NotifyAllCondVar(gSleepWakeup);
} else {
PRIntervalTime sleepDuration = gWatchdogHasTimeout
? gWatchdogTimeout - now
: PR_INTERVAL_NO_TIMEOUT;
#ifdef DEBUG #ifdef DEBUG
PRStatus status = PRStatus status =
#endif #endif
/* Trigger the operation callbacks every second. */ PR_WaitCondVar(gWatchdogWakeup, sleepDuration);
PR_WaitCondVar(gWatchdogWakeup, PR_SecondsToInterval(1)); JS_ASSERT(status == PR_SUCCESS);
JS_ASSERT(status == PR_SUCCESS); }
} }
/* Wake up the main thread waiting for the watchdog to terminate. */ PR_Unlock(gWatchdogLock);
PR_NotifyCondVar(gWatchdogWakeup);
JS_UNLOCK_GC(rt);
} }
static JSBool static bool
StartWatchdog(JSRuntime *rt) ScheduleWatchdog(JSRuntime *rt, jsdouble t)
{ {
if (gWatchdogThread || !gOperationLimit) if (t <= 0) {
return JS_TRUE; PR_Lock(gWatchdogLock);
gWatchdogHasTimeout = false;
PR_Unlock(gWatchdogLock);
return true;
}
JS_LOCK_GC(rt); PRIntervalTime interval = PRIntervalTime(ceil(t * PR_TicksPerSecond()));
gWatchdogThread = PR_CreateThread(PR_USER_THREAD, PRIntervalTime timeout = PR_IntervalNow() + interval;
WatchdogMain, PR_Lock(gWatchdogLock);
rt,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_UNJOINABLE_THREAD,
0);
if (!gWatchdogThread) { if (!gWatchdogThread) {
JS_UNLOCK_GC(rt); JS_ASSERT(!gWatchdogHasTimeout);
return JS_FALSE; gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
WatchdogMain,
rt,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_JOINABLE_THREAD,
0);
if (!gWatchdogThread) {
PR_Unlock(gWatchdogLock);
return false;
}
} else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) {
PR_NotifyCondVar(gWatchdogWakeup);
} }
JS_UNLOCK_GC(rt); gWatchdogHasTimeout = true;
return JS_TRUE; gWatchdogTimeout = timeout;
PR_Unlock(gWatchdogLock);
return true;
} }
static void #else /* !JS_THREADSAFE */
StopWatchdog(JSRuntime *rt)
{
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);
}
#else
static void
WatchdogHandler(int sig)
{
JSRuntime *rt = gRuntime;
JSContext *acx = NULL;
while ((acx = js_NextActiveContext(rt, acx)))
JS_TriggerOperationCallback(acx);
#ifndef XP_WIN
alarm(1);
#endif
}
#ifdef XP_WIN #ifdef XP_WIN
static HANDLE gTimerHandle = 0; static HANDLE gTimerHandle = 0;
VOID CALLBACK TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) VOID CALLBACK
TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{ {
WatchdogHandler(0); CancelExecution(rt);
} }
#endif
static JSBool
StartWatchdog(JSRuntime *rt)
{
if (!gOperationLimit)
return JS_TRUE;
#ifdef XP_WIN
JS_ASSERT(gTimerHandle == 0);
if (!CreateTimerQueueTimer(&gTimerHandle,
NULL,
(WAITORTIMERCALLBACK)TimerCallback,
NULL,
1000,
1000,
WT_EXECUTEINTIMERTHREAD))
return JS_FALSE;
#else #else
signal(SIGALRM, WatchdogHandler); /* set the Alarm signal capture */
alarm(1); static void
AlarmHandler(int sig)
{
CancelExecution(gRuntime);
}
#endif #endif
return JS_TRUE; static bool
InitWatchdog(JSRuntime *rt)
{
gRuntime = rt;
return true;
} }
static void static void
StopWatchdog(JSRuntime *rt) KillWatchdog()
{ {
#ifdef XP_WIN ScheduleWatchdog(gRuntime, -1);
DeleteTimerQueueTimer(NULL, gTimerHandle, NULL);
gTimerHandle = 0;
#else
alarm(0);
signal(SIGALRM, NULL);
#endif
} }
#endif /* JS_THREADSAFE */ static bool
ScheduleWatchdog(JSRuntime *rt, jsdouble t)
{
#ifdef XP_WIN
if (gTimerHandle) {
DeleteTimerQueueTimer(NULL, gTimerHandle, NULL);
gTimerHandle = 0;
}
if (t > 0 &&
!CreateTimerQueueTimer(&gTimerHandle,
NULL,
(WAITORTIMERCALLBACK)TimerCallback,
rt,
DWORD(ceil(t * 1000.0)),
0,
WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) {
gTimerHandle = 0;
return false;
}
#else
/* FIXME: use setitimer when available for sub-second resolution. */
if (t <= 0) {
alarm(0);
signal(SIGALRM, NULL);
} else {
signal(SIGALRM, AlarmHandler); /* set the Alarm signal capture */
alarm(ceil(t));
}
#endif
return true;
}
#endif /* !JS_THREADSAFE */
static void
CancelExecution(JSRuntime *rt)
{
gCanceled = true;
if (gExitCode == 0)
gExitCode = EXITCODE_TIMEOUT;
JS_TriggerAllOperationCallbacks(rt);
static const char msg[] = "Script runs for too long, terminating.\n";
#if defined(XP_UNIX) && !defined(JS_THREADSAFE)
/* It is not safe to call fputs from signals. */
write(2, msg, sizeof(msg) - 1);
#else
fputs(msg, stderr);
#endif
}
static JSBool static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t) SetTimeoutValue(JSContext *cx, jsdouble t)
{ {
/* NB: The next condition also filter out NaNs. */ /* NB: The next condition also filter out NaNs. */
if (!(t <= 3600.0)) { if (!(t <= MAX_TIMEOUT_INTERVAL)) {
JS_ReportError(cx, "Excessive timeout value"); JS_ReportError(cx, "Excessive timeout value");
return JS_FALSE; return JS_FALSE;
} }
gTimeoutInterval = t;
gOperationLimit = (t > 0) ? JSInt64(t*1000) : 0; if (!ScheduleWatchdog(cx->runtime, t)) {
JS_ReportError(cx, "Failed to create the watchdog");
if (!StartWatchdog(cx->runtime)) {
JS_ReportError(cx, "failed to create the watchdog");
return JS_FALSE; return JS_FALSE;
} }
return JS_TRUE; return JS_TRUE;
} }
static double
GetTimeoutValue(JSContext *cx)
{
if (!gOperationLimit)
return -1;
return gOperationLimit/PRMJ_USEC_PER_MSEC;
}
static JSBool static JSBool
Timeout(JSContext *cx, uintN argc, jsval *vp) Timeout(JSContext *cx, uintN argc, jsval *vp)
{ {
if (argc == 0) if (argc == 0)
return JS_NewNumberValue(cx, GetTimeoutValue(cx), vp); return JS_NewNumberValue(cx, gTimeoutInterval, vp);
if (argc > 1) { if (argc > 1) {
JS_ReportError(cx, "Wrong number of arguments"); JS_ReportError(cx, "Wrong number of arguments");
@ -4377,13 +4494,8 @@ main(int argc, char **argv, char **envp)
if (!rt) if (!rt)
return 1; return 1;
#ifdef JS_THREADSAFE if (!InitWatchdog(rt))
gWatchdogWakeup = JS_NEW_CONDVAR(rt->gcLock);
if (!gWatchdogWakeup)
return 1; return 1;
#else
gRuntime = rt;
#endif
JS_SetContextCallback(rt, ContextCallback); JS_SetContextCallback(rt, ContextCallback);
@ -4498,7 +4610,7 @@ main(int argc, char **argv, char **envp)
JS_DestroyContext(cx) JS_DestroyContext(cx)
); );
StopWatchdog(rt); KillWatchdog();
JS_DestroyRuntime(rt); JS_DestroyRuntime(rt);
JS_ShutDown(); JS_ShutDown();