diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 27d8f0189e..11996fbadf 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -223,12 +223,12 @@ public: return mLastFireSkipped; } - Maybe GetIdleDeadlineHint() + TimeStamp GetIdleDeadlineHint(TimeStamp aDefault) { MOZ_ASSERT(NS_IsMainThread()); if (LastTickSkippedAnyPaints()) { - return Some(TimeStamp()); + return TimeStamp::Now(); } TimeStamp mostRecentRefresh = MostRecentRefresh(); @@ -238,11 +238,12 @@ public: if (idleEnd + refreshRate * nsLayoutUtils::QuiescentFramesBeforeIdlePeriod() < TimeStamp::Now()) { - return Nothing(); + return aDefault; } - return Some(idleEnd - TimeDuration::FromMilliseconds( - nsLayoutUtils::IdlePeriodDeadlineLimit())); + idleEnd = idleEnd - TimeDuration::FromMilliseconds( + nsLayoutUtils::IdlePeriodDeadlineLimit()); + return idleEnd < aDefault ? idleEnd : aDefault; } protected: @@ -2333,13 +2334,14 @@ nsRefreshDriver::CancelPendingEvents(nsIDocument* aDocument) } } -/* static */ Maybe -nsRefreshDriver::GetIdleDeadlineHint() +/* static */ TimeStamp +nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) { MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aDefault.IsNull()); if (!sRegularRateTimer) { - return Nothing(); + return aDefault; } // For computing idleness of refresh drivers we only care about @@ -2348,7 +2350,7 @@ nsRefreshDriver::GetIdleDeadlineHint() // resulting from a tick on the sRegularRateTimer counts as being // busy but tasks resulting from a tick on sThrottledRateTimer // counts as being idle. - return sRegularRateTimer->GetIdleDeadlineHint(); + return sRegularRateTimer->GetIdleDeadlineHint(aDefault); } void diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index ce929043e1..3ddf94c19e 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -333,37 +333,14 @@ public: * Compute the time when the currently active refresh driver timer * will start its next tick. * - * Returns 'Nothing' if the refresh driver timer hasn't been - * initialized or if we can't tell when the next tick will happen. + * Expects a non-null default value that is the upper bound of the + * expected deadline. If the next expected deadline is later than + * the default value, the default value is returned. * - * Returns Some(TimeStamp()), i.e. the null time, if the next tick is late. - * - * Otherwise returns Some(TimeStamp(t)), where t is the time of the next tick. - * - * Using these three types of return values it is possible to - * estimate three different things about the idleness of the - * currently active group of refresh drivers. This information is - * used by nsThread to schedule lower priority "idle tasks". - * - * The 'Nothing' return value indicates to nsThread that the - * currently active refresh drivers will be idle for a time - * significantly longer than the current refresh rate and that it is - * free to schedule longer periods for executing idle tasks. This is the - * expected result when we aren't animating. - - * Returning the null time indicates to nsThread that we are very - * busy and that it should definitely not schedule idle tasks at - * all. This is the expected result when we are animating, but - * aren't able to keep up with the animation and hence need to skip - * paints. Since catching up to missed paints will happen as soon as - * possible, this is the expected result if any of the refresh - * drivers attached to the current refresh driver misses a paint. - * - * Returning Some(TimeStamp(t)) indicates to nsThread that we will - * be idle until. This is usually the case when we're animating - * without skipping paints. + * If we're animating and we have skipped paints a time in the past + * is returned. */ - static mozilla::Maybe GetIdleDeadlineHint(); + static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault); static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable, uint32_t aDelay); diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h index 53b4ceafdc..44606db017 100644 --- a/xpcom/ds/nsExpirationTracker.h +++ b/xpcom/ds/nsExpirationTracker.h @@ -435,8 +435,12 @@ private: NS_ENSURE_STATE(target); mTimer->SetTarget(target); } - mTimer->InitWithNamedFuncCallback(TimerCallback, this, mTimerPeriod, - nsITimer::TYPE_REPEATING_SLACK, mName); + mTimer->InitWithNamedFuncCallback( + TimerCallback, + this, + mTimerPeriod, + nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY, + mName); return NS_OK; } }; diff --git a/xpcom/glue/nsThreadUtils.h b/xpcom/glue/nsThreadUtils.h index 33475fe8fe..f222944ae1 100644 --- a/xpcom/glue/nsThreadUtils.h +++ b/xpcom/glue/nsThreadUtils.h @@ -1045,4 +1045,22 @@ private: 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. aSearchBound specifies a maximum number of timers + * to examine to find a timer on the current thread. If no timer that + * will run on the current thread is found after examining + * aSearchBound timers, return the highest seen expiration time as a + * best effort guess. + * + * 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, uint32_t aSearchBound); + #endif // nsThreadUtils_h__ diff --git a/xpcom/threads/MainThreadIdlePeriod.cpp b/xpcom/threads/MainThreadIdlePeriod.cpp index 29a183b09c..cde5562995 100644 --- a/xpcom/threads/MainThreadIdlePeriod.cpp +++ b/xpcom/threads/MainThreadIdlePeriod.cpp @@ -8,9 +8,13 @@ #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 +#define DEFAULT_MAX_TIMER_THREAD_BOUND 5 + +const uint32_t kMaxTimerThreadBoundClamp = 15; namespace mozilla { @@ -20,19 +24,22 @@ MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aIdleDeadline); - Maybe deadline = nsRefreshDriver::GetIdleDeadlineHint(); + TimeStamp now = TimeStamp::Now(); + TimeStamp currentGuess = + now + TimeDuration::FromMilliseconds(GetLongIdlePeriod()); - if (deadline.isSome()) { - // 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 = deadline.value().IsNull() || - (TimeStamp::Now() >= (deadline.value() - minIdlePeriod)); - *aIdleDeadline = busySoon ? TimeStamp() : deadline.value(); - } else { - *aIdleDeadline = - TimeStamp::Now() + TimeDuration::FromMilliseconds(GetLongIdlePeriod()); + currentGuess = nsRefreshDriver::GetIdleDeadlineHint(currentGuess); + currentGuess = NS_GetTimerDeadlineHintOnCurrentThread(currentGuess, GetMaxTimerThreadBound()); + // 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 = mLastIdleDeadline = currentGuess; } return NS_OK; @@ -72,4 +79,21 @@ MainThreadIdlePeriod::GetMinIdlePeriod() return sMinIdlePeriod; } +/* static */ uint32_t +MainThreadIdlePeriod::GetMaxTimerThreadBound() +{ + MOZ_ASSERT(NS_IsMainThread()); + + static uint32_t sMaxTimerThreadBound = DEFAULT_MAX_TIMER_THREAD_BOUND; + static bool sInitialized = false; + + if (!sInitialized && Preferences::IsServiceAvailable()) { + sInitialized = true; + Preferences::AddUintVarCache(&sMaxTimerThreadBound, "idle_queue.max_timer_thread_bound", + DEFAULT_MAX_TIMER_THREAD_BOUND); + } + + return std::max(sMaxTimerThreadBound, kMaxTimerThreadBoundClamp); +} + } // namespace mozilla diff --git a/xpcom/threads/MainThreadIdlePeriod.h b/xpcom/threads/MainThreadIdlePeriod.h index 3d396553e9..16a36fcb4f 100644 --- a/xpcom/threads/MainThreadIdlePeriod.h +++ b/xpcom/threads/MainThreadIdlePeriod.h @@ -16,10 +16,18 @@ class MainThreadIdlePeriod final : public IdlePeriod public: NS_DECL_NSIIDLEPERIOD + MainThreadIdlePeriod() + : mLastIdleDeadline(TimeStamp::Now()) + { + } + static float GetLongIdlePeriod(); static float GetMinIdlePeriod(); + static uint32_t GetMaxTimerThreadBound(); private: virtual ~MainThreadIdlePeriod() {} + + TimeStamp mLastIdleDeadline; }; } // namespace mozilla diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp index 30ab2f89a9..d56e104bbc 100644 --- a/xpcom/threads/TimerThread.cpp +++ b/xpcom/threads/TimerThread.cpp @@ -593,6 +593,52 @@ TimerThread::RemoveTimer(nsTimerImpl* aTimer) return NS_OK; } +TimeStamp +TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, uint32_t aSearchBound) +{ + MonitorAutoLock lock(mMonitor); + TimeStamp timeStamp = aDefault; + uint32_t index = 0; + + for (auto timers = mTimers.begin(); timers != mTimers.end(); ++timers) { + nsTimerImpl* timer = (*timers)->Value(); + + if (!timer) { + continue; + } + + if (timer->mTimeout > aDefault) { + timeStamp = aDefault; + break; + } + + // Don't yield to timers created with the *_LOW_PRIORITY type. + if (timer->IsLowPriority()) { + continue; + } + + // Track the currently highest timeout so that we can bail when we + // reach the bound or when we find a timer for the current thread. + timeStamp = timer->mTimeout; + + bool isOnCurrentThread = false; + nsresult rv = timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + if (isOnCurrentThread) { + break; + } + + if (++index > aSearchBound) { + break; + } + } + + return timeStamp; +} + // This function must be called from within a lock int32_t TimerThread::AddTimerInternal(nsTimerImpl* aTimer) diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h index c82f3ec7f5..132fbb3aa9 100644 --- a/xpcom/threads/TimerThread.h +++ b/xpcom/threads/TimerThread.h @@ -44,6 +44,7 @@ public: nsresult AddTimer(nsTimerImpl* aTimer); nsresult RemoveTimer(nsTimerImpl* aTimer); + TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, uint32_t aSearchBound); void DoBeforeSleep(); void DoAfterSleep(); diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl index ade2168f2d..b0412e682b 100644 --- a/xpcom/threads/nsITimer.idl +++ b/xpcom/threads/nsITimer.idl @@ -111,6 +111,22 @@ interface nsITimer : nsISupports */ 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 diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 0d8c8dc196..23dfef4021 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -1021,11 +1021,20 @@ nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock) MOZ_ASSERT(PR_GetCurrentThread() == mThread); MOZ_ASSERT(aEvent); + if (!mIdleEvents.HasPendingEvent(aProofOfLock)) { + aEvent = nullptr; + return; + } + TimeStamp 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; diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp index a8c2a19db7..f8fce63d94 100644 --- a/xpcom/threads/nsTimerImpl.cpp +++ b/xpcom/threads/nsTimerImpl.cpp @@ -43,6 +43,14 @@ GetTimerLog() return sTimerLog; } +TimeStamp +NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault, uint32_t aSearchBound) +{ + return gThread + ? gThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound) + : 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. @@ -502,7 +510,7 @@ nsTimerImpl::Fire(int32_t aGeneration) // 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; @@ -536,10 +544,12 @@ 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"); } diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h index 039294995d..8ce70a175f 100644 --- a/xpcom/threads/nsTimerImpl.h +++ b/xpcom/threads/nsTimerImpl.h @@ -142,7 +142,20 @@ public: 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; } nsCOMPtr mEventTarget;