Bug 1154053 - Limit concurrency of e10s memory reporting. r=erahm
authorJed Davis <jld@mozilla.com>
Wed, 06 May 2015 20:51:00 +0200
changeset 460012 5df6a8eccc53b8d27582a43ba3098e41442e89ff
parent 460011 c33b62fc04ac93aa522ac0b55b8273447d6c8c65
child 460013 57f2286a4647f6ea5f4b237cf8a46c19f2d61e8e
child 460150 8635863a78c0f4715266dc0753eb30a8b7bb8611
push id68151
push userkdavis@mozilla.com
push dateFri, 08 May 2015 14:55:48 +0000
treeherdertry@57f2286a4647 [default view] [failures only]
reviewerserahm
bugs1154053, 1151597
milestone40.0a1
Bug 1154053 - Limit concurrency of e10s memory reporting. r=erahm This changes the way nsMemoryReporterManger handles child processes; instead of using an observer message and trying to keep a count of child processes expected to answer, it directly iterates a copy of the list of content processes and explicitly handles children which exit before their reports start. Note that GC/CC logs still run at full concurrency, and that no child reports start until the parent is finished (see bug 1151597) regardless of concurrency limit.
dom/ipc/ContentParent.cpp
modules/libpref/init/all.js
toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
xpcom/base/nsMemoryReporterManager.cpp
xpcom/base/nsMemoryReporterManager.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -419,17 +419,17 @@ public:
 
     virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
     virtual bool RecvReport(const MemoryReport& aReport) override;
     virtual bool Recv__delete__() override;
 
 private:
     const uint32_t mGeneration;
-    // Non-null if we haven't yet called EndChildReport() on it.
+    // Non-null if we haven't yet called EndProcessReport() on it.
     nsRefPtr<nsMemoryReporterManager> mReporterManager;
 
     ContentParent* Owner()
     {
         return static_cast<ContentParent*>(Manager());
     }
 };
 
