Files
tubestation/dom/ipc/PreallocatedProcessManager.cpp
Nika Layzell 279b50ebe8 Bug 1728331 - Part 4: Make ContentParent KeepAlives explicit with RAII references, r=smaug,dom-worker-reviewers,asuth
This is a fairly significant patch, however it would be difficult to break it
down into smaller patches:

1) The various mechanisms used to manage ContentParent lifecycles have been
   merged together into a common "KeepAlive" system. A process will
   begin shutdown when its keepalive count reaches 0. (though it will
   still wait for all BrowserParents to also be dead before sending the
   actual shutdown message as before).

   This replaces a number of bespoke systems for tracking BrowserParent
   instances in different lifecycle states, remote workers, ongoing
   process switches, and preallocated processes.

2) KeepAlives are now managed automatically by a UniquePtr variant
   (Unique[Threadsafe]ContentParentKeepAlive). This makes the hand-off
   over KeepAlive lifecycles explicit, even for workers.

3) All KeepAlives are now keyed by a BrowserId, which will be 0 for keepalives
   not associated with a specific tab. This allows the new process
   selection logic to count all tabs other than the one being navigated
   when deciding which process to use.

4) The process switching logic now tracks it's KeepAlive with a BrowserId,
   meaning that ongoing process switches are considered when performing
   process selection, even if the BrowserParent hasn't been created yet.

Differential Revision: https://phabricator.services.mozilla.com/D213338
2024-06-24 23:19:28 +00:00

423 lines
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/AppShutdown.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 "nsTArray.h"
#include "prsystem.h"
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);
UniqueContentParentKeepAlive 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.IsEmpty(); }
static bool IsShutdown() {
return AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
}
bool IsEnabled() { return mEnabled && !IsShutdown(); }
bool mEnabled;
uint32_t mNumberPreallocs;
AutoTArray<UniqueContentParentKeepAlive, 3> 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;
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), mNumberPreallocs(1) {}
PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() {
// Note: mPreallocatedProcesses may not be null, but all processes should
// be dead (IsDead==true). We block Erase() when our observer sees
// shutdown starting.
}
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);
}
} 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();
// limit preallocated processes on low-mem machines
PRUint64 bytes = PR_GetPhysicalMemorySize();
if (bytes > 0 &&
bytes <=
StaticPrefs::dom_ipc_processPrelaunch_lowmem_mb() * 1024 * 1024) {
number = 1;
}
}
if (number >= 0) {
Enable(number);
// We have one prealloc queue for all types except File now
if (static_cast<uint64_t>(number) < mPreallocatedProcesses.Length()) {
CloseProcesses();
}
}
} else {
Disable();
}
}
UniqueContentParentKeepAlive PreallocatedProcessManagerImpl::Take(
const nsACString& aRemoteType) {
if (!IsEnabled()) {
return nullptr;
}
UniqueContentParentKeepAlive process;
if (!IsEmpty()) {
process = std::move(mPreallocatedProcesses.ElementAt(0));
mPreallocatedProcesses.RemoveElementAt(0);
// Don't set the priority to FOREGROUND here, since it may not have
// finished starting
// We took a preallocated process. Let's try to start up a new one
// soon.
ContentParent* last = mPreallocatedProcesses.SafeLastElement(nullptr).get();
// There could be a launching process that isn't the last, but that's
// ok (and unlikely)
if (!last || !last->IsLaunching()) {
AllocateAfterDelay();
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Use prealloc process %p%s, %lu available", process.get(),
process->IsLaunching() ? " (still launching)" : "",
(unsigned long)mPreallocatedProcesses.Length()));
}
if (process && !process->IsLaunching()) {
ProcessPriorityManager::SetProcessPriority(process.get(),
PROCESS_PRIORITY_FOREGROUND);
} // else this will get set by the caller when they call InitInternal()
return process;
}
void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) {
(void)mPreallocatedProcesses.RemoveElement(aParent);
}
void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) {
mNumberPreallocs = aProcesses;
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Enabling preallocation: %u", aProcesses));
if (mEnabled || IsShutdown()) {
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 IsEnabled() && sNumBlockers == 0 &&
mPreallocatedProcesses.Length() < mNumberPreallocs && !IsShutdown() &&
(FissionAutostart() ||
!ContentParent::IsMaxProcessCountReached(DEFAULT_REMOTE_TYPE));
}
void PreallocatedProcessManagerImpl::AllocateAfterDelay(bool aStartup) {
if (!IsEnabled()) {
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 (!IsEnabled()) {
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 (IsEnabled() && IsEmpty() && sNumBlockers > 0) {
// If it's too early to allocate a process let's retry later.
AllocateAfterDelay();
}
return;
}
UniqueContentParentKeepAlive process = ContentParent::MakePreallocProcess();
process->WaitForLaunchAsync(PROCESS_PRIORITY_PREALLOC)
->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this},
process = RefPtr{process.get()}](UniqueContentParentKeepAlive) {
if (process->IsDead()) {
self->Erase(process);
// 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 (self->CanAllocate()) {
// Continue prestarting processes if needed
if (self->mPreallocatedProcesses.Length() <
self->mNumberPreallocs) {
self->AllocateOnIdle();
}
}
},
[self = RefPtr{this}, process = RefPtr{process.get()}]() {
self->Erase(process);
});
mPreallocatedProcesses.AppendElement(std::move(process));
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Preallocated = %lu of %d processes",
(unsigned long)mPreallocatedProcesses.Length(), mNumberPreallocs));
}
void PreallocatedProcessManagerImpl::Disable() {
if (!mEnabled) {
return;
}
mEnabled = false;
CloseProcesses();
}
void PreallocatedProcessManagerImpl::CloseProcesses() {
// Drop our KeepAlives on these processes. This will automatically lead to the
// processes being shut down when no keepalives are left.
mPreallocatedProcesses.Clear();
}
inline PreallocatedProcessManagerImpl*
PreallocatedProcessManager::GetPPMImpl() {
if (PreallocatedProcessManagerImpl::IsShutdown()) {
return nullptr;
}
return PreallocatedProcessManagerImpl::Singleton();
}
/* static */
bool PreallocatedProcessManager::Enabled() {
if (auto impl = GetPPMImpl()) {
return impl->IsEnabled();
}
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 */
UniqueContentParentKeepAlive 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