Backed out changeset 8272530c90ef (
bug 1276626) for
bug 1284511. r=blassey, a=lizzard
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -986,17 +986,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; }
@@ -1096,33 +1095,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"
@@ -1180,18 +1171,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; }
@@ -1311,59 +1300,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()->Context());
- }
- }
+ // 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()->Context());
}
}
// Tell the manager that we've shut down.
self->Finished();
}
PRTime
@@ -1396,16 +1362,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())
@@ -1416,21 +1383,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);
@@ -3392,16 +3368,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),
mPendingResult(NS_OK)
{
}
#ifdef XP_WIN
static size_t
GetWindowsStackSize()
@@ -3726,31 +3703,33 @@ 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()->Context());
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
@@ -628,19 +628,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;
// mPendingResult is used to implement Components.returnCode. Only really
// meaningful while calling through XPCWrappedJS.
nsresult mPendingResult;