gecko-dev/dom/ipc/PreallocatedProcessManager.cpp
Randell Jesup d707e31043 Bug 1642290: stop the process preallocator during normal shutdown, not post-CC r=nika
Post-CC is no longer needed given the landing of bug 1642491

Differential Revision: https://phabricator.services.mozilla.com/D79017
2020-06-10 13:36:40 +00:00

466 lines
15 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/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 nsAString& aRemoteType);
bool Provide(ContentParent* aParent);
void Erase(ContentParent* aParent);
private:
static const char* const kObserverTopics[];
static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
PreallocatedProcessManagerImpl();
~PreallocatedProcessManagerImpl();
DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
void Init();
bool CanAllocate();
void AllocateAfterDelay();
void AllocateOnIdle();
void AllocateNow();
void RereadPrefs();
void Enable(uint32_t aProcesses);
void Disable();
void CloseProcesses();
void ObserveProcessShutdown(nsISupports* aSubject);
bool IsEmpty() const {
return mPreallocatedProcesses.empty() && !mLaunchInProgress;
}
bool mEnabled;
static bool sShutdown;
bool mLaunchInProgress;
uint32_t mNumberPreallocs;
std::deque<RefPtr<ContentParent>> mPreallocatedProcesses;
RefPtr<ContentParent> mPreallocatedE10SProcess; // There can be only one
// 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[] = {
"ipc:content-shutdown",
"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("ipc:content-shutdown", aTopic)) {
ObserveProcessShutdown(aSubject);
} else 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 nsAString& aRemoteType) {
if (!mEnabled || sShutdown) {
return nullptr;
}
RefPtr<ContentParent> process;
if (aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE)) {
// we can recycle processes via Provide() for e10s only
process = mPreallocatedE10SProcess.forget();
if (process) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Reuse " DEFAULT_REMOTE_TYPE " process %p",
mPreallocatedE10SProcess.get()));
}
}
if (!process && !mPreallocatedProcesses.empty()) {
process = mPreallocatedProcesses.front().forget();
mPreallocatedProcesses.pop_front(); // holds a nullptr
// We took a preallocated process. Let's try to start up a new one
// soon.
AllocateOnIdle();
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Use " PREALLOC_REMOTE_TYPE " process %p", process.get()));
}
if (process) {
ProcessPriorityManager::SetProcessPriority(process,
PROCESS_PRIORITY_FOREGROUND);
}
return process.forget();
}
bool PreallocatedProcessManagerImpl::Provide(ContentParent* aParent) {
MOZ_DIAGNOSTIC_ASSERT(
aParent->GetRemoteType().EqualsLiteral(DEFAULT_REMOTE_TYPE));
// This will take the already-running process even if there's a
// launch in progress; if that process hasn't been taken by the
// time the launch completes, the new process will be shut down.
if (mEnabled && !sShutdown && !mPreallocatedE10SProcess) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Store for reuse " DEFAULT_REMOTE_TYPE " process %p", aParent));
ProcessPriorityManager::SetProcessPriority(aParent,
PROCESS_PRIORITY_BACKGROUND);
mPreallocatedE10SProcess = aParent;
return true;
}
// We might get a call from both NotifyTabDestroying and NotifyTabDestroyed
// with the same ContentParent. Returning true here for both calls is
// important to avoid the cached process to be destroyed.
return aParent == mPreallocatedE10SProcess;
}
void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) {
// Ensure this ContentParent isn't cached
if (mPreallocatedE10SProcess == aParent) {
mPreallocatedE10SProcess = nullptr;
}
}
void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) {
mNumberPreallocs = aProcesses;
if (mEnabled) {
return;
}
mEnabled = true;
AllocateAfterDelay();
}
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.
// (And preallocated processes can't AddBlocker when taken, because
// it's possible for a short-lived process to be recycled through
// Provide() and Take() before reaching RecvFirstIdle.)
MOZ_DIAGNOSTIC_ASSERT(sNumBlockers > 0);
sNumBlockers--;
if (sNumBlockers == 0) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Blocked preallocation for %fms",
(TimeStamp::Now() - mBlockingStartTime).ToMilliseconds()));
PROFILER_ADD_TEXT_MARKER(
"Process", NS_LITERAL_CSTRING("Blocked preallocation"),
JS::ProfilingCategoryPair::DOM, mBlockingStartTime, TimeStamp::Now());
if (IsEmpty()) {
AllocateAfterDelay();
}
}
}
bool PreallocatedProcessManagerImpl::CanAllocate() {
return mEnabled && sNumBlockers == 0 &&
mPreallocatedProcesses.size() < mNumberPreallocs && !sShutdown &&
(FissionAutostart() || !ContentParent::IsMaxProcessCountReached(
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)));
}
void PreallocatedProcessManagerImpl::AllocateAfterDelay() {
if (!mEnabled) {
return;
}
NS_DelayedDispatchToCurrentThread(
NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this,
&PreallocatedProcessManagerImpl::AllocateOnIdle),
StaticPrefs::dom_ipc_processPrelaunch_delayMs());
}
void PreallocatedProcessManagerImpl::AllocateOnIdle() {
if (!mEnabled) {
return;
}
NS_DispatchToCurrentThreadQueue(
NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this,
&PreallocatedProcessManagerImpl::AllocateNow),
EventQueuePriority::Idle);
}
void PreallocatedProcessManagerImpl::AllocateNow() {
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(
GetCurrentThreadSerialEventTarget(), __func__,
[self, this](const RefPtr<ContentParent>& process) {
mLaunchInProgress = false;
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
}
if (mPreallocatedE10SProcess) {
mPreallocatedE10SProcess->ShutDownProcess(
ContentParent::SEND_SHUTDOWN_MESSAGE);
mPreallocatedE10SProcess = nullptr;
}
}
void PreallocatedProcessManagerImpl::ObserveProcessShutdown(
nsISupports* aSubject) {
nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
NS_ENSURE_TRUE_VOID(props);
uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN;
props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN);
for (auto it = mPreallocatedProcesses.begin();
it != mPreallocatedProcesses.end(); it++) {
if (childID == (*it)->ChildID()) {
mPreallocatedProcesses.erase(it);
break;
}
}
// The ContentParent is responsible for removing itself as a blocker
}
inline PreallocatedProcessManagerImpl*
PreallocatedProcessManager::GetPPMImpl() {
if (PreallocatedProcessManagerImpl::sShutdown) {
return nullptr;
}
return PreallocatedProcessManagerImpl::Singleton();
}
/* static */
void PreallocatedProcessManager::AddBlocker(const nsAString& aRemoteType,
ContentParent* aParent) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("AddBlocker: %s %p (sNumBlockers=%d)",
NS_ConvertUTF16toUTF8(aRemoteType).get(), aParent,
PreallocatedProcessManagerImpl::sNumBlockers));
if (auto impl = GetPPMImpl()) {
impl->AddBlocker(aParent);
}
}
/* static */
void PreallocatedProcessManager::RemoveBlocker(const nsAString& aRemoteType,
ContentParent* aParent) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("RemoveBlocker: %s %p (sNumBlockers=%d)",
NS_ConvertUTF16toUTF8(aRemoteType).get(), aParent,
PreallocatedProcessManagerImpl::sNumBlockers));
if (auto impl = GetPPMImpl()) {
impl->RemoveBlocker(aParent);
}
}
/* static */
already_AddRefed<ContentParent> PreallocatedProcessManager::Take(
const nsAString& aRemoteType) {
if (auto impl = GetPPMImpl()) {
return impl->Take(aRemoteType);
}
return nullptr;
}
/* static */
bool PreallocatedProcessManager::Provide(ContentParent* aParent) {
if (auto impl = GetPPMImpl()) {
return impl->Provide(aParent);
}
return false; // We didn't take the ContentParent
}
/* static */
void PreallocatedProcessManager::Erase(ContentParent* aParent) {
if (auto impl = GetPPMImpl()) {
impl->Erase(aParent);
}
}
} // namespace mozilla