@@ -459,17 +459,17 @@ MemoryReportRequestParent::Recv__delete_
     // that if possible.)
     return true;
 }
 
 void
 MemoryReportRequestParent::ActorDestroy(ActorDestroyReason aWhy)
 {
     if (mReporterManager) {
-        mReporterManager->EndChildReport(mGeneration, aWhy == Deletion);
+        mReporterManager->EndProcessReport(mGeneration, aWhy == Deletion);
         mReporterManager = nullptr;
     }
 }
 
 MemoryReportRequestParent::~MemoryReportRequestParent()
 {
     MOZ_ASSERT(!mReporterManager);
     MOZ_COUNT_DTOR(MemoryReportRequestParent);
@@ -643,17 +643,16 @@ static uint64_t gContentChildID = 1;
 // Can't be a static constant.
 #define MAGIC_PREALLOCATED_APP_MANIFEST_URL NS_LITERAL_STRING("{{template}}")
 
 static const char* sObserverTopics[] = {
     "xpcom-shutdown",
     "profile-before-change",
     NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
     NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC,
-    "child-memory-reporter-request",
     "memory-pressure",
     "child-gc-request",
     "child-cc-request",
     "child-mmu-request",
     "last-pb-context-exited",
     "file-watcher-update",
 #ifdef MOZ_WIDGET_GONK
     NS_VOLUME_STATE_CHANGED,
@@ -1971,28 +1970,16 @@ ContentParent::ActorDestroy(ActorDestroy
     if (obs) {
         size_t length = ArrayLength(sObserverTopics);
         for (size_t i = 0; i < length; ++i) {
             obs->RemoveObserver(static_cast<nsIObserver*>(this),
                                 sObserverTopics[i]);
         }
     }
 
-    // Tell the memory reporter manager that this ContentParent is going away.
-    nsRefPtr<nsMemoryReporterManager> mgr =
-        nsMemoryReporterManager::GetOrCreate();
-#ifdef MOZ_NUWA_PROCESS
-    bool isMemoryChild = !IsNuwaProcess();
-#else
-    bool isMemoryChild = true;
-#endif
-    if (mgr && isMemoryChild) {
-        mgr->DecrementNumChildProcesses();
-    }
-
     // remove the global remote preferences observers
     Preferences::RemoveObserver(this, "");
 
 #ifdef MOZ_NUWA_PROCESS
     // Remove the pref update requests.
     if (IsNuwaProcess() && sNuwaPrefUpdates) {
         delete sNuwaPrefUpdates;
         sNuwaPrefUpdates = nullptr;
@@ -2256,25 +2243,16 @@ ContentParent::ContentParent(mozIApplica
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     ChildPrivileges privs = aIsNuwaProcess
         ? base::PRIVILEGES_INHERIT
         : base::PRIVILEGES_DEFAULT;
     mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, privs);
 
     IToplevelProtocol::SetTransport(mSubprocess->GetChannel());
 
-    if (!aIsNuwaProcess) {
-        // Tell the memory reporter manager that this ContentParent exists.
-        nsRefPtr<nsMemoryReporterManager> mgr =
-            nsMemoryReporterManager::GetOrCreate();
-        if (mgr) {
-            mgr->IncrementNumChildProcesses();
-        }
-    }
-
     std::vector<std::string> extraArgs;
     if (aIsNuwaProcess) {
         extraArgs.push_back("-nuwa");
     }
     mSubprocess->LaunchAndWaitForProcessHandle(extraArgs);
 
     Open(mSubprocess->GetChannel(),
          base::GetProcId(mSubprocess->GetChildProcessHandle()));
@@ -2329,23 +2307,16 @@ ContentParent::ContentParent(ContentPare
 
     const FileDescriptor* fd = FindFdProtocolFdMapping(aFds, GetProtocolId());
 
     NS_ASSERTION(fd != nullptr, "IPC Channel for PContent is necessary!");
     mSubprocess = new GeckoExistingProcessHost(GeckoProcessType_Content,
                                                aPid,
                                                *fd);
 
-    // Tell the memory reporter manager that this ContentParent exists.
-    nsRefPtr<nsMemoryReporterManager> mgr =
-        nsMemoryReporterManager::GetOrCreate();
-    if (mgr) {
-        mgr->IncrementNumChildProcesses();
-    }
-
     mSubprocess->LaunchAndWaitForProcessHandle();
 
     // Clone actors routed by aTemplate for this instance.
     IToplevelProtocol::SetTransport(mSubprocess->GetChannel());
     ProtocolCloneContext cloneContext;
     cloneContext.SetContentParent(this);
     CloneManagees(aTemplate, &cloneContext);
     CloneOpenedToplevels(aTemplate, aFds, aPid, &cloneContext);
@@ -3057,56 +3028,16 @@ ContentParent::Observe(nsISupports* aSub
     // listening for alert notifications
     else if (!strcmp(aTopic, "alertfinished") ||
              !strcmp(aTopic, "alertclickcallback") ||
              !strcmp(aTopic, "alertshow") ) {
         if (!SendNotifyAlertsObserver(nsDependentCString(aTopic),
                                       nsDependentString(aData)))
             return NS_ERROR_NOT_AVAILABLE;
     }
-    else if (!strcmp(aTopic, "child-memory-reporter-request")) {
-        bool isNuwa = false;
-#ifdef MOZ_NUWA_PROCESS
-        isNuwa = IsNuwaProcess();
-#endif
-        if (!isNuwa) {
-            unsigned generation;
-            int anonymize, minimize, identOffset = -1;
-            nsDependentString msg(aData);
-            NS_ConvertUTF16toUTF8 cmsg(msg);
-
-            if (sscanf(cmsg.get(),
-                       "generation=%x anonymize=%d minimize=%d DMDident=%n",
-                       &generation, &anonymize, &minimize, &identOffset) < 3
-                || identOffset < 0) {
-                return NS_ERROR_INVALID_ARG;
-            }
-            // The pre-%n part of the string should be all ASCII, so the byte
-            // offset in identOffset should be correct as a char offset.
-            MOZ_ASSERT(cmsg[identOffset - 1] == '=');
-            MaybeFileDesc dmdFileDesc = void_t();
-#ifdef MOZ_DMD
-            nsAutoString dmdIdent(Substring(msg, identOffset));
-            if (!dmdIdent.IsEmpty()) {
-                FILE *dmdFile = nullptr;
-                nsresult rv = nsMemoryInfoDumper::OpenDMDFile(dmdIdent, Pid(), &dmdFile);
-                if (NS_WARN_IF(NS_FAILED(rv))) {
-                    // Proceed with the memory report as if DMD were disabled.
-                    dmdFile = nullptr;
-                }
-                if (dmdFile) {
-                    dmdFileDesc = FILEToFileDescriptor(dmdFile);
-                    fclose(dmdFile);
-                }
-            }
-#endif
-            unused << SendPMemoryReportRequestConstructor(
-              generation, anonymize, minimize, dmdFileDesc);
-        }
-    }
     else if (!strcmp(aTopic, "child-gc-request")){
         unused << SendGarbageCollect();
     }
     else if (!strcmp(aTopic, "child-cc-request")){
         unused << SendCycleCollect();
     }
     else if (!strcmp(aTopic, "child-mmu-request")){
         unused << SendMinimizeMemoryUsage();
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4744,8 +4744,18 @@ pref("dom.secureelement.enabled", false)
 #endif
 
 // Allow control characters appear in composition string.
 // When this is false, control characters except
 // CHARACTER TABULATION (horizontal tab) are removed from
 // both composition string and data attribute of compositionupdate
 // and compositionend events.
 pref("dom.compositionevent.allow_control_characters", false);
+
+#ifdef MOZ_WIDGET_GONK
+// Bug 1154053: Serialize B2G memory reports; smaller devices are
+// usually overcommitted on memory by using zRAM, so memory reporting
+// causes memory pressure from uncompressing cold heap memory.
+pref("memory.report_concurrency", 1);
+#else
+// Desktop probably doesn't have swapped-out children like that.
+pref("memory.report_concurrency", 10);
+#endif
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
@@ -30,16 +30,17 @@
   let numReady = 0;
 
   // Create some remote processes, and set up message-passing so that
   // we know when each child is fully initialized.
   let remotes = [];
 
   let prefs = [
     ["dom.ipc.processCount", 3],            // Allow up to 3 child processes
+    ["memory.report_concurrency", 2],       // Cover more child handling cases
     ["memory.system_memory_reporter", true] // Test SystemMemoryReporter
   ];
 
   SpecialPowers.pushPrefEnv({"set": prefs}, function() {
     for (let i = 0; i < numRemotes; i++) {
       let w = remotes[i] = window.open("remote.xul", "", "chrome");
 
       w.addEventListener("load", function loadHandler() {
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -18,19 +18,22 @@
 #include "nsIObserverService.h"
 #include "nsIGlobalObject.h"
 #include "nsIXPConnect.h"
 #if defined(XP_UNIX) || defined(MOZ_DMD)
 #include "nsMemoryInfoDumper.h"
 #endif
 #include "mozilla/Attributes.h"
 #include "mozilla/PodOperations.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h" // for dom::MemoryReport
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/FileDescriptorUtils.h"
 
 #ifdef XP_WIN
 #include <process.h>
 #ifndef getpid
 #define getpid _getpid
 #endif
 #else
 #include <unistd.h>
@@ -1241,17 +1244,16 @@ nsMemoryReporterManager::Init()
 
 nsMemoryReporterManager::nsMemoryReporterManager()
   : mMutex("nsMemoryReporterManager::mMutex")
   , mIsRegistrationBlocked(false)
   , mStrongReporters(new StrongReportersTable())
   , mWeakReporters(new WeakReportersTable())
   , mSavedStrongReporters(nullptr)
   , mSavedWeakReporters(nullptr)
-  , mNumChildProcesses(0)
   , mNextGeneration(1)
   , mGetReportsState(nullptr)
 {
 }
 
 nsMemoryReporterManager::~nsMemoryReporterManager()
 {
   delete mStrongReporters;
@@ -1266,39 +1268,16 @@ nsMemoryReporterManager::~nsMemoryReport
 
 #ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING
 #define MEMORY_REPORTING_LOG(format, ...) \
   printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__);
 #else
 #define MEMORY_REPORTING_LOG(...)
 #endif
 
-void
-nsMemoryReporterManager::IncrementNumChildProcesses()
-{
-  if (!NS_IsMainThread()) {
-    MOZ_CRASH();
-  }
-  mNumChildProcesses++;
-  MEMORY_REPORTING_LOG("IncrementNumChildProcesses --> %d\n",
-                       mNumChildProcesses);
-}
-
-void
-nsMemoryReporterManager::DecrementNumChildProcesses()
-{
-  if (!NS_IsMainThread()) {
-    MOZ_CRASH();
-  }
-  MOZ_ASSERT(mNumChildProcesses > 0);
-  mNumChildProcesses--;
-  MEMORY_REPORTING_LOG("DecrementNumChildProcesses --> %d\n",
-                       mNumChildProcesses);
-}
-
 NS_IMETHODIMP
 nsMemoryReporterManager::GetReports(
   nsIHandleReportCallback* aHandleReport,
   nsISupports* aHandleReportData,
   nsIFinishReportingCallback* aFinishReporting,
   nsISupports* aFinishReportingData,
   bool aAnonymize)
 {
@@ -1332,28 +1311,33 @@ nsMemoryReporterManager::GetReportsExten
   if (mGetReportsState) {
     // A request is in flight.  Don't start another one.  And don't report
     // an error;  just ignore it, and let the in-flight request finish.
     MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n",
                          generation, mGetReportsState->mGeneration);
     return NS_OK;
   }
 
-  MEMORY_REPORTING_LOG("GetReports (gen=%u, %d child(ren) present)\n",
-                       generation, mNumChildProcesses);
+  MEMORY_REPORTING_LOG("GetReports (gen=%u)\n", generation);
 
+  uint32_t concurrency = Preferences::GetUint("memory.report_concurrency", 1);
+  MOZ_ASSERT(concurrency >= 1);
+  if (concurrency < 1) {
+    concurrency = 1;
+  }
   mGetReportsState = new GetReportsState(generation,
                                          aAnonymize,
                                          aMinimize,
-                                         mNumChildProcesses,
+                                         concurrency,
                                          aHandleReport,
                                          aHandleReportData,
                                          aFinishReporting,
                                          aFinishReportingData,
                                          aDMDDumpIdent);
+  mGetReportsState->mChildrenPending = new nsTArray<nsRefPtr<mozilla::dom::ContentParent>>();
 
   if (aMinimize) {
     rv = MinimizeMemoryUsage(NS_NewRunnableMethod(
       this, &nsMemoryReporterManager::StartGettingReports));
   } else {
     rv = StartGettingReports();
   }
   return rv;
@@ -1375,59 +1359,50 @@ nsMemoryReporterManager::StartGettingRep
       // Proceed with the memory report as if DMD were disabled.
       parentDMDFile = nullptr;
     }
   }
 #endif
   GetReportsForThisProcessExtended(s->mHandleReport, s->mHandleReportData,
                                    s->mAnonymize, parentDMDFile);
 
-  MOZ_ASSERT(s->mNumChildProcessesCompleted == 0);
-  if (s->mNumChildProcesses > 0) {
+  nsTArray<ContentParent*> childWeakRefs;
+  ContentParent::GetAll(childWeakRefs);
+  if (!childWeakRefs.IsEmpty()) {
     // Request memory reports from child processes.  This happens
     // after the parent report so that the parent's main thread will
     // be free to process the child reports, instead of causing them
     // to be buffered and consume (possibly scarce) memory.
+
+    for (size_t i = 0; i < childWeakRefs.Length(); ++i) {
+      s->mChildrenPending->AppendElement(childWeakRefs[i]);
+    }
+
     nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
     // Don't use NS_ENSURE_* here; can't return until the report is finished.
     if (NS_WARN_IF(!timer)) {
       FinishReporting();
       return NS_ERROR_FAILURE;
     }
     rv = timer->InitWithFuncCallback(TimeoutCallback,
                                      this, kTimeoutLengthMS,
                                      nsITimer::TYPE_ONE_SHOT);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       FinishReporting();
       return rv;
     }
 
     MOZ_ASSERT(!s->mTimer);
     s->mTimer.swap(timer);
-
-    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    if (NS_WARN_IF(!obs)) {
-      FinishReporting();
-      return NS_ERROR_UNEXPECTED;
-    }
+  }
 
-    nsPrintfCString genStr("generation=%x anonymize=%d minimize=%d DMDident=",
-                           s->mGeneration, s->mAnonymize ? 1 : 0,
-                           s->mMinimize ? 1 : 0);
-    nsAutoString msg = NS_ConvertUTF8toUTF16(genStr);
-    msg += s->mDMDDumpIdent;
-
-    obs->NotifyObservers(nullptr, "child-memory-reporter-request",
-                         msg.get());
-
-    return NS_OK;
-  } else {
-    // If there are no child processes, we can finish up immediately.
-    return FinishReporting();
-  }
+  // The parent's report is done; make note of that, and start
+  // launching child process reports (if any).
+  EndProcessReport(s->mGeneration, true);
+  return NS_OK;
 }
 
 typedef nsCOMArray<nsIMemoryReporter> MemoryReporterArray;
 
 static PLDHashOperator
 StrongEnumerator(nsRefPtrHashKey<nsIMemoryReporter>* aElem, void* aData)
 {
   MemoryReporterArray* allReporters = static_cast<MemoryReporterArray*>(aData);
@@ -1497,27 +1472,22 @@ nsMemoryReporterManager::GetReportsState
 nsMemoryReporterManager::GetStateForGeneration(uint32_t aGeneration)
 {
   // Memory reporting only happens on the main thread.
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   GetReportsState* s = mGetReportsState;
 
   if (!s) {
-    // If we reach here, either:
+    // If we reach here, then:
     //
     // - A child process reported back too late, and no subsequent request
     //   is in flight.
     //
-    // - (Unlikely) A "child-memory-reporter-request" notification was
-    //   triggered from somewhere other than GetReports(), causing child
-    //   processes to report back when the nsMemoryReporterManager wasn't
-    //   expecting it.
-    //
-    // Either way, there's nothing to be done.  Just ignore it.
+    // So there's nothing to be done.  Just ignore it.
     MEMORY_REPORTING_LOG(
       "HandleChildReports: no request in flight (aGen=%u)\n",
       aGeneration);
     return nullptr;
   }
 
   if (aGeneration != s->mGeneration) {
     // If we reach here, a child process must have reported back, too late,
@@ -1554,86 +1524,147 @@ nsMemoryReporterManager::HandleChildRepo
                              aChildReport.path(),
                              aChildReport.kind(),
                              aChildReport.units(),
                              aChildReport.amount(),
                              aChildReport.desc(),
                              s->mHandleReportData);
 }
 
+/* static */ bool
+nsMemoryReporterManager::StartChildReport(mozilla::dom::ContentParent* aChild,
+                                          const GetReportsState* aState)
+{
+#ifdef MOZ_NUWA_PROCESS
+  if (aChild->IsNuwaProcess()) {
+    return false;
+  }
+#endif
+
+  if (!aChild->IsAlive()) {
+    MEMORY_REPORTING_LOG("StartChildReports (gen=%u): child exited before"
+                         " its report was started\n",
+                         aState->mGeneration);
+    return false;
+  }
+
+  mozilla::dom::MaybeFileDesc dmdFileDesc = void_t();
+#ifdef MOZ_DMD
+  if (!aState->mDMDDumpIdent.IsEmpty()) {
+    FILE *dmdFile = nullptr;
+    nsresult rv = nsMemoryInfoDumper::OpenDMDFile(aState->mDMDDumpIdent,
+                                                  aChild->Pid(), &dmdFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // Proceed with the memory report as if DMD were disabled.
+      dmdFile = nullptr;
+    }
+    if (dmdFile) {
+      dmdFileDesc = mozilla::ipc::FILEToFileDescriptor(dmdFile);
+      fclose(dmdFile);
+    }
+  }
+#endif
+  return aChild->SendPMemoryReportRequestConstructor(
+    aState->mGeneration, aState->mAnonymize, aState->mMinimize, dmdFileDesc);
+}
+
 void
-nsMemoryReporterManager::EndChildReport(uint32_t aGeneration, bool aSuccess)
+nsMemoryReporterManager::EndProcessReport(uint32_t aGeneration, bool aSuccess)
 {
   GetReportsState* s = GetStateForGeneration(aGeneration);
   if (!s) {
     return;
   }
 
-  s->mNumChildProcessesCompleted++;
+  MOZ_ASSERT(s->mNumProcessesRunning > 0);
+  s->mNumProcessesRunning--;
+  s->mNumProcessesCompleted++;
+  MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): process %u %s"
+                       " (%u running, %u pending)\n",
+                       aGeneration, s->mNumProcessesCompleted,
+                       aSuccess ? "completed" : "exited during report",
+                       s->mNumProcessesRunning,
+                       static_cast<unsigned>(s->mChildrenPending->Length()));
 
-  if (aSuccess) {
-    MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): completed child %d\n",
-                         aGeneration, s->mNumChildProcessesCompleted);
-  } else {
-    // Unfortunately, there's no way to indicate this in the report yet.
-    // (Also, we don't have the child's identifier at this point.)
-    MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): child %d exited"
-                         " during report\n",
-                         aGeneration, s->mNumChildProcessesCompleted);
+  // Start pending children up to the concurrency limit.
+  while (s->mNumProcessesRunning < s->mConcurrencyLimit &&
+         !s->mChildrenPending->IsEmpty()) {
+    // Pop last element from s->mChildrenPending
+    nsRefPtr<ContentParent> nextChild;
+    nextChild.swap(s->mChildrenPending->LastElement());
+    s->mChildrenPending->TruncateLength(s->mChildrenPending->Length() - 1);
+    // Start report (if the child is still alive and not Nuwa).
+    if (StartChildReport(nextChild, s)) {
+      ++s->mNumProcessesRunning;
+      MEMORY_REPORTING_LOG("HandleChildReports (aGen=%u): started child report"
+                           " (%u running, %u pending)\n",
+                           aGeneration, s->mNumProcessesRunning,
+                           static_cast<unsigned>(s->mChildrenPending->Length()));
+    }
   }
 
-  // If all the child processes have reported, we can cancel the timer and
-  // finish up.  Otherwise, just return.
-  if (s->mNumChildProcessesCompleted >= s->mNumChildProcesses) {
-    s->mTimer->Cancel();
+  // If all the child processes (if any) have reported, we can cancel
+  // the timer (if started) and finish up.  Otherwise, just return.
+  if (s->mNumProcessesRunning == 0) {
+    MOZ_ASSERT(s->mChildrenPending->IsEmpty());
+    if (s->mTimer) {
+      s->mTimer->Cancel();
+    }
     FinishReporting();
   }
 }
 
 /* static */ void
 nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData)
 {
   nsMemoryReporterManager* mgr = static_cast<nsMemoryReporterManager*>(aData);
   GetReportsState* s = mgr->mGetReportsState;
 
   // Release assert because: if the pointer is null we're about to
   // crash regardless of DEBUG, and this way the compiler doesn't
   // complain about unused variables.
   MOZ_RELEASE_ASSERT(s, "mgr->mGetReportsState");
-  MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u)\n",
-                       s->mGeneration);
+  MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u; %u running, %u pending)\n",
+                       s->mGeneration, s->mNumProcessesRunning,
+                       static_cast<unsigned>(s->mChildrenPending->Length()));
 
   // We don't bother sending any kind of cancellation message to the child
   // processes that haven't reported back.
   mgr->FinishReporting();
 }
 
 nsresult
 nsMemoryReporterManager::FinishReporting()
 {
   // Memory reporting only happens on the main thread.
   if (!NS_IsMainThread()) {
     MOZ_CRASH();
   }
 
   MOZ_ASSERT(mGetReportsState);
-  MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u)\n",
-                       mGetReportsState->mGeneration);
+  MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u; %u processes reported)\n",
+                       mGetReportsState->mGeneration,
+                       mGetReportsState->mNumProcessesCompleted);
 
   // Call this before deleting |mGetReportsState|.  That way, if
   // |mFinishReportData| calls GetReports(), it will silently abort, as
   // required.
   nsresult rv = mGetReportsState->mFinishReporting->Callback(
     mGetReportsState->mFinishReportingData);
 
   delete mGetReportsState;
   mGetReportsState = nullptr;
   return rv;
 }
 
