зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1563063 - Cross-process idle handling, r=farre
This is to get initial feedback/review. PIdleScheduler.ipdl has the documentation about the basic architecture. (v15) Differential Revision: https://phabricator.services.mozilla.com/D45162 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f26c05d126
Коммит
bcf140f437
|
@ -118,6 +118,7 @@
|
|||
#include "mozilla/dom/CDATASection.h"
|
||||
#include "mozilla/dom/ProcessingInstruction.h"
|
||||
#include "mozilla/dom/PostMessageEvent.h"
|
||||
#include "mozilla/ipc/IdleSchedulerChild.h"
|
||||
#include "nsDOMString.h"
|
||||
#include "nsNodeUtils.h"
|
||||
#include "nsLayoutUtils.h" // for GetFrameForPoint
|
||||
|
@ -16067,6 +16068,11 @@ void Document::AddToplevelLoadingDocument(Document* aDoc) {
|
|||
|
||||
if (!sLoadingForegroundTopLevelContentDocument) {
|
||||
sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
|
||||
mozilla::ipc::IdleSchedulerChild* idleScheduler =
|
||||
mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
|
||||
if (idleScheduler) {
|
||||
idleScheduler->SendRunningPrioritizedOperation();
|
||||
}
|
||||
}
|
||||
if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
|
||||
sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
|
||||
|
@ -16080,6 +16086,12 @@ void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
|
|||
if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
|
||||
delete sLoadingForegroundTopLevelContentDocument;
|
||||
sLoadingForegroundTopLevelContentDocument = nullptr;
|
||||
|
||||
mozilla::ipc::IdleSchedulerChild* idleScheduler =
|
||||
mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
|
||||
if (idleScheduler) {
|
||||
idleScheduler->SendPrioritizedOperationDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16114,6 +16126,12 @@ bool Document::HasRecentlyStartedForegroundLoads() {
|
|||
// Didn't find any loading foreground documents, just clear the array.
|
||||
delete sLoadingForegroundTopLevelContentDocument;
|
||||
sLoadingForegroundTopLevelContentDocument = nullptr;
|
||||
|
||||
mozilla::ipc::IdleSchedulerChild* idleScheduler =
|
||||
mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
|
||||
if (idleScheduler) {
|
||||
idleScheduler->SendPrioritizedOperationDone();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#include "mozilla/dom/MIDIPlatformService.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/IdleSchedulerParent.h"
|
||||
#include "mozilla/ipc/IPCStreamAlloc.h"
|
||||
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
||||
#include "mozilla/ipc/PBackgroundTestParent.h"
|
||||
|
@ -470,6 +471,13 @@ bool BackgroundParentImpl::DeallocPBackgroundStorageParent(
|
|||
return mozilla::dom::DeallocPBackgroundStorageParent(aActor);
|
||||
}
|
||||
|
||||
already_AddRefed<PIdleSchedulerParent>
|
||||
BackgroundParentImpl::AllocPIdleSchedulerParent() {
|
||||
AssertIsOnBackgroundThread();
|
||||
RefPtr<IdleSchedulerParent> actor = new IdleSchedulerParent();
|
||||
return actor.forget();
|
||||
}
|
||||
|
||||
mozilla::dom::PPendingIPCBlobParent*
|
||||
BackgroundParentImpl::AllocPPendingIPCBlobParent(const IPCBlob& aBlob) {
|
||||
MOZ_CRASH("PPendingIPCBlobParent actors should be manually constructed!");
|
||||
|
|
|
@ -131,6 +131,9 @@ class BackgroundParentImpl : public PBackgroundParent {
|
|||
virtual bool DeallocPBackgroundStorageParent(
|
||||
PBackgroundStorageParent* aActor) override;
|
||||
|
||||
virtual already_AddRefed<PIdleSchedulerParent> AllocPIdleSchedulerParent()
|
||||
override;
|
||||
|
||||
virtual PPendingIPCBlobParent* AllocPPendingIPCBlobParent(
|
||||
const IPCBlob& aBlob) override;
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/* -*- 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/ipc/IdleSchedulerChild.h"
|
||||
#include "mozilla/ipc/IdleSchedulerParent.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/PrioritizedEventQueue.h"
|
||||
#include "BackgroundChild.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
|
||||
static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr;
|
||||
|
||||
IdleSchedulerChild::~IdleSchedulerChild() {
|
||||
if (sMainThreadIdleScheduler == this) {
|
||||
sMainThreadIdleScheduler = nullptr;
|
||||
}
|
||||
MOZ_ASSERT(!mEventQueue);
|
||||
}
|
||||
|
||||
void IdleSchedulerChild::Init(PrioritizedEventQueue* aEventQueue) {
|
||||
mEventQueue = aEventQueue;
|
||||
|
||||
RefPtr<IdleSchedulerChild> scheduler = this;
|
||||
auto resolve =
|
||||
[&](Tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) {
|
||||
if (Get<0>(aResult)) {
|
||||
mActiveCounter.SetHandle(*Get<0>(aResult), false);
|
||||
mActiveCounter.Map(sizeof(int32_t));
|
||||
mChildId = Get<1>(aResult);
|
||||
if (mChildId && mEventQueue && mEventQueue->IsActive()) {
|
||||
SetActive();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto reject = [&](ResponseRejectReason) {};
|
||||
SendInitForIdleUse(std::move(resolve), std::move(reject));
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerChild::RecvIdleTime(uint64_t aId, TimeDuration aBudget) {
|
||||
if (mEventQueue) {
|
||||
mEventQueue->SetIdleToken(aId, aBudget);
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
void IdleSchedulerChild::SetActive() {
|
||||
if (mChildId && CanSend() && mActiveCounter.memory()) {
|
||||
++(static_cast<Atomic<int32_t>*>(
|
||||
mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]);
|
||||
++(static_cast<Atomic<int32_t>*>(mActiveCounter.memory())[mChildId]);
|
||||
}
|
||||
}
|
||||
|
||||
bool IdleSchedulerChild::SetPaused() {
|
||||
if (mChildId && CanSend() && mActiveCounter.memory()) {
|
||||
--(static_cast<Atomic<int32_t>*>(mActiveCounter.memory())[mChildId]);
|
||||
// The following expression reduces the global activity count and checks if
|
||||
// it drops below the cpu counter limit.
|
||||
return (static_cast<Atomic<int32_t>*>(
|
||||
mActiveCounter
|
||||
.memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER])-- ==
|
||||
static_cast<Atomic<int32_t>*>(
|
||||
mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IdleSchedulerChild* IdleSchedulerChild::GetMainThreadIdleScheduler() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (sMainThreadIdleScheduler) {
|
||||
return sMainThreadIdleScheduler;
|
||||
}
|
||||
|
||||
ipc::PBackgroundChild* background =
|
||||
ipc::BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (background) {
|
||||
sMainThreadIdleScheduler = new ipc::IdleSchedulerChild();
|
||||
background->SendPIdleSchedulerConstructor(sMainThreadIdleScheduler);
|
||||
}
|
||||
return sMainThreadIdleScheduler;
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- 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_ipc_IdleSchedulerChild_h__
|
||||
#define mozilla_ipc_IdleSchedulerChild_h__
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ipc/PIdleSchedulerChild.h"
|
||||
|
||||
class nsIIdlePeriod;
|
||||
|
||||
namespace mozilla {
|
||||
class PrioritizedEventQueue;
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class BackgroundChildImpl;
|
||||
|
||||
class IdleSchedulerChild final : public PIdleSchedulerChild {
|
||||
public:
|
||||
IdleSchedulerChild() = default;
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(IdleSchedulerChild)
|
||||
|
||||
IPCResult RecvIdleTime(uint64_t aId, TimeDuration aBudget);
|
||||
|
||||
void Init(PrioritizedEventQueue* aEventQueue);
|
||||
|
||||
void Disconnect() { mEventQueue = nullptr; }
|
||||
|
||||
// See similar methods on PrioritizedEventQueue.
|
||||
void SetActive();
|
||||
// Returns true if activity state dropped below cpu count.
|
||||
bool SetPaused();
|
||||
|
||||
static IdleSchedulerChild* GetMainThreadIdleScheduler();
|
||||
|
||||
private:
|
||||
~IdleSchedulerChild();
|
||||
|
||||
friend class BackgroundChildImpl;
|
||||
|
||||
// See IdleScheduleParent::sActiveChildCounter
|
||||
base::SharedMemory mActiveCounter;
|
||||
|
||||
PrioritizedEventQueue* mEventQueue = nullptr;
|
||||
|
||||
uint32_t mChildId = 0;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ipc_IdleSchedulerChild_h__
|
|
@ -0,0 +1,276 @@
|
|||
/* -*- 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/ScopeExit.h"
|
||||
#include "mozilla/StaticPrefs_page_load.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/ipc/IdleSchedulerParent.h"
|
||||
#include "nsIPropertyBag2.h"
|
||||
#include "nsSystemInfo.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsITimer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
|
||||
base::SharedMemory* IdleSchedulerParent::sActiveChildCounter = nullptr;
|
||||
std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT>
|
||||
IdleSchedulerParent::sInUseChildCounters;
|
||||
LinkedList<IdleSchedulerParent> IdleSchedulerParent::sDefault;
|
||||
LinkedList<IdleSchedulerParent> IdleSchedulerParent::sWaitingForIdle;
|
||||
LinkedList<IdleSchedulerParent> IdleSchedulerParent::sIdle;
|
||||
AutoTArray<IdleSchedulerParent*, 8>* IdleSchedulerParent::sPrioritized =
|
||||
nullptr;
|
||||
Atomic<int32_t> IdleSchedulerParent::sCPUsForChildProcesses(-1);
|
||||
uint32_t IdleSchedulerParent::sChildProcessesRunningPrioritizedOperation = 0;
|
||||
nsITimer* IdleSchedulerParent::sStarvationPreventer = nullptr;
|
||||
|
||||
IdleSchedulerParent::IdleSchedulerParent() {
|
||||
sDefault.insertBack(this);
|
||||
|
||||
if (sCPUsForChildProcesses == -1) {
|
||||
// nsISystemInfo can be initialized only on the main thread.
|
||||
// While waiting for the real logical core count behave as if there was just
|
||||
// one core.
|
||||
sCPUsForChildProcesses = 1;
|
||||
nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableFunction("cpucount getter", [thread]() {
|
||||
// Always pretend that there is at least one core for child processes.
|
||||
// If there are multiple logical cores, reserve one for the parent
|
||||
// process and for the non-main threads.
|
||||
nsCOMPtr<nsIPropertyBag2> infoService =
|
||||
do_GetService(NS_SYSTEMINFO_CONTRACTID);
|
||||
if (infoService) {
|
||||
int32_t cpus;
|
||||
nsresult rv = infoService->GetPropertyAsInt32(
|
||||
NS_LITERAL_STRING("cpucount"), &cpus);
|
||||
if (NS_SUCCEEDED(rv) && cpus > 1) {
|
||||
sCPUsForChildProcesses = cpus - 1;
|
||||
}
|
||||
|
||||
// We have a new cpu count, reschedule idle scheduler.
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableFunction("IdleSchedulerParent::Schedule", []() {
|
||||
if (sActiveChildCounter && sActiveChildCounter->memory()) {
|
||||
static_cast<Atomic<int32_t>*>(sActiveChildCounter->memory())
|
||||
[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] =
|
||||
static_cast<int32_t>(sCPUsForChildProcesses);
|
||||
}
|
||||
IdleSchedulerParent::Schedule(nullptr);
|
||||
});
|
||||
thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
});
|
||||
NS_DispatchToMainThread(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
IdleSchedulerParent::~IdleSchedulerParent() {
|
||||
// We can't know if an active process just crashed, so we just always expect
|
||||
// that is the case.
|
||||
if (mChildId) {
|
||||
sInUseChildCounters[mChildId] = false;
|
||||
if (sActiveChildCounter && sActiveChildCounter->memory() &&
|
||||
static_cast<Atomic<int32_t>*>(
|
||||
sActiveChildCounter->memory())[mChildId]) {
|
||||
--static_cast<Atomic<int32_t>*>(
|
||||
sActiveChildCounter
|
||||
->memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER];
|
||||
static_cast<Atomic<int32_t>*>(sActiveChildCounter->memory())[mChildId] =
|
||||
0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRunningPrioritizedOperation) {
|
||||
--sChildProcessesRunningPrioritizedOperation;
|
||||
}
|
||||
|
||||
if (isInList()) {
|
||||
remove();
|
||||
if (sDefault.isEmpty() && sWaitingForIdle.isEmpty() && sIdle.isEmpty()) {
|
||||
delete sActiveChildCounter;
|
||||
sActiveChildCounter = nullptr;
|
||||
|
||||
if (sStarvationPreventer) {
|
||||
sStarvationPreventer->Cancel();
|
||||
NS_RELEASE(sStarvationPreventer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Schedule(nullptr);
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerParent::RecvInitForIdleUse(
|
||||
InitForIdleUseResolver&& aResolve) {
|
||||
// Create a shared memory object which is shared across all the relevant
|
||||
// processes. Only first 4 bytes of the allocated are used currently to
|
||||
// count activity state of child processes
|
||||
if (!sActiveChildCounter) {
|
||||
sActiveChildCounter = new base::SharedMemory();
|
||||
size_t shmemSize = NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT * sizeof(int32_t);
|
||||
if (sActiveChildCounter->Create(shmemSize) &&
|
||||
sActiveChildCounter->Map(shmemSize)) {
|
||||
memset(sActiveChildCounter->memory(), 0, shmemSize);
|
||||
sInUseChildCounters[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER] = true;
|
||||
sInUseChildCounters[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] = true;
|
||||
static_cast<Atomic<int32_t>*>(
|
||||
sActiveChildCounter
|
||||
->memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] =
|
||||
static_cast<int32_t>(sCPUsForChildProcesses);
|
||||
} else {
|
||||
delete sActiveChildCounter;
|
||||
sActiveChildCounter = nullptr;
|
||||
}
|
||||
}
|
||||
Maybe<SharedMemoryHandle> activeCounter;
|
||||
SharedMemoryHandle handle;
|
||||
if (sActiveChildCounter &&
|
||||
sActiveChildCounter->ShareToProcess(OtherPid(), &handle)) {
|
||||
activeCounter.emplace(handle);
|
||||
}
|
||||
|
||||
uint32_t unusedId = 0;
|
||||
for (uint32_t i = 0; i < NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT; ++i) {
|
||||
if (!sInUseChildCounters[i]) {
|
||||
sInUseChildCounters[i] = true;
|
||||
unusedId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there wasn't an empty item, we'll fallback to 0.
|
||||
mChildId = unusedId;
|
||||
|
||||
aResolve(Tuple<const mozilla::Maybe<SharedMemoryHandle>&, const uint32_t&>(
|
||||
activeCounter, mChildId));
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerParent::RecvRequestIdleTime(uint64_t aId,
|
||||
TimeDuration aBudget) {
|
||||
mCurrentRequestId = aId;
|
||||
mRequestedIdleBudget = aBudget;
|
||||
|
||||
remove();
|
||||
sWaitingForIdle.insertBack(this);
|
||||
|
||||
Schedule(this);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerParent::RecvIdleTimeUsed(uint64_t aId) {
|
||||
if (mCurrentRequestId == aId) {
|
||||
// Ensure the object is back in the default queue.
|
||||
remove();
|
||||
sDefault.insertBack(this);
|
||||
}
|
||||
Schedule(nullptr);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerParent::RecvSchedule() {
|
||||
Schedule(nullptr);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerParent::RecvRunningPrioritizedOperation() {
|
||||
++mRunningPrioritizedOperation;
|
||||
if (mRunningPrioritizedOperation == 1) {
|
||||
++sChildProcessesRunningPrioritizedOperation;
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
IPCResult IdleSchedulerParent::RecvPrioritizedOperationDone() {
|
||||
MOZ_ASSERT(mRunningPrioritizedOperation);
|
||||
|
||||
--mRunningPrioritizedOperation;
|
||||
if (mRunningPrioritizedOperation == 0) {
|
||||
--sChildProcessesRunningPrioritizedOperation;
|
||||
Schedule(nullptr);
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
int32_t IdleSchedulerParent::ActiveCount() {
|
||||
if (sActiveChildCounter) {
|
||||
return (static_cast<Atomic<int32_t>*>(
|
||||
sActiveChildCounter
|
||||
->memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IdleSchedulerParent::Schedule(IdleSchedulerParent* aRequester) {
|
||||
if (sWaitingForIdle.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aRequester || !aRequester->mRunningPrioritizedOperation) {
|
||||
int32_t activeCount = ActiveCount();
|
||||
// Don't bail out so easily if we're running with very few cores.
|
||||
if (sCPUsForChildProcesses > 1 && sCPUsForChildProcesses <= activeCount) {
|
||||
// Too many processes are running, bail out.
|
||||
EnsureStarvationTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sChildProcessesRunningPrioritizedOperation > 0 &&
|
||||
sCPUsForChildProcesses / 2 <= activeCount) {
|
||||
// We're running a prioritized operation and don't have too many spare
|
||||
// cores for idle tasks, bail out.
|
||||
EnsureStarvationTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We can run run an idle task. If the requester is prioritized, just let it
|
||||
// run itself.
|
||||
RefPtr<IdleSchedulerParent> idleRequester;
|
||||
if (aRequester && aRequester->mRunningPrioritizedOperation) {
|
||||
aRequester->remove();
|
||||
idleRequester = aRequester;
|
||||
} else {
|
||||
idleRequester = sWaitingForIdle.popFirst();
|
||||
}
|
||||
|
||||
sIdle.insertBack(idleRequester);
|
||||
Unused << idleRequester->SendIdleTime(idleRequester->mCurrentRequestId,
|
||||
idleRequester->mRequestedIdleBudget);
|
||||
}
|
||||
|
||||
void IdleSchedulerParent::EnsureStarvationTimer() {
|
||||
// Even though idle runnables aren't really guaranteed to get run ever (which
|
||||
// is why most of them have the timer fallback), try to not let any child
|
||||
// process' idle handling to starve forever in case other processes are busy
|
||||
if (!sStarvationPreventer) {
|
||||
// Reuse StaticPrefs::page_load_deprioritization_period(), since that
|
||||
// is used on child side when deciding the minimum idle period.
|
||||
NS_NewTimerWithFuncCallback(
|
||||
&sStarvationPreventer, StarvationCallback, nullptr,
|
||||
StaticPrefs::page_load_deprioritization_period(),
|
||||
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "StarvationCallback");
|
||||
}
|
||||
}
|
||||
|
||||
void IdleSchedulerParent::StarvationCallback(nsITimer* aTimer, void* aData) {
|
||||
if (!sWaitingForIdle.isEmpty()) {
|
||||
RefPtr<IdleSchedulerParent> first = sWaitingForIdle.getFirst();
|
||||
// Treat the first process waiting for idle time as running prioritized
|
||||
// operation so that it gets run.
|
||||
++first->mRunningPrioritizedOperation;
|
||||
++sChildProcessesRunningPrioritizedOperation;
|
||||
Schedule(first);
|
||||
--first->mRunningPrioritizedOperation;
|
||||
--sChildProcessesRunningPrioritizedOperation;
|
||||
}
|
||||
NS_RELEASE(sStarvationPreventer);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,99 @@
|
|||
/* -*- 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_ipc_IdleSchedulerParent_h__
|
||||
#define mozilla_ipc_IdleSchedulerParent_h__
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/ipc/PIdleSchedulerParent.h"
|
||||
#include "base/shared_memory.h"
|
||||
#include <bitset>
|
||||
|
||||
#define NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT 1024
|
||||
#define NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER 0
|
||||
#define NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER 1
|
||||
|
||||
class nsITimer;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class BackgroundParentImpl;
|
||||
|
||||
class IdleSchedulerParent final
|
||||
: public PIdleSchedulerParent,
|
||||
public LinkedListElement<IdleSchedulerParent> {
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(IdleSchedulerParent)
|
||||
|
||||
IPCResult RecvInitForIdleUse(InitForIdleUseResolver&& aResolve);
|
||||
IPCResult RecvRequestIdleTime(uint64_t aId, TimeDuration aBudget);
|
||||
IPCResult RecvIdleTimeUsed(uint64_t aId);
|
||||
IPCResult RecvSchedule();
|
||||
IPCResult RecvRunningPrioritizedOperation();
|
||||
IPCResult RecvPrioritizedOperationDone();
|
||||
|
||||
private:
|
||||
friend class BackgroundParentImpl;
|
||||
IdleSchedulerParent();
|
||||
~IdleSchedulerParent();
|
||||
|
||||
static int32_t ActiveCount();
|
||||
static void Schedule(IdleSchedulerParent* aRequester);
|
||||
|
||||
static void EnsureStarvationTimer();
|
||||
static void StarvationCallback(nsITimer* aTimer, void* aData);
|
||||
|
||||
uint64_t mCurrentRequestId = 0;
|
||||
// For now we don't really use idle budget for scheduling.
|
||||
TimeDuration mRequestedIdleBudget;
|
||||
|
||||
// Counting all the prioritized operations the process is doing.
|
||||
uint32_t mRunningPrioritizedOperation = 0;
|
||||
|
||||
uint32_t mChildId = 0;
|
||||
|
||||
// Shared memory for counting how many child processes are running
|
||||
// tasks. This memory is shared across all the child processes.
|
||||
// The [0] is used for counting all the processes and
|
||||
// [childId] is for counting per process activity.
|
||||
// This way the global activity can be checked in a fast way by just looking
|
||||
// at [0] value.
|
||||
// [1] is used for cpu count for child processes.
|
||||
static base::SharedMemory* sActiveChildCounter;
|
||||
// A bit is set if there is a child with child Id as the offset.
|
||||
// The bit is used to check per child specific activity counters in
|
||||
// sActiveChildCounter.
|
||||
static std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT>
|
||||
sInUseChildCounters;
|
||||
|
||||
// Child is either running non-idle tasks or doesn't have any tasks to run.
|
||||
static LinkedList<IdleSchedulerParent> sDefault;
|
||||
|
||||
// Child has requested idle time, but hasn't got it yet.
|
||||
static LinkedList<IdleSchedulerParent> sWaitingForIdle;
|
||||
|
||||
// Child has gotten idle time and is running idle or normal tasks.
|
||||
static LinkedList<IdleSchedulerParent> sIdle;
|
||||
|
||||
static AutoTArray<IdleSchedulerParent*, 8>* sPrioritized;
|
||||
static Atomic<int32_t> sCPUsForChildProcesses;
|
||||
|
||||
// Counting all the child processes which have at least one prioritized
|
||||
// operation.
|
||||
static uint32_t sChildProcessesRunningPrioritizedOperation;
|
||||
|
||||
static nsITimer* sStarvationPreventer;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ipc_IdleSchedulerParent_h__
|
|
@ -23,6 +23,7 @@ include protocol PFileSystemRequest;
|
|||
include protocol PGamepadEventChannel;
|
||||
include protocol PGamepadTestChannel;
|
||||
include protocol PHttpBackgroundChannel;
|
||||
include protocol PIdleScheduler;
|
||||
include protocol PIPCBlobInputStream;
|
||||
include protocol PMediaTransport;
|
||||
include protocol PPendingIPCBlob;
|
||||
|
@ -91,6 +92,7 @@ sync protocol PBackground
|
|||
manages PGamepadEventChannel;
|
||||
manages PGamepadTestChannel;
|
||||
manages PHttpBackgroundChannel;
|
||||
manages PIdleScheduler;
|
||||
manages PIPCBlobInputStream;
|
||||
manages PMediaTransport;
|
||||
manages PPendingIPCBlob;
|
||||
|
@ -231,6 +233,8 @@ parent:
|
|||
async RemoveEndpoint(nsString aGroupName, nsCString aEndpointURL,
|
||||
PrincipalInfo aPrincipalInfo);
|
||||
|
||||
async PIdleScheduler();
|
||||
|
||||
async PMediaTransport();
|
||||
|
||||
child:
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 protocol PBackground;
|
||||
using mozilla::TimeDuration from "mozilla/TimeStamp.h";
|
||||
using base::SharedMemoryHandle from "base/shared_memory.h";
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* PIdleScheduler is the protocol for cross-process idle scheduling.
|
||||
* Only child processes participate in the scheduling and parent process
|
||||
* can run its idle tasks whenever it needs to.
|
||||
*
|
||||
* The scheduler keeps track of the following things.
|
||||
* - Activity of the main thread of each child process. A process is active
|
||||
* when it is running tasks. Because of performance cross-process
|
||||
* counters in shared memory are used for the activity tracking. There is
|
||||
* one counter counting the activity state of all the processes and one
|
||||
* counter for each process. This way if a child process crashes, the global
|
||||
* counter can be updated by decrementing the per process counter from it.
|
||||
* - Child processes running prioritized operation. Top level page loads is an
|
||||
* example of a prioritized operation. When such is ongoing, idle tasks are
|
||||
* less likely to run.
|
||||
* - Idle requests. When a child process locally has idle tasks to run, it
|
||||
* requests idle time from the scheduler. Initially requests go to a wait list
|
||||
* and the scheduler runs and if there are free logical cores for the child
|
||||
* processes, idle time is given to the child process, and the process goes to
|
||||
* the idle list. Once idle time has been consumed or there are no tasks to
|
||||
* process, child process informs the scheduler and the process is moved back
|
||||
* to the default queue.
|
||||
*/
|
||||
async refcounted protocol PIdleScheduler
|
||||
{
|
||||
manager PBackground;
|
||||
|
||||
child:
|
||||
async IdleTime(uint64_t id, TimeDuration budget);
|
||||
|
||||
parent:
|
||||
async InitForIdleUse() returns (SharedMemoryHandle? state, uint32_t childId);
|
||||
async RequestIdleTime(uint64_t id, TimeDuration budget);
|
||||
async IdleTimeUsed(uint64_t id);
|
||||
|
||||
// Child can send explicit Schedule message to parent if it thinks parent process
|
||||
// might be able to let some other process to use idle time.
|
||||
async Schedule();
|
||||
|
||||
// Note, these two messages can be sent even before InitForIdleUse.
|
||||
async RunningPrioritizedOperation();
|
||||
async PrioritizedOperationDone();
|
||||
|
||||
// This message is never sent. Each PIdleScheduler actor will stay alive as long as
|
||||
// its PBackground manager.
|
||||
async __delete__();
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
|
@ -27,6 +27,8 @@ EXPORTS.mozilla.ipc += [
|
|||
'FileDescriptorSetParent.h',
|
||||
'FileDescriptorUtils.h',
|
||||
'GeckoChildProcessHost.h',
|
||||
'IdleSchedulerChild.h',
|
||||
'IdleSchedulerParent.h',
|
||||
'InProcessChild.h',
|
||||
'InProcessParent.h',
|
||||
'InputStreamUtils.h',
|
||||
|
@ -150,6 +152,8 @@ UNIFIED_SOURCES += [
|
|||
'CrashReporterMetadataShmem.cpp',
|
||||
'FileDescriptor.cpp',
|
||||
'FileDescriptorUtils.cpp',
|
||||
'IdleSchedulerChild.cpp',
|
||||
'IdleSchedulerParent.cpp',
|
||||
'InProcessImpl.cpp',
|
||||
'InputStreamUtils.cpp',
|
||||
'IPCMessageUtils.cpp',
|
||||
|
@ -208,6 +212,7 @@ IPDL_SOURCES = [
|
|||
'PBackgroundTest.ipdl',
|
||||
'PChildToParentStream.ipdl',
|
||||
'PFileDescriptorSet.ipdl',
|
||||
'PIdleScheduler.ipdl',
|
||||
'PInProcess.ipdl',
|
||||
'PParentToChildStream.ipdl',
|
||||
'ProtocolTypes.ipdlh',
|
||||
|
|
|
@ -3597,6 +3597,11 @@
|
|||
value: 12
|
||||
mirror: always
|
||||
|
||||
- name: idle_period.cross_process_scheduling
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Prefs starting with "image."
|
||||
#---------------------------------------------------------------------------
|
||||
|
@ -6490,7 +6495,7 @@
|
|||
# Time in milliseconds during which certain tasks are deprioritized during
|
||||
# page load.
|
||||
- name: page_load.deprioritization_period
|
||||
type: uint32_t
|
||||
type: RelaxedAtomicUint32
|
||||
value: 5000
|
||||
mirror: always
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ class EventQueue final : public AbstractEventQueue {
|
|||
const MutexAutoLock& aProofOfLock) final;
|
||||
already_AddRefed<nsIRunnable> GetEvent(
|
||||
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
|
||||
void DidRunEvent(const MutexAutoLock& aProofOfLock) {}
|
||||
|
||||
bool IsEmpty(const MutexAutoLock& aProofOfLock) final;
|
||||
bool HasReadyEvent(const MutexAutoLock& aProofOfLock) final;
|
||||
|
|
|
@ -7,13 +7,34 @@
|
|||
#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"
|
||||
#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
|
||||
#include "InputEventStatistics.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static uint64_t sIdleRequestCounter = 0;
|
||||
|
||||
PrioritizedEventQueue::PrioritizedEventQueue(
|
||||
already_AddRefed<nsIIdlePeriod> aIdlePeriod)
|
||||
: mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
|
||||
mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
|
||||
mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
|
||||
mNormalQueue(MakeUnique<EventQueue>(EventQueuePriority::Normal)),
|
||||
mDeferredTimersQueue(
|
||||
MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
|
||||
mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
|
||||
mIdlePeriod(aIdlePeriod) {}
|
||||
|
||||
PrioritizedEventQueue::~PrioritizedEventQueue() {
|
||||
if (mIdleScheduler) {
|
||||
mIdleScheduler->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
||||
EventQueuePriority aPriority,
|
||||
const MutexAutoLock& aProofOfLock) {
|
||||
|
@ -54,7 +75,7 @@ void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
|||
}
|
||||
}
|
||||
|
||||
TimeStamp PrioritizedEventQueue::GetIdleDeadline() {
|
||||
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
|
||||
|
@ -62,9 +83,11 @@ TimeStamp PrioritizedEventQueue::GetIdleDeadline() {
|
|||
// 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
|
||||
|
@ -123,8 +146,8 @@ EventQueuePriority PrioritizedEventQueue::SelectQueue(
|
|||
//
|
||||
// HIGH
|
||||
// INPUT
|
||||
// DEFERREDTIMERS: if GetIdleDeadline()
|
||||
// IDLE: if GetIdleDeadline()
|
||||
// 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.
|
||||
|
@ -177,9 +200,6 @@ EventQueuePriority PrioritizedEventQueue::SelectQueue(
|
|||
|
||||
already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
|
||||
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) {
|
||||
auto guard =
|
||||
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.
|
||||
|
@ -187,6 +207,16 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
|
|||
#endif
|
||||
|
||||
EventQueuePriority queue = SelectQueue(true, aProofOfLock);
|
||||
auto guard = MakeScopeExit([&] {
|
||||
mHasPendingEventsPromisedIdleEvent = false;
|
||||
if (queue != EventQueuePriority::Idle &&
|
||||
queue != EventQueuePriority::DeferredTimers) {
|
||||
EnsureIsActive();
|
||||
if (mIdleToken && mIdleToken < TimeStamp::Now()) {
|
||||
ClearIdleToken();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (aPriority) {
|
||||
*aPriority = queue;
|
||||
|
@ -226,11 +256,29 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
|
|||
if (mIdleQueue->IsEmpty(aProofOfLock) &&
|
||||
mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
|
||||
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
|
||||
EnsureIsPaused();
|
||||
ClearIdleToken();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TimeStamp idleDeadline = GetIdleDeadline();
|
||||
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);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -252,9 +300,19 @@ already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
|
|||
#endif
|
||||
}
|
||||
|
||||
EnsureIsActive();
|
||||
return event.forget();
|
||||
}
|
||||
|
||||
void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
|
||||
if (IsEmpty(aProofOfLock)) {
|
||||
if (IsActive()) {
|
||||
SetPaused();
|
||||
}
|
||||
ClearIdleToken();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -291,11 +349,17 @@ bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
|
|||
return false;
|
||||
}
|
||||
|
||||
TimeStamp idleDeadline = GetIdleDeadline();
|
||||
if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
|
||||
mIdleQueue->HasReadyEvent(aProofOfLock))) {
|
||||
mHasPendingEventsPromisedIdleEvent = true;
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -337,3 +401,85 @@ 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()) {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
class nsIRunnable;
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
class IdleSchedulerChild;
|
||||
}
|
||||
|
||||
// This AbstractEventQueue implementation has one queue for each
|
||||
// EventQueuePriority. The type of queue used for each priority is determined by
|
||||
|
@ -40,22 +43,16 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
|
|||
public:
|
||||
static const bool SupportsPrioritization = true;
|
||||
|
||||
explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
|
||||
: mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
|
||||
mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
|
||||
mMediumHighQueue(
|
||||
MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
|
||||
mNormalQueue(MakeUnique<EventQueue>(EventQueuePriority::Normal)),
|
||||
mDeferredTimersQueue(
|
||||
MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
|
||||
mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
|
||||
mIdlePeriod(aIdlePeriod) {}
|
||||
explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod> aIdlePeriod);
|
||||
|
||||
virtual ~PrioritizedEventQueue();
|
||||
|
||||
void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
|
||||
EventQueuePriority aPriority,
|
||||
const MutexAutoLock& aProofOfLock) final;
|
||||
already_AddRefed<nsIRunnable> GetEvent(
|
||||
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
|
||||
void DidRunEvent(const MutexAutoLock& aProofOfLock);
|
||||
|
||||
bool IsEmpty(const MutexAutoLock& aProofOfLock) final;
|
||||
size_t Count(const MutexAutoLock& aProofOfLock) const final;
|
||||
|
@ -100,12 +97,50 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
|
|||
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 GetIdleDeadline();
|
||||
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;
|
||||
|
@ -150,6 +185,21 @@ class PrioritizedEventQueue final : public AbstractEventQueue {
|
|||
STATE_ENABLED
|
||||
};
|
||||
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 non-idle tasks.
|
||||
bool mActive = true;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -58,6 +58,7 @@ class SynchronizedEventQueue : public ThreadTargetSink {
|
|||
public:
|
||||
virtual already_AddRefed<nsIRunnable> GetEvent(
|
||||
bool aMayWait, EventQueuePriority* aPriority) = 0;
|
||||
virtual void DidRunEvent() = 0;
|
||||
virtual bool HasPendingEvent() = 0;
|
||||
|
||||
virtual bool HasPendingHighPriorityEvents() = 0;
|
||||
|
|
|
@ -156,6 +156,16 @@ already_AddRefed<nsIRunnable> ThreadEventQueue<InnerQueueT>::GetEvent(
|
|||
return event.forget();
|
||||
}
|
||||
|
||||
template <class InnerQueueT>
|
||||
void ThreadEventQueue<InnerQueueT>::DidRunEvent() {
|
||||
MutexAutoLock lock(mLock);
|
||||
if (mNestedQueues.IsEmpty()) {
|
||||
mBaseQueue->DidRunEvent(lock);
|
||||
} else {
|
||||
mNestedQueues.LastElement().mQueue->DidRunEvent(lock);
|
||||
}
|
||||
}
|
||||
|
||||
template <class InnerQueueT>
|
||||
bool ThreadEventQueue<InnerQueueT>::HasPendingEvent() {
|
||||
MutexAutoLock lock(mLock);
|
||||
|
|
|
@ -38,6 +38,7 @@ class ThreadEventQueue final : public SynchronizedEventQueue {
|
|||
|
||||
already_AddRefed<nsIRunnable> GetEvent(bool aMayWait,
|
||||
EventQueuePriority* aPriority) final;
|
||||
void DidRunEvent() final;
|
||||
bool HasPendingEvent() final;
|
||||
bool HasPendingHighPriorityEvents() final;
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ EXPORTS.mozilla += [
|
|||
'MozPromise.h',
|
||||
'Mutex.h',
|
||||
'PerformanceCounter.h',
|
||||
'PrioritizedEventQueue.h',
|
||||
'Queue.h',
|
||||
'RecursiveMutex.h',
|
||||
'ReentrantMonitor.h',
|
||||
|
|
|
@ -1223,6 +1223,7 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
|
|||
currentPerformanceCounter = mCurrentPerformanceCounter;
|
||||
|
||||
event->Run();
|
||||
mEvents->DidRunEvent();
|
||||
|
||||
mozilla::TimeDuration duration;
|
||||
// Remember the last 50ms+ task on mainthread for Long Task.
|
||||
|
|
Загрузка…
Ссылка в новой задаче