summaryrefslogtreecommitdiffstats
path: root/dom/ipc/PreallocatedProcessManager.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/ipc/PreallocatedProcessManager.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/ipc/PreallocatedProcessManager.cpp')
-rw-r--r--dom/ipc/PreallocatedProcessManager.cpp440
1 files changed, 440 insertions, 0 deletions
diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp
new file mode 100644
index 0000000000..b96521492f
--- /dev/null
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -0,0 +1,440 @@
+/* -*- 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);
+ 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(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<RefPtr<ContentParent>, 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();
+ }
+}
+
+already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take(
+ const nsACString& aRemoteType) {
+ if (!IsEnabled()) {
+ return nullptr;
+ }
+ RefPtr<ContentParent> process;
+ if (!IsEmpty()) {
+ process = 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);
+ // 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,
+ PROCESS_PRIORITY_FOREGROUND);
+ } // else this will get set by the caller when they call InitInternal()
+
+ return process.forget();
+}
+
+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;
+ }
+
+ RefPtr<ContentParent> process = ContentParent::MakePreallocProcess();
+ mPreallocatedProcesses.AppendElement(process);
+ MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+ ("Preallocated = %lu of %d processes",
+ (unsigned long)mPreallocatedProcesses.Length(), mNumberPreallocs));
+
+ RefPtr<PreallocatedProcessManagerImpl> self(this);
+ process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, this, process](const RefPtr<ContentParent>&) {
+ if (process->IsDead()) {
+ 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 {
+ // Continue prestarting processes if needed
+ if (CanAllocate()) {
+ if (mPreallocatedProcesses.Length() < mNumberPreallocs) {
+ AllocateOnIdle();
+ }
+ } else if (!IsEnabled()) {
+ // if this has a remote type set, it's been allocated for use
+ // already
+ if (process->mRemoteType == PREALLOC_REMOTE_TYPE) {
+ // This will Erase() it
+ process->ShutDownProcess(
+ ContentParent::SEND_SHUTDOWN_MESSAGE);
+ }
+ }
+ }
+ },
+ [self, this, process]() { Erase(process); });
+}
+
+void PreallocatedProcessManagerImpl::Disable() {
+ if (!mEnabled) {
+ return;
+ }
+
+ mEnabled = false;
+ CloseProcesses();
+}
+
+void PreallocatedProcessManagerImpl::CloseProcesses() {
+ while (!IsEmpty()) {
+ RefPtr<ContentParent> process(mPreallocatedProcesses.ElementAt(0));
+ mPreallocatedProcesses.RemoveElementAt(0);
+ 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::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 */
+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