Bug 870181 - Part 2: Lower CPU priority for non-high priority processes when there's an incoming call. r=bent
authorJustin Lebar <justin.lebar@gmail.com>
Thu, 09 May 2013 16:27:06 -0400
changeset 142430 6c53b7ecb2ee37e14b7d6a250af576e11deb23fe
parent 142429 2c50a1950fd8abe2bba80d273695a520d8d56a08
child 142431 1fb1b2e7660f77b389acac98d69cbe862380ad5d
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs870181
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 870181 - Part 2: Lower CPU priority for non-high priority processes when there's an incoming call. r=bent
dom/ipc/ProcessPriorityManager.cpp
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -138,16 +138,31 @@ public:
 
   /**
    * If a magic testing-only pref is set, notify the observer service on the
    * given topic with the given data.  This is used for testing
    */
   void FireTestOnlyObserverNotification(const char* aTopic,
                                         const nsACString& aData = EmptyCString());
 
+  /**
+   * Does some process, other than the one handled by aParticularManager, have
+   * priority FOREGROUND_HIGH?
+   */
+  bool OtherProcessHasHighPriority(
+    ParticularProcessPriorityManager* aParticularManager);
+
+  /**
+   * This must be called by a ParticularProcessPriorityManager when it changes
+   * its priority.
+   */
+  void NotifyProcessPriorityChanged(
+    ParticularProcessPriorityManager* aParticularManager,
+    hal::ProcessPriority aOldPriority);
+
 private:
   static bool sPrefListenersRegistered;
   static bool sInitialized;
   static StaticRefPtr<ProcessPriorityManagerImpl> sSingleton;
 
   static int PrefChangedCallback(const char* aPref, void* aClosure);
 
   ProcessPriorityManagerImpl();
@@ -159,16 +174,18 @@ private:
   already_AddRefed<ParticularProcessPriorityManager>
   GetParticularProcessPriorityManager(ContentParent* aContentParent);
 
   void ObserveContentParentCreated(nsISupports* aContentParent);
   void ObserveContentParentDestroyed(nsISupports* aSubject);
 
   nsDataHashtable<nsUint64HashKey, nsRefPtr<ParticularProcessPriorityManager> >
     mParticularManagers;
+
+  nsTHashtable<nsUint64HashKey> mHighPriorityChildIDs;
 };
 
 /**
  * This singleton class implements the parts of the process priority manager
  * that are available from all processes.
  */
 class ProcessPriorityManagerChild MOZ_FINAL
   : public nsIObserver
@@ -197,16 +214,17 @@ private:
 /**
  * This class manages the priority of one particular process.  It is
  * main-process only.
  */
 class ParticularProcessPriorityManager MOZ_FINAL
   : public WakeLockObserver
   , public nsIObserver
   , public nsITimerCallback
+  , public nsSupportsWeakReference
 {
 public:
   ParticularProcessPriorityManager(ContentParent* aContentParent);
   ~ParticularProcessPriorityManager();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITIMERCALLBACK
@@ -232,37 +250,49 @@ public:
   bool IsExpectingSystemMessage();
 
   void OnAudioChannelProcessChanged(nsISupports* aSubject);
   void OnRemoteBrowserFrameShown(nsISupports* aSubject);
   void OnTabParentDestroyed(nsISupports* aSubject);
   void OnFrameloaderVisibleChanged(nsISupports* aSubject);
   void OnChannelConnected(nsISupports* aSubject);
 
+  ProcessPriority CurrentPriority();
   ProcessPriority ComputePriority();
+  ProcessCPUPriority ComputeCPUPriority();
+
   void ScheduleResetPriority(const char* aTimeoutPref);
   void ResetPriority();
   void ResetPriorityNow();
+  void ResetCPUPriorityNow();
 
+  /**
+   * This overload is equivalent to SetPriorityNow(aPriority,
+   * ComputeCPUPriority()).
+   */
   void SetPriorityNow(ProcessPriority aPriority);
 
+  void SetPriorityNow(ProcessPriority aPriority,
+                      ProcessCPUPriority aCPUPriority);
+
   void ShutDown();
 
 private:
   void FireTestOnlyObserverNotification(
     const char* aTopic,
     const nsACString& aData = EmptyCString());
 
   void FireTestOnlyObserverNotification(
     const char* aTopic,
     const char* aData = nullptr);
 
   ContentParent* mContentParent;
   uint64_t mChildID;
   ProcessPriority mPriority;
+  ProcessCPUPriority mCPUPriority;
   bool mHoldsCPUWakeLock;
   bool mHoldsHighPriorityWakeLock;
 
   /**
    * Used to implement NameWithComma().
    */
   nsAutoCString mNameWithComma;
 
@@ -337,27 +367,29 @@ ProcessPriorityManagerImpl::GetSingleton
 
   return sSingleton;
 }
 
 ProcessPriorityManagerImpl::ProcessPriorityManagerImpl()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   mParticularManagers.Init();
