diff options
Diffstat (limited to 'dom/ipc/PreallocatedProcessManager.cpp')
-rw-r--r-- | dom/ipc/PreallocatedProcessManager.cpp | 440 |
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 |