Backed out changeset 8272530c90ef (bug 1276626) a=lizzard CLOSED TREE FIREFOX_BETA_49_END
authorTooru Fujisawa <arai_a@mac.com>
Thu, 08 Sep 2016 09:35:46 +0900
changeset 342630 ddeb7907bc1e854738b164bab5ff71c3dcf1d786
parent 342629 41b93f219e9fb42592bb0fdfe599474ebcdccc87
child 342631 b22f7ff530bb0cedd0b38d5a837ab3482071e086
child 350295 da53bdac7d0251987216190403fefe2db233a604
push id1195
push userkwierso@gmail.com
push dateMon, 12 Sep 2016 20:40:28 +0000
treeherdermozilla-release@b22f7ff530bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslizzard
bugs1276626
milestone49.0
backs out8272530c90ef5a0df5f5dd973c6fdd2a067e4b27
Backed out changeset 8272530c90ef (bug 1276626) a=lizzard CLOSED TREE
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -999,17 +999,16 @@ class Watchdog
     explicit Watchdog(WatchdogManager* aManager)
       : mManager(aManager)
       , mLock(nullptr)
       , mWakeup(nullptr)
       , mThread(nullptr)
       , mHibernating(false)
       , mInitialized(false)
       , mShuttingDown(false)
-      , mSlowScriptSecondHalfCount(0)
       , mMinScriptRunTimeSeconds(1)
     {}
     ~Watchdog() { MOZ_ASSERT(!Initialized()); }
 
     WatchdogManager* Manager() { return mManager; }
     bool Initialized() { return mInitialized; }
     bool ShuttingDown() { return mShuttingDown; }
     PRLock* GetLock() { return mLock; }
@@ -1109,33 +1108,25 @@ class Watchdog
         PR_NotifyCondVar(mWakeup);
     }
 
     int32_t MinScriptRunTimeSeconds()
     {
         return mMinScriptRunTimeSeconds;
     }
 
-    uint32_t GetSlowScriptSecondHalfCount() { return mSlowScriptSecondHalfCount; }
-    void IncrementSlowScriptSecondHalfCount() { mSlowScriptSecondHalfCount++; }
-    void ResetSlowScriptSecondHalfCount() { mSlowScriptSecondHalfCount = 0; }
-
   private:
     WatchdogManager* mManager;
 
     PRLock* mLock;
     PRCondVar* mWakeup;
     PRThread* mThread;
     bool mHibernating;
     bool mInitialized;
     bool mShuttingDown;
-
-    // See the comment in WatchdogMain.
-    uint32_t mSlowScriptSecondHalfCount;
-
     mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
 };
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
@@ -1193,18 +1184,16 @@ class WatchdogManager : public nsIObserv
         // The watchdog reads this state, so acquire the lock before writing it.
         MOZ_ASSERT(NS_IsMainThread());
         Maybe<AutoLockWatchdog> lock;
         if (mWatchdog)
             lock.emplace(mWatchdog);
 
         // Write state.
         mTimestamps[TimestampRuntimeStateChange] = PR_Now();
-        if (mWatchdog)
-            mWatchdog->ResetSlowScriptSecondHalfCount();
         mRuntimeState = active ? RUNTIME_ACTIVE : RUNTIME_INACTIVE;
 
         // The watchdog may be hibernating, waiting for the runtime to go
         // active. Wake it up if necessary.
         if (active && mWatchdog && mWatchdog->Hibernating())
             mWatchdog->WakeUp();
     }
     bool IsRuntimeActive() { return mRuntimeState == RUNTIME_ACTIVE; }
@@ -1324,59 +1313,36 @@ WatchdogMain(void* arg)
 
         // Rise and shine.
         manager->RecordTimestamp(TimestampWatchdogWakeup);
 
         // Don't request an interrupt callback unless the current script has
         // been running long enough that we might show the slow script dialog.
         // Triggering the callback from off the main thread can be expensive.
 
