Bug 1368286 - Take the idle queue into account in nsThread::HasPendingEvents(); r=smaug

This commit is contained in:
Ehsan Akhgari 2017-06-02 20:40:12 -04:00
parent 394af562be
commit 19d6dc61bb
2 changed files with 66 additions and 12 deletions

View File

@ -644,6 +644,7 @@ nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
, mIsMainThread(aMainThread)
, mLastUnlabeledRunnable(TimeStamp::Now())
, mCanInvokeJS(false)
, mHasPendingEventsPromisedIdleEvent(false)
{
}
@ -1037,6 +1038,43 @@ nsThread::Shutdown()
return NS_OK;
}
TimeStamp
nsThread::GetIdleDeadline()
{
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 HasPendingEvents() has been called and it has returned true because of
// pending idle events, there is a risk that we may decide here that we aren't
// idle and return null, in which case HasPendingEvents() has effectively
// lied. Since we can't go back and fix the past, we have to adjust what we
// do here and forcefully pick the idle queue task here. Note that this means
// that we are choosing to run a task from the idle queue when we would
// normally decide that we aren't in an idle period, but this can only happen
// if we fall out of the idle period in between the call to HasPendingEvents()
// and here, which should hopefully be quite rare. We are effectively
// choosing to prioritize the sanity of our API semantics over the optimal
// scheduling.
if (!mHasPendingEventsPromisedIdleEvent &&
(!idleDeadline || idleDeadline < TimeStamp::Now())) {
return TimeStamp();
}
if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
// If HasPendingEvents() has been called and it has returned true, but we're no
// longer in the idle period, we must return a valid timestamp to pretend that
// we are still in the idle period.
return TimeStamp::Now();
}
return idleDeadline;
}
NS_IMETHODIMP
nsThread::HasPendingEvents(bool* aResult)
{
@ -1046,7 +1084,21 @@ nsThread::HasPendingEvents(bool* aResult)
{
MutexAutoLock lock(mLock);
*aResult = mEvents->HasPendingEvent(lock);
mHasPendingEventsPromisedIdleEvent = false;
bool hasPendingEvent = mEvents->HasPendingEvent(lock);
bool hasPendingIdleEvent = false;
if (!hasPendingEvent) {
// Note that GetIdleDeadline() checks mHasPendingEventsPromisedIdleEvent,
// but that's OK since we set it to false in the beginning of this method!
TimeStamp idleDeadline = GetIdleDeadline();
// Only examine the idle queue if we are in an idle period.
if (idleDeadline) {
hasPendingIdleEvent = mIdleEvents.HasPendingEvent(lock);
mHasPendingEventsPromisedIdleEvent = hasPendingIdleEvent;
}
}
*aResult = hasPendingEvent || hasPendingIdleEvent;
}
return NS_OK;
}
@ -1154,21 +1206,13 @@ nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
MOZ_ASSERT(aEvent);
if (!mIdleEvents.HasPendingEvent(aProofOfLock)) {
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
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()) {
TimeStamp idleDeadline = GetIdleDeadline();
if (!idleDeadline) {
aEvent = nullptr;
return;
}
@ -1191,6 +1235,10 @@ nsThread::GetEvent(bool aWait, nsIRunnable** aEvent,
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
MOZ_ASSERT(aEvent);
MakeScopeExit([&] {
mHasPendingEventsPromisedIdleEvent = false;
});
// We'll try to get an event to execute in three stages.
// [1] First we just try to get it from the regular queue without waiting.
mEvents->GetEvent(false, aEvent, aPriority, aProofOfLock);

View File

@ -96,6 +96,8 @@ public:
private:
void DoMainThreadSpecificProcessing(bool aReallyWait);
// Returns a null TimeStamp if we're not in the idle period.
mozilla::TimeStamp GetIdleDeadline();
void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
void GetEvent(bool aWait, nsIRunnable** aEvent,
unsigned short* aPriority,
@ -278,6 +280,10 @@ protected:
// Set to true if this thread creates a JSRuntime.
bool mCanInvokeJS;
// Set to true if HasPendingEvents() has been called and returned true because
// of a pending idle event. This is used to remember to return that idle
// event from GetIdleEvent() to ensure that HasPendingEvents() never lies.
bool mHasPendingEventsPromisedIdleEvent;
};
#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM \