Bug 1589561. Factor out idle handling from PrioritizedEventQueue. r=smaug

We could try to move the EnforcePendingTaskGuarantee() bit into PeekIdleDeadline, but
then we'd need to check HasReadyEvent() on mDeferredTimersQueue and mIdleQueue
before we unlock the mutex and PeekIdleDeadline, and it's not clear that that
state cannot change once the mutex is unlocked...

The EnsureIsActive() call at the end of GetIdleDeadlineInternal in the !aIsPeek
case only makes sense if there are in fact idle tasks available to run when
GetDeadlineForIdleTask is called, because otherwise it would incorrectly set us
active when we are not running any tasks.

Differential Revision: https://phabricator.services.mozilla.com/D49696

--HG--
rename : xpcom/threads/PrioritizedEventQueue.cpp => xpcom/threads/IdlePeriodState.cpp
rename : xpcom/threads/PrioritizedEventQueue.h => xpcom/threads/IdlePeriodState.h
extra : moz-landing-system : lando
This commit is contained in:
Boris Zbarsky 2019-10-18 17:28:50 +00:00
parent 3549bf5217
commit 4a394c3f69
7 changed files with 492 additions and 269 deletions

View File

@ -7,7 +7,7 @@
#include "mozilla/ipc/IdleSchedulerChild.h"
#include "mozilla/ipc/IdleSchedulerParent.h"
#include "mozilla/Atomics.h"
#include "mozilla/PrioritizedEventQueue.h"
#include "mozilla/IdlePeriodState.h"
#include "BackgroundChild.h"
namespace mozilla {
@ -19,11 +19,11 @@ IdleSchedulerChild::~IdleSchedulerChild() {
if (sMainThreadIdleScheduler == this) {
sMainThreadIdleScheduler = nullptr;
}
MOZ_ASSERT(!mEventQueue);
MOZ_ASSERT(!mIdlePeriodState);
}
void IdleSchedulerChild::Init(PrioritizedEventQueue* aEventQueue) {
mEventQueue = aEventQueue;
void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) {
mIdlePeriodState = aIdlePeriodState;
RefPtr<IdleSchedulerChild> scheduler = this;
auto resolve =
@ -32,7 +32,7 @@ void IdleSchedulerChild::Init(PrioritizedEventQueue* aEventQueue) {
mActiveCounter.SetHandle(*Get<0>(aResult), false);
mActiveCounter.Map(sizeof(int32_t));
mChildId = Get<1>(aResult);
if (mChildId && mEventQueue && mEventQueue->IsActive()) {
if (mChildId && mIdlePeriodState && mIdlePeriodState->IsActive()) {
SetActive();
}
}
@ -43,8 +43,8 @@ void IdleSchedulerChild::Init(PrioritizedEventQueue* aEventQueue) {
}
IPCResult IdleSchedulerChild::RecvIdleTime(uint64_t aId, TimeDuration aBudget) {
if (mEventQueue) {
mEventQueue->SetIdleToken(aId, aBudget);
if (mIdlePeriodState) {
mIdlePeriodState->SetIdleToken(aId, aBudget);
}
return IPC_OK();
}

View File

@ -14,7 +14,7 @@
class nsIIdlePeriod;
namespace mozilla {
class PrioritizedEventQueue;
class IdlePeriodState;
namespace ipc {
@ -28,9 +28,9 @@ class IdleSchedulerChild final : public PIdleSchedulerChild {
IPCResult RecvIdleTime(uint64_t aId, TimeDuration aBudget);
void Init(PrioritizedEventQueue* aEventQueue);
void Init(IdlePeriodState* aIdlePeriodState);
void Disconnect() { mEventQueue = nullptr; }
void Disconnect() { mIdlePeriodState = nullptr; }
// See similar methods on PrioritizedEventQueue.
void SetActive();
@ -47,7 +47,7 @@ class IdleSchedulerChild final : public PIdleSchedulerChild {
// See IdleScheduleParent::sActiveChildCounter
base::SharedMemory mActiveCounter;
PrioritizedEventQueue* mEventQueue = nullptr;
IdlePeriodState* mIdlePeriodState = nullptr;
uint32_t mChildId = 0;
};

View File

@ -0,0 +1,252 @@
/* -*- 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 "mozilla/IdlePeriodState.h"
#include "mozilla/StaticPrefs_idle_period.h"
#include "mozilla/ipc/IdleSchedulerChild.h"
#include "nsIIdlePeriod.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsXULAppAPI.h"
static uint64_t sIdleRequestCounter = 0;
namespace mozilla {
IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
: mIdlePeriod(aIdlePeriod) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
}
IdlePeriodState::~IdlePeriodState() {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (mIdleScheduler) {
mIdleScheduler->Disconnect();
}
}
size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = 0;
if (mIdlePeriod) {
n += aMallocSizeOf(mIdlePeriod);
}
return n;
}
void IdlePeriodState::FlagNotIdle(const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
EnsureIsActive();
if (mIdleToken && mIdleToken < TimeStamp::Now()) {
ClearIdleToken(aProofOfUnlock);
}
}
void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
EnsureIsPaused(aProofOfUnlock);
ClearIdleToken(aProofOfUnlock);
}
TimeStamp IdlePeriodState::GetIdleDeadlineInternal(
bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
bool shuttingDown;
TimeStamp localIdleDeadline =
GetLocalIdleDeadline(shuttingDown, aProofOfUnlock);
if (!localIdleDeadline) {
if (!aIsPeek) {
EnsureIsPaused(aProofOfUnlock);
ClearIdleToken(aProofOfUnlock);
}
return TimeStamp();
}
TimeStamp idleDeadline =
mHasPendingEventsPromisedIdleEvent || shuttingDown
? localIdleDeadline
: GetIdleToken(localIdleDeadline, aProofOfUnlock);
if (!idleDeadline) {
if (!aIsPeek) {
EnsureIsPaused(aProofOfUnlock);
// Don't call ClearIdleToken() here, since we may have a pending
// request already.
RequestIdleToken(localIdleDeadline, aProofOfUnlock);
}
return TimeStamp();
}
if (!aIsPeek) {
EnsureIsActive();
}
return idleDeadline;
}
TimeStamp IdlePeriodState::GetLocalIdleDeadline(
bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
// 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()) {
aShuttingDown = true;
return TimeStamp::Now();
}
aShuttingDown = false;
TimeStamp idleDeadline;
// This GetIdlePeriodHint call is why we need aProofOfUnlock, since getting
// the idle period might need to lock the timer thread. Can we get right of
// this somehow?
Unused << aProofOfUnlock;
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;
}
TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint,
const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (XRE_IsParentProcess()) {
return aLocalIdlePeriodHint;
}
if (mIdleToken) {
TimeStamp now = TimeStamp::Now();
if (mIdleToken < now) {
ClearIdleToken(aProofOfUnlock);
return mIdleToken;
}
return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
: aLocalIdlePeriodHint;
}
return TimeStamp();
}
void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint,
const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(!mActive);
if (!mIdleSchedulerInitialized) {
mIdleSchedulerInitialized = true;
if (StaticPrefs::idle_period_cross_process_scheduling() &&
XRE_IsContentProcess() &&
// Disable when recording/replaying, as IdleSchedulerChild uses mutable
// shared memory which needs special handling.
!recordreplay::IsRecordingOrReplaying()) {
// For now cross-process idle scheduler is supported only on the main
// threads of the child processes.
mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
if (mIdleScheduler) {
mIdleScheduler->Init(this);
}
}
}
if (mIdleScheduler && !mIdleRequestId) {
TimeStamp now = TimeStamp::Now();
if (aLocalIdlePeriodHint <= now) {
return;
}
mIdleRequestId = ++sIdleRequestCounter;
// This is presumably the bit where we need the "not holding mutexes"
// guarantee. Can we avoid that somehow?
Unused << aProofOfUnlock;
mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
aLocalIdlePeriodHint - now);
}
}
void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (mIdleRequestId == aId) {
mIdleToken = TimeStamp::Now() + aDuration;
}
}
void IdlePeriodState::SetActive() {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(!mActive);
if (mIdleScheduler) {
mIdleScheduler->SetActive();
}
mActive = true;
}
void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
MOZ_ASSERT(mActive);
if (mIdleScheduler && mIdleScheduler->SetPaused()) {
// We may have gotten a free cpu core for running idle tasks.
// We don't try to catch the case when there are prioritized processes
// running.
// This SendSchedule call is why we need to aProofOfUnlock, because IPC can
// do weird things with mutexes.
Unused << aProofOfUnlock;
mIdleScheduler->SendSchedule();
}
mActive = false;
}
void IdlePeriodState::ClearIdleToken(const MutexAutoUnlock& aProofOfUnlock) {
MOZ_ASSERT(NS_IsMainThread(),
"Why are we touching idle state off the main thread?");
if (mIdleRequestId) {
if (mIdleScheduler) {
// This SendIdleTimeUsed call is why we need a proof of unlock. Can we
// eliminate that requirement somehow?
Unused << aProofOfUnlock;
mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
}
mIdleRequestId = 0;
mIdleToken = TimeStamp();
}
}
} // namespace mozilla

View File

@ -0,0 +1,190 @@
/* -*- 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/. */
#ifndef mozilla_IdlePeriodState_h
#define mozilla_IdlePeriodState_h
/**
* A class for tracking the state of our idle period. This includes keeping
* track of both the state of our process-local idle period estimate and, for
* content processes, managing communication with the parent process for
* cross-pprocess idle detection.
*/
#include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "nsCOMPtr.h"
#include <stdint.h>
class nsIIdlePeriod;
namespace mozilla {
namespace ipc {
class IdleSchedulerChild;
} // namespace ipc
class IdlePeriodState {
public:
explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
~IdlePeriodState();
// Integration with memory reporting.
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
// Notification that whoever we are tracking idle state for has found a
// non-idle task to process.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
void FlagNotIdle(const MutexAutoUnlock& aProofOfUnlock);
// Notification that whoever we are tracking idle state for has no more
// tasks (idle or not) to process.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock);
// Notification that whoever we are tracking idle state has idle tasks that
// they are considering ready to run and that we should keep claiming they are
// ready to run until they call ForgetPendingTaskGuarantee().
void EnforcePendingTaskGuarantee() {
mHasPendingEventsPromisedIdleEvent = true;
}
// Notification that whoever we are tracking idle state for is done with our
// "we have an idle event ready to run" guarantee. When this happens, we can
// reset mHasPendingEventsPromisedIdleEvent to false, because we have
// fulfilled our contract.
void ForgetPendingTaskGuarantee() {
mHasPendingEventsPromisedIdleEvent = false;
}
// Get our current idle deadline so we can run an idle task with that
// deadline. This can return a null timestamp (which means we are not idle
// right now), and it can also queue up queries to our parent process, if
// we're a content process, to find out whether we're idle. This should only
// be called when there is an actual idle task that might run.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
TimeStamp GetDeadlineForIdleTask(const MutexAutoUnlock& aProofOfUnlock) {
return GetIdleDeadlineInternal(false, aProofOfUnlock);
}
// Peek our current idle deadline. This can return a null timestamp (which
// means we are not idle right now). This method does not have any
// side-effects on our state, apart from guaranteeing that if it returns
// non-null then GetDeadlineForIdleTask will return non-null until
// ForgetPendingTaskGuarantee() is called.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
TimeStamp PeekIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) {
return GetIdleDeadlineInternal(true, aProofOfUnlock);
}
void SetIdleToken(uint64_t aId, TimeDuration aDuration);
bool IsActive() { return mActive; }
protected:
void EnsureIsActive() {
if (!mActive) {
SetActive();
}
}
void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) {
if (mActive) {
SetPaused(aProofOfUnlock);
}
}
// Returns a null TimeStamp if we're not in the idle period.
TimeStamp GetLocalIdleDeadline(bool& aShuttingDown,
const MutexAutoUnlock& aProofOfUnlock);
// Gets the idle token, which is the end time of the idle period.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint,
const MutexAutoUnlock& aProofOfUnlock);
// In case of child processes, requests idle time from the cross-process
// idle scheduler.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
void RequestIdleToken(TimeStamp aLocalIdlePeriodHint,
const MutexAutoUnlock& aProofOfUnlock);
// Mark that we don't have idle time to use, nor are expecting to get an idle
// token from the idle scheduler.
void ClearIdleToken(const MutexAutoUnlock& aProofOfUnlock);
// SetActive should be called when the event queue is running any type of
// tasks.
void SetActive();
// SetPaused should be called once the event queue doesn't have more
// tasks to process, or is waiting for the idle token.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
void SetPaused(const MutexAutoUnlock& aProofOfUnlock);
// Get or peek our idle deadline. When peeking, we generally don't change any
// of our internal state. When getting, we may request an idle token as
// needed.
//
// Do not call this while holding any mutexes, because it may lock mutexes
// itself.
TimeStamp GetIdleDeadlineInternal(bool aIsPeek,
const MutexAutoUnlock& aProofOfUnlock);
// Set to true if we have claimed we have a ready-to-run idle task when asked.
// In that case, we will ensure that we allow at least one task to run when
// someone tries to run a task, even if we have run out of idle period at that
// point. This ensures that we never fail to produce a task to run if we
// claim we have a task ready to run.
bool mHasPendingEventsPromisedIdleEvent = false;
// mIdlePeriod keeps track of the current idle period. Calling
// mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
// the current idle period will end.
nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
// If non-null, this timestamp represents the end time of the idle period. An
// idle period starts when we get the idle token from the parent process and
// ends when either there are no more things we want to run at idle priority
// or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline.
TimeStamp mIdleToken;
// The id of the last idle request to the cross-process idle scheduler.
uint64_t mIdleRequestId = 0;
// If we're in a content process, we use mIdleScheduler to communicate with
// the parent process for purposes of cross-process idle tracking.
RefPtr<ipc::IdleSchedulerChild> mIdleScheduler;
// mIdleSchedulerInitialized is true if our mIdleScheduler has been
// initialized. It may be null even after initialiazation, in various
// situations.
bool mIdleSchedulerInitialized = false;
// mActive is true when the PrioritizedEventQueue or TaskController we are
// associated with is running tasks.
bool mActive = true;
};
} // namespace mozilla
#endif // mozilla_IdlePeriodState_h

View File

@ -7,7 +7,6 @@
#include "PrioritizedEventQueue.h"
#include "mozilla/EventQueue.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_idle_period.h"
#include "mozilla/StaticPrefs_threads.h"
#include "mozilla/ipc/IdleSchedulerChild.h"
#include "nsThreadManager.h"
@ -16,10 +15,8 @@
using namespace mozilla;
static uint64_t sIdleRequestCounter = 0;
PrioritizedEventQueue::PrioritizedEventQueue(
already_AddRefed<nsIIdlePeriod> aIdlePeriod)
already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
: mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
@ -27,13 +24,9 @@ PrioritizedEventQueue::PrioritizedEventQueue(
mDeferredTimersQueue(
MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
mIdlePeriod(aIdlePeriod) {}
mIdlePeriodState(std::move(aIdlePeriod)) {}
PrioritizedEventQueue::~PrioritizedEventQueue() {
if (mIdleScheduler) {
mIdleScheduler->Disconnect();
}
}
PrioritizedEventQueue::~PrioritizedEventQueue() = default;
void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
EventQueuePriority aPriority,
@ -75,53 +68,6 @@ void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
}
}
TimeStamp PrioritizedEventQueue::GetLocalIdleDeadline(bool& aShuttingDown) {
// 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()) {
aShuttingDown = true;
return TimeStamp::Now();
}
aShuttingDown = false;
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;
}
EventQueuePriority PrioritizedEventQueue::SelectQueue(
bool aUpdateState, const MutexAutoLock& aProofOfLock) {
size_t inputCount = mInputQueue->Count(aProofOfLock);
@ -208,13 +154,11 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
EventQueuePriority queue = SelectQueue(true, aProofOfLock);
auto guard = MakeScopeExit([&] {
mHasPendingEventsPromisedIdleEvent = false;
mIdlePeriodState.ForgetPendingTaskGuarantee();
if (queue != EventQueuePriority::Idle &&
queue != EventQueuePriority::DeferredTimers) {
EnsureIsActive();
if (mIdleToken && mIdleToken < TimeStamp::Now()) {
ClearIdleToken();
}
MutexAutoUnlock unlock(*mMutex);
mIdlePeriodState.FlagNotIdle(unlock);
}
});
@ -255,30 +199,19 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
if (mIdleQueue->IsEmpty(aProofOfLock) &&
mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
EnsureIsPaused();
ClearIdleToken();
return nullptr;
}
bool shuttingDown;
TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
if (!localIdleDeadline) {
EnsureIsPaused();
ClearIdleToken();
return nullptr;
}
TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
? localIdleDeadline
: GetIdleToken(localIdleDeadline);
if (!idleDeadline) {
EnsureIsPaused();
// Don't call ClearIdleToken() here, since we may have a pending
// request already.
MutexAutoUnlock unlock(*mMutex);
RequestIdleToken(localIdleDeadline);
mIdlePeriodState.RanOutOfTasks(unlock);
return nullptr;
}
TimeStamp idleDeadline;
{
// Scope for MutexAutoUnlock
MutexAutoUnlock unlock(*mMutex);
idleDeadline = mIdlePeriodState.GetDeadlineForIdleTask(unlock);
}
if (!idleDeadline) {
return nullptr;
}
@ -300,16 +233,14 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
#endif
}
EnsureIsActive();
return event.forget();
}
void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
if (IsEmpty(aProofOfLock)) {
if (IsActive()) {
SetPaused();
}
ClearIdleToken();
// Certainly no more idle tasks.
MutexAutoUnlock unlock(*mMutex);
mIdlePeriodState.RanOutOfTasks(unlock);
}
}
@ -325,7 +256,7 @@ bool PrioritizedEventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) {
}
bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
mHasPendingEventsPromisedIdleEvent = false;
mIdlePeriodState.ForgetPendingTaskGuarantee();
EventQueuePriority queue = SelectQueue(false, aProofOfLock);
@ -349,17 +280,15 @@ bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
return false;
}
bool shuttingDown;
TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
if (localIdleDeadline) {
TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
? localIdleDeadline
: GetIdleToken(localIdleDeadline);
if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
mIdleQueue->HasReadyEvent(aProofOfLock))) {
mHasPendingEventsPromisedIdleEvent = true;
return true;
}
TimeStamp idleDeadline;
{
MutexAutoUnlock unlock(*mMutex);
idleDeadline = mIdlePeriodState.PeekIdleDeadline(unlock);
}
if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
mIdleQueue->HasReadyEvent(aProofOfLock))) {
mIdlePeriodState.EnforcePendingTaskGuarantee();
return true;
}
return false;
@ -401,88 +330,3 @@ void PrioritizedEventQueue::ResumeInputEventPrioritization(
MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
mInputQueueState = STATE_ENABLED;
}
mozilla::TimeStamp PrioritizedEventQueue::GetIdleToken(
TimeStamp aLocalIdlePeriodHint) {
if (XRE_IsParentProcess()) {
return aLocalIdlePeriodHint;
}
if (mIdleToken) {
TimeStamp now = TimeStamp::Now();
if (mIdleToken < now) {
ClearIdleToken();
return mIdleToken;
}
return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
: aLocalIdlePeriodHint;
}
return TimeStamp();
}
void PrioritizedEventQueue::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) {
MOZ_ASSERT(!mActive);
if (!mIdleSchedulerInitialized) {
mIdleSchedulerInitialized = true;
if (StaticPrefs::idle_period_cross_process_scheduling() &&
XRE_IsContentProcess() && NS_IsMainThread() &&
// Disable when recording/replaying, as IdleSchedulerChild uses mutable
// shared memory which needs special handling.
!recordreplay::IsRecordingOrReplaying()) {
// For now cross-process idle scheduler is supported only on the main
// threads of the child processes.
mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
if (mIdleScheduler) {
mIdleScheduler->Init(this);
}
}
}
if (mIdleScheduler && !mIdleRequestId) {
TimeStamp now = TimeStamp::Now();
if (aLocalIdlePeriodHint <= now) {
return;
}
mIdleRequestId = ++sIdleRequestCounter;
mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
aLocalIdlePeriodHint - now);
}
}
void PrioritizedEventQueue::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
if (mIdleRequestId == aId) {
mIdleToken = TimeStamp::Now() + aDuration;
}
}
void PrioritizedEventQueue::SetActive() {
MOZ_ASSERT(!mActive);
if (mIdleScheduler) {
mIdleScheduler->SetActive();
}
mActive = true;
}
void PrioritizedEventQueue::SetPaused() {
MOZ_ASSERT(mActive);
if (mIdleScheduler && mIdleScheduler->SetPaused()) {
MutexAutoUnlock unlock(*mMutex);
// We may have gotten a free cpu core for running idle tasks.
// We don't try to catch the case when there are prioritized processes
// running.
mIdleScheduler->SendSchedule();
}
mActive = false;
}
void PrioritizedEventQueue::ClearIdleToken() {
if (mIdleRequestId) {
if (mIdleScheduler) {
MutexAutoUnlock unlock(*mMutex);
mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
}
mIdleRequestId = 0;
mIdleToken = TimeStamp();
}
}

