/* -*- 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 Take(const nsACString& aRemoteType); void Erase(ContentParent* aParent); private: static const char* const kObserverTopics[]; static StaticRefPtr 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, 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::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 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 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(number) < mPreallocatedProcesses.Length()) { CloseProcesses(); } } } else { Disable(); } } already_AddRefed PreallocatedProcessManagerImpl::Take( const nsACString& aRemoteType) { if (!IsEnabled()) { return nullptr; } RefPtr 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 process = ContentParent::MakePreallocProcess(); mPreallocatedProcesses.AppendElement(process); MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, ("Preallocated = %lu of %d processes", (unsigned long)mPreallocatedProcesses.Length(), mNumberPreallocs)); RefPtr self(this); process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC) ->Then( GetCurrentSerialEventTarget(), __func__, [self, this, process](const RefPtr&) { 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](ContentParent::LaunchError) { Erase(process); }); } void PreallocatedProcessManagerImpl::Disable() { if (!mEnabled) { return; } mEnabled = false; CloseProcesses(); } void PreallocatedProcessManagerImpl::CloseProcesses() { while (!IsEmpty()) { RefPtr 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 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 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