1311425 - Make requestIdleCallback aware of timeouts.

Prepare for handling several sources of idleness.

Make idle callbacks aware of nsITimers.

Add pref for how far into the timer queue.

1311425 - Avoid expiration timers when scheduling idle runnables.

review comment fix to ensure low priority timers aren't taken into account when calling NS_GetTimerDeadlineHintOnCurrentThread.
This commit is contained in:
win7-7 2024-04-27 19:21:20 +03:00 • committed by wuggy
commit 5499c5f9a3
12 changed files with 188 additions and 60 deletions

View file

@ -223,12 +223,12 @@ public:
return mLastFireSkipped;
}
Maybe<TimeStamp> 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<TimeStamp>
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

View file

@ -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<mozilla::TimeStamp> GetIdleDeadlineHint();
static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
uint32_t aDelay);

View file

@ -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;
}
};

View file

@ -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__

View file

@ -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<TimeStamp> 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

View file

@ -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

View file

@ -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)

View file

@ -44,6 +44,7 @@ public:
nsresult AddTimer(nsTimerImpl* aTimer);
nsresult RemoveTimer(nsTimerImpl* aTimer);
TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, uint32_t aSearchBound);
void DoBeforeSleep();
void DoAfterSleep();

View file

@ -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

View file

@ -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;

View file

@ -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");
}

View file

@ -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<nsIEventTarget> mEventTarget;