Bug 716647 - Part 6 1/2: Add shell function to deterministically request interrupt. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 24 Apr 2014 01:59:38 -0700
changeset 198447 079f4f0ed6a6ebbbdbc985ed5799c066f50759f4
parent 198446 a49c8cdf14c43d0b69993be83eff5df5e8de36e4
child 198448 462059b115ec4bd72d6e0646b99699640a3eecc6
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs716647
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 716647 - Part 6 1/2: Add shell function to deterministically request interrupt. (r=jimb)
js/src/jit-test/tests/debug/Frame-eval-19.js
js/src/shell/js.cpp
--- a/js/src/jit-test/tests/debug/Frame-eval-19.js
+++ b/js/src/jit-test/tests/debug/Frame-eval-19.js
@@ -1,24 +1,28 @@
 // Eval-in-frame of optimized frames to break out of an infinite loop.
 
 load(libdir + "jitopts.js");
 
-if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation))
+if (!jitTogglesMatch(Opts_IonEagerNoParallelCompilation))
   quit(0);
 
-withJitOptions(Opts_Ion2NoParallelCompilation, function () {
+withJitOptions(Opts_IonEagerNoParallelCompilation, function () {
   var g = newGlobal();
   var dbg = new Debugger;
 
   g.eval("" + function f(d) { g(d); });
   g.eval("" + function g(d) { h(d); });
-  g.eval("" + function h(d) { while (d); });
+  g.eval("" + function h(d) {
+    var i = 0;
+    while (d)
+      interruptIf(d && i++ == 4000);
+  });
 
-  timeout(5, function () {
+  setInterruptCallback(function () {
     dbg.addDebuggee(g);
     var frame = dbg.getNewestFrame();
     if (frame.callee.name != "h" || frame.implementation != "ion")
       return true;
     frame.eval("d = false;");
     return true;
   });
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -107,18 +107,18 @@ static size_t gMaxStackSize = 128 * size
 #endif
 
 /*
  * Limit the timeout to 30 minutes to prevent an overflow on platfoms
  * that represent the time internally in microseconds using 32-bit int.
  */
 static double MAX_TIMEOUT_INTERVAL = 1800.0;
 static double gTimeoutInterval = -1.0;
-static volatile bool gTimedOut = false;
-static Maybe<JS::PersistentRootedValue> gTimeoutFunc;
+static volatile bool gServiceInterrupt = false;
+static Maybe<JS::PersistentRootedValue> gInterruptFunc;
 
 static bool enableDisassemblyDumps = false;
 
 static bool printTiming = false;
 static const char *jsCacheDir = nullptr;
 static const char *jsCacheAsmJSPath = nullptr;
 static bool jsCachingEnabled = true;
 mozilla::Atomic<bool> jsCacheOpened(false);
@@ -330,17 +330,17 @@ struct JSShellContextData {
     /* Creation timestamp, used by the elapsed() shell builtin. */
     int64_t startTime;
 };
 
 static JSShellContextData *
 NewContextData()
 {
     /* Prevent creation of new contexts after we have been canceled. */
-    if (gTimedOut)
+    if (gServiceInterrupt)
         return nullptr;
 
     JSShellContextData *data = (JSShellContextData *)
                                js_calloc(sizeof(JSShellContextData), 1);
     if (!data)
         return nullptr;
     data->startTime = PRMJ_Now();
     return data;
@@ -353,26 +353,26 @@ GetContextData(JSContext *cx)
 
     JS_ASSERT(data);
     return data;
 }
 
 static bool
 ShellInterruptCallback(JSContext *cx)
 {
-    if (!gTimedOut)
+    if (!gServiceInterrupt)
         return true;
 
     bool result;
-    RootedValue timeoutFunc(cx, gTimeoutFunc.ref());
-    if (!timeoutFunc.isNull()) {
+    RootedValue interruptFunc(cx, gInterruptFunc.ref());
+    if (!interruptFunc.isNull()) {
         JS::AutoSaveExceptionState savedExc(cx);
-        JSAutoCompartment ac(cx, &timeoutFunc.toObject());
+        JSAutoCompartment ac(cx, &interruptFunc.toObject());
         RootedValue rval(cx);
-        if (!JS_CallFunctionValue(cx, JS::NullPtr(), timeoutFunc,
+        if (!JS_CallFunctionValue(cx, JS::NullPtr(), interruptFunc,
                                   JS::HandleValueArray::empty(), &rval))
         {
             return false;
         }
         if (rval.isBoolean())
             result = rval.toBoolean();
         else
             result = false;
@@ -446,17 +446,17 @@ RunFile(JSContext *cx, Handle<JSObject*>
     }
 
     #ifdef DEBUG
         if (dumpEntrainedVariables)
             AnalyzeEntrainedVariables(cx, script);
     #endif
     if (script && !compileOnly) {
         if (!JS_ExecuteScript(cx, obj, script)) {
-            if (!gQuitting && !gTimedOut)
+            if (!gQuitting && !gServiceInterrupt)
                 gExitCode = EXITCODE_RUNTIME_ERROR;
         }
         int64_t t2 = PRMJ_Now() - t1;
         if (printTiming)
             printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
     }
 }
 
@@ -509,17 +509,17 @@ ReadEvalPrintLoop(JSContext *cx, Handle<
          * cleanly.  This should be whenever we get a complete statement that
          * coincides with the end of a line.
          */
         int startline = lineno;
         typedef Vector<char, 32, ContextAllocPolicy> CharBuffer;
         CharBuffer buffer(cx);
         do {
             ScheduleWatchdog(cx->runtime(), -1);
-            gTimedOut = false;
+            gServiceInterrupt = false;
             errno = 0;
 
             char *line = GetLine(in, startline == lineno ? "js> " : "");
             if (!line) {
                 if (errno) {
                     JS_ReportError(cx, strerror(errno));
                     return;
                 }
@@ -3076,25 +3076,25 @@ Sleep_fn(JSContext *cx, unsigned argc, V
         t_ticks = (t_secs <= 0.0)
                   ? 0
                   : int64_t(PRMJ_USEC_PER_SEC * t_secs);
     }
     PR_Lock(gWatchdogLock);
     int64_t to_wakeup = PRMJ_Now() + t_ticks;
     for (;;) {
         PR_WaitCondVar(gSleepWakeup, t_ticks);
-        if (gTimedOut)
+        if (gServiceInterrupt)
             break;
         int64_t now = PRMJ_Now();
         if (!IsBefore(now, to_wakeup))
             break;
         t_ticks = to_wakeup - now;
     }
     PR_Unlock(gWatchdogLock);
-    return !gTimedOut;
+    return !gServiceInterrupt;
 }
 
 static bool
 InitWatchdog(JSRuntime *rt)
 {
     JS_ASSERT(!gWatchdogThread);
     gWatchdogLock = PR_NewLock();
     if (gWatchdogLock) {
@@ -3276,20 +3276,20 @@ ScheduleWatchdog(JSRuntime *rt, double t
     return true;
 }
 
 #endif /* !JS_THREADSAFE */
 
 static void
 CancelExecution(JSRuntime *rt)
 {
-    gTimedOut = true;
+    gServiceInterrupt = true;
     JS_RequestInterruptCallback(rt);
 
-    if (!gTimeoutFunc.ref().get().isNull()) {
+    if (!gInterruptFunc.ref().get().isNull()) {
         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. */
         /* Dummy assignment avoids GCC warning on "attribute warn_unused_result" */
         ssize_t dummy = write(2, msg, sizeof(msg) - 1);
         (void)dummy;
 #else
         fputs(msg, stderr);
@@ -3333,24 +3333,64 @@ Timeout(JSContext *cx, unsigned argc, Va
         return false;
 
     if (args.length() > 1) {
         RootedValue value(cx, args[1]);
         if (!value.isObject() || !value.toObject().is<JSFunction>()) {
             JS_ReportError(cx, "Second argument must be a timeout function");
             return false;
         }
-        gTimeoutFunc.ref() = value;
+        gInterruptFunc.ref() = value;
     }
 
     args.rval().setUndefined();
     return SetTimeoutValue(cx, t);
 }
 
 static bool
+InterruptIf(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() != 1) {
+        JS_ReportError(cx, "Wrong number of arguments");
+        return false;
+    }
+
+    if (ToBoolean(args[0])) {
+        gServiceInterrupt = true;
+        JS_RequestInterruptCallback(cx->runtime());
+    }
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+SetInterruptCallback(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() != 1) {
+        JS_ReportError(cx, "Wrong number of arguments");
+        return false;
+    }
+
+    RootedValue value(cx, args[0]);
+    if (!value.isObject() || !value.toObject().is<JSFunction>()) {
+        JS_ReportError(cx, "Argument must be a function");
+        return false;
+    }
+    gInterruptFunc.ref() = value;
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
 Elapsed(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() == 0) {
         double d = 0.0;
         JSShellContextData *data = GetContextData(cx);
         if (data)
             d = PRMJ_Now() - data->startTime;
@@ -4593,17 +4633,28 @@ static const JSFunctionSpecWithHelp shel
 "  its value."),
 
 #endif
 
     JS_FN_HELP("timeout", Timeout, 1, 0,
 "timeout([seconds], [func])",
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  A negative value (default) means that the execution time is unlimited.\n"
-"  If a second argument is provided, it will be invoked when the timer elapses.\n"),
+"  If a second argument is provided, it will be invoked when the timer elapses.\n"
+"  Calling this function will replace any callback set by |setInterruptCallback|.\n"),
+
+    JS_FN_HELP("interruptIf", InterruptIf, 1, 0,
+"interruptIf(cond)",
+"  Requests interrupt callback if cond is true. If a callback function is set via\n"
+"  |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."),
+
+    JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0,
+"setInterruptCallback(func)",
+"  Sets func as the interrupt callback function.\n"
+"  Calling this function will replace any callback set by |timeout|.\n"),
 
     JS_FN_HELP("elapsed", Elapsed, 0, 0,
 "elapsed()",
 "  Execution time elapsed for the current context."),
 
     JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0,
 "decompileFunction(func)",
 "  Decompile a function."),
@@ -6129,17 +6180,17 @@ main(int argc, char **argv, char **envp)
     rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS);
     if (!rt)
         return 1;
 
     JS::SetOutOfMemoryCallback(rt, my_OOMCallback);
     if (!SetRuntimeOptions(rt, op))
         return 1;
 
-    gTimeoutFunc.construct(rt, NullValue());
+    gInterruptFunc.construct(rt, NullValue());
 
     JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff);
 #ifdef JSGC_GENERATIONAL
     Maybe<JS::AutoDisableGenerationalGC> noggc;
     if (op.getBoolOption("no-ggc"))
         noggc.construct(rt);
 #endif
 
@@ -6179,17 +6230,17 @@ main(int argc, char **argv, char **envp)
     if (OOM_printAllocationCount)
         printf("OOM max count: %u\n", OOM_counter);
 #endif
 
     DestroyContext(cx, true);
 
     KillWatchdog();
 
-    gTimeoutFunc.destroy();
+    gInterruptFunc.destroy();
 
 #ifdef JS_THREADSAFE
     for (size_t i = 0; i < workerThreads.length(); i++)
         PR_JoinThread(workerThreads[i]);
 #endif
 
 #ifdef JSGC_GENERATIONAL
     if (!noggc.empty())