Bug 1342854 Run a limited number of timer callbacks in the same event loop runnable. r=smaug
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -66,16 +66,21 @@ static int32_t gTimeoutBucketingStrategy
// timer code can handle, really. See DELAY_INTERVAL_LIMIT in
// nsTimerImpl.h for details.
#define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT
uint32_t TimeoutManager::sNestingLevel = 0;
namespace {
+// The maximum number of timer callbacks we will try to run in a single event
+// loop runnable.
+#define DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS 5
+uint32_t gTargetMaxConsecutiveCallbacks;
+
// The number of queued runnables within the TabGroup ThrottledEventQueue
// at which to begin applying back pressure to the window.
#define DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE 5000
static uint32_t gThrottledEventQueueBackPressure;
// The amount of delay to apply to timers when back pressure is triggered.
// As the length of the ThrottledEventQueue grows delay is increased. The
// delay is scaled such that every kThrottledEventQueueBackPressure runnables
@@ -177,16 +182,20 @@ TimeoutManager::Initialize()
"dom.timeout.back_pressure_delay_ms",
DEFAULT_BACK_PRESSURE_DELAY_MS);
Preferences::AddUintVarCache(&gBackPressureDelayReductionThresholdMS,
"dom.timeout.back_pressure_delay_reduction_threshold_ms",
DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS);
Preferences::AddUintVarCache(&gBackPressureDelayMinimumMS,
"dom.timeout.back_pressure_delay_minimum_ms",
DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS);
+
+ Preferences::AddUintVarCache(&gTargetMaxConsecutiveCallbacks,
+ "dom.timeout.max_consecutive_callbacks",
+ DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS);
}
uint32_t
TimeoutManager::GetTimeoutId(Timeout::Reason aReason)
{
switch (aReason) {
case Timeout::Reason::eIdleCallbackTimeout:
return ++mIdleCallbackTimeoutCounter;
@@ -439,16 +448,20 @@ TimeoutManager::RunTimeout(Timeout* aTim
// nothing past that point is expired.
{
// Use a nested scope in order to make sure the strong references held by
// the iterator are freed after the loop.
OrderedTimeoutIterator expiredIter(mNormalTimeouts,
mTrackingTimeouts,
nullptr,
nullptr);
+
+ uint32_t numTimersToRun = 0;
+ bool targetTimerSeen = false;
+
while (true) {
Timeout* timeout = expiredIter.Next();
if (!timeout || timeout->When() > deadline) {
break;
}
if (timeout->mFiringDepth == 0) {
// Mark any timeouts that are on the list to be fired with the
@@ -456,29 +469,39 @@ TimeoutManager::RunTimeout(Timeout* aTim
timeout->mFiringDepth = firingDepth;
last_expired_timeout_is_normal = expiredIter.PickedNormalIter();
if (last_expired_timeout_is_normal) {
last_expired_normal_timeout = timeout;
} else {
last_expired_tracking_timeout = timeout;
}
- // Run available timers until we see our target timer. After
- // that, however, stop coalescing timers so we can yield the
- // main thread. Further timers that are ready will get picked
- // up by their own nsITimer runnables when they execute.
+ // Note that we have seen our target timer. This means we can now
+ // stop processing timers once we hit our threshold below.
+ if (timeout == aTimeout) {
+ targetTimerSeen = true;
+ }
+
+ // Run only a limited number of timers based on the configured
+ // maximum. Note, we must always run our target timer however.
+ // Further timers that are ready will get picked up by their own
+ // nsITimer runnables when they execute.
//
// For chrome windows, however, we do coalesce all timers and
// do not yield the main thread. This is partly because we
// trust chrome windows not to misbehave and partly because a
// number of browser chrome tests have races that depend on this
// coalescing.
- if (timeout == aTimeout && !mWindow.IsChromeWindow()) {
+ if (targetTimerSeen &&
+ numTimersToRun >= gTargetMaxConsecutiveCallbacks &&
+ !mWindow.IsChromeWindow()) {
break;
}
+
+ numTimersToRun += 1;
}
expiredIter.UpdateIterator();
}
}
// Maybe the timeout that the event was fired for has been deleted
// and there are no others timeouts with deadlines that make them