Bug 1305091 - Stop using content processes when they are running out of memory, r?mrbkap r?ksteuber data-r?francois draft
authorBenjamin Smedberg <benjamin@smedbergs.us>
Tue, 07 Feb 2017 11:46:44 -0500
changeset 479979 93e7324c08b5654613b7fc79de959e06c445f598
parent 469123 f3d187bd0733b1182dffc97b5dfe623e18f92a44
child 544838 51a409fd0d8b32d9aba128c55b6e8e130675d540
push id44418
push userbsmedberg@mozilla.com
push dateTue, 07 Feb 2017 16:48:58 +0000
reviewersmrbkap, ksteuber
bugs1305091
milestone54.0a1
Bug 1305091 - Stop using content processes when they are running out of memory, r?mrbkap r?ksteuber data-r?francois MozReview-Commit-ID: K67de7xbzqk
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
toolkit/components/telemetry/Scalars.yaml
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1247,17 +1247,17 @@ ContentParent::ShutDownMessageManager()
       CHILD_PROCESS_SHUTDOWN_MESSAGE, false,
       nullptr, nullptr, nullptr, nullptr);
 
   mMessageManager->Disconnect();
   mMessageManager = nullptr;
 }
 
 void
-ContentParent::MarkAsDead()
+ContentParent::MarkAsTroubled()
 {
   if (sBrowserContentParents) {
     nsTArray<ContentParent*>* contentParents =
       sBrowserContentParents->Get(mRemoteType);
     if (contentParents) {
       contentParents->RemoveElement(this);
       if (contentParents->IsEmpty()) {
         sBrowserContentParents->Remove(mRemoteType);
@@ -1271,17 +1271,23 @@ ContentParent::MarkAsDead()
 
   if (sPrivateContent) {
     sPrivateContent->RemoveElement(this);
     if (!sPrivateContent->Length()) {
       delete sPrivateContent;
       sPrivateContent = nullptr;
     }
   }
-
+  mIsAvailable = false;
+}
+
+void
+ContentParent::MarkAsDead()
+{
+  MarkAsTroubled();
   mIsAlive = false;
 }
 
 void
 ContentParent::OnChannelError()
 {
   RefPtr<ContentParent> content(this);
   PContentParent::OnChannelError();
@@ -1571,18 +1577,18 @@ ContentParent::ActorDestroy(ActorDestroy
 
 bool
 ContentParent::ShouldKeepProcessAlive() const
 {
   if (!sBrowserContentParents) {
     return false;
   }
 
-  // If we have already been marked as dead, don't prevent shutdown.
-  if (!IsAlive()) {
+  // If we have already been marked as troubled/dead, don't prevent shutdown.
+  if (!IsAvailable()) {
     return false;
   }
 
   auto contentParents = sBrowserContentParents->Get(mRemoteType);
   if (!contentParents) {
     return false;
   }
 
@@ -1716,16 +1722,17 @@ ContentParent::GetTestShellSingleton()
 
 void
 ContentParent::InitializeMembers()
 {
   mSubprocess = nullptr;
   mChildID = gContentChildID++;
   mGeolocationWatchID = -1;
   mNumDestroyingTabs = 0;
+  mIsAvailable = true;
   mIsAlive = true;
   mSendPermissionUpdates = false;
   mCalledClose = false;
   mCalledKillHard = false;
   mCreatedPairedMinidumps = false;
   mShutdownPending = false;
   mIPCOpen = true;
   mHangMonitorActor = nullptr;
@@ -4569,19 +4576,24 @@ ContentParent::RecvNotifyPushSubscriptio
   PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers()));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvNotifyLowMemory()
 {
+  MarkAsTroubled();
+
+  Telemetry::ScalarAdd(Telemetry::ScalarID::DOM_CONTENTPROCESS_TROUBLED_DUE_TO_MEMORY, 1);
+
 #ifdef MOZ_CRASHREPORTER
   nsThread::SaveMemoryReportNearOOM(nsThread::ShouldSaveMemoryReport::kForceReport);
 #endif
+
   return IPC_OK();
 }
 
 /* static */ void
 ContentParent::BroadcastBlobURLRegistration(const nsACString& aURI,
                                             BlobImpl* aBlobImpl,
                                             nsIPrincipal* aPrincipal,
                                             ContentParent* aIgnoreThisCP)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -323,16 +323,20 @@ public:
   DeallocateTabId(const TabId& aTabId,
                   const ContentParentId& aCpId,
                   bool aMarkedDestroying);
 
   void ReportChildAlreadyBlocked();
 
   bool RequestRunToCompletion();
 
+  bool IsAvailable() const
+  {
+    return mIsAvailable;
+  }
   bool IsAlive() const override;
 
   virtual bool IsForBrowser() const override
   {
     return mIsForBrowser;
   }
 
   GeckoChildProcessHost* Process() const
@@ -584,16 +588,24 @@ protected:
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
   void OnVarChanged(const GfxVarUpdate& aVar) override;
   void OnCompositorUnexpectedShutdown() override;
 
 private:
+  /**
+   * A map of the remote content process type to a list of content parents
+   * currently available to host *new* tabs/frames of that type.
+   *
+   * If a content process is identified as troubled or dead, it will be
+   * removed from this list, but will still be in the sContentParents list for
+   * the GetAll/GetAllEvenIfDead APIs.
+   */
   static nsClassHashtable<nsStringHashKey, nsTArray<ContentParent*>>* sBrowserContentParents;
   static nsTArray<ContentParent*>* sPrivateContent;
   static StaticAutoPtr<LinkedList<ContentParent> > sContentParents;
 
   static void JoinProcessesIOThread(const nsTArray<ContentParent*>* aProcesses,
                                     Monitor* aMonitor, bool* aDone);
 
   static hal::ProcessPriority GetInitialProcessPriority(Element* aFrameElement);
@@ -664,16 +676,22 @@ private:
 
   /**
    * Decide whether the process should be kept alive even when it would normally
    * be shut down, for example when all its tabs are closed.
    */
   bool ShouldKeepProcessAlive() const;
 
   /**
+   * Mark this ContentParent as "troubled". This means that it is still alive,
+   * but it won't be returned for new tabs in GetNewOrUsedBrowserProcess.
+   */
+  void MarkAsTroubled();
+
+  /**
    * Mark this ContentParent as dead for the purposes of Get*().
    * This method is idempotent.
    */
   void MarkAsDead();
 
   /**
    * How we will shut down this ContentParent and its subprocess.
    */
@@ -1112,20 +1130,22 @@ private:
   // that even content processes that are 100% blocked (say from
   // SIGSTOP), are still killed eventually.  This task enforces that
   // timer.
   nsCOMPtr<nsITimer> mForceKillTimer;
   // How many tabs we're waiting to finish their destruction
   // sequence.  Precisely, how many TabParents have called
   // NotifyTabDestroying() but not called NotifyTabDestroyed().
   int32_t mNumDestroyingTabs;
-  // True only while this is ready to be used to host remote tabs.
-  // This must not be used for new purposes after mIsAlive goes to
-  // false, but some previously scheduled IPC traffic may still pass
-  // through.
+  // True only while this process is in "good health" and may be used for
+  // new remote tabs.
+  bool mIsAvailable;
+  // True only while remote content is being actively used from this process.
+  // After mIsAlive goes to false, some previously scheduled IPC traffic may
+  // still pass through.
   bool mIsAlive;
 
   bool mSendPermissionUpdates;
   bool mIsForBrowser;
 
   // These variables track whether we've called Close() and KillHard() on our
   // channel.
   bool mCalledClose;
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -448,8 +448,25 @@ webrtc.nicer:
     expires: "57"
     kind: uint
     notification_emails:
       - webrtc-ice-telemetry-alerts@mozilla.com
     release_channel_collection: opt-in
     record_in_processes:
       - 'main'
       - 'content'
+
+# The following section contains content process base counters.
+dom.contentprocess:
+  troubled_due_to_memory:
+    bug_numbers:
+      - 1305091
+    description: >
+      The number of content processes that were marked as troubled because
+      it was running low on virtual memory.
+    expires: "58"
+    kind: uint
+    notification_emails:
+      - benjamin@smedbergs.us
+      - mconley@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'