Bug 1342854 Run a limited number of timer callbacks in the same event loop runnable. r=smaug
authorBen Kelly <ben@wanderview.com>
Mon, 27 Feb 2017 17:33:30 -0500
changeset 345147 e1a939a64f4b0ce63d1879705685c866f4297b36
parent 345146 4ee8c67b75bc1e300ffa5376198b1b991d0928e7
child 345148 cf4daf78f4797ef07e13729c8f3913af9576ca3b
push id38073
push usercbook@mozilla.com
push dateTue, 28 Feb 2017 12:04:44 +0000
treeherderautoland@0d6ca3d14e5b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1342854
milestone54.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 1342854 Run a limited number of timer callbacks in the same event loop runnable. r=smaug
dom/base/TimeoutManager.cpp
--- 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