Bug 1154053 - Limit concurrency of e10s memory reporting. r=erahm
authorJed Davis <jld@mozilla.com>
Wed, 06 May 2015 20:51:00 +0200
changeset 274398 5df6a8eccc53b8d27582a43ba3098e41442e89ff
parent 274397 c33b62fc04ac93aa522ac0b55b8273447d6c8c65
child 274399 8635863a78c0f4715266dc0753eb30a8b7bb8611
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm
bugs1154053, 1151597
milestone40.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 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__