Bug 1311425 - Make idle callbacks aware of nsITimers, r=froydnj
authorAndreas Farre <farre@mozilla.com>
Wed, 24 May 2017 21:12:55 -0400
changeset 360853 5fb5f0d2c60bec0ba64f99d8f0f63f9bc556b744
parent 360852 43f38cc244b968aae811d727ba36dc64929ba2d8
child 360854 a16dd2fce0f15cd0464b9c1913de40b5c90ac4bc
push id31903
push userryanvm@gmail.com
push dateFri, 26 May 2017 19:44:10 +0000
treeherdermozilla-central@bce03a8eac30 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1311425
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 1311425 - Make idle callbacks aware of nsITimers, r=froydnj
xpcom/threads/MainThreadIdlePeriod.cpp
xpcom/threads/MainThreadIdlePeriod.h
xpcom/threads/TimerThread.cpp
xpcom/threads/TimerThread.h
xpcom/threads/nsITimer.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThreadUtils.h
xpcom/threads/nsTimerImpl.cpp
xpcom/threads/nsTimerImpl.h
--- a/xpcom/threads/MainThreadIdlePeriod.cpp
+++ b/xpcom/threads/MainThreadIdlePeriod.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MainThreadIdlePeriod.h"
 
 #include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
 #include "nsRefreshDriver.h"
+#include "nsThreadUtils.h"
 
 #define DEFAULT_LONG_IDLE_PERIOD 50.0f
 #define DEFAULT_MIN_IDLE_PERIOD 3.0f
 
 namespace mozilla {
 
 NS_IMETHODIMP
 MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline)
@@ -21,25 +22,28 @@ MainThreadIdlePeriod::GetIdlePeriodHint(
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aIdleDeadline);
 
   TimeStamp now = TimeStamp::Now();
   TimeStamp currentGuess =
     now + TimeDuration::FromMilliseconds(GetLongIdlePeriod());
 
   currentGuess = nsRefreshDriver::GetIdleDeadlineHint(currentGuess);
+  currentGuess = NS_GetTimerDeadlineHintOnCurrentThread(currentGuess);
+
   // If the idle period is too small, then just return a null time
   // to indicate we are busy. Otherwise return the actual deadline.
   TimeDuration minIdlePeriod =
     TimeDuration::FromMilliseconds(GetMinIdlePeriod());
   bool busySoon = currentGuess.IsNull() ||
                   (now >= (currentGuess - minIdlePeriod)) ||
