Bug 1363829 P16 Allow setTimeout() to fire early based on nsIThread::GetAllowedEarlyFiringMicroseconds(). r=ehsan
authorBen Kelly <ben@wanderview.com>
Wed, 31 May 2017 17:13:20 -0700
changeset 361752 95421442eec4c1a357d8fd647b2f28dfd8e44760
parent 361751 d27d106fc45d32435d4468ed8282f4233af5aa22
child 361753 7c6116ac71e954805dccf3146d6fffcf28bbc0cf
push id31940
push usercbook@mozilla.com
push dateThu, 01 Jun 2017 11:51:11 +0000
treeherdermozilla-central@0bcea6bac179 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1363829
milestone55.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 1363829 P16 Allow setTimeout() to fire early based on nsIThread::GetAllowedEarlyFiringMicroseconds(). r=ehsan
dom/base/TimeoutExecutor.cpp
dom/base/TimeoutExecutor.h
--- a/dom/base/TimeoutExecutor.cpp
+++ b/dom/base/TimeoutExecutor.cpp
@@ -21,17 +21,17 @@ TimeoutExecutor::~TimeoutExecutor()
 }
 
 nsresult
 TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
                                    const TimeStamp& aNow)
 {
   MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
-  MOZ_DIAGNOSTIC_ASSERT(aDeadline <= aNow);
+  MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
 
   nsresult rv =
     mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mMode = Mode::Immediate;
   mDeadline = aDeadline;
 
@@ -39,23 +39,27 @@ TimeoutExecutor::ScheduleImmediate(const
 }
 
 nsresult
 TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
                                  const TimeStamp& aNow)
 {
   MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
-  MOZ_DIAGNOSTIC_ASSERT(aDeadline > aNow);
+  MOZ_DIAGNOSTIC_ASSERT(aDeadline > (aNow + mAllowedEarlyFiringTime));
 
   nsresult rv = NS_OK;
 
   if (!mTimer) {
     mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
+
+    uint32_t earlyMicros = 0;
+    MOZ_ALWAYS_SUCCEEDS(mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
+    mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
   }
 
   // Always call Cancel() in case we are re-using a timer.  Otherwise
   // the subsequent SetTarget() may fail.
   rv = mTimer->Cancel();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mTimer->SetTarget(mOwner->EventTarget());
@@ -92,17 +96,17 @@ TimeoutExecutor::ScheduleDelayed(const T
 nsresult
 TimeoutExecutor::Schedule(const TimeStamp& aDeadline)
 {
   TimeStamp now(TimeStamp::Now());
 
   // Schedule an immediate runnable if the desired deadline has passed
   // or is slightly in the future.  This is similar to how nsITimer will
   // fire timers early based on the interval resolution.
-  if (aDeadline <= now) {
+  if (aDeadline <= (now + mAllowedEarlyFiringTime)) {
     return ScheduleImmediate(aDeadline, now);
   }
 
   return ScheduleDelayed(aDeadline, now);
 }
 
 nsresult
 TimeoutExecutor::MaybeReschedule(const TimeStamp& aDeadline)
@@ -135,18 +139,19 @@ TimeoutExecutor::MaybeExecute()
 
   TimeStamp deadline(mDeadline);
 
   // Sometimes nsITimer or canceled timers will fire too early.  If this
   // happens then just cap our deadline to our maximum time in the future
   // and proceed.  If there are no timers ready we will get rescheduled
   // by TimeoutManager.
   TimeStamp now(TimeStamp::Now());
-  if (deadline > now) {
-    deadline = now;
+  TimeStamp limit = now + mAllowedEarlyFiringTime;
+  if (deadline > limit) {
+    deadline = limit;
   }
 
   Cancel();
 
   mOwner->RunTimeout(now, deadline);
 }
 
 TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner)
--- a/dom/base/TimeoutExecutor.h
+++ b/dom/base/TimeoutExecutor.h
@@ -17,16 +17,22 @@ namespace dom {
 class TimeoutExecutor final : public nsIRunnable
                             , public nsITimerCallback
                             , public nsINamed
 {
   TimeoutManager* mOwner;
   nsCOMPtr<nsITimer> mTimer;
   TimeStamp mDeadline;
 
+  // Limits how far we allow timers to fire into the future from their
+  // deadline.  Starts off at zero, but is then adjusted when we start
+  // using nsITimer.  The nsITimer implementation may sometimes fire
+  // early and we should allow that to minimize additional wakeups.
+  TimeDuration mAllowedEarlyFiringTime;
+
   // The TimeoutExecutor is repeatedly scheduled by the TimeoutManager
   // to fire for the next soonest Timeout.  Since the executor is re-used
   // it needs to handle switching between a few states.
   enum class Mode
   {
     // None indicates the executor is idle.  It may be scheduled or shutdown.
     None,
     // Immediate means the executor is scheduled to run a Timeout with a