зеркало из https://github.com/mozilla/gecko-dev.git
429 строки
14 KiB
C++
429 строки
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();
|
|
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.
|
|
AllocateOnIdle();
|
|
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;
|
|
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.
|
|
|
|
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() {
|
|
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(
|
|
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
|