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
423 lines
14 KiB
C++
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
|