View File

@ -9,12 +9,13 @@
#include "mozilla/AbstractEventQueue.h"
#include "mozilla/EventQueue.h"
#include "mozilla/IdlePeriodState.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsIIdlePeriod.h"
class nsIIdlePeriod;
class nsIRunnable;
namespace mozilla {
@ -43,7 +44,7 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
public:
static const bool SupportsPrioritization = true;
explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod> aIdlePeriod);
explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
virtual ~PrioritizedEventQueue();
@ -90,58 +91,15 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
n += mDeferredTimersQueue->SizeOfIncludingThis(aMallocSizeOf);
n += mIdleQueue->SizeOfIncludingThis(aMallocSizeOf);
if (mIdlePeriod) {
n += aMallocSizeOf(mIdlePeriod);
}
n += mIdlePeriodState.SizeOfExcludingThis(aMallocSizeOf);
return n;
}
void SetIdleToken(uint64_t aId, TimeDuration aDuration);
bool IsActive() { return mActive; }
void EnsureIsActive() {
if (!mActive) {
SetActive();
}
}
void EnsureIsPaused() {
if (mActive) {
SetPaused();
}
}
private:
EventQueuePriority SelectQueue(bool aUpdateState,
const MutexAutoLock& aProofOfLock);
// Returns a null TimeStamp if we're not in the idle period.
mozilla::TimeStamp GetLocalIdleDeadline(bool& aShuttingDown);
// SetActive should be called when the event queue is running any type of
// tasks.
void SetActive();
// SetPaused should be called once the event queue doesn't have more
// tasks to process, or is waiting for the idle token.
void SetPaused();
// Gets the idle token, which is the end time of the idle period.
TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint);
// In case of child processes, requests idle time from the cross-process
// idle scheduler.
void RequestIdleToken(TimeStamp aLocalIdlePeriodHint);
// Returns true if the event queue either is waiting for an idle token
// from the idle scheduler or has one.
bool HasIdleRequest() { return mIdleRequestId != 0; }
// Mark that the event queue doesn't have idle time to use, nor is expecting
// to get idle token from the idle scheduler.
void ClearIdleToken();
UniquePtr<EventQueue> mHighQueue;
UniquePtr<EventQueue> mInputQueue;
UniquePtr<EventQueue> mMediumHighQueue;
@ -165,17 +123,6 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
// secondary queue and prevents starving the normal queue.
bool mProcessHighPriorityQueue = false;
// mIdlePeriod keeps track of the current idle period. If at any
// time the main event queue is empty, calling
// mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
// the current idle period will end.
nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
// 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 = false;
TimeStamp mInputHandlingStartTime;
enum InputEventQueueState {
@ -186,20 +133,8 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
};
InputEventQueueState mInputQueueState = STATE_DISABLED;
// If non-null, tells the end time of the idle period.
// Idle period starts when we get idle token from the parent process and
// ends when either there are no runnables in the event queues or
// mIdleToken < TimeStamp::Now()
TimeStamp mIdleToken;
// The id of the last idle request to the cross-process idle scheduler.
uint64_t mIdleRequestId = 0;
RefPtr<ipc::IdleSchedulerChild> mIdleScheduler;
bool mIdleSchedulerInitialized = false;
// mActive tells whether the event queue is running tasks.
bool mActive = true;
// Tracking of our idle state of various sorts.
IdlePeriodState mIdlePeriodState;
};
} // namespace mozilla

View File

@ -48,6 +48,7 @@ EXPORTS.mozilla += [
'DataMutex.h',
'DeadlockDetector.h',
'EventQueue.h',
'IdlePeriodState.h',
'IdleTaskRunner.h',
'LazyIdleThread.h',
'MainThreadIdlePeriod.h',
@ -86,6 +87,7 @@ UNIFIED_SOURCES += [
'BlockingResourceBase.cpp',
'CPUUsageWatcher.cpp',
'EventQueue.cpp',
'IdlePeriodState.cpp',
'InputEventStatistics.cpp',
'LazyIdleThread.cpp',
'MainThreadIdlePeriod.cpp',