Bug 834059 - Send processses into the foreground when they first launch, so they don't get killed quite so quickly. r=cjones

This patch also sends the preallocated process into the background immediately after it's launched.
This commit is contained in:
Justin Lebar 2013-01-26 16:14:01 -05:00
Родитель ea653f66d2
Коммит 0f0d04a754
7 изменённых файлов: 220 добавлений и 113 удалений

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

@ -543,7 +543,8 @@ pref("ui.showHideScrollbars", 1);
// documents a 1s grace period before they're eligible to be marked as
// background.
pref("dom.ipc.processPriorityManager.enabled", true);
pref("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
pref("dom.ipc.processPriorityManager.temporaryPriorityMS", 5000);
// Kernel parameters for how processes are killed on low-memory.
pref("gonk.systemMemoryPressureRecoveryPollMS", 5000);

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

@ -102,12 +102,14 @@
#include "nsIPrincipal.h"
#include "nsDeviceStorage.h"
#include "AudioChannelService.h"
#include "ProcessPriorityManager.h"
using namespace base;
using namespace mozilla;
using namespace mozilla::docshell;
using namespace mozilla::dom::bluetooth;
using namespace mozilla::dom::devicestorage;
using namespace mozilla::dom::ipc;
using namespace mozilla::dom::sms;
using namespace mozilla::dom::indexedDB;
using namespace mozilla::hal_sandbox;
@ -289,13 +291,8 @@ ContentChild::Init(MessageLoop* aIOLoop,
#endif
#endif
bool startBackground = true;
SendGetProcessAttributes(&mID, &startBackground,
&mIsForApp, &mIsForBrowser);
hal::SetProcessPriority(
GetCurrentProcId(),
startBackground ? hal::PROCESS_PRIORITY_BACKGROUND:
hal::PROCESS_PRIORITY_FOREGROUND);
SendGetProcessAttributes(&mID, &mIsForApp, &mIsForBrowser);
if (mIsForApp && !mIsForBrowser) {
SetProcessName(NS_LITERAL_STRING("(Preallocated app)"));
} else {
@ -529,12 +526,19 @@ PBrowserChild*
ContentChild::AllocPBrowser(const IPCTabContext& aContext,
const uint32_t& aChromeFlags)
{
static bool firstIdleTaskPosted = false;
if (!firstIdleTaskPosted) {
static bool hasRunOnce = false;
if (!hasRunOnce) {
hasRunOnce = true;
MOZ_ASSERT(!sFirstIdleTask);
sFirstIdleTask = NewRunnableFunction(FirstIdle);
MessageLoop::current()->PostIdleTask(FROM_HERE, sFirstIdleTask);
firstIdleTaskPosted = true;
// If we are the preallocated process transforming into an app process,
// we'll have background priority at this point. Give ourselves a
// priority boost for a few seconds, so we don't get killed while we're
// loading our first TabChild.
TemporarilySetProcessPriorityToForeground();
}
// We'll happily accept any kind of IPCTabContext here; we don't need to

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

@ -32,6 +32,7 @@
#include "mozilla/dom/bluetooth/PBluetoothParent.h"
#include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
#include "SmsParent.h"
#include "mozilla/Hal.h"
#include "mozilla/hal_sandbox/PHalParent.h"
#include "mozilla/ipc/TestShellParent.h"
#include "mozilla/layers/CompositorParent.h"
@ -119,7 +120,7 @@ using namespace mozilla::dom::bluetooth;
using namespace mozilla::dom::devicestorage;
using namespace mozilla::dom::sms;
using namespace mozilla::dom::indexedDB;
using namespace mozilla::hal_sandbox;
using namespace mozilla::hal;
using namespace mozilla::ipc;
using namespace mozilla::layers;
using namespace mozilla::net;
@ -870,14 +871,23 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL,
mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content,
aOSPrivileges);
bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
if (useOffMainThreadCompositing) {
// We need the subprocess's ProcessHandle to create the
// PCompositor channel below. Block just until we have that.
mSubprocess->LaunchAndWaitForProcessHandle();
} else {
mSubprocess->AsyncLaunch();
mSubprocess->LaunchAndWaitForProcessHandle();
// Set the subprocess's priority (bg if we're a preallocated process, fg
// otherwise). We do this first because we're likely /lowering/ its CPU and
// memory priority, which it has inherited from this process.
if (Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
ProcessPriority priority;
if (aAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL) {
priority = PROCESS_PRIORITY_BACKGROUND;
} else {
priority = PROCESS_PRIORITY_FOREGROUND;
}
SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
priority);
}
Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
// NB: internally, this will send an IPC message to the child
@ -888,6 +898,7 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL,
// PBrowsers are created, because they rely on the Compositor
// already being around. (Creation is async, so can't happen
// on demand.)
bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
if (useOffMainThreadCompositing) {
DebugOnly<bool> opened = PCompositor::Open(this);
MOZ_ASSERT(opened);
@ -1359,12 +1370,10 @@ ContentParent::AllocPImageBridge(mozilla::ipc::Transport* aTransport,
}
bool
ContentParent::RecvGetProcessAttributes(uint64_t* aId, bool* aStartBackground,
ContentParent::RecvGetProcessAttributes(uint64_t* aId,
bool* aIsForApp, bool* aIsForBrowser)
{
*aId = mChildID = gContentChildID++;
*aStartBackground =
(mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL);
*aIsForApp = IsForApp();
*aIsForBrowser = mIsForBrowser;
@ -1650,14 +1659,14 @@ ContentParent::DeallocPCrashReporter(PCrashReporterParent* crashreporter)
return true;
}
PHalParent*
hal_sandbox::PHalParent*
ContentParent::AllocPHal()
{
return CreateHalParent();
return hal_sandbox::CreateHalParent();
}
bool
ContentParent::DeallocPHal(PHalParent* aHal)
ContentParent::DeallocPHal(hal_sandbox::PHalParent* aHal)
{
delete aHal;
return true;

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

@ -203,7 +203,6 @@ private:
base::ProcessId aOtherProcess) MOZ_OVERRIDE;
virtual bool RecvGetProcessAttributes(uint64_t* aId,
bool* aStartBackground,
bool* aIsForApp,
bool* aIsForBrowser) MOZ_OVERRIDE;
virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline) MOZ_OVERRIDE;

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

@ -343,8 +343,7 @@ parent:
* !isForBrowser|, we're probably loading <xul:browser remote>.
*/
sync GetProcessAttributes()
returns (uint64_t id, bool startBackground,
bool isForApp, bool isForBrowser);
returns (uint64_t id, bool isForApp, bool isForBrowser);
sync GetXPCOMProcessAttributes()
returns (bool isOffline);

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

@ -15,6 +15,7 @@
#include "mozilla/TimeStamp.h"
#include "AudioChannelService.h"
#include "prlog.h"
#include "nsPrintfCString.h"
#include "nsWeakPtr.h"
#include "nsXULAppAPI.h"
#include "nsIInterfaceRequestorUtils.h"
@ -139,43 +140,98 @@ IsBackgroundPriority(ProcessPriority aPriority)
class ProcessPriorityManager MOZ_FINAL
: public nsIObserver
, public nsIDOMEventListener
, public nsITimerCallback
{
public:
ProcessPriorityManager();
void Init();
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSIOBSERVER
NS_DECL_NSIDOMEVENTLISTENER
ProcessPriority GetPriority() const { return mProcessPriority; }
private:
void SetPriority(ProcessPriority aPriority);
void OnAudioChannelAgentChanged();
void OnContentDocumentGlobalCreated(nsISupports* aOuterWindow);
void OnInnerWindowDestroyed();
void OnGracePeriodTimerFired();
void RecomputeNumVisibleWindows();
/**
* If this process is not already in the foreground, move it into the
* foreground and set a timer to call ResetPriorityNow() in a few seconds.
*/
void TemporarilySetIsForeground();
// mProcessPriority tracks the priority we've given this process in hal,
// except that, when the grace period timer is active, mProcessPriority ==
// BACKGROUND or HOMESCREEN_BACKGROUND even though hal still thinks we're a
// foreground process.
/**
* Recompute this process's priority and apply it, potentially after a brief
* delay.
*
* If the new priority is FOREGROUND, it takes effect immediately.
*
* If the new priority is a BACKGROUND* priority and this process's priority
* is currently a BACKGROUND* priority, the new priority takes effect
* immediately.
*
* But if the new priority is a BACKGROUND* priority and this process is not
* currently in the background, we schedule a timer and run
* ResetPriorityNow() after a short period of time.
*/
void ResetPriority();
/**
* Recompute this process's priority and apply it immediately.
*/
void ResetPriorityNow();
private:
void OnContentDocumentGlobalCreated(nsISupports* aOuterWindow);
/**
* Compute whether this process is in the foreground and return the result.
*/
bool ComputeIsInForeground();
/**
* Set this process's priority to FOREGROUND immediately.
*/
void SetIsForeground();
/**
* Set this process's priority to the appropriate BACKGROUND* priority
* immediately.
*/
void SetIsBackgroundNow();
/**
* If mResetPriorityTimer is null (i.e., not running), create a timer and set
* it to invoke ResetPriorityNow() after
* dom.ipc.processPriorityManager.aTimeoutPref ms.
*/
void
ScheduleResetPriority(const char* aTimeoutPref);
// mProcessPriority tracks the priority we've given this process in hal.
ProcessPriority mProcessPriority;
nsTArray<nsWeakPtr> mWindows;
nsCOMPtr<nsITimer> mGracePeriodTimer;
// When this timer expires, we set mResetPriorityTimer to null and run
// ResetPriorityNow().
nsCOMPtr<nsITimer> mResetPriorityTimer;
nsWeakPtr mMemoryMinimizerRunnable;
TimeStamp mStartupTime;
};
NS_IMPL_ISUPPORTS2(ProcessPriorityManager, nsIObserver, nsIDOMEventListener)
ProcessPriorityManager::ProcessPriorityManager()
: mProcessPriority(PROCESS_PRIORITY_FOREGROUND)
, mStartupTime(TimeStamp::Now())
: mProcessPriority(ProcessPriority(-1))
{
// When our parent process forked us, it set our priority either to
// FOREGROUND (if our parent launched this process to meet an immediate need)
// or one of the BACKGROUND priorities (if our parent launched this process
// to meet a future need).
//
// We don't know which situation we're in, so we set mProcessPriority to -1
// so that the next time ResetPriorityNow is run, we'll definitely call into
// hal and set our priority.
}
void
@ -189,8 +245,6 @@ ProcessPriorityManager::Init()
os->AddObserver(this, "content-document-global-created", /* ownsWeak = */ false);
os->AddObserver(this, "inner-window-destroyed", /* ownsWeak = */ false);
os->AddObserver(this, "audio-channel-agent-changed", /* ownsWeak = */ false);
SetPriority(PROCESS_PRIORITY_FOREGROUND);
}
NS_IMETHODIMP
@ -201,12 +255,9 @@ ProcessPriorityManager::Observe(
{
if (!strcmp(aTopic, "content-document-global-created")) {
OnContentDocumentGlobalCreated(aSubject);
} else if (!strcmp(aTopic, "inner-window-destroyed")) {
OnInnerWindowDestroyed();
} else if (!strcmp(aTopic, "timer-callback")) {
OnGracePeriodTimerFired();
} else if (!strcmp(aTopic, "audio-channel-agent-changed")) {
OnAudioChannelAgentChanged();
} else if (!strcmp(aTopic, "inner-window-destroyed") ||
!strcmp(aTopic, "audio-channel-agent-changed")) {
ResetPriority();
} else {
MOZ_ASSERT(false);
}
@ -218,22 +269,15 @@ ProcessPriorityManager::HandleEvent(
nsIDOMEvent* aEvent)
{
LOG("Got visibilitychange.");
RecomputeNumVisibleWindows();
ResetPriority();
return NS_OK;
}
void
ProcessPriorityManager::OnAudioChannelAgentChanged()
{
if (IsBackgroundPriority(mProcessPriority)) {
SetPriority(GetBackgroundPriority());
}
}
void
ProcessPriorityManager::OnContentDocumentGlobalCreated(
nsISupports* aOuterWindow)
{
LOG("DocumentGlobalCreated");
// Get the inner window (the topic of content-document-global-created is
// the /outer/ window!).
nsCOMPtr<nsPIDOMWindow> outerWindow = do_QueryInterface(aOuterWindow);
@ -265,17 +309,35 @@ ProcessPriorityManager::OnContentDocumentGlobalCreated(
/* wantsUntrusted = */ false);
mWindows.AppendElement(weakWin);
RecomputeNumVisibleWindows();
ResetPriority();
}
void
ProcessPriorityManager::OnInnerWindowDestroyed()
ProcessPriorityManager::ResetPriority()
{
RecomputeNumVisibleWindows();
if (ComputeIsInForeground()) {
SetIsForeground();
} else if (IsBackgroundPriority(mProcessPriority)) {
// If we're already in the background, recompute our background priority
// and set it immediately.
SetIsBackgroundNow();
} else {
ScheduleResetPriority("backgroundGracePeriodMS");
}
}
void
ProcessPriorityManager::RecomputeNumVisibleWindows()
ProcessPriorityManager::ResetPriorityNow()
{
if (ComputeIsInForeground()) {
SetIsForeground();
} else {
SetIsBackgroundNow();
}
}
bool
ProcessPriorityManager::ComputeIsInForeground()
{
// We could try to be clever and count the number of visible windows, instead
// of iterating over mWindows every time one window's visibility state changes.
@ -314,68 +376,38 @@ ProcessPriorityManager::RecomputeNumVisibleWindows()
// but then we might not clean up all the weak refs.
}
SetPriority(allHidden ?
GetBackgroundPriority() :
PROCESS_PRIORITY_FOREGROUND);
return !allHidden;
}
void
ProcessPriorityManager::SetPriority(ProcessPriority aPriority)
ProcessPriorityManager::SetIsForeground()
{
if (aPriority == mProcessPriority) {
if (mProcessPriority == PROCESS_PRIORITY_FOREGROUND) {
return;
}
if (IsBackgroundPriority(aPriority)) {
// If this is a foreground --> background transition, give ourselves a
// grace period before informing hal.
uint32_t gracePeriodMS = Preferences::GetUint("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
if (mGracePeriodTimer) {
LOG("Grace period timer already active.");
return;
}
LOG("Initializing grace period timer.");
mProcessPriority = aPriority;
mGracePeriodTimer = do_CreateInstance("@mozilla.org/timer;1");
mGracePeriodTimer->Init(this, gracePeriodMS, nsITimer::TYPE_ONE_SHOT);
} else if (aPriority == PROCESS_PRIORITY_FOREGROUND) {
// If this is a background --> foreground transition, do it immediately, and
// cancel the outstanding grace period timer, if there is one.
if (mGracePeriodTimer) {
mGracePeriodTimer->Cancel();
mGracePeriodTimer = nullptr;
}
// Cancel the memory minimization procedure we might have started.
nsCOMPtr<nsICancelableRunnable> runnable =
do_QueryReferent(mMemoryMinimizerRunnable);
if (runnable) {
runnable->Cancel();
}
LOG("Setting priority to %d.", aPriority);
mProcessPriority = aPriority;
hal::SetProcessPriority(getpid(), aPriority);
} else {
MOZ_ASSERT(false);
// Cancel the memory minimization procedure we might have started.
nsCOMPtr<nsICancelableRunnable> runnable =
do_QueryReferent(mMemoryMinimizerRunnable);
if (runnable) {
runnable->Cancel();
}
LOG("Setting priority to FOREGROUND.");
mProcessPriority = PROCESS_PRIORITY_FOREGROUND;
hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_FOREGROUND);
}
void
ProcessPriorityManager::OnGracePeriodTimerFired()
ProcessPriorityManager::SetIsBackgroundNow()
{
LOG("Grace period timer fired; setting priority to %d.",
mProcessPriority);
ProcessPriority backgroundPriority = GetBackgroundPriority();
if (mProcessPriority == backgroundPriority) {
return;
}
// mProcessPriority should already be one of the BACKGROUND values: We set it
// in SetPriority(BACKGROUND), and we canceled this timer if there was an
// intervening SetPriority(FOREGROUND) call.
MOZ_ASSERT(IsBackgroundPriority(mProcessPriority));
mGracePeriodTimer = nullptr;
mProcessPriority = backgroundPriority;
LOG("Setting priority to BACKGROUND (type %d)", mProcessPriority);
hal::SetProcessPriority(getpid(), mProcessPriority);
// We're in the background; dump as much memory as we can.
@ -396,6 +428,46 @@ ProcessPriorityManager::OnGracePeriodTimerFired()
}
}
void
ProcessPriorityManager::ScheduleResetPriority(const char* aTimeoutPref)
{
if (mResetPriorityTimer) {
// The timer is already running.
return;
}
uint32_t timeout = Preferences::GetUint(
nsPrintfCString("dom.ipc.processPriorityManager.%s", aTimeoutPref).get());
LOG("Scheduling reset timer to fire in %dms.", timeout);
mResetPriorityTimer = do_CreateInstance("@mozilla.org/timer;1");
mResetPriorityTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
}
NS_IMETHODIMP
ProcessPriorityManager::Notify(nsITimer* aTimer)
{
LOG("Reset priority timer callback; about to ResetPriorityNow.");
ResetPriorityNow();
mResetPriorityTimer = nullptr;
return NS_OK;
}
void
ProcessPriorityManager::TemporarilySetIsForeground()
{
LOG("TemporarilySetIsForeground");
SetIsForeground();
// Each call to TemporarilySetIsForeground guarantees us temporaryPriorityMS
// in the foreground. So cancel our timer if it's running (which is due to a
// previous call to either TemporarilySetIsForeground() or ResetPriority()).
if (mResetPriorityTimer) {
mResetPriorityTimer->Cancel();
mResetPriorityTimer = nullptr;
}
ScheduleResetPriority("temporaryPriorityMS");
}
} // anonymous namespace
void
@ -408,6 +480,7 @@ InitProcessPriorityManager()
// If IPC tabs aren't enabled at startup, don't bother with any of this.
if (!Preferences::GetBool("dom.ipc.processPriorityManager.enabled") ||
Preferences::GetBool("dom.ipc.tabs.disabled")) {
LOG("InitProcessPriorityManager bailing due to prefs.");
return;
}
@ -439,6 +512,17 @@ CurrentProcessIsForeground()
return sManager->GetPriority() >= PROCESS_PRIORITY_FOREGROUND;
}
void
TemporarilySetProcessPriorityToForeground()
{
if (sManager) {
sManager->TemporarilySetIsForeground();
} else {
LOG("TemporarilySetProcessPriorityToForeground called before "
"InitProcessPriorityManager. Bailing.");
}
}
} // namespace ipc
} // namespace dom
} // namespace mozilla

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

@ -33,6 +33,17 @@ void InitProcessPriorityManager();
*/
bool CurrentProcessIsForeground();
/**
* If this process is in the background, temporarily boost its priority to the
* foreground. This priority boost will expire after a few seconds
* (dom.ipc.processPriorityManager.temporaryPriorityMS).
*
* You might want to call this function when a process starts loading some
* things, but doesn't yet have a foreground window. The hope would be that by
* once the timer here expires, the process will have a foreground window.
*/
void TemporarilySetProcessPriorityToForeground();
} // namespace ipc
} // namespace dom
} // namespace mozilla