зеркало из https://github.com/mozilla/gecko-dev.git
346 строки
12 KiB
C++
346 строки
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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 "PrioritizedEventQueue.h"
|
|
#include "mozilla/EventQueue.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "nsThreadManager.h"
|
|
#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
|
|
#include "InputEventStatistics.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
template<class InnerQueueT>
|
|
PrioritizedEventQueue<InnerQueueT>::PrioritizedEventQueue(UniquePtr<InnerQueueT> aHighQueue,
|
|
UniquePtr<InnerQueueT> aInputQueue,
|
|
UniquePtr<InnerQueueT> aNormalQueue,
|
|
UniquePtr<InnerQueueT> aIdleQueue,
|
|
already_AddRefed<nsIIdlePeriod> aIdlePeriod)
|
|
: mHighQueue(Move(aHighQueue))
|
|
, mInputQueue(Move(aInputQueue))
|
|
, mNormalQueue(Move(aNormalQueue))
|
|
, mIdleQueue(Move(aIdleQueue))
|
|
, mIdlePeriod(aIdlePeriod)
|
|
{
|
|
static_assert(IsBaseOf<AbstractEventQueue, InnerQueueT>::value,
|
|
"InnerQueueT must be an AbstractEventQueue subclass");
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
void
|
|
PrioritizedEventQueue<InnerQueueT>::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
|
EventPriority aPriority,
|
|
const MutexAutoLock& aProofOfLock)
|
|
{
|
|
// Double check the priority with a QI.
|
|
RefPtr<nsIRunnable> event(aEvent);
|
|
EventPriority priority = aPriority;
|
|
|
|
if (priority == EventPriority::Input && mInputQueueState == STATE_DISABLED) {
|
|
priority = EventPriority::Normal;
|
|
}
|
|
|
|
switch (priority) {
|
|
case EventPriority::High:
|
|
mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
|
|
break;
|
|
case EventPriority::Input:
|
|
mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
|
|
break;
|
|
case EventPriority::Normal:
|
|
mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
|
|
break;
|
|
case EventPriority::Idle:
|
|
mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
|
|
break;
|
|
case EventPriority::Count:
|
|
MOZ_CRASH("EventPriority::Count isn't a valid priority");
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
TimeStamp
|
|
PrioritizedEventQueue<InnerQueueT>::GetIdleDeadline()
|
|
{
|
|
// If we are shutting down, we won't honor the idle period, and we will
|
|
// always process idle runnables. This will ensure that the idle queue
|
|
// gets exhausted at shutdown time to prevent intermittently leaking
|
|
// some runnables inside that queue and even worse potentially leaving
|
|
// some important cleanup work unfinished.
|
|
if (gXPCOMThreadsShutDown || nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
|
|
return TimeStamp::Now();
|
|
}
|
|
|
|
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(*mMutex);
|
|
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;
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
EventPriority
|
|
PrioritizedEventQueue<InnerQueueT>::SelectQueue(bool aUpdateState,
|
|
const MutexAutoLock& aProofOfLock)
|
|
{
|
|
bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
|
|
bool normalPending = !mNormalQueue->IsEmpty(aProofOfLock);
|
|
size_t inputCount = mInputQueue->Count(aProofOfLock);
|
|
|
|
if (aUpdateState &&
|
|
mInputQueueState == STATE_ENABLED &&
|
|
mInputHandlingStartTime.IsNull() &&
|
|
inputCount > 0) {
|
|
mInputHandlingStartTime =
|
|
InputEventStatistics::Get()
|
|
.GetInputHandlingStartTime(inputCount);
|
|
}
|
|
|
|
// We check the different queues in the following order. The conditions we use
|
|
// are meant to avoid starvation and to ensure that we don't process an event
|
|
// at the wrong time.
|
|
//
|
|
// HIGH: if mProcessHighPriorityQueue
|
|
// INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
|
|
// NORMAL: if normalPending
|
|
//
|
|
// If we still don't have an event, then we take events from the queues
|
|
// in the following order:
|
|
//
|
|
// HIGH
|
|
// INPUT
|
|
// IDLE: if GetIdleDeadline()
|
|
//
|
|
// If we don't get an event in this pass, then we return null since no events
|
|
// are ready.
|
|
|
|
// This variable determines which queue we will take an event from.
|
|
EventPriority queue;
|
|
|
|
if (mProcessHighPriorityQueue) {
|
|
queue = EventPriority::High;
|
|
} else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
|
|
(mInputQueueState == STATE_ENABLED &&
|
|
!mInputHandlingStartTime.IsNull() &&
|
|
TimeStamp::Now() > mInputHandlingStartTime))) {
|
|
queue = EventPriority::Input;
|
|
} else if (normalPending) {
|
|
MOZ_ASSERT(mInputQueueState != STATE_FLUSHING,
|
|
"Shouldn't consume normal event when flusing input events");
|
|
queue = EventPriority::Normal;
|
|
} else if (highPending) {
|
|
queue = EventPriority::High;
|
|
} else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) {
|
|
MOZ_ASSERT(mInputQueueState != STATE_DISABLED,
|
|
"Shouldn't consume input events when the input queue is disabled");
|
|
queue = EventPriority::Input;
|
|
} else {
|
|
// We may not actually return an idle event in this case.
|
|
queue = EventPriority::Idle;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(queue == EventPriority::Input,
|
|
mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND);
|
|
|
|
if (aUpdateState) {
|
|
mProcessHighPriorityQueue = highPending;
|
|
}
|
|
|
|
return queue;
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
already_AddRefed<nsIRunnable>
|
|
PrioritizedEventQueue<InnerQueueT>::GetEvent(EventPriority* aPriority,
|
|
const MutexAutoLock& aProofOfLock)
|
|
{
|
|
MakeScopeExit([&] {
|
|
mHasPendingEventsPromisedIdleEvent = false;
|
|
});
|
|
|
|
#ifndef RELEASE_OR_BETA
|
|
// Clear mNextIdleDeadline so that it is possible to determine that
|
|
// we're running an idle runnable in ProcessNextEvent.
|
|
*mNextIdleDeadline = TimeStamp();
|
|
#endif
|
|
|
|
EventPriority queue = SelectQueue(true, aProofOfLock);
|
|
|
|
if (aPriority) {
|
|
*aPriority = queue;
|
|
}
|
|
|
|
if (queue == EventPriority::High) {
|
|
nsCOMPtr<nsIRunnable> event = mHighQueue->GetEvent(aPriority, aProofOfLock);
|
|
MOZ_ASSERT(event);
|
|
mInputHandlingStartTime = TimeStamp();
|
|
mProcessHighPriorityQueue = false;
|
|
return event.forget();
|
|
}
|
|
|
|
if (queue == EventPriority::Input) {
|
|
nsCOMPtr<nsIRunnable> event = mInputQueue->GetEvent(aPriority, aProofOfLock);
|
|
MOZ_ASSERT(event);
|
|
return event.forget();
|
|
}
|
|
|
|
if (queue == EventPriority::Normal) {
|
|
nsCOMPtr<nsIRunnable> event = mNormalQueue->GetEvent(aPriority, aProofOfLock);
|
|
return event.forget();
|
|
}
|
|
|
|
// If we get here, then all queues except idle are empty.
|
|
MOZ_ASSERT(queue == EventPriority::Idle);
|
|
|
|
if (mIdleQueue->IsEmpty(aProofOfLock)) {
|
|
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
|
|
return nullptr;
|
|
}
|
|
|
|
TimeStamp idleDeadline = GetIdleDeadline();
|
|
if (!idleDeadline) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
|
|
if (event) {
|
|
nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
|
|
if (idleEvent) {
|
|
idleEvent->SetDeadline(idleDeadline);
|
|
}
|
|
|
|
#ifndef RELEASE_OR_BETA
|
|
// Store the next idle deadline to be able to determine budget use
|
|
// in ProcessNextEvent.
|
|
*mNextIdleDeadline = idleDeadline;
|
|
#endif
|
|
}
|
|
|
|
return event.forget();
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
bool
|
|
PrioritizedEventQueue<InnerQueueT>::IsEmpty(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
// Just check IsEmpty() on the sub-queues. Don't bother checking the idle
|
|
// deadline since that only determines whether an idle event is ready or not.
|
|
return mHighQueue->IsEmpty(aProofOfLock)
|
|
&& mInputQueue->IsEmpty(aProofOfLock)
|
|
&& mNormalQueue->IsEmpty(aProofOfLock)
|
|
&& mIdleQueue->IsEmpty(aProofOfLock);
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
bool
|
|
PrioritizedEventQueue<InnerQueueT>::HasReadyEvent(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
mHasPendingEventsPromisedIdleEvent = false;
|
|
|
|
EventPriority queue = SelectQueue(false, aProofOfLock);
|
|
|
|
if (queue == EventPriority::High) {
|
|
return mHighQueue->HasReadyEvent(aProofOfLock);
|
|
} else if (queue == EventPriority::Input) {
|
|
return mInputQueue->HasReadyEvent(aProofOfLock);
|
|
} else if (queue == EventPriority::Normal) {
|
|
return mNormalQueue->HasReadyEvent(aProofOfLock);
|
|
}
|
|
|
|
MOZ_ASSERT(queue == EventPriority::Idle);
|
|
|
|
// If we get here, then both the high and normal queues are empty.
|
|
|
|
if (mIdleQueue->IsEmpty(aProofOfLock)) {
|
|
return false;
|
|
}
|
|
|
|
TimeStamp idleDeadline = GetIdleDeadline();
|
|
if (idleDeadline && mIdleQueue->HasReadyEvent(aProofOfLock)) {
|
|
mHasPendingEventsPromisedIdleEvent = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
size_t
|
|
PrioritizedEventQueue<InnerQueueT>::Count(const MutexAutoLock& aProofOfLock) const
|
|
{
|
|
MOZ_CRASH("unimplemented");
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
void
|
|
PrioritizedEventQueue<InnerQueueT>::EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
|
|
mInputQueueState = STATE_ENABLED;
|
|
mInputHandlingStartTime = TimeStamp();
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
void
|
|
PrioritizedEventQueue<InnerQueueT>::
|
|
FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
MOZ_ASSERT(mInputQueueState == STATE_ENABLED || mInputQueueState == STATE_SUSPEND);
|
|
mInputQueueState =
|
|
mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
void
|
|
PrioritizedEventQueue<InnerQueueT>::
|
|
SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
MOZ_ASSERT(mInputQueueState == STATE_ENABLED || mInputQueueState == STATE_FLUSHING);
|
|
mInputQueueState = STATE_SUSPEND;
|
|
}
|
|
|
|
template<class InnerQueueT>
|
|
void
|
|
PrioritizedEventQueue<InnerQueueT>::
|
|
ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
|
|
mInputQueueState = STATE_ENABLED;
|
|
}
|
|
|
|
namespace mozilla {
|
|
template class PrioritizedEventQueue<EventQueue>;
|
|
template class PrioritizedEventQueue<LabeledEventQueue>;
|
|
}
|