Bug 844323 - Part 1: Move process preallocation logic out of ContentParent and into a new file, PreallocatedProcessManager. r=bent

Also make the PreallocatedProcessManager respond to pref changes.  This allows us to write a test involving the preallocated process.  Making this change was the main motivation for this patch; I moved the logic out of ContentParent because with the pref-watching code it was becoming unweildy.
This commit is contained in:
Justin Lebar 2013-04-25 20:53:26 -04:00
Родитель d5a4a10ea7
Коммит c73b99561e
7 изменённых файлов: 359 добавлений и 101 удалений

Просмотреть файл

@ -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<nsStringHashKey, ContentParent*>* ContentParent::sAppContentParents;
nsTArray<ContentParent*>* ContentParent::sNonAppContentParents;
nsTArray<ContentParent*>* ContentParent::sPrivateContent;
LinkedList<ContentParent> 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<ContentParent> 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>
ContentParent::PreallocateAppProcess()
{
MOZ_ASSERT(!sPreallocatedAppProcess);
if (sPreallocateAppProcessTask) {
// We were called directly while a delayed task was scheduled.
sPreallocateAppProcessTask->Cancel();
sPreallocateAppProcessTask = nullptr;
}
sPreallocatedAppProcess =
nsRefPtr<ContentParent> 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<ContentParent>
@ -266,9 +236,7 @@ ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs,
ProcessPriority aInitialPriority)
{
nsRefPtr<ContentParent> process = sPreallocatedAppProcess.get();
sPreallocatedAppProcess = nullptr;
nsRefPtr<ContentParent> 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<TabParent*>(browser);
}
static PLDHashOperator
AppendToTArray(const nsAString& aKey, ContentParent* aValue, void* aArray)
{
nsTArray<ContentParent*> *array =
static_cast<nsTArray<ContentParent*>*>(aArray);
array->AppendElement(aValue);
return PL_DHASH_NEXT;
}
void
ContentParent::GetAll(nsTArray<ContentParent*>& 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;
}

Просмотреть файл

@ -58,6 +58,7 @@ class ContentParent : public PContentParent
, public nsIThreadObserver
, public nsIDOMGeoPositionCallback
, public mozilla::dom::ipc::MessageManagerCallback
, public mozilla::LinkedListElement<ContentParent>
{
typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
@ -84,6 +85,11 @@ public:
static already_AddRefed<ContentParent>
GetNewOrUsed(bool aForBrowserElement = false);
/**
* Create a subprocess suitable for use as a preallocated app process.
*/
static already_AddRefed<ContentParent> 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<nsStringHashKey, ContentParent*> *sAppContentParents;
static nsTArray<ContentParent*>* sNonAppContentParents;
static nsTArray<ContentParent*>* sPrivateContent;
static LinkedList<ContentParent> sContentParents;
static void JoinProcessesIOThread(const nsTArray<ContentParent*>* 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;

Просмотреть файл

@ -24,6 +24,7 @@ CPPSRCS = \
CrashReporterParent.cpp \
CrashReporterChild.cpp \
PermissionMessageUtils.cpp \
PreallocatedProcessManager.cpp \
ProcessPriorityManager.cpp \
StructuredCloneUtils.cpp \
TabParent.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<ContentParent> Take();
private:
static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
PreallocatedProcessManagerImpl();
DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
void Init();
void RereadPrefs();
void Enable();
void Disable();
void ObserveProcessShutdown(nsISupports* aSubject);
bool mEnabled;
nsRefPtr<ContentParent> mPreallocatedAppProcess;
};
/* static */ StaticRefPtr<PreallocatedProcessManagerImpl>
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<nsIObserverService> 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<ContentParent>
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<nsIPropertyBag2> 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<ContentParent>
PreallocatedProcessManager::Take()
{
return PreallocatedProcessManagerImpl::Singleton()->Take();
}
} // namespace mozilla

Просмотреть файл

@ -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<ContentParent> Take();
private:
PreallocatedProcessManager();
DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager);
};
} // namespace mozilla
#endif // defined mozilla_PreallocatedProcessManager_h

Просмотреть файл

@ -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"

Просмотреть файл

@ -35,6 +35,7 @@ EXPORTS.mozilla.dom += [
EXPORTS.mozilla += [
'AppProcessChecker.h',
'PreallocatedProcessManager.h',
'ProcessPriorityManager.h',
]