+  mHighPriorityChildIDs.Init();
 }
 
 void
 ProcessPriorityManagerImpl::Init()
 {
   LOG("Starting up.  This is the master process.");
 
   // The master process's priority never changes; set it here and then forget
   // about it.  We'll manage only subprocesses' priorities using the process
   // priority manager.
-  hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER);
+  hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER,
+                          PROCESS_CPU_PRIORITY_NORMAL);
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->AddObserver(this, "ipc:content-created", /* ownsWeak */ false);
     os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ false);
   }
 }
 
@@ -413,16 +445,28 @@ ProcessPriorityManagerImpl::ObserveConte
 {
   // Do nothing; it's sufficient to get the PPPM.  But assign to nsRefPtr so we
   // don't leak the already_AddRefed object.
   nsCOMPtr<nsIObserver> cp = do_QueryInterface(aContentParent);
   nsRefPtr<ParticularProcessPriorityManager> pppm =
     GetParticularProcessPriorityManager(static_cast<ContentParent*>(cp.get()));
 }
 
+static PLDHashOperator
+EnumerateParticularProcessPriorityManagers(
+  const uint64_t& aKey,
+  nsRefPtr<ParticularProcessPriorityManager> aValue,
+  void* aUserData)
+{
+  nsTArray<nsRefPtr<ParticularProcessPriorityManager> >* aArray =
+    static_cast<nsTArray<nsRefPtr<ParticularProcessPriorityManager> >*>(aUserData);
+  aArray->AppendElement(aValue);
+  return PL_DHASH_NEXT;
+}
+
 void
 ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject)
 {
   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);
@@ -431,45 +475,105 @@ ProcessPriorityManagerImpl::ObserveConte
   nsRefPtr<ParticularProcessPriorityManager> pppm;
   mParticularManagers.Get(childID, &pppm);
   MOZ_ASSERT(pppm);
   if (pppm) {
     pppm->ShutDown();
   }
 
   mParticularManagers.Remove(childID);
+
+  if (mHighPriorityChildIDs.Contains(childID)) {
+    mHighPriorityChildIDs.RemoveEntry(childID);
+
+    // We just lost a high-priority process; reset everyone's CPU priorities.
+    nsTArray<nsRefPtr<ParticularProcessPriorityManager> > pppms;
+    mParticularManagers.EnumerateRead(
+      &EnumerateParticularProcessPriorityManagers,
+      &pppms);
+
+    for (uint32_t i = 0; i < pppms.Length(); i++) {
+      pppms[i]->ResetCPUPriorityNow();
+    }
+  }
+}
+
+bool
+ProcessPriorityManagerImpl::OtherProcessHasHighPriority(
+  ParticularProcessPriorityManager* aParticularManager)
+{
+  if (mHighPriorityChildIDs.Contains(aParticularManager->ChildID())) {
+    return mHighPriorityChildIDs.Count() > 1;
+  }
+  return mHighPriorityChildIDs.Count() > 0;
 }
 
-NS_IMPL_ISUPPORTS2(ParticularProcessPriorityManager,
+void
+ProcessPriorityManagerImpl::NotifyProcessPriorityChanged(
+  ParticularProcessPriorityManager* aParticularManager,
+  ProcessPriority aOldPriority)
+{
+  // This priority change can only affect other processes' priorities if we're
+  // changing to/from FOREGROUND_HIGH.
+
+  if (aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH &&
+      aParticularManager->CurrentPriority() <
+        PROCESS_PRIORITY_FOREGROUND_HIGH) {
+
+    return;
+  }
+
+  if (aParticularManager->CurrentPriority() >=
+      PROCESS_PRIORITY_FOREGROUND_HIGH) {
+    mHighPriorityChildIDs.PutEntry(aParticularManager->ChildID());
+  } else {
+    mHighPriorityChildIDs.RemoveEntry(aParticularManager->ChildID());
+  }
+
+  nsTArray<nsRefPtr<ParticularProcessPriorityManager> > pppms;
+  mParticularManagers.EnumerateRead(
+    &EnumerateParticularProcessPriorityManagers,
+    &pppms);
+
+  for (uint32_t i = 0; i < pppms.Length(); i++) {
+    if (pppms[i] != aParticularManager) {
+      pppms[i]->ResetCPUPriorityNow();
+    }
+  }
+}
+
+NS_IMPL_ISUPPORTS3(ParticularProcessPriorityManager,
                    nsIObserver,
-                   nsITimerCallback);
+                   nsITimerCallback,
+                   nsISupportsWeakReference);
 
 ParticularProcessPriorityManager::ParticularProcessPriorityManager(
   ContentParent* aContentParent)
   : mContentParent(aContentParent)
   , mChildID(aContentParent->ChildID())
   , mPriority(PROCESS_PRIORITY_UNKNOWN)
