diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/ipc/PreallocatedProcessManager.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/ipc/PreallocatedProcessManager.cpp')
-rw-r--r-- | dom/ipc/PreallocatedProcessManager.cpp | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp new file mode 100644 index 0000000000..9c297ca74c --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -0,0 +1,428 @@ +/* -*- 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 |