diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index fd872b1bce43..addd2e4f72a4 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -80,6 +80,8 @@ #include "nsThreadUtils.h" #include "nsToolkitCompsCID.h" #include "nsWidgetsCID.h" +#include "PreallocatedProcessManager.h" +#include "ProcessPriorityManager.h" #include "SandboxHal.h" #include "StructuredCloneUtils.h" #include "TabParent.h" @@ -197,6 +199,7 @@ MemoryReportRequestParent::~MemoryReportRequestParent() nsDataHashtable* ContentParent::sAppContentParents; nsTArray* ContentParent::sNonAppContentParents; nsTArray* ContentParent::sPrivateContent; +LinkedList ContentParent::sContentParents; // This is true when subprocess launching is enabled. This is the // case between StartUp() and ShutDown() or JoinAllSubprocesses(). @@ -205,60 +208,27 @@ static bool sCanLaunchSubprocesses; // The first content child has ID 1, so the chrome process can have ID 0. static uint64_t gContentChildID = 1; -// Try to keep an app process always preallocated, to get -// initialization off the critical path of app startup. -static bool sKeepAppProcessPreallocated; -static StaticRefPtr sPreallocatedAppProcess; -static CancelableTask* sPreallocateAppProcessTask; -// This number is fairly arbitrary ... the intention is to put off -// launching another app process until the last one has finished -// loading its content, to reduce CPU/memory/IO contention. -static int sPreallocateDelayMs; // We want the prelaunched process to know that it's for apps, but not // actually for any app in particular. Use a magic manifest URL. // Can't be a static constant. #define MAGIC_PREALLOCATED_APP_MANIFEST_URL NS_LITERAL_STRING("{{template}}") -/*static*/ void +// PreallocateAppProcess is called by the PreallocatedProcessManager. +// ContentParent then takes this process back within +// MaybeTakePreallocatedAppProcess. + +/*static*/ already_AddRefed ContentParent::PreallocateAppProcess() { - MOZ_ASSERT(!sPreallocatedAppProcess); - - if (sPreallocateAppProcessTask) { - // We were called directly while a delayed task was scheduled. - sPreallocateAppProcessTask->Cancel(); - sPreallocateAppProcessTask = nullptr; - } - - sPreallocatedAppProcess = + nsRefPtr process = new ContentParent(MAGIC_PREALLOCATED_APP_MANIFEST_URL, /*isBrowserElement=*/false, // Final privileges are set when we // transform into our app. base::PRIVILEGES_INHERIT, PROCESS_PRIORITY_BACKGROUND); - sPreallocatedAppProcess->Init(); -} - -/*static*/ void -ContentParent::DelayedPreallocateAppProcess() -{ - sPreallocateAppProcessTask = nullptr; - if (!sPreallocatedAppProcess) { - PreallocateAppProcess(); - } -} - -/*static*/ void -ContentParent::ScheduleDelayedPreallocateAppProcess() -{ - if (!sKeepAppProcessPreallocated || sPreallocateAppProcessTask) { - return; - } - sPreallocateAppProcessTask = - NewRunnableFunction(DelayedPreallocateAppProcess); - MessageLoop::current()->PostDelayedTask( - FROM_HERE, sPreallocateAppProcessTask, sPreallocateDelayMs); + process->Init(); + return process.forget(); } /*static*/ already_AddRefed @@ -266,9 +236,7 @@ ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL, ChildPrivileges aPrivs, ProcessPriority aInitialPriority) { - nsRefPtr process = sPreallocatedAppProcess.get(); - sPreallocatedAppProcess = nullptr; - + nsRefPtr process = PreallocatedProcessManager::Take(); if (!process) { return nullptr; } @@ -284,14 +252,6 @@ ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL, return process.forget(); } -/*static*/ void -ContentParent::FirstIdle(void) -{ - // The parent has gone idle for the first time. This would be a good - // time to preallocate an app process. - ScheduleDelayedPreallocateAppProcess(); -} - /*static*/ void ContentParent::StartUp() { @@ -299,22 +259,10 @@ ContentParent::StartUp() return; } - sKeepAppProcessPreallocated = - Preferences::GetBool("dom.ipc.processPrelaunch.enabled", false); - if (sKeepAppProcessPreallocated) { - ClearOnShutdown(&sPreallocatedAppProcess); - - sPreallocateDelayMs = Preferences::GetUint( - "dom.ipc.processPrelaunch.delayMs", 1000); - - MOZ_ASSERT(!sPreallocateAppProcessTask); - - // Let's not slow down the main process initialization. Wait until - // the main process goes idle before we preallocate a process - MessageLoop::current()->PostIdleTask(FROM_HERE, NewRunnableFunction(FirstIdle)); - } - sCanLaunchSubprocesses = true; + + // Try to preallocate a process that we can transform into an app later. + PreallocatedProcessManager::AllocateAfterDelay(); } /*static*/ void @@ -544,30 +492,14 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext, return static_cast(browser); } -static PLDHashOperator -AppendToTArray(const nsAString& aKey, ContentParent* aValue, void* aArray) -{ - nsTArray *array = - static_cast*>(aArray); - array->AppendElement(aValue); - return PL_DHASH_NEXT; -} - void ContentParent::GetAll(nsTArray& aArray) { aArray.Clear(); - if (gNonAppContentParents) { - aArray.AppendElements(*gNonAppContentParents); - } - - if (gAppContentParents) { - gAppContentParents->EnumerateRead(&AppendToTArray, &aArray); - } - - if (sPreallocatedAppProcess) { - aArray.AppendElement(sPreallocatedAppProcess); + for (ContentParent* cp = sContentParents.getFirst(); cp; + cp = cp->getNext()) { + aArray.AppendElement(cp); } } @@ -814,6 +746,11 @@ ContentParent::MarkAsDead() } mIsAlive = false; + + // Remove from sContentParents. + if (isInList()) { + remove(); + } } void @@ -922,10 +859,6 @@ ContentParent::ActorDestroy(ActorDestroyReason why) #endif } - if (sPreallocatedAppProcess == this) { - sPreallocatedAppProcess = nullptr; - } - mMessageManager->Disconnect(); // clear the child memory reporters @@ -1086,6 +1019,9 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL, , mSendPermissionUpdates(false) , mIsForBrowser(aIsForBrowser) { + // Insert ourselves into the global linked list of ContentParent objects. + sContentParents.insertBack(this); + // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the // PID along with the warning. nsDebugImpl::SetMultiprocessMode("Parent"); @@ -1380,11 +1316,11 @@ ContentParent::RecvGetShowPasswordSetting(bool* showPassword) bool ContentParent::RecvFirstIdle() { - // When the ContentChild goes idle, it sends us a FirstIdle message - // which we use as a good time to prelaunch another process. If we - // prelaunch any sooner than this, then we'll be competing with the + // When the ContentChild goes idle, it sends us a FirstIdle message which we + // use as an indicator that it's a good time to prelaunch another process. + // If we prelaunch any sooner than this, then we'll be competing with the // child process and slowing it down. - ScheduleDelayedPreallocateAppProcess(); + PreallocatedProcessManager::AllocateOnIdle(); return true; } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 0ab964b3c605..2fda4e1e5a98 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -58,6 +58,7 @@ class ContentParent : public PContentParent , public nsIThreadObserver , public nsIDOMGeoPositionCallback , public mozilla::dom::ipc::MessageManagerCallback + , public mozilla::LinkedListElement { typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost; typedef mozilla::ipc::OptionalURIParams OptionalURIParams; @@ -84,6 +85,11 @@ public: static already_AddRefed GetNewOrUsed(bool aForBrowserElement = false); + /** + * Create a subprocess suitable for use as a preallocated app process. + */ + static already_AddRefed PreallocateAppProcess(); + /** * Get or create a content process for the given TabContext. aFrameElement * should be the frame/iframe element with which this process will @@ -154,14 +160,11 @@ private: static nsDataHashtable *sAppContentParents; static nsTArray* sNonAppContentParents; static nsTArray* sPrivateContent; + static LinkedList sContentParents; static void JoinProcessesIOThread(const nsTArray* aProcesses, Monitor* aMonitor, bool* aDone); - static void PreallocateAppProcess(); - static void DelayedPreallocateAppProcess(); - static void ScheduleDelayedPreallocateAppProcess(); - // Take the preallocated process and transform it into a "real" app process, // for the specified manifest URL. If there is no preallocated process (or // if it's dead), this returns false. @@ -172,8 +175,6 @@ private: static hal::ProcessPriority GetInitialProcessPriority(nsIDOMElement* aFrameElement); - static void FirstIdle(); - // Hide the raw constructor methods since we don't want client code // using them. using PContentParent::SendPBrowserConstructor; diff --git a/dom/ipc/Makefile.in b/dom/ipc/Makefile.in index 98ec839a3822..896848e9a0d1 100644 --- a/dom/ipc/Makefile.in +++ b/dom/ipc/Makefile.in @@ -24,6 +24,7 @@ CPPSRCS = \ CrashReporterParent.cpp \ CrashReporterChild.cpp \ PermissionMessageUtils.cpp \ + PreallocatedProcessManager.cpp \ ProcessPriorityManager.cpp \ StructuredCloneUtils.cpp \ TabParent.cpp \ diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp new file mode 100644 index 000000000000..64eca90a6673 --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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/dom/ContentParent.h" + +using namespace mozilla; +using namespace mozilla::hal; +using namespace mozilla::dom; + +namespace { + +/** + * This singleton class implements the static methods on + * PreallocatedProcessManager. + */ +class PreallocatedProcessManagerImpl MOZ_FINAL + : public nsIObserver +{ +public: + static PreallocatedProcessManagerImpl* Singleton(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + // See comments on PreallocatedProcessManager for these methods. + void AllocateAfterDelay(); + void AllocateOnIdle(); + void AllocateNow(); + already_AddRefed Take(); + +private: + static mozilla::StaticRefPtr sSingleton; + + PreallocatedProcessManagerImpl(); + DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl); + + void Init(); + + void RereadPrefs(); + void Enable(); + void Disable(); + + void ObserveProcessShutdown(nsISupports* aSubject); + + bool mEnabled; + nsRefPtr mPreallocatedAppProcess; +}; + +/* static */ StaticRefPtr +PreallocatedProcessManagerImpl::sSingleton; + +/* static */ PreallocatedProcessManagerImpl* +PreallocatedProcessManagerImpl::Singleton() +{ + if (!sSingleton) { + sSingleton = new PreallocatedProcessManagerImpl(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + + return sSingleton; +} + +NS_IMPL_ISUPPORTS1(PreallocatedProcessManagerImpl, nsIObserver) + +PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl() + : mEnabled(false) +{} + +void +PreallocatedProcessManagerImpl::Init() +{ + Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled"); + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->AddObserver(this, "ipc:content-shutdown", + /* weakRef = */ false); + } + RereadPrefs(); +} + +NS_IMETHODIMP +PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject, + const char* aTopic, + const PRUnichar* aData) +{ + if (!strcmp("ipc:content-shutdown", aTopic)) { + ObserveProcessShutdown(aSubject); + } else if (!strcmp("nsPref:changed", aTopic)) { + // The only other observer we registered was for our prefs. + RereadPrefs(); + } else { + MOZ_ASSERT(false); + } + + return NS_OK; +} + +void +PreallocatedProcessManagerImpl::RereadPrefs() +{ + if (Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) { + Enable(); + } else { + Disable(); + } +} + +already_AddRefed +PreallocatedProcessManagerImpl::Take() +{ + return mPreallocatedAppProcess.forget(); +} + +void +PreallocatedProcessManagerImpl::Enable() +{ + if (mEnabled) { + return; + } + + mEnabled = true; + AllocateAfterDelay(); +} + +void +PreallocatedProcessManagerImpl::AllocateAfterDelay() +{ + if (!mEnabled || mPreallocatedAppProcess) { + return; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateOnIdle), + Preferences::GetUint("dom.ipc.processPrelaunch.delayMs", 1000)); +} + +void +PreallocatedProcessManagerImpl::AllocateOnIdle() +{ + if (!mEnabled || mPreallocatedAppProcess) { + return; + } + + MessageLoop::current()->PostIdleTask( + FROM_HERE, + NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateNow)); +} + +void +PreallocatedProcessManagerImpl::AllocateNow() +{ + if (!mEnabled || mPreallocatedAppProcess) { + return; + } + + mPreallocatedAppProcess = ContentParent::PreallocateAppProcess(); +} + +void +PreallocatedProcessManagerImpl::Disable() +{ + if (!mEnabled) { + return; + } + + mEnabled = false; + + if (mPreallocatedAppProcess) { + mPreallocatedAppProcess->ShutDown(); + mPreallocatedAppProcess = nullptr; + } +} + +void +PreallocatedProcessManagerImpl::ObserveProcessShutdown(nsISupports* aSubject) +{ + if (!mPreallocatedAppProcess) { + return; + } + + nsCOMPtr props = do_QueryInterface(aSubject); + NS_ENSURE_TRUE_VOID(props); + + uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; + props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID); + NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN); + + if (childID == mPreallocatedAppProcess->ChildID()) { + mPreallocatedAppProcess = nullptr; + } +} + +} // anonymous namespace + +namespace mozilla { + +/* static */ void +PreallocatedProcessManager::AllocateAfterDelay() +{ + PreallocatedProcessManagerImpl::Singleton()->AllocateAfterDelay(); +} + +/* static */ void +PreallocatedProcessManager::AllocateOnIdle() +{ + PreallocatedProcessManagerImpl::Singleton()->AllocateOnIdle(); +} + +/* static */ void +PreallocatedProcessManager::AllocateNow() +{ + PreallocatedProcessManagerImpl::Singleton()->AllocateNow(); +} + +/* static */ already_AddRefed +PreallocatedProcessManager::Take() +{ + return PreallocatedProcessManagerImpl::Singleton()->Take(); +} + +} // namespace mozilla diff --git a/dom/ipc/PreallocatedProcessManager.h b/dom/ipc/PreallocatedProcessManager.h new file mode 100644 index 000000000000..19c2b419738e --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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/. */ + +#ifndef mozilla_PreallocatedProcessManager_h +#define mozilla_PreallocatedProcessManager_h + +#include "base/basictypes.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsAutoPtr.h" + +namespace mozilla { +namespace dom { +class ContentParent; +} + +/** + * This class manages a ContentParent that it starts up ahead of any particular + * need. You can then call Take() to get this process and use it. Since we + * already started it up, it should be ready for use faster than if you'd + * created the process when you needed it. + * + * This class watches the dom.ipc.processPrelaunch.enabled pref. If it changes + * from false to true, it preallocates a process. If it changes from true to + * false, it kills the preallocated process, if any. + * + * We don't expect this pref to flip between true and false in production, but + * flipping the pref is important for tests. + * + * The static methods here are implemented by forwarding calls on to a + * PreallocatedProcessManagerImpl singleton class, so if you add a new static + * method here, you'll need to write a corresponding public method on the + * singleton. + */ +class PreallocatedProcessManager MOZ_FINAL +{ + typedef mozilla::dom::ContentParent ContentParent; + +public: + /** + * Create a process after a delay. We wait for a period of time (specified + * by the dom.ipc.processPrelaunch.delayMs pref), then wait for this process + * to go idle, then allocate the new process. + * + * If the dom.ipc.processPrelaunch.enabled pref is false, or if we already + * have a preallocated process, this function does nothing. + */ + static void AllocateAfterDelay(); + + /** + * Create a process once this process goes idle. + * + * If the dom.ipc.processPrelaunch.enabled pref is false, or if we already + * have a preallocated process, this function does nothing. + */ + static void AllocateOnIdle(); + + /** + * Create a process right now. + * + * If the dom.ipc.processPrelaunch.enabled pref is false, or if we already + * have a preallocated process, this function does nothing. + */ + static void AllocateNow(); + + /** + * Take the preallocated process, if we have one. If we don't have one, this + * returns null. + * + * If you call Take() twice in a row, the second call is guaranteed to return + * null. + * + * After you Take() the preallocated process, you need to call one of the + * Allocate* functions (or change the dom.ipc.processPrelaunch pref from + * false to true) before we'll create a new process. + */ + static already_AddRefed Take(); + +private: + PreallocatedProcessManager(); + DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager); +}; + +} // namespace mozilla + +#endif // defined mozilla_PreallocatedProcessManager_h diff --git a/dom/ipc/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp index d5fabb982ebc..fc2aaa6bcdc4 100644 --- a/dom/ipc/ProcessPriorityManager.cpp +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -5,7 +5,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ClearOnShutdown.h" -#include "mozilla/dom/ipc/ProcessPriorityManager.h" +#include "mozilla/ProcessPriorityManager.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/TabChild.h" #include "mozilla/Hal.h" diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index 3802a9b2a0fe..e4f7a13c5f14 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -35,6 +35,7 @@ EXPORTS.mozilla.dom += [ EXPORTS.mozilla += [ 'AppProcessChecker.h', + 'PreallocatedProcessManager.h', 'ProcessPriorityManager.h', ]