+  , mCPUPriority(PROCESS_CPU_PRIORITY_NORMAL)
   , mHoldsCPUWakeLock(false)
   , mHoldsHighPriorityWakeLock(false)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   LOGP("Creating ParticularProcessPriorityManager.");
 }
 
 void
 ParticularProcessPriorityManager::Init()
 {
   RegisterWakeLockObserver(this);
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
-    os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ false);
-    os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ false);
-    os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ false);
-    os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ false);
+    os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ true);
+    os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ true);
+    os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true);
+    os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ true);
   }
 
   // This process may already hold the CPU lock; for example, our parent may
   // have acquired it on our behalf.
   WakeLockInformation info1, info2;
   GetWakeLockInfo(NS_LITERAL_STRING("cpu"), &info1);
   mHoldsCPUWakeLock = info1.lockingProcesses().Contains(ChildID());
 
@@ -734,16 +838,22 @@ ParticularProcessPriorityManager::IsExpe
       return true;
     }
   }
 
   return false;
 }
 
 ProcessPriority
+ParticularProcessPriorityManager::CurrentPriority()
+{
+  return mPriority;
+}
+
+ProcessPriority
 ParticularProcessPriorityManager::ComputePriority()
 {
   if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) &&
       HasAppType("critical")) {
     return PROCESS_PRIORITY_FOREGROUND_HIGH;
   }
 
   bool isVisible = false;
@@ -770,52 +880,91 @@ ParticularProcessPriorityManager::Comput
     return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE;
   }
 
   return HasAppType("homescreen") ?
          PROCESS_PRIORITY_BACKGROUND_HOMESCREEN :
          PROCESS_PRIORITY_BACKGROUND;
 }
 
+ProcessCPUPriority
+ParticularProcessPriorityManager::ComputeCPUPriority()
+{
+  if (mPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
+    return PROCESS_CPU_PRIORITY_NORMAL;
+  }
+
+  return ProcessPriorityManagerImpl::GetSingleton()->
+    OtherProcessHasHighPriority(this) ?
+    PROCESS_CPU_PRIORITY_LOW :
+    PROCESS_CPU_PRIORITY_NORMAL;
+}
+
+void
+ParticularProcessPriorityManager::ResetCPUPriorityNow()
+{
+  SetPriorityNow(mPriority);
+}
+
 void
 ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority)
 {
+  SetPriorityNow(aPriority, ComputeCPUPriority());
+}
+
+void
+ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
+                                                 ProcessCPUPriority aCPUPriority)
+{
   if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
     MOZ_ASSERT(false);
     return;
   }
 
-  if (mPriority == aPriority) {
+  if (!mContentParent ||
+      !ProcessPriorityManagerImpl::PrefsEnabled() ||
+      (mPriority == aPriority && mCPUPriority == aCPUPriority)) {
     return;
   }
 
   // If the prefs were disabled after this ParticularProcessPriorityManager was
   // created, we can at least avoid any further calls to
   // hal::SetProcessPriority.  Supporting dynamic enabling/disabling of the
   // ProcessPriorityManager is mostly for testing.
   if (!ProcessPriorityManagerImpl::PrefsEnabled()) {
     return;
   }
 
   LOGP("Changing priority from %s to %s.",
-       ProcessPriorityToString(mPriority),
-       ProcessPriorityToString(aPriority));
+       ProcessPriorityToString(mPriority, mCPUPriority),
+       ProcessPriorityToString(aPriority, aCPUPriority));
+
+  ProcessPriority oldPriority = mPriority;
+
   mPriority = aPriority;
-  hal::SetProcessPriority(Pid(), mPriority);
+  mCPUPriority = aCPUPriority;
+  hal::SetProcessPriority(Pid(), mPriority, mCPUPriority);
 
-  unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
+  if (oldPriority != mPriority) {
+    unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
+  }
 
   if (aPriority >= PROCESS_PRIORITY_FOREGROUND) {
     unused << mContentParent->SendCancelMinimizeMemoryUsage();
   } else {
     unused << mContentParent->SendMinimizeMemoryUsage();
   }
 
   FireTestOnlyObserverNotification("process-priority-set",
-                                   ProcessPriorityToString(mPriority));
+    ProcessPriorityToString(mPriority, mCPUPriority));
+
+  if (oldPriority != mPriority) {
+    ProcessPriorityManagerImpl::GetSingleton()->
+      NotifyProcessPriorityChanged(this, oldPriority);
+  }
 }
 
 void
 ParticularProcessPriorityManager::ShutDown()
 {
   MOZ_ASSERT(mContentParent);
 
   UnregisterWakeLockObserver(this);