+                  currentGuess < mLastIdleDeadline;
 
   if (!busySoon) {
-    *aIdleDeadline = currentGuess;
+    *aIdleDeadline = mLastIdleDeadline = currentGuess;
   }
 
   return NS_OK;
 }
 
 /* static */ float
 MainThreadIdlePeriod::GetLongIdlePeriod()
 {
--- a/xpcom/threads/MainThreadIdlePeriod.h
+++ b/xpcom/threads/MainThreadIdlePeriod.h
@@ -12,17 +12,24 @@
 
 namespace mozilla {
 
 class MainThreadIdlePeriod final : public IdlePeriod
 {
 public:
   NS_DECL_NSIIDLEPERIOD
 
+  MainThreadIdlePeriod()
+    : mLastIdleDeadline(TimeStamp::Now())
+  {
+  }
+
   static float GetLongIdlePeriod();
   static float GetMinIdlePeriod();
 private:
   virtual ~MainThreadIdlePeriod() {}
+
+  TimeStamp mLastIdleDeadline;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_dom_mainthreadidleperiod_h
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -584,16 +584,53 @@ TimerThread::RemoveTimer(nsTimerImpl* aT
   if (mWaiting) {
     mNotified = true;
     mMonitor.Notify();
   }
 
   return NS_OK;
 }
 
+TimeStamp
+TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault)
+{
+  MonitorAutoLock lock(mMonitor);
+  TimeStamp timeStamp = aDefault;
+
+  for (auto timers = mTimers.begin(); timers != mTimers.end(); ++timers) {
+    nsTimerImpl* timer = (*timers)->Value();
+
+    if (!timer) {
+      continue;
+    }
+
+    if (timer->mTimeout > aDefault) {
+      break;
+    }
+
+    // Don't yield to timers created with the *_LOW_PRIORITY type.
+    if (timer->IsLowPriority()) {
+      continue;
+    }
+
+    bool isOnCurrentThread = false;
+    nsresult rv = timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+
+    if (isOnCurrentThread) {
+      timeStamp = timer->mTimeout;
+      break;
+    }
+  }
+
+  return timeStamp;
+}
+
 // This function must be called from within a lock
 bool
 TimerThread::AddTimerInternal(nsTimerImpl* aTimer)
 {
   mMonitor.AssertCurrentThreadOwns();
   if (mShutdown) {
     return false;
   }
--- a/xpcom/threads/TimerThread.h
+++ b/xpcom/threads/TimerThread.h
@@ -41,16 +41,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
   NS_DECL_NSIOBSERVER
 
   nsresult Shutdown();
 
   nsresult AddTimer(nsTimerImpl* aTimer);
   nsresult RemoveTimer(nsTimerImpl* aTimer);
+  TimeStamp FindNextFireTimeForCurrentThread(TimeStamp );
 
   void DoBeforeSleep();
   void DoAfterSleep();
 
   bool IsOnTimerThread() const
   {
     return mThread == NS_GetCurrentThread();
   }
--- a/xpcom/threads/nsITimer.idl
+++ b/xpcom/threads/nsITimer.idl
@@ -110,16 +110,32 @@ interface nsITimer : nsISupports
    * guarantees that it will not queue up new events to fire the callback
    * until the previous callback event finishes firing.  If the callback
    * takes a long time, then the next callback will be scheduled immediately
    * afterward, but only once.  This is the only non-slack timer available.
    */
   const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3;
 
   /**
+   * Same as TYPE_REPEATING_SLACK with the exception that idle events
+   * won't yield to timers with this type.  Use this when you want an
+   * idle callback to be scheduled to run even though this timer is
+   * about to fire.
+   */
+  const short TYPE_REPEATING_SLACK_LOW_PRIORITY = 4;
+
+  /**
+   * Same as TYPE_ONE_SHOT with the exception that idle events won't
+   * yield to timers with this type.  Use this when you want an idle
+   * callback to be scheduled to run even though this timer is about
+   * to fire.
+   */
+  const short TYPE_ONE_SHOT_LOW_PRIORITY = 5;
+
+  /**
    * Initialize a timer that will fire after the said delay.
    * A user must keep a reference to this timer till it is
    * is no longer needed or has been cancelled.
    *
    * @param aObserver   the callback object that observes the
    *                    ``timer-callback'' topic with the subject being
    *                    the timer itself when the timer fires:
    *
@@ -239,9 +255,8 @@ interface nsITimer : nsISupports
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
 %}
 };
 
 %{C++
 #define NS_TIMER_CONTRACTID "@mozilla.org/timer;1"
 #define NS_TIMER_CALLBACK_TOPIC "timer-callback"
 %}
-
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -1148,18 +1148,30 @@ void canary_alarm_handler(int signum)
   } while(0)
 
 void
 nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == mThread);
   MOZ_ASSERT(aEvent);
 
+  if (!mIdleEvents.HasPendingEvent(aProofOfLock)) {
+    aEvent = nullptr;
+    return;
+  }
+
   TimeStamp idleDeadline;
-  mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+  {
+    // Releasing the lock temporarily since getting the idle period
+    // might need to lock the timer thread. Unlocking here might make
+    // us receive an event on the main queue, but we've committed to
+    // run an idle event anyhow.
+    MutexAutoUnlock unlock(mLock);
+    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+  }
 
   if (!idleDeadline || idleDeadline < TimeStamp::Now()) {
     aEvent = nullptr;
     return;
   }
 
   mIdleEvents.GetEvent(false, aEvent, aProofOfLock);
 
--- a/xpcom/threads/nsThreadUtils.h
+++ b/xpcom/threads/nsThreadUtils.h
@@ -1762,16 +1762,30 @@ private:
 #if defined(XP_MACOSX)
   int oldPriority;
 #endif
 };
 
 void
 NS_SetMainThread();
 
+/**
+ * Return the expiration time of the next timer to run on the current
+ * thread.  If that expiration time is greater than aDefault, then
+ * return aDefault.
+ *
+ * Timers with either the type nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY or
+ * nsITIMER::TYPE_REPEATING_SLACK_LOW_PRIORITY will be skipped when
+ * searching for the next expiration time.  This enables timers to
+ * have lower priority than callbacks dispatched from
+ * nsIThread::IdleDispatch.
+ */
+extern mozilla::TimeStamp
+NS_GetTimerDeadlineHintOnCurrentThread(mozilla::TimeStamp aDefault);
+
 namespace mozilla {
 
 /**
  * Cooperative thread scheduling is governed by two rules:
  * - Only one thread in the pool of cooperatively scheduled threads runs at a
  *   time.
  * - Thread switching happens at well-understood safe points.
  *
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -39,16 +39,24 @@ static TimerThread*     gThread = nullpt
 static mozilla::LazyLogModule sTimerLog("nsTimerImpl");
 
 mozilla::LogModule*
 GetTimerLog()
 {
   return sTimerLog;
 }
 
+TimeStamp
+NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault)
+{
+  return gThread
+           ? gThread->FindNextFireTimeForCurrentThread(aDefault)
+           : TimeStamp();
+}
+
 // This module prints info about which timers are firing, which is useful for
 // wakeups for the purposes of power profiling. Set the following environment
 // variable before starting the browser.
 //
 //   MOZ_LOG=TimerFirings:4
 //
 // Then a line will be printed for every timer that fires. The name used for a
 // |Callback::Type::Function| timer depends on the circumstances.
@@ -500,17 +508,17 @@ nsTimerImpl::Fire(int32_t aGeneration)
   }
 
   Callback trash; // Swap into here to dispose of callback after the unlock
   MutexAutoLock lock(mMutex);
   if (aGeneration == mGeneration && IsRepeating()) {
     // Repeating timer has not been re-init or canceled; reschedule
     mCallbackDuringFire.swap(mCallback);
     TimeDuration delay = TimeDuration::FromMilliseconds(mDelay);
-    if (mType == nsITimer::TYPE_REPEATING_SLACK) {
+    if (IsSlack()) {
       mTimeout = TimeStamp::Now() + delay;
     } else {
       mTimeout = mTimeout + delay;
     }
     if (gThread) {
       gThread->AddTimer(this);
     }
   }
@@ -532,20 +540,22 @@ nsTimerImpl::Fire(int32_t aGeneration)
 #endif
 
 // See the big comment above GetTimerFiringsLog() to understand this code.
 void
 nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay)
 {
   const char* typeStr;
   switch (aType) {
-    case nsITimer::TYPE_ONE_SHOT:                   typeStr = "ONE_SHOT"; break;
-    case nsITimer::TYPE_REPEATING_SLACK:            typeStr = "SLACK   "; break;
+    case nsITimer::TYPE_ONE_SHOT:                     typeStr = "ONE_SHOT  "; break;
+    case nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY:        typeStr = "ONE_LOW   "; break;
+    case nsITimer::TYPE_REPEATING_SLACK:              typeStr = "SLACK     "; break;
+    case nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY: typeStr = "SLACK_LOW "; break;
     case nsITimer::TYPE_REPEATING_PRECISE:          /* fall through */
-    case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP: typeStr = "PRECISE "; break;
+    case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP:   typeStr = "PRECISE   "; break;
     default:                              MOZ_CRASH("bad type");
   }
 
   switch (aCallback.mType) {
     case Callback::Type::Function: {
       bool needToFreeName = false;
       const char* annotation = "";
       const char* name;
@@ -709,9 +719,8 @@ nsTimerImpl::GetTLSTraceInfo()
 
 TracedTaskCommon
 nsTimerImpl::GetTracedTask()
 {
   return mTracedTask;
 }
 
 #endif
-
--- a/xpcom/threads/nsTimerImpl.h
+++ b/xpcom/threads/nsTimerImpl.h
@@ -147,17 +147,30 @@ public:
                   "invalid ordering of timer types!");
     static_assert(
         nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE,
         "invalid ordering of timer types!");
     static_assert(
         nsITimer::TYPE_REPEATING_PRECISE <
           nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
         "invalid ordering of timer types!");
-    return mType >= nsITimer::TYPE_REPEATING_SLACK;
+    return mType >= nsITimer::TYPE_REPEATING_SLACK &&
+           mType < nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY;
+  }
+
+  bool IsLowPriority() const
+  {
+    return mType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY ||
+           mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY;
+  }
+
+  bool IsSlack() const
+  {
+    return mType == nsITimer::TYPE_REPEATING_SLACK ||
+           mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY;
   }
 
   void GetName(nsACString& aName);
 
   void SetHolder(nsTimerImplHolder* aHolder);
 
   nsCOMPtr<nsIEventTarget> mEventTarget;