-        // If we spend too much time running JS code in an event handler, then
-        // we want to show the slow script UI.  The timeout T is controlled by
-        // prefs.  We want to avoid showing the slow script dialog if the
-        // user's laptop goes to sleep in the middle of running a script.  To
-        // ensure this, we invoke the interrupt callback only after the T/2
-        // elapsed twice.  If the computer is put to sleep during one of the
-        // T/2 periods, the script still has the other T/2 seconds to finish.
-        //
-        //   + <-- TimestampRuntimeStateChange = PR_Now()
-        //   |     mSlowScriptSecondHalfCount = 0
-        //   |
-        //   | T/2
-        //   |
-        //   + <-- mSlowScriptSecondHalfCount = 1
-        //   |
-        //   | T/2
-        //   |
-        //   + <-- mSlowScriptSecondHalfCount = 2
-        //   |     Invoke interrupt callback
-        //   |
-        //   | T/2
-        //   |
-        //   + <-- mSlowScriptSecondHalfCount = 3
-        //   |
-        //   | T/2
-        //   |
-        //   + <-- mSlowScriptSecondHalfCount = 4
-        //         Invoke interrupt callback
-        //
-        PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC;
-        if (manager->IsRuntimeActive()) {
-            uint32_t count = self->GetSlowScriptSecondHalfCount() + 1;
-            if (manager->TimeSinceLastRuntimeStateChange() >= usecs * count / 2) {
-                self->IncrementSlowScriptSecondHalfCount();
-                if (count % 2 == 0) {
-                    bool debuggerAttached = false;
-                    nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
-                    if (dbg)
-                        dbg->GetIsDebuggerAttached(&debuggerAttached);
-                    if (!debuggerAttached)
-                        JS_RequestInterruptCallback(manager->Runtime()->Runtime());
-                }
-            }
+        // We want to avoid showing the slow script dialog if the user's laptop
+        // goes to sleep in the middle of running a script. To ensure this, we
+        // invoke the interrupt callback after only half the timeout has
+        // elapsed. The callback simply records the fact that it was called in
+        // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2)
+        // seconds and invoke the callback again. This time around it sees
+        // mSlowScriptSecondHalf is set and so it shows the slow script
+        // dialog. If the computer is put to sleep during one of the (timeout/2)
+        // periods, the script still has the other (timeout/2) seconds to
+        // finish.
+        PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2;
+        if (manager->IsRuntimeActive() &&
+            manager->TimeSinceLastRuntimeStateChange() >= usecs)
+        {
+            bool debuggerAttached = false;
+            nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
+            if (dbg)
+                dbg->GetIsDebuggerAttached(&debuggerAttached);
+            if (!debuggerAttached)
+                JS_RequestInterruptCallback(manager->Runtime()->Runtime());
         }
     }
 
     // Tell the manager that we've shut down.
     self->Finished();
 }
 
 PRTime
