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:
Olli Pettay 2019-09-18 23:53:42 +00:00
Родитель f26c05d126
Коммит bcf140f437
19 изменённых файлов: 864 добавлений и 24 удалений

Просмотреть файл

@ -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.