diff options
Diffstat (limited to 'xpcom/threads/IdlePeriodState.cpp')
-rw-r--r-- | xpcom/threads/IdlePeriodState.cpp | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/xpcom/threads/IdlePeriodState.cpp b/xpcom/threads/IdlePeriodState.cpp new file mode 100644 index 0000000000..95f155e339 --- /dev/null +++ b/xpcom/threads/IdlePeriodState.cpp @@ -0,0 +1,254 @@ +/* -*- 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() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + EnsureIsActive(); + if (mIdleToken && mIdleToken < TimeStamp::Now()) { + ClearIdleToken(); + } +} + +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(); +} + +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(); + } + 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 can do all sorts of IPC stuff that might + // take mutexes. This is one reason why we need the + // MutexAutoUnlock reference! + RequestIdleToken(localIdleDeadline); + } + 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 the reason we need a MutexAutoUnlock here. + 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 (!ShouldGetIdleToken()) { + return aLocalIdlePeriodHint; + } + + if (mIdleToken) { + TimeStamp now = TimeStamp::Now(); + if (mIdleToken < now) { + ClearIdleToken(); + return mIdleToken; + } + return mIdleToken < aLocalIdlePeriodHint ? mIdleToken + : aLocalIdlePeriodHint; + } + return TimeStamp(); +} + +void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + + if (!mIdleSchedulerInitialized) { + mIdleSchedulerInitialized = true; + if (ShouldGetIdleToken()) { + // 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 IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + // We check the request ID. It's possible that the server may be granting a + // an ealier request that the client has since cancelled and re-requested. + 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 the MutexAutoUnlock here, because + // IPC can do weird things with mutexes. + mIdleScheduler->SendSchedule(); + } + mActive = false; +} + +void IdlePeriodState::ClearIdleToken() { + 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 to not be holding + // any locks here, because IPC can do weird things with mutexes. + // Ideally we'd have a MutexAutoUnlock& reference here, but some + // callers end up here while just not holding any locks at all. + mIdleScheduler->SendIdleTimeUsed(mIdleRequestId); + } + mIdleRequestId = 0; + mIdleToken = TimeStamp(); + } +} + +bool IdlePeriodState::ShouldGetIdleToken() { + return StaticPrefs::idle_period_cross_process_scheduling() && + XRE_IsContentProcess(); +} +} // namespace mozilla |