@@ -1409,16 +1375,17 @@ XPCJSRuntime::InterruptCallback(JSContex
 {
     XPCJSRuntime* self = XPCJSRuntime::Get();
 
     // Normally we record mSlowScriptCheckpoint when we start to process an
     // event. However, we can run JS outside of event handlers. This code takes
     // care of that case.
     if (self->mSlowScriptCheckpoint.IsNull()) {
         self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+        self->mSlowScriptSecondHalf = false;
         self->mSlowScriptActualWait = mozilla::TimeDuration();
         self->mTimeoutAccumulated = false;
         return true;
     }
 
     // Sometimes we get called back during XPConnect initialization, before Gecko
     // has finished bootstrapping. Avoid crashing in nsContentUtils below.
     if (!nsContentUtils::IsInitialized())
@@ -1429,21 +1396,30 @@ XPCJSRuntime::InterruptCallback(JSContex
     // is.
     TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
     bool chrome = nsContentUtils::IsCallerChrome();
     const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
                                   : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
     int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
 
     // If there's no limit, or we're within the limit, let it go.
-    if (limit == 0 || duration.ToSeconds() < limit)
+    if (limit == 0 || duration.ToSeconds() < limit / 2.0)
         return true;
 
     self->mSlowScriptActualWait += duration;
 
+    // In order to guard against time changes or laptops going to sleep, we
+    // don't trigger the slow script warning until (limit/2) seconds have
+    // elapsed twice.
+    if (!self->mSlowScriptSecondHalf) {
+        self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+        self->mSlowScriptSecondHalf = true;
+        return true;
+    }
+
     //
     // This has gone on long enough! Time to take action. ;-)
     //
 
     // Get the DOM window associated with the running script. If the script is
     // running in a non-DOM scope, we have to just let it keep running.
     RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
     RefPtr<nsGlobalWindow> win = WindowOrNull(global);
@@ -3414,16 +3390,17 @@ XPCJSRuntime::XPCJSRuntime()
    mGCIsRunning(false),
    mNativesToReleaseArray(),
    mDoingFinalization(false),
    mVariantRoots(nullptr),
    mWrappedJSRoots(nullptr),
    mObjectHolderRoots(nullptr),
    mWatchdogManager(new WatchdogManager(this)),
    mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()),
+   mSlowScriptSecondHalf(false),
    mTimeoutAccumulated(false)
 {
 }
 
 #ifdef XP_WIN
 static size_t
 GetWindowsStackSize()
 {
@@ -3752,16 +3729,17 @@ XPCJSRuntime::BeforeProcessTask(bool aMi
             // condition is triggered here by a Promise "then" callback.
 
             NS_DispatchToMainThread(new Runnable());
         }
     }
 
     // Start the slow script timer.
     mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
+    mSlowScriptSecondHalf = false;
     mSlowScriptActualWait = mozilla::TimeDuration();
     mTimeoutAccumulated = false;
 
     // As we may be entering a nested event loop, we need to
     // cancel any ongoing performance measurement.
     js::ResetPerformanceMonitoring(Get()->Runtime());
 
     // Push a null JSContext so that we don't see any script during
@@ -3771,16 +3749,17 @@ XPCJSRuntime::BeforeProcessTask(bool aMi
     CycleCollectedJSRuntime::BeforeProcessTask(aMightBlock);
 }
 
 void
 XPCJSRuntime::AfterProcessTask(uint32_t aNewRecursionDepth)
 {
     // Now that we're back to the event loop, reset the slow script checkpoint.
     mSlowScriptCheckpoint = mozilla::TimeStamp();
+    mSlowScriptSecondHalf = false;
 
     // Call cycle collector occasionally.
     MOZ_ASSERT(NS_IsMainThread());
     nsJSContext::MaybePokeCC();
 
     CycleCollectedJSRuntime::AfterProcessTask(aNewRecursionDepth);
 
     // Now that we are certain that the event is complete,
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -635,19 +635,33 @@ private:
     nsTArray<xpcGCCallback> extraGCCallbacks;
     RefPtr<WatchdogManager> mWatchdogManager;
     JS::GCSliceCallback mPrevGCSliceCallback;
     JS::PersistentRootedObject mUnprivilegedJunkScope;
     JS::PersistentRootedObject mPrivilegedJunkScope;
     JS::PersistentRootedObject mCompilationScope;
     RefPtr<AsyncFreeSnowWhite> mAsyncSnowWhiteFreer;
 
-    // mSlowScriptCheckpoint is set to the time when we started processing the
-    // current event.  We use it to determine whether the interrupt callback
-    // needs to do anything.
+    // If we spend too much time running JS code in an event handler, then we
+    // want to show the slow script UI. The timeout T is controlled by prefs. We
+    // invoke the interrupt callback once after T/2 seconds and set
+    // mSlowScriptSecondHalf to true. After another T/2 seconds, we invoke the
+    // interrupt callback again. Since mSlowScriptSecondHalf is now true, it
+    // shows the slow script UI. The reason we invoke the callback twice is to
+    // ensure that putting the computer to sleep while running a script doesn't
+    // cause the UI to be shown. If the laptop goes to sleep during one of the
+    // timeout periods, the script still has the other T/2 seconds to complete
+    // before the slow script UI is shown.
+    bool mSlowScriptSecondHalf;
+
+    // mSlowScriptCheckpoint is set to the time when:
+    // 1. We started processing the current event, or
+    // 2. mSlowScriptSecondHalf was set to true
+    // (whichever comes later). We use it to determine whether the interrupt
+    // callback needs to do anything.
     mozilla::TimeStamp mSlowScriptCheckpoint;
     // Accumulates total time we actually waited for telemetry
     mozilla::TimeDuration mSlowScriptActualWait;
     bool mTimeoutAccumulated;
 
     friend class Watchdog;
     friend class AutoLockWatchdog;
     friend class XPCIncrementalReleaseRunnable;