gecko-dev/dom/ipc/PreallocatedProcessManager.cpp

438 строки
14 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/PreallocatedProcessManager.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsIPropertyBag2.h"
#include "ProcessPriorityManager.h"
#include "nsServiceManagerUtils.h"
#include "nsIXULRuntime.h"
#include <deque>
using namespace mozilla::hal;
using namespace mozilla::dom;
namespace mozilla {
/**
* This singleton class implements the static methods on
* PreallocatedProcessManager.
*/
class PreallocatedProcessManagerImpl final : public nsIObserver {
friend class PreallocatedProcessManager;
public:
static PreallocatedProcessManagerImpl* Singleton();
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
// See comments on PreallocatedProcessManager for these methods.
void AddBlocker(ContentParent* aParent);
void RemoveBlocker(ContentParent* aParent);
already_AddRefed<ContentParent> Take(const nsACString& aRemoteType);
void Erase(ContentParent* aParent);
private:
static const char* const kObserverTopics[];
static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
PreallocatedProcessManagerImpl();
~PreallocatedProcessManagerImpl();
PreallocatedProcessManagerImpl(const PreallocatedProcessManagerImpl&) =
delete;
const PreallocatedProcessManagerImpl& operator=(
const PreallocatedProcessManagerImpl&) = delete;
void Init();
bool CanAllocate();
void AllocateAfterDelay(bool aStartup = false);
void AllocateOnIdle();
void AllocateNow();
void RereadPrefs();
void Enable(uint32_t aProcesses);
void Disable();
void CloseProcesses();
bool IsEmpty() const {
return mPreallocatedProcesses.empty() && !mLaunchInProgress;
}
bool mEnabled;
static bool sShutdown;
bool mLaunchInProgress;
uint32_t mNumberPreallocs;
std::deque<RefPtr<ContentParent>> mPreallocatedProcesses;
// Even if we have multiple PreallocatedProcessManagerImpls, we'll have
// one blocker counter
static uint32_t sNumBlockers;
TimeStamp mBlockingStartTime;
};
/* static */
uint32_t PreallocatedProcessManagerImpl::sNumBlockers = 0;
bool PreallocatedProcessManagerImpl::sShutdown = false;
const char* const PreallocatedProcessManagerImpl::kObserverTopics[] = {
"memory-pressure",
"profile-change-teardown",
NS_XPCOM_SHUTDOWN_OBSERVER_ID,
};
/* static */
StaticRefPtr<PreallocatedProcessManagerImpl>
PreallocatedProcessManagerImpl::sSingleton;
/* static */
PreallocatedProcessManagerImpl* PreallocatedProcessManagerImpl::Singleton() {
MOZ_ASSERT(NS_IsMainThread());
if (!sSingleton) {
sSingleton = new PreallocatedProcessManagerImpl;
sSingleton->Init();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
// PreallocatedProcessManagers live until shutdown
}
NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
: mEnabled(false), mLaunchInProgress(false), mNumberPreallocs(1) {}
PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() {
// This shouldn't happen, because the promise callbacks should
// hold strong references, but let't make absolutely sure:
MOZ_RELEASE_ASSERT(!mLaunchInProgress);
}
void PreallocatedProcessManagerImpl::Init() {
Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
// We have to respect processCount at all time. This is especially important
// for testing.
Preferences::AddStrongObserver(this, "dom.ipc.processCount");
// A StaticPref, but we need to adjust the number of preallocated processes
// if the value goes up or down, so we need to run code on change.
Preferences::AddStrongObserver(this,
"dom.ipc.processPrelaunch.fission.number");
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
MOZ_ASSERT(os);
for (auto topic : kObserverTopics) {
os->AddObserver(this, topic, /* ownsWeak */ false);
}
RereadPrefs();
}
NS_IMETHODIMP
PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData) {
if (!strcmp("nsPref:changed", aTopic)) {
// The only other observer we registered was for our prefs.
RereadPrefs();
} else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) ||
!strcmp("profile-change-teardown", aTopic)) {
Preferences::RemoveObserver(this, "dom.ipc.processPrelaunch.enabled");
Preferences::RemoveObserver(this, "dom.ipc.processCount");
Preferences::RemoveObserver(this,
"dom.ipc.processPrelaunch.fission.number");
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
MOZ_ASSERT(os);
for (auto topic : kObserverTopics) {
os->RemoveObserver(this, topic);
}
// Let's prevent any new preallocated processes from starting. ContentParent
// will handle the shutdown of the existing process and the
// mPreallocatedProcesses reference will be cleared by the ClearOnShutdown
// of the manager singleton.
sShutdown = true;
} else if (!strcmp("memory-pressure", aTopic)) {
CloseProcesses();
} else {
MOZ_ASSERT_UNREACHABLE("Unknown topic");
}
return NS_OK;
}
void PreallocatedProcessManagerImpl::RereadPrefs() {
if (mozilla::BrowserTabsRemoteAutostart() &&
Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) {
int32_t number = 1;
if (mozilla::FissionAutostart()) {
number = StaticPrefs::dom_ipc_processPrelaunch_fission_number();
}
if (number >= 0) {
Enable(number);
// We have one prealloc queue for all types except File now
if (static_cast<uint64_t>(number) < mPreallocatedProcesses.size()) {
CloseProcesses();
}
}
} else {
Disable();
}
}
already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take(
const nsACString& aRemoteType) {
if (!mEnabled || sShutdown) {
return nullptr;
}
RefPtr<ContentParent> process;
if (!mPreallocatedProcesses.empty()) {
process = mPreallocatedProcesses.front().forget();
mPreallocatedProcesses.pop_front(); // holds a nullptr
ProcessPriorityManager::SetProcessPriority(process,
PROCESS_PRIORITY_FOREGROUND);
// We took a preallocated process. Let's try to start up a new one
// soon.
AllocateAfterDelay();
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Use prealloc process %p", process.get()));
}
return process.forget();
}
void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) {
// Ensure this ContentParent isn't cached
for (auto it = mPreallocatedProcesses.begin();
it != mPreallocatedProcesses.end(); it++) {
if (*it == aParent) {
mPreallocatedProcesses.erase(it);
break;
}
}
}
void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) {
mNumberPreallocs = aProcesses;
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Enabling preallocation: %u", aProcesses));
if (mEnabled) {
return;
}
mEnabled = true;
AllocateAfterDelay(/* aStartup */ true);
}
void PreallocatedProcessManagerImpl::AddBlocker(ContentParent* aParent) {
if (sNumBlockers == 0) {
mBlockingStartTime = TimeStamp::Now();
}
sNumBlockers++;
}
void PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent) {
// This used to assert that the blocker existed, but preallocated
// processes aren't blockers anymore because it's not useful and
// interferes with async launch, and it's simpler if content
// processes don't need to remember whether they were preallocated.
MOZ_DIAGNOSTIC_ASSERT(sNumBlockers > 0);
sNumBlockers--;
if (sNumBlockers == 0) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Blocked preallocation for %fms",
(TimeStamp::Now() - mBlockingStartTime).ToMilliseconds()));
PROFILER_MARKER_TEXT("Process", DOM,
MarkerTiming::IntervalUntilNowFrom(mBlockingStartTime),
"Blocked preallocation");
if (IsEmpty()) {
AllocateAfterDelay();
}
}
}
bool PreallocatedProcessManagerImpl::CanAllocate() {
return mEnabled && sNumBlockers == 0 &&
mPreallocatedProcesses.size() < mNumberPreallocs && !sShutdown &&
(FissionAutostart() ||
!ContentParent::IsMaxProcessCountReached(DEFAULT_REMOTE_TYPE));
}
void PreallocatedProcessManagerImpl::AllocateAfterDelay(bool aStartup) {
if (!mEnabled) {
return;
}
long delay = aStartup ? StaticPrefs::dom_ipc_processPrelaunch_startupDelayMs()
: StaticPrefs::dom_ipc_processPrelaunch_delayMs();
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Starting delayed process start, delay=%ld", delay));
NS_DelayedDispatchToCurrentThread(
NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this,
&PreallocatedProcessManagerImpl::AllocateOnIdle),
delay);
}
void PreallocatedProcessManagerImpl::AllocateOnIdle() {
if (!mEnabled) {
return;
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Starting process allocate on idle"));
NS_DispatchToCurrentThreadQueue(
NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this,
&PreallocatedProcessManagerImpl::AllocateNow),
EventQueuePriority::Idle);
}
void PreallocatedProcessManagerImpl::AllocateNow() {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Trying to start process now"));
if (!CanAllocate()) {
if (mEnabled && !sShutdown && IsEmpty() && sNumBlockers > 0) {
// If it's too early to allocate a process let's retry later.
AllocateAfterDelay();
}
return;
}
RefPtr<PreallocatedProcessManagerImpl> self(this);
mLaunchInProgress = true;
ContentParent::PreallocateProcess()->Then(
GetCurrentSerialEventTarget(), __func__,
[self, this](const RefPtr<ContentParent>& process) {
mLaunchInProgress = false;
if (process->IsDead()) {
// Process died in startup (before we could add it). If it
// dies after this, MarkAsDead() will Erase() this entry.
// Shouldn't be in the sBrowserContentParents, so we don't need
// RemoveFromList(). We won't try to kick off a new
// preallocation here, to avoid possible looping if something is
// causing them to consistently fail; if everything is ok on the
// next allocation request we'll kick off creation.
} else {
if (CanAllocate()) {
// slight perf reason for push_back - while the cpu cache
// probably has stack/etc associated with the most recent
// process created, we don't know that it has finished startup.
// If we added it to the queue on completion of startup, we
// could push_front it, but that would require a bunch more
// logic.
mPreallocatedProcesses.push_back(process);
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Preallocated = %lu of %d processes",
(unsigned long)mPreallocatedProcesses.size(),
mNumberPreallocs));
// Continue prestarting processes if needed
if (mPreallocatedProcesses.size() < mNumberPreallocs) {
AllocateOnIdle();
}
} else {
process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
}
}
},
[self, this](ContentParent::LaunchError err) {
mLaunchInProgress = false;
});
}
void PreallocatedProcessManagerImpl::Disable() {
if (!mEnabled) {
return;
}
mEnabled = false;
CloseProcesses();
}
void PreallocatedProcessManagerImpl::CloseProcesses() {
while (!mPreallocatedProcesses.empty()) {
RefPtr<ContentParent> process(mPreallocatedProcesses.front().forget());
mPreallocatedProcesses.pop_front();
process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
// drop ref and let it free
}
// Make sure to also clear out the recycled E10S process cache, as it's also
// controlled by the same preference, and can be cleaned up due to memory
// pressure.
if (RefPtr<ContentParent> recycled =
ContentParent::sRecycledE10SProcess.forget()) {
recycled->MaybeBeginShutDown();
}
}
inline PreallocatedProcessManagerImpl*
PreallocatedProcessManager::GetPPMImpl() {
if (PreallocatedProcessManagerImpl::sShutdown) {
return nullptr;
}
return PreallocatedProcessManagerImpl::Singleton();
}
/* static */
bool PreallocatedProcessManager::Enabled() {
if (auto impl = GetPPMImpl()) {
return impl->mEnabled;
}
return false;
}
/* static */
void PreallocatedProcessManager::AddBlocker(const nsACString& aRemoteType,
ContentParent* aParent) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("AddBlocker: %s %p (sNumBlockers=%d)",
PromiseFlatCString(aRemoteType).get(), aParent,
PreallocatedProcessManagerImpl::sNumBlockers));
if (auto impl = GetPPMImpl()) {
impl->AddBlocker(aParent);
}
}
/* static */
void PreallocatedProcessManager::RemoveBlocker(const nsACString& aRemoteType,
ContentParent* aParent) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("RemoveBlocker: %s %p (sNumBlockers=%d)",
PromiseFlatCString(aRemoteType).get(), aParent,
PreallocatedProcessManagerImpl::sNumBlockers));
if (auto impl = GetPPMImpl()) {
impl->RemoveBlocker(aParent);
}
}
/* static */
already_AddRefed<ContentParent> PreallocatedProcessManager::Take(
const nsACString& aRemoteType) {
if (auto impl = GetPPMImpl()) {
return impl->Take(aRemoteType);
}
return nullptr;
}
/* static */
void PreallocatedProcessManager::Erase(ContentParent* aParent) {
if (auto impl = GetPPMImpl()) {
impl->Erase(aParent);
}
}
} // namespace mozilla