mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 08:15:31 +00:00
8032d4373e
Differential Revision: https://phabricator.services.mozilla.com/D74672
447 lines
17 KiB
C++
447 lines
17 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 "mozilla/StaticPrefs_threads.h"
|
|
#include "mozilla/ipc/IdleSchedulerChild.h"
|
|
#include "nsThreadManager.h"
|
|
#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
|
|
#include "InputEventStatistics.h"
|
|
#include "MainThreadUtils.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
PrioritizedEventQueue::PrioritizedEventQueue(
|
|
already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
|
|
: mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
|
|
mInputQueue(
|
|
MakeUnique<EventQueueSized<32>>(EventQueuePriority::InputHigh)),
|
|
mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
|
|
mNormalQueue(MakeUnique<EventQueueSized<64>>(EventQueuePriority::Normal)),
|
|
mDeferredTimersQueue(
|
|
MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
|
|
mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)) {
|
|
mInputTaskManager = new InputTaskManager();
|
|
mIdleTaskManager = new IdleTaskManager(std::move(aIdlePeriod));
|
|
TaskController::Get()->SetIdleTaskManager(mIdleTaskManager);
|
|
}
|
|
|
|
PrioritizedEventQueue::~PrioritizedEventQueue() = default;
|
|
|
|
void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
|
EventQueuePriority aPriority,
|
|
const MutexAutoLock& aProofOfLock,
|
|
mozilla::TimeDuration* aDelay) {
|
|
RefPtr<nsIRunnable> event(aEvent);
|
|
|
|
if (UseTaskController()) {
|
|
TaskController* tc = TaskController::Get();
|
|
|
|
TaskManager* manager = nullptr;
|
|
if (aPriority == EventQueuePriority::InputHigh) {
|
|
if (mInputTaskManager->State() == InputTaskManager::STATE_DISABLED) {
|
|
aPriority = EventQueuePriority::Normal;
|
|
} else {
|
|
manager = mInputTaskManager;
|
|
}
|
|
} else if (aPriority == EventQueuePriority::DeferredTimers ||
|
|
aPriority == EventQueuePriority::Idle) {
|
|
manager = mIdleTaskManager;
|
|
}
|
|
|
|
tc->DispatchRunnable(event.forget(), static_cast<uint32_t>(aPriority),
|
|
manager);
|
|
return;
|
|
}
|
|
|
|
// Double check the priority with a QI.
|
|
EventQueuePriority priority = aPriority;
|
|
|
|
if (priority == EventQueuePriority::InputHigh &&
|
|
mInputTaskManager->State() == InputTaskManager::STATE_DISABLED) {
|
|
priority = EventQueuePriority::Normal;
|
|
} else if (priority == EventQueuePriority::MediumHigh &&
|
|
!StaticPrefs::threads_medium_high_event_queue_enabled()) {
|
|
priority = EventQueuePriority::Normal;
|
|
}
|
|
|
|
switch (priority) {
|
|
case EventQueuePriority::High:
|
|
mHighQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
|
|
break;
|
|
case EventQueuePriority::InputHigh:
|
|
mInputQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
|
|
break;
|
|
case EventQueuePriority::MediumHigh:
|
|
mMediumHighQueue->PutEvent(event.forget(), priority, aProofOfLock,
|
|
aDelay);
|
|
break;
|
|
case EventQueuePriority::Normal:
|
|
mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
|
|
break;
|
|
case EventQueuePriority::InputLow:
|
|
MOZ_ASSERT(false, "InputLow is a TaskController's internal priority!");
|
|
break;
|
|
case EventQueuePriority::DeferredTimers: {
|
|
if (NS_IsMainThread()) {
|
|
mDeferredTimersQueue->PutEvent(event.forget(), priority, aProofOfLock,
|
|
aDelay);
|
|
} else {
|
|
// We don't want to touch our idle queues from off the main
|
|
// thread. Queue it indirectly.
|
|
IndirectlyQueueRunnable(event.forget(), priority, aProofOfLock, aDelay);
|
|
}
|
|
break;
|
|
}
|
|
case EventQueuePriority::Idle: {
|
|
if (NS_IsMainThread()) {
|
|
mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
|
|
} else {
|
|
// We don't want to touch our idle queues from off the main
|
|
// thread. Queue it indirectly.
|
|
IndirectlyQueueRunnable(event.forget(), priority, aProofOfLock, aDelay);
|
|
}
|
|
break;
|
|
}
|
|
case EventQueuePriority::Count:
|
|
MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
|
|
break;
|
|
}
|
|
}
|
|
|
|
EventQueuePriority PrioritizedEventQueue::SelectQueue(
|
|
bool aUpdateState, const MutexAutoLock& aProofOfLock) {
|
|
size_t inputCount = mInputQueue->Count(aProofOfLock);
|
|
|
|
if (aUpdateState &&
|
|
mInputTaskManager->State() == InputTaskManager::STATE_ENABLED &&
|
|
mInputTaskManager->InputHandlingStartTime().IsNull() && inputCount > 0) {
|
|
mInputTaskManager->SetInputHandlingStartTime(
|
|
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:
|
|
// INPUT: if inputCount > 0 && TimeStamp::Now() >
|
|
// mInputTaskManager->InputHandlingStartTime(...);
|
|
// MEDIUMHIGH: if medium high
|
|
// pending NORMAL: if normal pending
|
|
//
|
|
// If we still don't have an event, then we take events from the queues
|
|
// in the following order:
|
|
// INPUT
|
|
// DEFERREDTIMERS: if GetLocalIdleDeadline()
|
|
// IDLE: if GetLocalIdleDeadline()
|
|
//
|
|
// 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.
|
|
EventQueuePriority queue;
|
|
if (!mHighQueue->IsEmpty(aProofOfLock)) {
|
|
queue = EventQueuePriority::High;
|
|
} else if (inputCount > 0 &&
|
|
(mInputTaskManager->State() == InputTaskManager::STATE_FLUSHING ||
|
|
(mInputTaskManager->State() == InputTaskManager::STATE_ENABLED &&
|
|
!mInputTaskManager->InputHandlingStartTime().IsNull() &&
|
|
TimeStamp::Now() >
|
|
mInputTaskManager->InputHandlingStartTime()))) {
|
|
queue = EventQueuePriority::InputHigh;
|
|
} else if (!mMediumHighQueue->IsEmpty(aProofOfLock)) {
|
|
MOZ_ASSERT(
|
|
mInputTaskManager->State() != InputTaskManager::STATE_FLUSHING,
|
|
"Shouldn't consume medium high event when flushing input events");
|
|
queue = EventQueuePriority::MediumHigh;
|
|
} else if (!mNormalQueue->IsEmpty(aProofOfLock)) {
|
|
MOZ_ASSERT(mInputTaskManager->State() != InputTaskManager::STATE_FLUSHING,
|
|
"Shouldn't consume normal event when flushing input events");
|
|
queue = EventQueuePriority::Normal;
|
|
} else if (inputCount > 0 &&
|
|
mInputTaskManager->State() != InputTaskManager::STATE_SUSPEND) {
|
|
MOZ_ASSERT(
|
|
mInputTaskManager->State() != InputTaskManager::STATE_DISABLED,
|
|
"Shouldn't consume input events when the input queue is disabled");
|
|
queue = EventQueuePriority::InputHigh;
|
|
} else if (!mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
|
|
// We may not actually return an idle event in this case.
|
|
queue = EventQueuePriority::DeferredTimers;
|
|
} else {
|
|
// We may not actually return an idle event in this case.
|
|
queue = EventQueuePriority::Idle;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(
|
|
queue == EventQueuePriority::InputHigh ||
|
|
queue == EventQueuePriority::InputLow,
|
|
mInputTaskManager->State() != InputTaskManager::STATE_DISABLED &&
|
|
mInputTaskManager->State() != InputTaskManager::STATE_SUSPEND);
|
|
|
|
return queue;
|
|
}
|
|
|
|
void PrioritizedEventQueue::IndirectlyQueueRunnable(
|
|
already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority,
|
|
const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aDelay) {
|
|
nsCOMPtr<nsIRunnable> event(aEvent);
|
|
|
|
nsCOMPtr<nsIRunnable> mainThreadRunnable = NS_NewRunnableFunction(
|
|
"idle runnable shim",
|
|
[event = std::move(event), priority = aPriority]() mutable {
|
|
NS_DispatchToCurrentThreadQueue(event.forget(), priority);
|
|
});
|
|
mNormalQueue->PutEvent(mainThreadRunnable.forget(),
|
|
EventQueuePriority::Normal, aProofOfLock, aDelay);
|
|
}
|
|
|
|
// The delay returned is the queuing delay a hypothetical Input event would
|
|
// see due to the current running event if it had arrived while the current
|
|
// event was queued. This means that any event running at priority below
|
|
// Input doesn't cause queuing delay for Input events, and we return
|
|
// TimeDuration() for those cases.
|
|
already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
|
|
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock,
|
|
TimeDuration* aHypotheticalInputEventDelay) {
|
|
MOZ_ASSERT_UNREACHABLE("Who is managing to call this?");
|
|
bool ignored;
|
|
return GetEvent(aPriority, aProofOfLock, aHypotheticalInputEventDelay,
|
|
&ignored);
|
|
}
|
|
|
|
already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
|
|
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock,
|
|
TimeDuration* aHypotheticalInputEventDelay, bool* aIsIdleEvent) {
|
|
EventQueuePriority queue = SelectQueue(true, aProofOfLock);
|
|
|
|
if (aPriority) {
|
|
*aPriority = queue;
|
|
}
|
|
*aIsIdleEvent = false;
|
|
|
|
// Since Input events will only be delayed behind Input or High events,
|
|
// the amount of time a lower-priority event spent in the queue is
|
|
// irrelevant in knowing how long an input event would be delayed.
|
|
// Alternatively, we could export the delay and let the higher-level code
|
|
// key off the returned priority level (though then it'd need to know
|
|
// if the thread's queue was a PrioritizedEventQueue or normal/other
|
|
// EventQueue).
|
|
nsCOMPtr<nsIRunnable> event;
|
|
switch (queue) {
|
|
default:
|
|
MOZ_CRASH();
|
|
break;
|
|
|
|
case EventQueuePriority::High:
|
|
event = mHighQueue->GetEvent(nullptr, aProofOfLock,
|
|
aHypotheticalInputEventDelay);
|
|
MOZ_ASSERT(event);
|
|
mInputTaskManager->SetInputHandlingStartTime(TimeStamp());
|
|
break;
|
|
|
|
case EventQueuePriority::InputHigh:
|
|
event = mInputQueue->GetEvent(nullptr, aProofOfLock,
|
|
aHypotheticalInputEventDelay);
|
|
MOZ_ASSERT(event);
|
|
break;
|
|
|
|
// All queue priorities below Input don't add their queuing time to the
|
|
// time an input event will be delayed, so report 0 for time-in-queue
|
|
// if we're below Input; input events will only be delayed by the time
|
|
// an event actually runs (if the event is below Input event's priority)
|
|
case EventQueuePriority::MediumHigh:
|
|
event = mMediumHighQueue->GetEvent(nullptr, aProofOfLock);
|
|
*aHypotheticalInputEventDelay = TimeDuration();
|
|
break;
|
|
|
|
case EventQueuePriority::Normal:
|
|
event = mNormalQueue->GetEvent(nullptr, aProofOfLock);
|
|
*aHypotheticalInputEventDelay = TimeDuration();
|
|
break;
|
|
|
|
case EventQueuePriority::Idle:
|
|
case EventQueuePriority::DeferredTimers:
|
|
*aHypotheticalInputEventDelay = TimeDuration();
|
|
// If we get here, then all queues except deferredtimers and idle are
|
|
// empty.
|
|
|
|
if (!HasIdleRunnables(aProofOfLock)) {
|
|
return nullptr;
|
|
}
|
|
|
|
TimeStamp idleDeadline =
|
|
mIdleTaskManager->State().GetCachedIdleDeadline();
|
|
if (!idleDeadline) {
|
|
return nullptr;
|
|
}
|
|
|
|
event = mDeferredTimersQueue->GetEvent(nullptr, aProofOfLock);
|
|
if (!event) {
|
|
event = mIdleQueue->GetEvent(nullptr, aProofOfLock);
|
|
}
|
|
if (event) {
|
|
*aIsIdleEvent = true;
|
|
nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
|
|
if (idleEvent) {
|
|
idleEvent->SetDeadline(idleDeadline);
|
|
}
|
|
}
|
|
break;
|
|
} // switch (queue)
|
|
|
|
if (!event) {
|
|
*aHypotheticalInputEventDelay = TimeDuration();
|
|
}
|
|
|
|
MOZ_ASSERT_IF(aPriority, *aPriority == queue);
|
|
|
|
return event.forget();
|
|
}
|
|
|
|
void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
|
|
if (IsEmpty(aProofOfLock)) {
|
|
// Certainly no more idle tasks. Unlocking here is OK, because
|
|
// our caller does nothing after this except return, which unlocks
|
|
// its lock anyway.
|
|
MutexAutoUnlock unlock(*mMutex);
|
|
mIdleTaskManager->State().RanOutOfTasks(unlock);
|
|
}
|
|
}
|
|
|
|
bool PrioritizedEventQueue::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) &&
|
|
mMediumHighQueue->IsEmpty(aProofOfLock) &&
|
|
mNormalQueue->IsEmpty(aProofOfLock) &&
|
|
mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
|
|
mIdleQueue->IsEmpty(aProofOfLock);
|
|
}
|
|
|
|
bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
|
|
mIdleTaskManager->State().ForgetPendingTaskGuarantee();
|
|
|
|
EventQueuePriority queue = SelectQueue(false, aProofOfLock);
|
|
|
|
if (queue == EventQueuePriority::High) {
|
|
return mHighQueue->HasReadyEvent(aProofOfLock);
|
|
} else if (queue == EventQueuePriority::InputHigh) {
|
|
return mInputQueue->HasReadyEvent(aProofOfLock);
|
|
} else if (queue == EventQueuePriority::MediumHigh) {
|
|
return mMediumHighQueue->HasReadyEvent(aProofOfLock);
|
|
} else if (queue == EventQueuePriority::Normal) {
|
|
return mNormalQueue->HasReadyEvent(aProofOfLock);
|
|
}
|
|
|
|
MOZ_ASSERT(queue == EventQueuePriority::Idle ||
|
|
queue == EventQueuePriority::DeferredTimers);
|
|
|
|
// If we get here, then both the high and normal queues are empty.
|
|
|
|
if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
|
|
mIdleQueue->IsEmpty(aProofOfLock)) {
|
|
return false;
|
|
}
|
|
|
|
// Temporarily unlock so we can peek our idle deadline.
|
|
{
|
|
MutexAutoUnlock unlock(*mMutex);
|
|
mIdleTaskManager->State().CachePeekedIdleDeadline(unlock);
|
|
}
|
|
TimeStamp idleDeadline = mIdleTaskManager->State().GetCachedIdleDeadline();
|
|
mIdleTaskManager->State().ClearCachedIdleDeadline();
|
|
|
|
// Re-check the emptiness of the queues, since we had the lock released for a
|
|
// bit.
|
|
if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
|
|
mIdleQueue->HasReadyEvent(aProofOfLock))) {
|
|
mIdleTaskManager->State().EnforcePendingTaskGuarantee();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PrioritizedEventQueue::HasPendingHighPriorityEvents(
|
|
const MutexAutoLock& aProofOfLock) {
|
|
return !mHighQueue->IsEmpty(aProofOfLock);
|
|
}
|
|
|
|
bool PrioritizedEventQueue::HasIdleRunnables(
|
|
const MutexAutoLock& aProofOfLock) const {
|
|
return !mIdleQueue->IsEmpty(aProofOfLock) ||
|
|
!mDeferredTimersQueue->IsEmpty(aProofOfLock);
|
|
}
|
|
|
|
size_t PrioritizedEventQueue::Count(const MutexAutoLock& aProofOfLock) const {
|
|
MOZ_CRASH("unimplemented");
|
|
}
|
|
|
|
void InputTaskManager::EnableInputEventPrioritization() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
|
|
mInputQueueState = STATE_ENABLED;
|
|
mInputHandlingStartTime = TimeStamp();
|
|
}
|
|
|
|
void InputTaskManager::FlushInputEventPrioritization() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
|
|
mInputQueueState == STATE_SUSPEND);
|
|
mInputQueueState =
|
|
mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
|
|
}
|
|
|
|
void InputTaskManager::SuspendInputEventPrioritization() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
|
|
mInputQueueState == STATE_FLUSHING);
|
|
mInputQueueState = STATE_SUSPEND;
|
|
}
|
|
|
|
void InputTaskManager::ResumeInputEventPrioritization() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
|
|
mInputQueueState = STATE_ENABLED;
|
|
}
|
|
|
|
int32_t InputTaskManager::GetPriorityModifierForEventLoopTurn(
|
|
const MutexAutoLock& aProofOfLock) {
|
|
size_t inputCount = PendingTaskCount();
|
|
if (State() == STATE_ENABLED && InputHandlingStartTime().IsNull() &&
|
|
inputCount > 0) {
|
|
SetInputHandlingStartTime(
|
|
InputEventStatistics::Get().GetInputHandlingStartTime(inputCount));
|
|
}
|
|
|
|
if (inputCount > 0 && (State() == InputTaskManager::STATE_FLUSHING ||
|
|
(State() == InputTaskManager::STATE_ENABLED &&
|
|
!InputHandlingStartTime().IsNull() &&
|
|
TimeStamp::Now() > InputHandlingStartTime()))) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t modifier = static_cast<int32_t>(EventQueuePriority::InputLow) -
|
|
static_cast<int32_t>(EventQueuePriority::MediumHigh);
|
|
return modifier;
|
|
}
|
|
|
|
void InputTaskManager::WillRunTask() {
|
|
TaskManager::WillRunTask();
|
|
mStartTimes.AppendElement(TimeStamp::Now());
|
|
}
|
|
|
|
void InputTaskManager::DidRunTask() {
|
|
TaskManager::DidRunTask();
|
|
MOZ_ASSERT(!mStartTimes.IsEmpty());
|
|
TimeStamp start = mStartTimes.PopLastElement();
|
|
InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - start);
|
|
}
|