Bug 1351148 Part2: Add a priority queue for input events. r=smaug.

MozReview-Commit-ID: 5ud1Ex9UNVo
This commit is contained in:
Stone Shih
2017-03-21 15:44:12 +08:00
parent b38e4d9094
commit e0dd0293e8
36 changed files with 751 additions and 109 deletions

View File

@@ -41,6 +41,7 @@
#include "nsThreadSyncDispatch.h"
#include "LeakRefPtr.h"
#include "GeckoProfiler.h"
#include "InputEventStatistics.h"
#ifdef MOZ_CRASHREPORTER
#include "nsServiceManagerUtils.h"
@@ -815,47 +816,158 @@ nsThread::DispatchInternal(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags
return PutEvent(event.take(), aTarget);
}
NS_IMPL_ISUPPORTS(nsThread::nsChainedEventQueue::EnablePrioritizationRunnable,
nsIRunnable)
void
nsThread::nsChainedEventQueue::EnablePrioritization(MutexAutoLock& aProofOfLock)
{
MOZ_ASSERT(!mIsInputPrioritizationEnabled);
// When enabling event prioritization, there may be some pending events with
// different priorities in the normal queue. Create an event in the normal
// queue to consume all pending events in the time order to make sure we won't
// preempt a pending event (e.g. input) in the normal queue by another newly
// created event with the same priority.
mNormalQueue->PutEvent(new EnablePrioritizationRunnable(this), aProofOfLock);
mInputHandlingStartTime = TimeStamp();
mIsInputPrioritizationEnabled = true;
}
bool
nsThread::nsChainedEventQueue::GetEvent(bool aMayWait, nsIRunnable** aEvent,
unsigned short* aPriority,
mozilla::MutexAutoLock& aProofOfLock)
nsThread::nsChainedEventQueue::
GetNormalOrInputOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
unsigned short* aPriority,
MutexAutoLock& aProofOfLock)
{
bool retVal = false;
do {
if (mProcessSecondaryQueueRunnable) {
MOZ_ASSERT(mSecondaryQueue->HasPendingEvent(aProofOfLock));
retVal = mSecondaryQueue->GetEvent(aMayWait, aEvent, aProofOfLock);
// Use mProcessHighPriorityQueueRunnable to prevent the high priority events
// from consuming all cpu time and causing starvation.
if (mProcessHighPriorityQueueRunnable) {
MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
if (aPriority) {
*aPriority = nsIRunnablePriority::PRIORITY_HIGH;
}
mProcessSecondaryQueueRunnable = false;
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
mInputHandlingStartTime = TimeStamp();
mProcessHighPriorityQueueRunnable = false;
return retVal;
}
mProcessHighPriorityQueueRunnable =
mHighQueue->HasPendingEvent(aProofOfLock);
// We don't want to wait if mSecondaryQueue has some events.
bool reallyMayWait =
aMayWait && !mSecondaryQueue->HasPendingEvent(aProofOfLock);
retVal =
mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
if (aPriority) {
*aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
uint32_t pendingInputCount = mInputQueue->Count(aProofOfLock);
if (pendingInputCount > 0) {
if (mInputHandlingStartTime.IsNull()) {
mInputHandlingStartTime =
InputEventStatistics::Get()
.GetInputHandlingStartTime(mInputQueue->Count(aProofOfLock));
}
if (TimeStamp::Now() > mInputHandlingStartTime) {
retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
return retVal;
}
}
// Let's see if we should next time process an event from the secondary
// queue.
mProcessSecondaryQueueRunnable =
mSecondaryQueue->HasPendingEvent(aProofOfLock);
// We don't want to wait if there are some high priority events or input
// events in the queues.
bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable &&
pendingInputCount == 0;
retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
if (*aEvent) {
// We got an event, return early.
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
return retVal;
}
} while(aMayWait || mProcessSecondaryQueueRunnable);
if (pendingInputCount > 0 && !mProcessHighPriorityQueueRunnable) {
// Handle input events if we have time for them.
MOZ_ASSERT(mInputQueue->HasPendingEvent(aProofOfLock));
retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
return retVal;
}
} while (aMayWait || mProcessHighPriorityQueueRunnable);
return retVal;
}
bool
nsThread::nsChainedEventQueue::
GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
unsigned short* aPriority,
MutexAutoLock& aProofOfLock)
{
bool retVal = false;
do {
// Use mProcessHighPriorityQueueRunnable to prevent the high priority events
// from consuming all cpu time and causing starvation.
if (mProcessHighPriorityQueueRunnable) {
MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
mProcessHighPriorityQueueRunnable = false;
return retVal;
}
mProcessHighPriorityQueueRunnable =
mHighQueue->HasPendingEvent(aProofOfLock);
// We don't want to wait if there are some events in the high priority
// queue.
bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable;
retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
if (*aEvent) {
// We got an event, return early.
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
return retVal;
}
} while (aMayWait || mProcessHighPriorityQueueRunnable);
return retVal;
}
void
nsThread::nsChainedEventQueue::PutEvent(already_AddRefed<nsIRunnable> aEvent,
MutexAutoLock& aProofOfLock)
{
RefPtr<nsIRunnable> event(aEvent);
nsCOMPtr<nsIRunnablePriority> runnablePrio(do_QueryInterface(event));
uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
if (runnablePrio) {
runnablePrio->GetPriority(&prio);
}
switch (prio) {
case nsIRunnablePriority::PRIORITY_NORMAL:
mNormalQueue->PutEvent(event.forget(), aProofOfLock);
break;
case nsIRunnablePriority::PRIORITY_INPUT:
if (mIsInputPrioritizationEnabled) {
mInputQueue->PutEvent(event.forget(), aProofOfLock);
} else {
mNormalQueue->PutEvent(event.forget(), aProofOfLock);
}
break;
case nsIRunnablePriority::PRIORITY_HIGH:
if (mIsInputPrioritizationEnabled) {
mHighQueue->PutEvent(event.forget(), aProofOfLock);
} else {
// During startup, ContentParent sends SetXPCOMProcessAttributes to
// initialize ContentChild and enable input event prioritization. After
// that, ContentParent sends PBrowserConstructor to create PBrowserChild.
// To prevent PBrowserConstructor preempt SetXPCOMProcessAttributes and
// cause problems, we have to put high priority events in mNormalQueue to
// keep the correct order of initialization.
mNormalQueue->PutEvent(event.forget(), aProofOfLock);
}
break;
default:
MOZ_ASSERT(false);
break;
}
}
//-----------------------------------------------------------------------------
// nsIEventTarget
@@ -1165,6 +1277,24 @@ nsThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
return NS_OK;
}
NS_IMETHODIMP
nsThread::EnableEventPrioritization()
{
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mLock);
// Only support event prioritization for main event queue.
mEventsRoot.EnablePrioritization(lock);
return NS_OK;
}
NS_IMETHODIMP
nsThread::IsEventPrioritizationEnabled(bool* aResult)
{
MOZ_ASSERT(NS_IsMainThread());
*aResult = mEventsRoot.IsPrioritizationEnabled();
return NS_OK;
}
#ifdef MOZ_CANARY
void canary_alarm_handler(int signum);
@@ -1442,7 +1572,10 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
sMainThreadRunnableName[length] = '\0';
}
#endif
Maybe<AutoTimeDurationHelper> timeDurationHelper;
if (priority == nsIRunnablePriority::PRIORITY_INPUT) {
timeDurationHelper.emplace();
}
event->Run();
} else if (aMayWait) {
MOZ_ASSERT(ShuttingDown(),