Bug 1342854 Run a limited number of timer callbacks in the same event loop runnable. r=smaug a=jcristau
authorBen Kelly <ben@wanderview.com>
Tue, 28 Feb 2017 09:11:41 -0500
changeset 378751 c269adcb4e01ae92e215efe6232a5ab28a9c3949
parent 378750 b53997386a332c2d1f36ce2aee1feccd8ba6fc56
child 378752 101239f350b5bd19db60d97c14d55d07cf9c658d
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jcristau
bugs1342854
milestone53.0a2
Bug 1342854 Run a limited number of timer callbacks in the same event loop runnable. r=smaug a=jcristau
dom/base/TimeoutManager.cpp
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -57,16 +57,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
@@ -149,16 +154,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;
@@ -375,16 +384,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
@@ -392,29 +405,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