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 490323 e1a939a64f4b0ce63d1879705685c866f4297b36
parent 490322 4ee8c67b75bc1e300ffa5376198b1b991d0928e7
child 490324 cf4daf78f4797ef07e13729c8f3913af9576ca3b
child 490636 171bcf90d5a0d8b227769b7c2d7b7d25729b43f7
push id47063
push userbmo:rail@mozilla.com
push dateTue, 28 Feb 2017 03:41:15 +0000
reviewerssmaug
bugs1342854
milestone54.0a1
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