michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et ft=cpp : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/PreallocatedProcessManager.h" michael@0: #include "mozilla/ClearOnShutdown.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "nsIPropertyBag2.h" michael@0: #include "ProcessPriorityManager.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: // This number is fairly arbitrary ... the intention is to put off michael@0: // launching another app process until the last one has finished michael@0: // loading its content, to reduce CPU/memory/IO contention. michael@0: #define DEFAULT_ALLOCATE_DELAY 1000 michael@0: #define NUWA_FORK_WAIT_DURATION_MS 2000 // 2 seconds. michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::hal; michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace { michael@0: michael@0: /** michael@0: * This singleton class implements the static methods on michael@0: * PreallocatedProcessManager. michael@0: */ michael@0: class PreallocatedProcessManagerImpl MOZ_FINAL michael@0: : public nsIObserver michael@0: { michael@0: public: michael@0: static PreallocatedProcessManagerImpl* Singleton(); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: // See comments on PreallocatedProcessManager for these methods. michael@0: void AllocateAfterDelay(); michael@0: void AllocateOnIdle(); michael@0: void AllocateNow(); michael@0: already_AddRefed Take(); michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: public: michael@0: void ScheduleDelayedNuwaFork(); michael@0: void DelayedNuwaFork(); michael@0: void PublishSpareProcess(ContentParent* aContent); michael@0: void MaybeForgetSpare(ContentParent* aContent); michael@0: void OnNuwaReady(); michael@0: bool PreallocatedProcessReady(); michael@0: already_AddRefed GetSpareProcess(); michael@0: void RunAfterPreallocatedProcessReady(nsIRunnable* aRunnable); michael@0: michael@0: private: michael@0: void NuwaFork(); michael@0: michael@0: // initialization off the critical path of app startup. michael@0: CancelableTask* mPreallocateAppProcessTask; michael@0: michael@0: // The array containing the preallocated processes. 4 as the inline storage size michael@0: // should be enough so we don't need to grow the nsAutoTArray. michael@0: nsAutoTArray, 4> mSpareProcesses; michael@0: michael@0: nsTArray > mDelayedContentParentRequests; michael@0: michael@0: // Nuwa process is ready for creating new process. michael@0: bool mIsNuwaReady; michael@0: #endif michael@0: michael@0: private: michael@0: static mozilla::StaticRefPtr sSingleton; michael@0: michael@0: PreallocatedProcessManagerImpl(); michael@0: DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl); michael@0: michael@0: void Init(); michael@0: michael@0: void RereadPrefs(); michael@0: void Enable(); michael@0: void Disable(); michael@0: michael@0: void ObserveProcessShutdown(nsISupports* aSubject); michael@0: michael@0: bool mEnabled; michael@0: bool mShutdown; michael@0: nsRefPtr mPreallocatedAppProcess; michael@0: }; michael@0: michael@0: /* static */ StaticRefPtr michael@0: PreallocatedProcessManagerImpl::sSingleton; michael@0: michael@0: /* static */ PreallocatedProcessManagerImpl* michael@0: PreallocatedProcessManagerImpl::Singleton() michael@0: { michael@0: if (!sSingleton) { michael@0: sSingleton = new PreallocatedProcessManagerImpl(); michael@0: sSingleton->Init(); michael@0: ClearOnShutdown(&sSingleton); michael@0: } michael@0: michael@0: return sSingleton; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver) michael@0: michael@0: PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl() michael@0: : mEnabled(false) michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: , mPreallocateAppProcessTask(nullptr) michael@0: , mIsNuwaReady(false) michael@0: #endif michael@0: , mShutdown(false) michael@0: {} michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::Init() michael@0: { michael@0: Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled"); michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (os) { michael@0: os->AddObserver(this, "ipc:content-shutdown", michael@0: /* weakRef = */ false); michael@0: os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, michael@0: /* weakRef = */ false); michael@0: } michael@0: RereadPrefs(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!strcmp("ipc:content-shutdown", aTopic)) { michael@0: ObserveProcessShutdown(aSubject); michael@0: } else if (!strcmp("nsPref:changed", aTopic)) { michael@0: // The only other observer we registered was for our prefs. michael@0: RereadPrefs(); michael@0: } else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { michael@0: mShutdown = true; michael@0: } else { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::RereadPrefs() michael@0: { michael@0: if (Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) { michael@0: Enable(); michael@0: } else { michael@0: Disable(); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: PreallocatedProcessManagerImpl::Take() michael@0: { michael@0: return mPreallocatedAppProcess.forget(); michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::Enable() michael@0: { michael@0: if (mEnabled) { michael@0: return; michael@0: } michael@0: michael@0: mEnabled = true; michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: ScheduleDelayedNuwaFork(); michael@0: #else michael@0: AllocateAfterDelay(); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::AllocateAfterDelay() michael@0: { michael@0: if (!mEnabled || mPreallocatedAppProcess) { michael@0: return; michael@0: } michael@0: michael@0: MessageLoop::current()->PostDelayedTask( michael@0: FROM_HERE, michael@0: NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateOnIdle), michael@0: Preferences::GetUint("dom.ipc.processPrelaunch.delayMs", michael@0: DEFAULT_ALLOCATE_DELAY)); michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::AllocateOnIdle() michael@0: { michael@0: if (!mEnabled || mPreallocatedAppProcess) { michael@0: return; michael@0: } michael@0: michael@0: MessageLoop::current()->PostIdleTask( michael@0: FROM_HERE, michael@0: NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateNow)); michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::AllocateNow() michael@0: { michael@0: if (!mEnabled || mPreallocatedAppProcess) { michael@0: return; michael@0: } michael@0: michael@0: mPreallocatedAppProcess = ContentParent::PreallocateAppProcess(); michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::RunAfterPreallocatedProcessReady(nsIRunnable* aRequest) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mDelayedContentParentRequests.AppendElement(aRequest); michael@0: michael@0: // This is an urgent NuwaFork() request. Request to fork at once. michael@0: DelayedNuwaFork(); michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::ScheduleDelayedNuwaFork() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mPreallocateAppProcessTask) { michael@0: // Make sure there is only one request running. michael@0: return; michael@0: } michael@0: michael@0: mPreallocateAppProcessTask = NewRunnableMethod( michael@0: this, &PreallocatedProcessManagerImpl::DelayedNuwaFork); michael@0: MessageLoop::current()->PostDelayedTask( michael@0: FROM_HERE, mPreallocateAppProcessTask, michael@0: Preferences::GetUint("dom.ipc.processPrelaunch.delayMs", michael@0: DEFAULT_ALLOCATE_DELAY)); michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::DelayedNuwaFork() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mPreallocateAppProcessTask = nullptr; michael@0: michael@0: if (!mIsNuwaReady) { michael@0: if (!mPreallocatedAppProcess && !mShutdown && mEnabled) { michael@0: mPreallocatedAppProcess = ContentParent::RunNuwaProcess(); michael@0: } michael@0: // else mPreallocatedAppProcess is starting. It will NuwaFork() when ready. michael@0: } else if (mSpareProcesses.IsEmpty()) { michael@0: NuwaFork(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Get a spare ContentParent from mSpareProcesses list. michael@0: */ michael@0: already_AddRefed michael@0: PreallocatedProcessManagerImpl::GetSpareProcess() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mSpareProcesses.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr process = mSpareProcesses.LastElement(); michael@0: mSpareProcesses.RemoveElementAt(mSpareProcesses.Length() - 1); michael@0: michael@0: if (mSpareProcesses.IsEmpty() && mIsNuwaReady) { michael@0: NS_ASSERTION(mPreallocatedAppProcess != nullptr, michael@0: "Nuwa process is not present!"); michael@0: ScheduleDelayedNuwaFork(); michael@0: } michael@0: michael@0: return process.forget(); michael@0: } michael@0: michael@0: /** michael@0: * Publish a ContentParent to spare process list. michael@0: */ michael@0: void michael@0: PreallocatedProcessManagerImpl::PublishSpareProcess(ContentParent* aContent) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) { michael@0: AutoJSContext cx; michael@0: nsCOMPtr ppmm = michael@0: do_GetService("@mozilla.org/parentprocessmessagemanager;1"); michael@0: nsresult rv = ppmm->BroadcastAsyncMessage( michael@0: NS_LITERAL_STRING("TEST-ONLY:nuwa-add-new-process"), michael@0: JS::NullHandleValue, JS::NullHandleValue, cx, 1); michael@0: } michael@0: michael@0: mSpareProcesses.AppendElement(aContent); michael@0: michael@0: if (!mDelayedContentParentRequests.IsEmpty()) { michael@0: nsCOMPtr runnable = mDelayedContentParentRequests[0]; michael@0: mDelayedContentParentRequests.RemoveElementAt(0); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::MaybeForgetSpare(ContentParent* aContent) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!mDelayedContentParentRequests.IsEmpty()) { michael@0: if (!mPreallocateAppProcessTask) { michael@0: // This NuwaFork request is urgent. Don't delay it. michael@0: DelayedNuwaFork(); michael@0: } michael@0: } michael@0: michael@0: if (mSpareProcesses.RemoveElement(aContent)) { michael@0: return; michael@0: } michael@0: michael@0: if (aContent == mPreallocatedAppProcess) { michael@0: mPreallocatedAppProcess = nullptr; michael@0: mIsNuwaReady = false; michael@0: ScheduleDelayedNuwaFork(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::OnNuwaReady() michael@0: { michael@0: NS_ASSERTION(!mIsNuwaReady, "Multiple Nuwa processes created!"); michael@0: ProcessPriorityManager::SetProcessPriority(mPreallocatedAppProcess, michael@0: hal::PROCESS_PRIORITY_MASTER); michael@0: mIsNuwaReady = true; michael@0: if (Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) { michael@0: AutoJSContext cx; michael@0: nsCOMPtr ppmm = michael@0: do_GetService("@mozilla.org/parentprocessmessagemanager;1"); michael@0: nsresult rv = ppmm->BroadcastAsyncMessage( michael@0: NS_LITERAL_STRING("TEST-ONLY:nuwa-ready"), michael@0: JS::NullHandleValue, JS::NullHandleValue, cx, 1); michael@0: } michael@0: NuwaFork(); michael@0: } michael@0: michael@0: bool michael@0: PreallocatedProcessManagerImpl::PreallocatedProcessReady() michael@0: { michael@0: return !mSpareProcesses.IsEmpty(); michael@0: } michael@0: michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::NuwaFork() michael@0: { michael@0: mPreallocatedAppProcess->SendNuwaFork(); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::Disable() michael@0: { michael@0: if (!mEnabled) { michael@0: return; michael@0: } michael@0: michael@0: mEnabled = false; michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: // Cancel pending fork. michael@0: if (mPreallocateAppProcessTask) { michael@0: mPreallocateAppProcessTask->Cancel(); michael@0: mPreallocateAppProcessTask = nullptr; michael@0: } michael@0: #endif michael@0: michael@0: if (mPreallocatedAppProcess) { michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: while (mSpareProcesses.Length() > 0){ michael@0: nsRefPtr process = mSpareProcesses[0]; michael@0: process->Close(); michael@0: mSpareProcesses.RemoveElementAt(0); michael@0: } michael@0: mIsNuwaReady = false; michael@0: #endif michael@0: mPreallocatedAppProcess->Close(); michael@0: mPreallocatedAppProcess = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: PreallocatedProcessManagerImpl::ObserveProcessShutdown(nsISupports* aSubject) michael@0: { michael@0: if (!mPreallocatedAppProcess) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr props = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE_VOID(props); michael@0: michael@0: uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID); michael@0: NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN); michael@0: michael@0: if (childID == mPreallocatedAppProcess->ChildID()) { michael@0: mPreallocatedAppProcess = nullptr; michael@0: } michael@0: } michael@0: michael@0: inline PreallocatedProcessManagerImpl* GetPPMImpl() michael@0: { michael@0: return PreallocatedProcessManagerImpl::Singleton(); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: michael@0: /* static */ void michael@0: PreallocatedProcessManager::AllocateAfterDelay() michael@0: { michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: GetPPMImpl()->ScheduleDelayedNuwaFork(); michael@0: #else michael@0: GetPPMImpl()->AllocateAfterDelay(); michael@0: #endif michael@0: } michael@0: michael@0: /* static */ void michael@0: PreallocatedProcessManager::AllocateOnIdle() michael@0: { michael@0: GetPPMImpl()->AllocateOnIdle(); michael@0: } michael@0: michael@0: /* static */ void michael@0: PreallocatedProcessManager::AllocateNow() michael@0: { michael@0: GetPPMImpl()->AllocateNow(); michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: PreallocatedProcessManager::Take() michael@0: { michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: return GetPPMImpl()->GetSpareProcess(); michael@0: #else michael@0: return GetPPMImpl()->Take(); michael@0: #endif michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: /* static */ void michael@0: PreallocatedProcessManager::PublishSpareProcess(ContentParent* aContent) michael@0: { michael@0: GetPPMImpl()->PublishSpareProcess(aContent); michael@0: } michael@0: michael@0: /* static */ void michael@0: PreallocatedProcessManager::MaybeForgetSpare(ContentParent* aContent) michael@0: { michael@0: GetPPMImpl()->MaybeForgetSpare(aContent); michael@0: } michael@0: michael@0: /* static */ void michael@0: PreallocatedProcessManager::OnNuwaReady() michael@0: { michael@0: GetPPMImpl()->OnNuwaReady(); michael@0: } michael@0: michael@0: /*static */ bool michael@0: PreallocatedProcessManager::PreallocatedProcessReady() michael@0: { michael@0: return GetPPMImpl()->PreallocatedProcessReady(); michael@0: } michael@0: michael@0: /* static */ void michael@0: PreallocatedProcessManager::RunAfterPreallocatedProcessReady(nsIRunnable* aRequest) michael@0: { michael@0: GetPPMImpl()->RunAfterPreallocatedProcessReady(aRequest); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: } // namespace mozilla