+nsMemoryReporterManager::GetReportsState::~GetReportsState()
+{
+  delete mChildrenPending;
+}
+
 static void
 CrashIfRefcountIsZero(nsISupports* aObj)
 {
   // This will probably crash if the object's refcount is 0.
   uint32_t refcnt = NS_ADDREF(aObj);
   if (refcnt <= 1) {
     MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero");
   }
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -2,31 +2,32 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 nsMemoryReporterManager_h__
 #define nsMemoryReporterManager_h__
 
+#include "mozilla/Mutex.h"
+#include "nsHashKeys.h"
 #include "nsIMemoryReporter.h"
 #include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
-#include "mozilla/Mutex.h"
 #include "nsTHashtable.h"
-#include "nsHashKeys.h"
-
-class nsITimer;
 
 namespace mozilla {
 namespace dom {
+class ContentParent;
 class MemoryReport;
 }
 }
 
+class nsITimer;
+
 class nsMemoryReporterManager final : public nsIMemoryReporterManager
 {
   virtual ~nsMemoryReporterManager();
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTERMANAGER
 
@@ -38,34 +39,37 @@ public:
     nsCOMPtr<nsIMemoryReporterManager> imgr =
       do_GetService("@mozilla.org/memory-reporter-manager;1");
     return static_cast<nsMemoryReporterManager*>(imgr.get());
   }
 
   typedef nsTHashtable<nsRefPtrHashKey<nsIMemoryReporter>> StrongReportersTable;
   typedef nsTHashtable<nsPtrHashKey<nsIMemoryReporter>> WeakReportersTable;
 
-  void IncrementNumChildProcesses();
-  void DecrementNumChildProcesses();
-
   // Inter-process memory reporting proceeds as follows.
   //
   // - GetReports() (declared within NS_DECL_NSIMEMORYREPORTERMANAGER)
-  //   synchronously gets memory reports for the current process, tells all
-  //   child processes to get memory reports, and sets up some state
-  //   (mGetReportsState) for when the child processes report back, including a
-  //   timer.  Control then returns to the main event loop.
+  //   synchronously gets memory reports for the current process, sets up some
+  //   state (mGetReportsState) for when child processes report back --
+  //   including a timer -- and starts telling child processes to get memory
+  //   reports.  Control then returns to the main event loop.
+  //
+  //   The number of concurrent child process reports is limited by the pref
+  //   "memory.report_concurrency" in order to prevent the memory overhead of
+  //   memory reporting from causing problems, especially on B2G when swapping
+  //   to compressed RAM; see bug 1154053.
   //
   // - HandleChildReport() is called (asynchronously) once per child process
   //   reporter callback.
   //
-  // - EndChildReport() is called (asynchronously) once per child process that
-  //   finishes reporting back.  If all child processes do so before time-out,
-  //   the timer is cancelled.  (The number of child processes is part of the
-  //   saved request state.)
+  // - EndProcessReport() is called (asynchronously) once per process that
+  //   finishes reporting back, including the parent.  If all processes do so
+  //   before time-out, the timer is cancelled.  If there are child processes
+  //   whose requests have not yet been sent, they will be started until the
+  //   concurrency limit is (again) reached.
   //
   // - TimeoutCallback() is called (asynchronously) if all the child processes
   //   don't respond within the time threshold.
   //
   // - FinishReporting() finishes things off.  It is *always* called -- either
   //   from EndChildReport() (if all child processes have reported back) or
   //   from TimeoutCallback() (if time-out occurs).
   //
@@ -99,22 +103,22 @@ public:
   //   this late stage.
   //
   // - If the time-out occurs after a child process has sent some reports but
   //   before it has signaled completion (see bug 1151597), then what it
   //   successfully sent will be included, with no explicit indication that it
   //   is incomplete.
   //
   // Now, what what happens if a child process is created/destroyed in the
-  // middle of a request?  Well, GetReportsState contains a copy of
-  // mNumChildProcesses which it uses to determine finished-ness.  So...
+  // middle of a request?  Well, GetReportsState is initialized with an array
+  // of child process actors as of when the report started.  So...
   //
-  // - If a process is created, it won't have received the request for reports,
-  //   and the GetReportsState's mNumChildProcesses won't account for it.  So
-  //   the reported data will reflect how things were when the request began.
+  // - If a process is created after reporting starts, it won't be sent a
+  //   request for reports.  So the reported data will reflect how things were
+  //   when the request began.
   //
   // - If a process is destroyed before it starts reporting back, the reported
   //   data will reflect how things are when the request ends.
   //
   // - If a process is destroyed after it starts reporting back but before it
   //   finishes, the reported data will contain a partial report for it.
   //
   // - If a process is destroyed after reporting back, but before all other
@@ -122,17 +126,17 @@ public:
   //   data.  So the reported data will reflect how things were when the
   //   request began.
   //
   // The inconsistencies between these cases are unfortunate but difficult to
   // avoid.  It's enough of an edge case to not be worth doing more.
   //
   void HandleChildReport(uint32_t aGeneration,
                          const mozilla::dom::MemoryReport& aChildReport);
-  void EndChildReport(uint32_t aGeneration, bool aSuccess);
+  void EndProcessReport(uint32_t aGeneration, bool aSuccess);
 
   // Functions that (a) implement distinguished amounts, and (b) are outside of
   // this module.
   struct AmountFns
   {
     mozilla::InfallibleAmountFn mJSMainRuntimeGCHeap;
     mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak;
     mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem;
@@ -194,59 +198,69 @@ private:
 
   StrongReportersTable* mStrongReporters;
   WeakReportersTable* mWeakReporters;
 
   // These two are only used for testing purposes.
   StrongReportersTable* mSavedStrongReporters;
   WeakReportersTable* mSavedWeakReporters;
 
-  uint32_t mNumChildProcesses;
   uint32_t mNextGeneration;
 
   struct GetReportsState
   {
     uint32_t                             mGeneration;
     bool                                 mAnonymize;
     bool                                 mMinimize;
     nsCOMPtr<nsITimer>                   mTimer;
-    uint32_t                             mNumChildProcesses;
-    uint32_t                             mNumChildProcessesCompleted;
+    // This is a pointer to an nsTArray because otherwise C++ is
+    // unhappy unless this header includes ContentParent.h, which not
+    // everything that includes this header knows how to find.
+    nsTArray<nsRefPtr<mozilla::dom::ContentParent>>* mChildrenPending;
+    uint32_t                             mNumProcessesRunning;
+    uint32_t                             mNumProcessesCompleted;
+    uint32_t                             mConcurrencyLimit;
     nsCOMPtr<nsIHandleReportCallback>    mHandleReport;
     nsCOMPtr<nsISupports>                mHandleReportData;
     nsCOMPtr<nsIFinishReportingCallback> mFinishReporting;
     nsCOMPtr<nsISupports>                mFinishReportingData;
     nsString                             mDMDDumpIdent;
 
     GetReportsState(uint32_t aGeneration, bool aAnonymize, bool aMinimize,
-                    uint32_t aNumChildProcesses,
+                    uint32_t aConcurrencyLimit,
                     nsIHandleReportCallback* aHandleReport,
                     nsISupports* aHandleReportData,
                     nsIFinishReportingCallback* aFinishReporting,
                     nsISupports* aFinishReportingData,
                     const nsAString& aDMDDumpIdent)
       : mGeneration(aGeneration)
       , mAnonymize(aAnonymize)
       , mMinimize(aMinimize)
-      , mNumChildProcesses(aNumChildProcesses)
-      , mNumChildProcessesCompleted(0)
+      , mChildrenPending(nullptr)
+      , mNumProcessesRunning(1) // reporting starts with the parent
+      , mNumProcessesCompleted(0)
+      , mConcurrencyLimit(aConcurrencyLimit)
       , mHandleReport(aHandleReport)
       , mHandleReportData(aHandleReportData)
       , mFinishReporting(aFinishReporting)
       , mFinishReportingData(aFinishReportingData)
       , mDMDDumpIdent(aDMDDumpIdent)
     {
     }
+
+    ~GetReportsState();
   };
 
   // When this is non-null, a request is in flight.  Note: We use manual
   // new/delete for this because its lifetime doesn't match block scope or
   // anything like that.
   GetReportsState* mGetReportsState;
 
   GetReportsState* GetStateForGeneration(uint32_t aGeneration);
+  static bool StartChildReport(mozilla::dom::ContentParent* aChild,
+                               const GetReportsState* aState);
 };
 
 #define NS_MEMORY_REPORTER_MANAGER_CID \
 { 0xfb97e4f5, 0x32dd, 0x497a, \
 { 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 } }
 
 #endif // nsMemoryReporterManager_h__