Bug 1602757: add preallocation cache for webIsolated (fission) processes r=nika,smaug
☠☠ backed out by 8912e3abc781 ☠ ☠
authorRandell Jesup <rjesup@wgate.com>
Sat, 23 May 2020 05:37:26 +0000
changeset 2913075 a7d7edf158a46612d9fd4d415aa02d99952cf8dd
parent 2913074 7aa26615adf97c056e3b8ebe491e0c66569732af
child 2913076 45e22f062bced99e58d597d058a277a9392198a6
push id542231
push userbclary@mozilla.com
push dateSun, 24 May 2020 06:08:15 +0000
treeherdertry@5bd349510b42 [default view] [failures only]
reviewersnika, smaug
bugs1602757
milestone78.0a1
Bug 1602757: add preallocation cache for webIsolated (fission) processes r=nika,smaug Differential Revision: https://phabricator.services.mozilla.com/D69589
dom/base/ChromeUtils.cpp
dom/chrome-webidl/ChromeUtils.webidl
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PreallocatedProcessManager.cpp
dom/ipc/PreallocatedProcessManager.h
dom/ipc/ProcessPriorityManager.cpp
modules/libpref/init/StaticPrefList.yaml
widget/ProcInfo.h
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -755,16 +755,17 @@ static WebIDLProcType ProcTypeToWebIDL(m
     PROCTYPE_TO_WEBIDL_CASE(GPU, Gpu);
     PROCTYPE_TO_WEBIDL_CASE(VR, Vr);
     PROCTYPE_TO_WEBIDL_CASE(RDD, Rdd);
     PROCTYPE_TO_WEBIDL_CASE(Socket, Socket);
     PROCTYPE_TO_WEBIDL_CASE(RemoteSandboxBroker, RemoteSandboxBroker);
 #ifdef MOZ_ENABLE_FORKSERVER
     PROCTYPE_TO_WEBIDL_CASE(ForkServer, ForkServer);
 #endif
+    PROCTYPE_TO_WEBIDL_CASE(Preallocated, Preallocated);
     PROCTYPE_TO_WEBIDL_CASE(Unknown, Unknown);
   }
 
   MOZ_ASSERT(false, "Unhandled case in ProcTypeToWebIDL");
   return WebIDLProcType::Unknown;
 }
 
 #undef PROCTYPE_TO_WEBIDL_CASE
@@ -856,16 +857,19 @@ already_AddRefed<Promise> ChromeUtils::R
                       } else if (StringBeginsWith(
                                      remoteType,
                                      NS_LITERAL_STRING(
                                          WITH_COOP_COEP_REMOTE_TYPE_PREFIX))) {
                         type = mozilla::ProcType::WebCOOPCOEP;
                       } else if (remoteType.EqualsLiteral(
                                      LARGE_ALLOCATION_REMOTE_TYPE)) {
                         type = mozilla::ProcType::WebLargeAllocation;
+                      } else if (remoteType.EqualsLiteral(
+                                     PREALLOC_REMOTE_TYPE)) {
+                        type = mozilla::ProcType::Preallocated;
                       } else {
                         MOZ_CRASH("Unknown remoteType");
                       }
 
                       // By convention, everything after '=' is the origin.
                       nsAString::const_iterator cursor;
                       nsAString::const_iterator end;
                       remoteType.BeginReading(cursor);
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -512,16 +512,17 @@ enum WebIDLProcType {
  "gpu",
  "vr",
  "rdd",
  "socket",
  "remoteSandboxBroker",
 #ifdef MOZ_ENABLE_FORKSERVER
  "forkServer",
 #endif
+ "preallocated",
  "unknown",
 };
 
 /**
  * These dictionaries hold information about Firefox running processes and
  * threads.
  *
  * See widget/ProcInfo.h for fields documentation.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/PerfStats.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/RemoteDecoderManagerChild.h"
 #include "mozilla/Unused.h"
 #include "mozilla/SchedulerGroup.h"
 #include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_fission.h"
 #include "mozilla/StaticPrefs_media.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/RemoteDecoderManagerChild.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/BrowsingContextGroup.h"
 #include "mozilla/dom/BrowserBridgeHost.h"
@@ -2561,31 +2562,52 @@ mozilla::ipc::IPCResult ContentChild::Re
   mAppInfo.sourceURL.Assign(sourceURL);
   mAppInfo.updateURL.Assign(updateURL);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvRemoteType(
     const nsString& aRemoteType) {
-  MOZ_ASSERT(DOMStringIsNull(mRemoteType));
-
-  mRemoteType.Assign(aRemoteType);
-
-  // For non-default ("web") types, update the process name so about:memory's
-  // process names are more obvious.
+  if (!DOMStringIsNull(mRemoteType)) {
+    // Preallocated processes are type PREALLOC_REMOTE_TYPE; they can become
+    // anything except a File: process.
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Changing remoteType of process %d from %s to %s", getpid(),
+             NS_ConvertUTF16toUTF8(mRemoteType).get(),
+             NS_ConvertUTF16toUTF8(aRemoteType).get()));
+    // prealloc->anything (but file) or web->web allowed
+    MOZ_RELEASE_ASSERT(!aRemoteType.EqualsLiteral(FILE_REMOTE_TYPE) &&
+                       (mRemoteType.EqualsLiteral(PREALLOC_REMOTE_TYPE) ||
+                        (mRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) &&
+                         aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE))));
+  } else {
+    // Initial setting of remote type.  Either to 'prealloc' or the actual
+    // final type (if we didn't use a preallocated process)
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Setting remoteType of process %d to %s", getpid(),
+             NS_ConvertUTF16toUTF8(aRemoteType).get()));
+  }
+
+  // Update the process name so about:memory's process names are more obvious.
   if (aRemoteType.EqualsLiteral(FILE_REMOTE_TYPE)) {
     SetProcessName(NS_LITERAL_STRING("file:// Content"));
   } else if (aRemoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) {
     SetProcessName(NS_LITERAL_STRING("WebExtensions"));
   } else if (aRemoteType.EqualsLiteral(PRIVILEGEDABOUT_REMOTE_TYPE)) {
     SetProcessName(NS_LITERAL_STRING("Privileged Content"));
   } else if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
     SetProcessName(NS_LITERAL_STRING("Large Allocation Web Content"));
-  }
+  } else if (RemoteTypePrefix(aRemoteType)
+                 .EqualsLiteral(FISSION_WEB_REMOTE_TYPE)) {
+    SetProcessName(NS_LITERAL_STRING("Isolated Web Content"));
+  }
+  // else "prealloc", "web" or "webCOOP+COEP" type -> "Web Content" already set
+
+  mRemoteType.Assign(aRemoteType);
 
   return IPC_OK();
 }
 
 // Call RemoteTypePrefix() on the result to remove URIs if you want to use this
 // for telemetry.
 const nsAString& ContentChild::GetRemoteType() const { return mRemoteType; }
 
@@ -3504,17 +3526,16 @@ mozilla::ipc::IPCResult ContentChild::Re
     topContext->GetSessionStorageManager()->LoadSessionStorageData(
         nullptr, aOriginAttrs, aOriginKey, aDefaultData, aSessionData);
   } else {
     NS_WARNING("Got session storage data for a discarded session");
   }
   return IPC_OK();
 }
 
-
 mozilla::ipc::IPCResult ContentChild::RecvOnAllowAccessFor(
     const MaybeDiscarded<BrowsingContext>& aContext,
     const nsCString& aTrackingOrigin, uint32_t aCookieBehavior,
     const ContentBlockingNotifier::StorageAccessGrantedReason& aReason) {
   MOZ_ASSERT(!aContext.IsNull(), "Browsing context cannot be null");
 
   ContentBlocking::OnAllowAccessFor(aContext.GetMaybeDiscarded(),
                                     aTrackingOrigin, aCookieBehavior, aReason);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -15,16 +15,19 @@
 
 #include "chrome/common/process_watcher.h"
 #include "mozilla/Result.h"
 
 #ifdef ACCESSIBILITY
 #  include "mozilla/a11y/PDocAccessible.h"
 #endif
 #include "GeckoProfiler.h"
+#ifdef MOZ_GECKO_PROFILER
+#  include "ProfilerMarkerPayload.h"
+#endif
 #include "GMPServiceParent.h"
 #include "HandlerServiceParent.h"
 #include "IHistory.h"
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #  include "mozilla/a11y/AccessibleWrap.h"
 #  include "mozilla/a11y/Compatibility.h"
 #endif
 #include <utility>
@@ -346,16 +349,21 @@ const nsIID nsIConsoleService::COMTypeIn
 
 namespace mozilla {
 namespace CubebUtils {
 extern FileDescriptor CreateAudioIPCConnection();
 }
 
 namespace dom {
 
+LazyLogModule gProcessLog("Process");
+
+/* static */
+LogModule* ContentParent::GetLog() { return gProcessLog; }
+
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
 #define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
 
 // IPC receiver for remote GC/CC logging.
 class CycleCollectWithLogsParent final : public PCycleCollectWithLogsParent {
  public:
   MOZ_COUNTED_DTOR(CycleCollectWithLogsParent)
 
@@ -472,16 +480,20 @@ ContentParentsMemoryReporter::CollectRep
 
     aHandleReport->Callback(/* process */ EmptyCString(), path, KIND_OTHER,
                             UNITS_COUNT, numQueuedMessages, desc, aData);
   }
 
   return NS_OK;
 }
 
+// A hashtable (by type) of processes/ContentParents.  This includes
+// processes that are in the Preallocator cache (which would be type
+// 'prealloc'), and recycled processes ('web' and in the future
+// eTLD+1-locked) processes).
 nsClassHashtable<nsStringHashKey, nsTArray<ContentParent*>>*
     ContentParent::sBrowserContentParents;
 
 namespace {
 
 uint64_t ComputeLoadedOriginHash(nsIPrincipal* aPrincipal) {
   uint32_t originNoSuffix =
       BasePrincipal::Cast(aPrincipal)->GetOriginNoSuffixHash();
@@ -629,18 +641,20 @@ static const char* sObserverTopics[] = {
     NS_NETWORK_LINK_TYPE_TOPIC,
 };
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
 /*static*/ RefPtr<ContentParent::LaunchPromise>
 ContentParent::PreallocateProcess() {
   RefPtr<ContentParent> process = new ContentParent(
-      /* aOpener = */ nullptr, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
-
+      /* aOpener = */ nullptr, NS_LITERAL_STRING(PREALLOC_REMOTE_TYPE));
+
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("Preallocating process of type " PREALLOC_REMOTE_TYPE));
   return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC);
 }
 
 /*static*/
 void ContentParent::StartUp() {
   // We could launch sub processes from content process
   // FIXME Bug 1023701 - Stop using ContentParent static methods in
   // child process
@@ -709,16 +723,18 @@ const nsDependentSubstring RemoteTypePre
   int32_t equalIdx = aContentProcessType.FindChar(L'=');
   if (equalIdx == kNotFound) {
     equalIdx = aContentProcessType.Length();
   }
   return StringHead(aContentProcessType, equalIdx);
 }
 
 bool IsWebRemoteType(const nsAString& aContentProcessType) {
+  // Note: matches webIsolated as well as web (and webLargeAllocation, and
+  // webCOOP+COEP)
   return StringBeginsWith(aContentProcessType,
                           NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
 }
 
 bool IsWebCoopCoepRemoteType(const nsAString& aContentProcessType) {
   return StringBeginsWith(aContentProcessType,
                           NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX));
 }
@@ -726,17 +742,17 @@ bool IsWebCoopCoepRemoteType(const nsASt
 /*static*/
 uint32_t ContentParent::GetMaxProcessCount(
     const nsAString& aContentProcessType) {
   // Max process count is based only on the prefix.
   const nsDependentSubstring processTypePrefix =
       RemoteTypePrefix(aContentProcessType);
 
   // Check for the default remote type of "web", as it uses different prefs.
-  if (processTypePrefix.EqualsLiteral("web")) {
+  if (processTypePrefix.EqualsLiteral(DEFAULT_REMOTE_TYPE)) {
     return GetMaxWebProcessCount();
   }
 
   // Read the pref controling this remote type. `dom.ipc.processCount` is not
   // used as a fallback, as it is intended to control the number of "web"
   // content processes, checked in `mozilla::GetMaxWebProcessCount()`.
   nsAutoCString processCountPref("dom.ipc.processCount.");
   AppendUTF16toUTF8(processTypePrefix, processCountPref);
@@ -751,37 +767,67 @@ uint32_t ContentParent::GetMaxProcessCou
 
 /*static*/
 bool ContentParent::IsMaxProcessCountReached(
     const nsAString& aContentProcessType) {
   return GetPoolSize(aContentProcessType) >=
          GetMaxProcessCount(aContentProcessType);
 }
 
+// Really more ReleaseUnneededProcesses()
 /*static*/
 void ContentParent::ReleaseCachedProcesses() {
-  if (!GetPoolSize(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE))) {
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("ReleaseCachedProcesses:"));
+  if (!sBrowserContentParents) {
     return;
   }
 
-  // We might want to extend this for other process types as well in the
-  // future...
-  nsTArray<ContentParent*>& contentParents =
-      GetOrCreatePool(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
+#ifdef DEBUG
+  int num = 0;
+  for (auto iter = sBrowserContentParents->Iter(); !iter.Done(); iter.Next()) {
+    nsTArray<ContentParent*>* contentParents = iter.Data().get();
+    num += contentParents->Length();
+    for (auto* cp : *contentParents) {
+      MOZ_LOG(
+          ContentParent::GetLog(), LogLevel::Debug,
+          ("%s: %zu processes", NS_ConvertUTF16toUTF8(cp->mRemoteType).get(),
+           contentParents->Length()));
+      break;
+    }
+  }
+#endif
+  // We process the toRelease array outside of the iteration to avoid modifying
+  // the list (via RemoveFromList()) while we're iterating it.
   nsTArray<ContentParent*> toRelease;
-
-  // Shuting down these processes will change the array so let's use another
-  // array for the removal.
-  for (auto* cp : contentParents) {
-    if (cp->ManagedPBrowserParent().Count() == 0) {
-      toRelease.AppendElement(cp);
+  for (auto iter = sBrowserContentParents->Iter(); !iter.Done(); iter.Next()) {
+    nsTArray<ContentParent*>* contentParents = iter.Data().get();
+
+    // Shutting down these processes will change the array so let's use another
+    // array for the removal.
+    for (auto* cp : *contentParents) {
+      if (cp->ManagedPBrowserParent().Count() == 0 &&
+          !cp->HasActiveWorkerOrJSPlugin() &&
+          cp->mRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE)) {
+        toRelease.AppendElement(cp);
+      } else {
+        MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+                ("  Skipping %p (%s), count %d, HasActiveWorkerOrJSPlugin %d",
+                 cp, NS_ConvertUTF16toUTF8(cp->mRemoteType).get(),
+                 cp->ManagedPBrowserParent().Count(),
+                 cp->HasActiveWorkerOrJSPlugin()));
+      }
     }
   }
 
   for (auto* cp : toRelease) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("  Shutdown %p (%s)", cp,
+             NS_ConvertUTF16toUTF8(cp->mRemoteType).get()));
+    PreallocatedProcessManager::Erase(cp);
     // Start a soft shutdown.
     cp->ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
     // Make sure we don't select this process for new tabs.
     cp->MarkAsDead();
     // Make sure that this process is no longer accessible from JS by its
     // message manager.
     cp->ShutDownMessageManager();
   }
@@ -836,92 +882,154 @@ already_AddRefed<ContentParent> ContentP
   nsIContentProcessInfo* openerInfo =
       aOpener ? aOpener->mScriptableHelper.get() : nullptr;
   int32_t index;
   if (cpp && NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, openerInfo, infos,
                                               aMaxContentParents, &index))) {
     // If the provider returned an existing ContentParent, use that one.
     if (0 <= index && static_cast<uint32_t>(index) <= aMaxContentParents) {
       RefPtr<ContentParent> retval = aContentParents[index];
+#ifdef MOZ_GECKO_PROFILER
+      if (profiler_thread_is_being_profiled()) {
+        nsPrintfCString marker("Reused process %u",
+                               (unsigned int)retval->ChildID());
+        TimeStamp now = TimeStamp::Now();
+        PROFILER_ADD_MARKER_WITH_PAYLOAD("Process", DOM, TextMarkerPayload,
+                                         (marker, now, now));
+      }
+#endif
+      MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+              ("GetUsedProcess: Reused process %p (%u) for %s", retval.get(),
+               (unsigned int)retval->ChildID(),
+               NS_ConvertUTF16toUTF8(aRemoteType).get()));
       return retval.forget();
     }
   } else {
     // If there was a problem with the JS chooser, fall back to a random
     // selection.
     NS_WARNING("nsIContentProcessProvider failed to return a process");
     RefPtr<ContentParent> random;
     if (aContentParents.Length() >= aMaxContentParents &&
         (random = MinTabSelect(aContentParents, aOpener, aMaxContentParents))) {
+      MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+              ("GetUsedProcess: Reused random process %p (%d) for %s",
+               random.get(), (unsigned int)random->ChildID(),
+               NS_ConvertUTF16toUTF8(aRemoteType).get()));
       return random.forget();
     }
   }
 
-  // Try to take the preallocated process only for the default process type.
+  // Try to take the preallocated process except for blacklisted types.
   // The preallocated process manager might not had the chance yet to release
   // the process after a very recent ShutDownProcess, let's make sure we don't
   // try to reuse a process that is being shut down.
   RefPtr<ContentParent> p;
-  if (aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) &&
-      (p = PreallocatedProcessManager::Take()) && !p->mShutdownPending) {
+  bool preallocated = false;
+  if (!aRemoteType.EqualsLiteral(FILE_REMOTE_TYPE) &&
+      !aRemoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE) &&  // Bug 1638119
+      (p = PreallocatedProcessManager::Take(aRemoteType)) &&
+      !p->mShutdownPending) {
+    // p may be a preallocated process, or (if not PREALLOC_REMOTE_TYPE)
+    // a perviously-used process that's being recycled.  Currently this is
+    // only done for short-duration web (DEFAULT_REMOTE_TYPE) processes
+    preallocated = p->mRemoteType.EqualsLiteral(PREALLOC_REMOTE_TYPE);
     // For pre-allocated process we have not set the opener yet.
+#ifdef MOZ_GECKO_PROFILER
+    if (profiler_thread_is_being_profiled()) {
+      nsPrintfCString marker("Assigned %s process %u",
+                             preallocated ? "preallocated" : "reused web",
+                             (unsigned int)p->ChildID());
+      TimeStamp now = TimeStamp::Now();
+      PROFILER_ADD_MARKER_WITH_PAYLOAD("Process", DOM, TextMarkerPayload,
+                                       (marker, now, now));
+    }
+#endif
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Adopted %s process %p for type %s",
+             preallocated ? "preallocated" : "reused web", p.get(),
+             NS_ConvertUTF16toUTF8(aRemoteType).get()));
     p->mOpener = aOpener;
+    p->mActivateTS = TimeStamp::Now();
     aContentParents.AppendElement(p);
-    p->mActivateTS = TimeStamp::Now();
+    if (preallocated) {
+      p->mRemoteType.Assign(aRemoteType);
+      // Specialize this process for the appropriate eTLD+1
+      Unused << p->SendRemoteType(p->mRemoteType);
+    } else {
+      // we only allow "web" to "web" for security reasons
+      MOZ_RELEASE_ASSERT(p->mRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) &&
+                         aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE));
+    }
     return p.forget();
   }
 
   return nullptr;
 }
 
 /*static*/
 already_AddRefed<ContentParent>
 ContentParent::GetNewOrUsedBrowserProcessInternal(Element* aFrameElement,
                                                   const nsAString& aRemoteType,
                                                   ProcessPriority aPriority,
                                                   ContentParent* aOpener,
                                                   bool aPreferUsed,
                                                   bool aIsSync) {
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("GetNewOrUsedProcess for type %s",
+           NS_ConvertUTF16toUTF8(aRemoteType).get()));
   nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
   uint32_t maxContentParents = GetMaxProcessCount(aRemoteType);
   if (aRemoteType.EqualsLiteral(
           LARGE_ALLOCATION_REMOTE_TYPE)  // We never want to re-use
                                          // Large-Allocation processes.
       && contentParents.Length() >= maxContentParents) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("GetNewOrUsedProcess: returning Large Used process"));
     return GetNewOrUsedBrowserProcessInternal(
         aFrameElement, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), aPriority,
         aOpener, /*aPreferUsed =*/false, aIsSync);
   }
 
   // Let's try and reuse an existing process.
   RefPtr<ContentParent> contentParent = GetUsedBrowserProcess(
       aOpener, aRemoteType, contentParents, maxContentParents, aPreferUsed);
 
   if (contentParent) {
     // We have located a process. It may not have finished initializing,
     // this will be for the caller to handle.
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("GetNewOrUsedProcess: Used process %p (launching %d)",
+             contentParent.get(), contentParent->IsLaunching()));
     return contentParent.forget();
   }
 
   // No reusable process. Let's create and launch one.
   // The life cycle will be set to `LifecycleState::LAUNCHING`.
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("Launching new process immediately for type %s",
+           NS_ConvertUTF16toUTF8(aRemoteType).get()));
+
   contentParent = new ContentParent(aOpener, aRemoteType);
   if (!contentParent->BeginSubprocessLaunch(aIsSync, aPriority)) {
     // Launch aborted because of shutdown. Bailout.
     contentParent->LaunchSubprocessReject();
     return nullptr;
   }
   // Store this process for future reuse.
   contentParents.AppendElement(contentParent);
 
   // Until the new process is ready let's not allow to start up any
   // preallocated processes. The blocker will be removed once we receive
   // the first idle message.
-  PreallocatedProcessManager::AddBlocker(contentParent);
+  contentParent->mIsAPreallocBlocker = true;
+  PreallocatedProcessManager::AddBlocker(aRemoteType, contentParent);
 
   MOZ_ASSERT(contentParent->IsLaunching());
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("GetNewOrUsedProcess: new process %p", contentParent.get()));
   return contentParent.forget();
 }
 
 /*static*/
 RefPtr<ContentParent::LaunchPromise>
 ContentParent::GetNewOrUsedBrowserProcessAsync(Element* aFrameElement,
                                                const nsAString& aRemoteType,
                                                ProcessPriority aPriority,
@@ -1409,16 +1517,18 @@ void ContentParent::Init() {
   RefPtr<GeckoMediaPluginServiceParent> gmps(
       GeckoMediaPluginServiceParent::GetSingleton());
   gmps->UpdateContentProcessGMPCapabilities();
 
   mScriptableHelper = new ScriptableCPInfo(this);
 }
 
 void ContentParent::MaybeAsyncSendShutDownMessage() {
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("MaybeAsyncSendShutDownMessage %p", this));
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!TryToRecycle());
 
 #ifdef DEBUG
   // Calling this below while the lock is acquired will deadlock.
   bool shouldKeepProcessAlive = ShouldKeepProcessAlive();
 #endif
 
@@ -1551,16 +1661,18 @@ void ContentParent::RemoveFromList() {
     if (!sPrivateContent->Length()) {
       delete sPrivateContent;
       sPrivateContent = nullptr;
     }
   }
 }
 
 void ContentParent::MarkAsDead() {
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("Marking ContentProcess %p as dead", this));
   if (!mShutdownPending) {
     RemoveFromList();
   }
 
 #ifdef MOZ_WIDGET_ANDROID
   if (mLifecycleState == LifecycleState::ALIVE) {
     nsCOMPtr<nsIEventTarget> launcherThread(GetIPCLauncher());
     MOZ_ASSERT(launcherThread);
@@ -1714,20 +1826,31 @@ void ContentParent::ActorDestroy(ActorDe
   MOZ_ASSERT(idleService);
   RefPtr<ParentIdleListener> listener;
   for (int32_t i = mIdleListeners.Length() - 1; i >= 0; --i) {
     listener = static_cast<ParentIdleListener*>(mIdleListeners[i].get());
     idleService->RemoveIdleObserver(listener, listener->mTime);
   }
   mIdleListeners.Clear();
 
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("destroying Subprocess in ActorDestroy: ContentParent %p "
+           "mSubprocess %p handle %ld",
+           this, mSubprocess,
+           mSubprocess ? (long)mSubprocess->GetChildProcessHandle() : -1));
   // FIXME (bug 1520997): does this really need an additional dispatch?
   MessageLoop::current()->PostTask(NS_NewRunnableFunction(
-      "DelayedDeleteSubprocessRunnable",
-      [subprocess = mSubprocess] { subprocess->Destroy(); }));
+      "DelayedDeleteSubprocessRunnable", [subprocess = mSubprocess] {
+        MOZ_LOG(
+            ContentParent::GetLog(), LogLevel::Debug,
+            ("destroyed Subprocess in ActorDestroy: Subprocess %p handle %ld",
+             subprocess,
+             subprocess ? (long)subprocess->GetChildProcessHandle() : -1));
+        subprocess->Destroy();
+      }));
   mSubprocess = nullptr;
 
   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
   cpm->RemoveContentProcess(this->ChildID());
 
   if (mDriverCrashGuard) {
     mDriverCrashGuard->NotifyCrashed();
   }
@@ -1751,44 +1874,81 @@ void ContentParent::ActorDestroy(ActorDe
   }
 
   MOZ_DIAGNOSTIC_ASSERT(mGroups.IsEmpty());
 }
 
 void ContentParent::ActorDealloc() { mSelfRef = nullptr; }
 
 bool ContentParent::TryToRecycle() {
+  // We can only do this if we have a separate cache for recycled
+  // 'web' processes, and handle them differently than webIsolated ones
+  if (!mRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE)) {
+    return false;
+  }
   // This life time check should be replaced by a memory health check (memory
   // usage + fragmentation).
+
+  // Note that this is specifically to help with edge cases that rapidly
+  // create-and-destroy processes
   const double kMaxLifeSpan = 5;
+  MOZ_LOG(
+      ContentParent::GetLog(), LogLevel::Debug,
+      ("TryToRecycle ContentProcess %p (%u) with lifespan %f seconds", this,
+       (unsigned int)ChildID(), (TimeStamp::Now() - mActivateTS).ToSeconds()));
+
   if (mShutdownPending || mCalledKillHard || !IsAlive() ||
       !mRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) ||
-      (TimeStamp::Now() - mActivateTS).ToSeconds() > kMaxLifeSpan ||
-      !PreallocatedProcessManager::Provide(this)) {
+      (TimeStamp::Now() - mActivateTS).ToSeconds() > kMaxLifeSpan) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("TryToRecycle did not take ownership of %p", this));
+    // It's possible that the process was already cached via Provide() (such
+    // as from TabDestroyed), and we're being called from a different path,
+    // such as UnregisterRemoveWorkerActor(), and we're now past kMaxLifeSpan
+    // (or some other).  Ensure that if we're going to destroy this process
+    // that we don't have it in the cache.
+    PreallocatedProcessManager::Erase(this);
     return false;
-  }
-
-  // The PreallocatedProcessManager took over the ownership let's not keep a
-  // reference to it, until we don't take it back.
-  RemoveFromList();
+  } else {
+    // This will either cache it and take ownership, realize it was already
+    // cached (due to this being called a second time via a different
+    // path), or it will decide to not take ownership (if it has another
+    // already cached)
+    bool retval = PreallocatedProcessManager::Provide(this);
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Provide did %stake ownership of %p", retval ? "" : "not ", this));
+    if (retval) {
+      // The PreallocatedProcessManager took over the ownership let's not keep a
+      // reference to it
+      RemoveFromList();
+    }
+    return retval;
+  }
   return true;
 }
 
-bool ContentParent::ShouldKeepProcessAlive() {
+bool ContentParent::HasActiveWorkerOrJSPlugin() {
   if (IsForJSPlugin()) {
     return true;
   }
 
   // If we have active workers, we need to stay alive.
   {
     const auto lock = mRemoteWorkerActorData.Lock();
     if (lock->mCount) {
       return true;
     }
   }
+  return false;
+}
+
+bool ContentParent::ShouldKeepProcessAlive() {
+  if (HasActiveWorkerOrJSPlugin()) {
+    return true;
+  }
 
   if (!sBrowserContentParents) {
     return false;
   }
 
   // If we have already been marked as dead, don't prevent shutdown.
   if (!IsAlive()) {
     return false;
@@ -1836,16 +1996,18 @@ void ContentParent::NotifyTabDestroying(
   if (uint32_t(mNumDestroyingTabs) != tabCount) {
     return;
   }
 
   if (ShouldKeepProcessAlive()) {
     return;
   }
 
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("NotifyTabDestroying %p", this));
   if (TryToRecycle()) {
     return;
   }
 
   // We're dying now, so prevent this content process from being
   // recycled during its shutdown procedure.
   MarkAsDead();
   StartForceKillTimer();
@@ -1882,18 +2044,21 @@ void ContentParent::NotifyTabDestroyed(c
   for (auto& permissionRequestParent : parentArray) {
     Unused << PContentPermissionRequestParent::Send__delete__(
         permissionRequestParent);
   }
 
   // There can be more than one PBrowser for a given app process
   // because of popup windows.  When the last one closes, shut
   // us down.
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("NotifyTabDestroyed %p", this));
   if (ManagedPBrowserParent().Count() == 1 && !ShouldKeepProcessAlive() &&
       !TryToRecycle()) {
+    MarkAsDead();
     MaybeAsyncSendShutDownMessage();
   }
 }
 
 TestShellParent* ContentParent::CreateTestShell() {
   return static_cast<TestShellParent*>(SendPTestShellConstructor());
 }
 
@@ -2088,28 +2253,41 @@ bool ContentParent::BeginSubprocessLaunc
   return mSubprocess->AsyncLaunch(std::move(extraArgs));
 }
 
 void ContentParent::LaunchSubprocessReject() {
   NS_ERROR("failed to launch child in the parent");
   // Now that communication with the child is complete, we can cleanup
   // the preference serializer.
   mPrefSerializer = nullptr;
-  PreallocatedProcessManager::RemoveBlocker(this);
+  if (mIsAPreallocBlocker) {
+    PreallocatedProcessManager::RemoveBlocker(mRemoteType, this);
+    mIsAPreallocBlocker = false;
+  }
   MarkAsDead();
 }
 
 bool ContentParent::LaunchSubprocessResolve(bool aIsSync,
                                             ProcessPriority aPriority) {
   AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER);
   // Now that communication with the child is complete, we can cleanup
   // the preference serializer.
   mPrefSerializer = nullptr;
 
   const auto launchResumeTS = TimeStamp::Now();
+#ifdef MOZ_GECKO_PROFILER
+  if (profiler_thread_is_being_profiled()) {
+    nsPrintfCString marker("Process start%s for %u",
+                           mIsAPreallocBlocker ? " (immediate)" : "",
+                           (unsigned int)ChildID());
+    PROFILER_ADD_MARKER_WITH_PAYLOAD(
+        mIsAPreallocBlocker ? "Process Immediate Launch" : "Process Launch",
+        DOM, TextMarkerPayload, (marker, mLaunchTS, launchResumeTS));
+  }
+#endif
 
   if (!sCreatedFirstContentProcess) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "ipc:first-content-process-created", nullptr);
     sCreatedFirstContentProcess = true;
   }
 
   base::ProcessId procId =
@@ -2205,16 +2383,17 @@ RefPtr<ContentParent::LaunchPromise> Con
 ContentParent::ContentParent(ContentParent* aOpener,
                              const nsAString& aRemoteType, int32_t aJSPluginID)
     : mSelfRef(nullptr),
       mSubprocess(nullptr),
       mLaunchTS(TimeStamp::Now()),
       mLaunchYieldTS(mLaunchTS),
       mActivateTS(mLaunchTS),
       mOpener(aOpener),
+      mIsAPreallocBlocker(false),
       mRemoteType(aRemoteType),
       mChildID(gContentChildID++),
       mGeolocationWatchID(-1),
       mJSPluginID(aJSPluginID),
       mRemoteWorkerActorData("ContentParent::mRemoteWorkerActorData"),
       mNumDestroyingTabs(0),
       mLifecycleState(LifecycleState::LAUNCHING),
       mIsForBrowser(!mRemoteType.IsEmpty()),
@@ -2247,46 +2426,65 @@ ContentParent::ContentParent(ContentPare
   // a deadlock with the plugin process when sending CPOWs.
   GetIPCChannel()->SetChannelFlags(
       MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   bool isFile = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
   mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, isFile);
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("CreateSubprocess: ContentParent %p mSubprocess %p handle %ld", this,
+           mSubprocess,
+           mSubprocess ? (long)mSubprocess->GetChildProcessHandle() : -1));
 }
 
 ContentParent::~ContentParent() {
   if (mForceKillTimer) {
     mForceKillTimer->Cancel();
   }
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
+  if (mIsAPreallocBlocker) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Removing blocker on ContentProcess destruction"));
+    PreallocatedProcessManager::RemoveBlocker(mRemoteType, this);
+    mIsAPreallocBlocker = false;
+  }
+
   // We should be removed from all these lists in ActorDestroy.
   MOZ_ASSERT(!sPrivateContent || !sPrivateContent->Contains(this));
   if (IsForJSPlugin()) {
     MOZ_ASSERT(!sJSPluginContentParents ||
                !sJSPluginContentParents->Get(mJSPluginID));
   } else {
     MOZ_ASSERT(!sBrowserContentParents ||
                !sBrowserContentParents->Contains(mRemoteType) ||
-               !sBrowserContentParents->Get(mRemoteType)->Contains(this));
+               !sBrowserContentParents->Get(mRemoteType)->Contains(this) ||
+               sCanLaunchSubprocesses ==
+                   false);  // aka in shutdown - avoid timing issues
   }
 
   // Normally mSubprocess is destroyed in ActorDestroy, but that won't
   // happen if the process wasn't launched or if it failed to launch.
   if (mSubprocess) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+            ("DestroySubprocess: ContentParent %p mSubprocess %p handle %ld",
+             this, mSubprocess,
+             mSubprocess ? (long)mSubprocess->GetChildProcessHandle() : -1));
     mSubprocess->Destroy();
   }
 }
 
 bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
   XPCOMInitData xpcomInit;
 
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("ContentParent::InitInternal: %p", (void*)this));
   nsCOMPtr<nsIIOService> io(do_GetIOService());
   MOZ_ASSERT(io, "No IO service?");
   DebugOnly<nsresult> rv = io->GetOffline(&xpcomInit.isOffline());
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
 
   rv = io->GetConnectivity(&xpcomInit.isConnected());
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting connectivity?");
 
@@ -2856,17 +3054,20 @@ mozilla::ipc::IPCResult ContentParent::R
 #endif
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvFirstIdle() {
   // When the ContentChild goes idle, it sends us a FirstIdle message
   // which we use as a good time to signal the PreallocatedProcessManager
   // that it can start allocating processes from now on.
-  PreallocatedProcessManager::RemoveBlocker(this);
+  if (mIsAPreallocBlocker) {
+    PreallocatedProcessManager::RemoveBlocker(mRemoteType, this);
+    mIsAPreallocBlocker = false;
+  }
   return IPC_OK();
 }
 
 // We want ContentParent to show up in CC logs for debugging purposes, but we
 // don't actually cycle collect it.
 NS_IMPL_CYCLE_COLLECTION_0(ContentParent)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ContentParent)
@@ -3364,16 +3565,20 @@ void ContentParent::KillHard(const char*
   }
 
   if (!KillProcess(otherProcessHandle, base::PROCESS_END_KILLED_BY_USER,
                    false)) {
     NS_WARNING("failed to kill subprocess!");
   }
 
   if (mSubprocess) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+            ("KillHard Subprocess: ContentParent %p mSubprocess %p handle %ld",
+             this, mSubprocess,
+             mSubprocess ? (long)mSubprocess->GetChildProcessHandle() : -1));
     mSubprocess->SetAlreadyDead();
   }
 
   // EnsureProcessTerminated has responsibilty for closing otherProcessHandle.
   XRE_GetIOMessageLoop()->PostTask(
       NewRunnableFunction("EnsureProcessTerminatedRunnable",
                           &ProcessWatcher::EnsureProcessTerminated,
                           otherProcessHandle, /*force=*/true));
@@ -5984,18 +6189,21 @@ void ContentParent::UnregisterRemoveWork
   {
     auto lock = mRemoteWorkerActorData.Lock();
     if (--lock->mCount) {
       return;
     }
   }
 
   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
+          ("UnregisterRemoveWorkerActor %p", this));
   if (!cpm->GetBrowserParentCountByProcessId(ChildID()) &&
       !ShouldKeepProcessAlive() && !TryToRecycle()) {
+    MarkAsDead();
     MaybeAsyncSendShutDownMessage();
   }
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvWindowClose(
     const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller) {
   if (aContext.IsNullOrDiscarded()) {
     MOZ_LOG(
@@ -6375,17 +6583,17 @@ mozilla::ipc::IPCResult ContentParent::R
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvHistoryGo(
     const MaybeDiscarded<BrowsingContext>& aContext, int32_t aOffset,
     HistoryGoResolver&& aResolveRequestedIndex) {
   if (!aContext.IsDiscarded()) {
     nsSHistory* shistory =
-      static_cast<nsSHistory*>(aContext.get_canonical()->GetSessionHistory());
+        static_cast<nsSHistory*>(aContext.get_canonical()->GetSessionHistory());
     nsTArray<nsSHistory::LoadEntryResult> loadResults;
     nsresult rv = shistory->GotoIndex(aOffset, loadResults);
     if (NS_FAILED(rv)) {
       return IPC_FAIL(this, "GotoIndex failed");
     }
     aResolveRequestedIndex(shistory->GetRequestedIndex());
     shistory->LoadURIs(loadResults);
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -49,25 +49,26 @@
 
 #define CHILD_PROCESS_SHUTDOWN_MESSAGE \
   NS_LITERAL_STRING("child-process-shutdown")
 
 // These must match the similar ones in E10SUtils.jsm and ProcInfo.h.
 // Process names as reported by about:memory are defined in
 // ContentChild:RecvRemoteType.  Add your value there too or it will be called
 // "Web Content".
+#define PREALLOC_REMOTE_TYPE "prealloc"
 #define DEFAULT_REMOTE_TYPE "web"
-#define FISSION_WEB_REMOTE_TYPE "webIsolated"
 #define FILE_REMOTE_TYPE "file"
 #define EXTENSION_REMOTE_TYPE "extension"
 #define PRIVILEGEDABOUT_REMOTE_TYPE "privilegedabout"
 #define PRIVILEGEDMOZILLA_REMOTE_TYPE "privilegedmozilla"
+
+// These must start with the DEFAULT_REMOTE_TYPE above.
+#define FISSION_WEB_REMOTE_TYPE "webIsolated"
 #define WITH_COOP_COEP_REMOTE_TYPE_PREFIX "webCOOP+COEP="
-
-// This must start with the DEFAULT_REMOTE_TYPE above.
 #define LARGE_ALLOCATION_REMOTE_TYPE "webLargeAllocation"
 
 class nsConsoleService;
 class nsIContentProcessInfo;
 class nsICycleCollectorLogSink;
 class nsIDumpGCAndCCLogsCallback;
 class nsIRemoteTab;
 class nsITimer;
@@ -160,16 +161,18 @@ class ContentParent final
 
  public:
   using LaunchError = mozilla::ipc::LaunchError;
   using LaunchPromise =
       mozilla::MozPromise<RefPtr<ContentParent>, LaunchError, false>;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTPARENT_IID)
 
+  static LogModule* GetLog();
+
   /**
    * Create a subprocess suitable for use later as a content process.
    */
   static RefPtr<LaunchPromise> PreallocateProcess();
 
   /**
    * Start up the content-process machinery.  This might include
    * scheduling pre-launch tasks.
@@ -793,16 +796,21 @@ class ContentParent final
 
   /**
    * Removing it from the static array so it won't be returned for new tabs in
    * GetNewOrUsedBrowserProcess.
    */
   void RemoveFromList();
 
   /**
+   * Return if the process has an active worker or JSPlugin
+   */
+  bool HasActiveWorkerOrJSPlugin();
+
+  /**
    * 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();
 
   /**
    * Mark this ContentParent as "troubled". This means that it is still alive,
    * but it won't be returned for new tabs in GetNewOrUsedBrowserProcess.
@@ -1348,16 +1356,18 @@ class ContentParent final
   // details.
 
   GeckoChildProcessHost* mSubprocess;
   const TimeStamp mLaunchTS;  // used to calculate time to start content process
   TimeStamp mLaunchYieldTS;   // used to calculate async launch main thread time
   TimeStamp mActivateTS;
   ContentParent* mOpener;
 
+  bool mIsAPreallocBlocker;  // We called AddBlocker for this ContentParent
+
   nsString mRemoteType;
 
   ContentParentId mChildID;
   int32_t mGeolocationWatchID;
 
   // This contains the id for the JS plugin (@see nsFakePluginTag) if this is
   // the ContentParent for a process containing iframes for that JS plugin. If
   // this is not a ContentParent for a JS plugin then it contains the value
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -6,113 +6,137 @@
 
 #include "mozilla/PreallocatedProcessManager.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_fission.h"
 #include "nsIPropertyBag2.h"
 #include "ProcessPriorityManager.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIXULRuntime.h"
+#include <deque>
 
 using namespace mozilla::hal;
 using namespace mozilla::dom;
 
 namespace mozilla {
 /**
  * This singleton class implements the static methods on
  * PreallocatedProcessManager.
  */
 class PreallocatedProcessManagerImpl final : public nsIObserver {
+  friend class PreallocatedProcessManager;
+
  public:
   static PreallocatedProcessManagerImpl* Singleton();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   // See comments on PreallocatedProcessManager for these methods.
   void AddBlocker(ContentParent* aParent);
   void RemoveBlocker(ContentParent* aParent);
-  already_AddRefed<ContentParent> Take();
+  already_AddRefed<ContentParent> Take(const nsAString& aRemoteType);
   bool Provide(ContentParent* aParent);
+  void Erase(ContentParent* aParent);
 
  private:
   static const char* const kObserverTopics[];
 
-  static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
+  static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
 
   PreallocatedProcessManagerImpl();
   ~PreallocatedProcessManagerImpl();
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
 
   void Init();
 
   bool CanAllocate();
   void AllocateAfterDelay();
   void AllocateOnIdle();
   void AllocateNow();
 
   void RereadPrefs();
-  void Enable();
+  void Enable(uint32_t aProcesses);
   void Disable();
-  void CloseProcess();
+  void CloseProcesses();
 
   void ObserveProcessShutdown(nsISupports* aSubject);
 
+  bool IsEmpty() const {
+    return mPreallocatedProcesses.empty() && !mLaunchInProgress;
+  }
+
   bool mEnabled;
   bool mShutdown;
   bool mLaunchInProgress;
-  RefPtr<ContentParent> mPreallocatedProcess;
-  nsTHashtable<nsUint64HashKey> mBlockers;
+  uint32_t mNumberPreallocs;
+  std::deque<RefPtr<ContentParent>> mPreallocatedProcesses;
+  RefPtr<ContentParent> mPreallocatedE10SProcess;  // There can be only one
+  // Even if we have multiple PreallocatedProcessManagerImpls, we'll have
+  // one blocker counter
+  static uint32_t sNumBlockers;
+  TimeStamp mBlockingStartTime;
+};
 
-  bool IsEmpty() const { return !mPreallocatedProcess && !mLaunchInProgress; }
-};
+/* static */
+uint32_t PreallocatedProcessManagerImpl::sNumBlockers = 0;
 
 const char* const PreallocatedProcessManagerImpl::kObserverTopics[] = {
     "ipc:content-shutdown",
     "memory-pressure",
     "profile-change-teardown",
     NS_XPCOM_SHUTDOWN_OBSERVER_ID,
 };
 
 /* static */
 StaticRefPtr<PreallocatedProcessManagerImpl>
     PreallocatedProcessManagerImpl::sSingleton;
 
 /* static */
 PreallocatedProcessManagerImpl* PreallocatedProcessManagerImpl::Singleton() {
   MOZ_ASSERT(NS_IsMainThread());
   if (!sSingleton) {
-    sSingleton = new PreallocatedProcessManagerImpl();
+    sSingleton = new PreallocatedProcessManagerImpl;
     sSingleton->Init();
-    ClearOnShutdown(&sSingleton);
+    ClearOnShutdown(&sSingleton,
+                    ShutdownPhase::ShutdownPostLastCycleCollection);
   }
-
   return sSingleton;
+  //  PreallocatedProcessManagers live until shutdown
 }
 
 NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
 
 PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
-    : mEnabled(false), mShutdown(false), mLaunchInProgress(false) {}
+    : mEnabled(false),
+      mShutdown(false),
+      mLaunchInProgress(false),
+      mNumberPreallocs(1) {}
 
 PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() {
   // This shouldn't happen, because the promise callbacks should
   // hold strong references, but let't make absolutely sure:
   MOZ_RELEASE_ASSERT(!mLaunchInProgress);
 }
 
 void PreallocatedProcessManagerImpl::Init() {
   Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
   // We have to respect processCount at all time. This is especially important
   // for testing.
   Preferences::AddStrongObserver(this, "dom.ipc.processCount");
+  // A StaticPref, but we need to adjust the number of preallocated processes
+  // if the value goes up or down, so we need to run code on change.
+  Preferences::AddStrongObserver(this,
+                                 "dom.ipc.processPrelaunch.fission.number");
+
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   MOZ_ASSERT(os);
   for (auto topic : kObserverTopics) {
     os->AddObserver(this, topic, /* ownsWeak */ false);
   }
   RereadPrefs();
 }
 
@@ -124,112 +148,164 @@ PreallocatedProcessManagerImpl::Observe(
     ObserveProcessShutdown(aSubject);
   } else if (!strcmp("nsPref:changed", aTopic)) {
     // The only other observer we registered was for our prefs.
     RereadPrefs();
   } else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) ||
              !strcmp("profile-change-teardown", aTopic)) {
     Preferences::RemoveObserver(this, "dom.ipc.processPrelaunch.enabled");
     Preferences::RemoveObserver(this, "dom.ipc.processCount");
+    Preferences::RemoveObserver(this,
+                                "dom.ipc.processPrelaunch.fission.number");
+
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     MOZ_ASSERT(os);
     for (auto topic : kObserverTopics) {
       os->RemoveObserver(this, topic);
     }
     // Let's prevent any new preallocated processes from starting. ContentParent
     // will handle the shutdown of the existing process and the
-    // mPreallocatedProcess reference will be cleared by the ClearOnShutdown of
-    // the manager singleton.
+    // mPreallocatedProcesses reference will be cleared by the ClearOnShutdown
+    // of the manager singleton.
     mShutdown = true;
   } else if (!strcmp("memory-pressure", aTopic)) {
-    CloseProcess();
+    CloseProcesses();
   } else {
     MOZ_ASSERT_UNREACHABLE("Unknown topic");
   }
 
   return NS_OK;
 }
 
 void PreallocatedProcessManagerImpl::RereadPrefs() {
   if (mozilla::BrowserTabsRemoteAutostart() &&
       Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) {
-    Enable();
+    int32_t number = 1;
+    if (StaticPrefs::fission_autostart()) {
+      number = StaticPrefs::dom_ipc_processPrelaunch_fission_number();
+    }
+    if (number >= 0) {
+      Enable(number);
+      // We have one prealloc queue for all types except File now
+      if (static_cast<uint64_t>(number) < mPreallocatedProcesses.size()) {
+        CloseProcesses();
+      }
+    }
   } else {
     Disable();
   }
-
-  if (ContentParent::IsMaxProcessCountReached(
-          NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE))) {
-    CloseProcess();
-  }
 }
 
-already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take() {
+already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take(
+    const nsAString& aRemoteType) {
   if (!mEnabled || mShutdown) {
     return nullptr;
   }
-
-  if (mPreallocatedProcess) {
-    // The preallocated process is taken. Let's try to start up a new one soon.
-    ProcessPriorityManager::SetProcessPriority(mPreallocatedProcess,
+  RefPtr<ContentParent> process;
+  if (aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE)) {
+    // we can recycle processes via Provide() for e10s only
+    process = mPreallocatedE10SProcess.forget();
+    if (process) {
+      MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+              ("Reuse " DEFAULT_REMOTE_TYPE " process %p",
+               mPreallocatedE10SProcess.get()));
+    }
+  }
+  if (!process && !mPreallocatedProcesses.empty()) {
+    process = mPreallocatedProcesses.front().forget();
+    mPreallocatedProcesses.pop_front();  // holds a nullptr
+    // We took a preallocated process. Let's try to start up a new one
+    // soon.
+    AllocateOnIdle();
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Use " PREALLOC_REMOTE_TYPE " process %p", process.get()));
+  }
+  if (process) {
+    ProcessPriorityManager::SetProcessPriority(process,
                                                PROCESS_PRIORITY_FOREGROUND);
-    AllocateOnIdle();
   }
-
-  return mPreallocatedProcess.forget();
+  return process.forget();
 }
 
 bool PreallocatedProcessManagerImpl::Provide(ContentParent* aParent) {
+  MOZ_DIAGNOSTIC_ASSERT(
+      aParent->GetRemoteType().EqualsLiteral(DEFAULT_REMOTE_TYPE));
+
   // This will take the already-running process even if there's a
   // launch in progress; if that process hasn't been taken by the
   // time the launch completes, the new process will be shut down.
-  if (mEnabled && !mShutdown && !mPreallocatedProcess) {
-    mPreallocatedProcess = aParent;
+  if (mEnabled && !mShutdown && !mPreallocatedE10SProcess) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Store for reuse " DEFAULT_REMOTE_TYPE " process %p", aParent));
+    ProcessPriorityManager::SetProcessPriority(aParent,
+                                               PROCESS_PRIORITY_BACKGROUND);
+    mPreallocatedE10SProcess = aParent;
+    return true;
   }
 
   // We might get a call from both NotifyTabDestroying and NotifyTabDestroyed
   // with the same ContentParent. Returning true here for both calls is
   // important to avoid the cached process to be destroyed.
-  return aParent == mPreallocatedProcess;
+  return aParent == mPreallocatedE10SProcess;
 }
 
-void PreallocatedProcessManagerImpl::Enable() {
+void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) {
+  // Ensure this ContentParent isn't cached
+  if (mPreallocatedE10SProcess == aParent) {
+    mPreallocatedE10SProcess = nullptr;
+  }
+}
+
+void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) {
+  mNumberPreallocs = aProcesses;
   if (mEnabled) {
     return;
   }
 
   mEnabled = true;
   AllocateAfterDelay();
 }
 
 void PreallocatedProcessManagerImpl::AddBlocker(ContentParent* aParent) {
-  uint64_t childID = aParent->ChildID();
-  MOZ_ASSERT(!mBlockers.Contains(childID));
-  mBlockers.PutEntry(childID);
+  if (sNumBlockers == 0) {
+    mBlockingStartTime = TimeStamp::Now();
+  }
+  sNumBlockers++;
 }
 
 void PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent) {
-  uint64_t childID = aParent->ChildID();
   // This used to assert that the blocker existed, but preallocated
   // processes aren't blockers anymore because it's not useful and
   // interferes with async launch, and it's simpler if content
   // processes don't need to remember whether they were preallocated.
   // (And preallocated processes can't AddBlocker when taken, because
   // it's possible for a short-lived process to be recycled through
   // Provide() and Take() before reaching RecvFirstIdle.)
-  mBlockers.RemoveEntry(childID);
-  if (IsEmpty() && mBlockers.IsEmpty()) {
-    AllocateAfterDelay();
+
+  MOZ_DIAGNOSTIC_ASSERT(sNumBlockers > 0);
+  sNumBlockers--;
+  if (sNumBlockers == 0) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Blocked preallocation for %fms",
+             (TimeStamp::Now() - mBlockingStartTime).ToMilliseconds()));
+    PROFILER_ADD_TEXT_MARKER(
+        "Process", NS_LITERAL_CSTRING("Blocked preallocation"),
+        JS::ProfilingCategoryPair::DOM, mBlockingStartTime, TimeStamp::Now());
+    if (IsEmpty()) {
+      AllocateAfterDelay();
+    }
   }
 }
 
 bool PreallocatedProcessManagerImpl::CanAllocate() {
-  return mEnabled && mBlockers.IsEmpty() && IsEmpty() && !mShutdown &&
-         !ContentParent::IsMaxProcessCountReached(
-             NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
+  return mEnabled && sNumBlockers == 0 &&
+         mPreallocatedProcesses.size() < mNumberPreallocs && !mShutdown &&
+         (StaticPrefs::fission_autostart() ||
+          !ContentParent::IsMaxProcessCountReached(
+              NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)));
 }
 
 void PreallocatedProcessManagerImpl::AllocateAfterDelay() {
   if (!mEnabled) {
     return;
   }
 
   NS_DelayedDispatchToCurrentThread(
@@ -246,92 +322,133 @@ void PreallocatedProcessManagerImpl::All
   NS_DispatchToCurrentThreadQueue(
       NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this,
                         &PreallocatedProcessManagerImpl::AllocateNow),
       EventQueuePriority::Idle);
 }
 
 void PreallocatedProcessManagerImpl::AllocateNow() {
   if (!CanAllocate()) {
-    if (mEnabled && !mShutdown && IsEmpty() && !mBlockers.IsEmpty()) {
+    if (mEnabled && !mShutdown && IsEmpty() && sNumBlockers > 0) {
       // If it's too early to allocate a process let's retry later.
       AllocateAfterDelay();
     }
     return;
   }
 
   RefPtr<PreallocatedProcessManagerImpl> self(this);
   mLaunchInProgress = true;
 
   ContentParent::PreallocateProcess()->Then(
       GetCurrentThreadSerialEventTarget(), __func__,
 
       [self, this](const RefPtr<ContentParent>& process) {
         mLaunchInProgress = false;
         if (CanAllocate()) {
-          mPreallocatedProcess = process;
+          // slight perf reason for push_back - while the cpu cache
+          // probably has stack/etc associated with the most recent
+          // process created, we don't know that it has finished startup.
+          // If we added it to the queue on completion of startup, we
+          // could push_front it, but that would require a bunch more
+          // logic.
+          mPreallocatedProcesses.push_back(process);
+          MOZ_LOG(
+              ContentParent::GetLog(), LogLevel::Debug,
+              ("Preallocated = %lu of %d processes",
+               (unsigned long)mPreallocatedProcesses.size(), mNumberPreallocs));
+
+          // Continue prestarting processes if needed
+          if (mPreallocatedProcesses.size() < mNumberPreallocs) {
+            AllocateOnIdle();
+          }
         } else {
           process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
         }
       },
 
       [self, this](ContentParent::LaunchError err) {
         mLaunchInProgress = false;
       });
 }
 
 void PreallocatedProcessManagerImpl::Disable() {
   if (!mEnabled) {
     return;
   }
 
   mEnabled = false;
-  CloseProcess();
+  CloseProcesses();
 }
 
-void PreallocatedProcessManagerImpl::CloseProcess() {
-  if (mPreallocatedProcess) {
-    mPreallocatedProcess->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
-    mPreallocatedProcess = nullptr;
+void PreallocatedProcessManagerImpl::CloseProcesses() {
+  while (!mPreallocatedProcesses.empty()) {
+    RefPtr<ContentParent> process(mPreallocatedProcesses.front().forget());
+    mPreallocatedProcesses.pop_front();
+    process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
+    // drop ref and let it free
+  }
+  if (mPreallocatedE10SProcess) {
+    mPreallocatedE10SProcess->ShutDownProcess(
+        ContentParent::SEND_SHUTDOWN_MESSAGE);
+    mPreallocatedE10SProcess = nullptr;
   }
 }
 
 void PreallocatedProcessManagerImpl::ObserveProcessShutdown(
     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);
   NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN);
 
-  if (mPreallocatedProcess && childID == mPreallocatedProcess->ChildID()) {
-    mPreallocatedProcess = nullptr;
+  for (auto it = mPreallocatedProcesses.begin();
+       it != mPreallocatedProcesses.end(); it++) {
+    if (childID == (*it)->ChildID()) {
+      mPreallocatedProcesses.erase(it);
+      break;
+    }
   }
-
-  mBlockers.RemoveEntry(childID);
+  // The ContentParent is responsible for removing itself as a blocker
 }
 
 inline PreallocatedProcessManagerImpl* GetPPMImpl() {
   return PreallocatedProcessManagerImpl::Singleton();
 }
 
 /* static */
-void PreallocatedProcessManager::AddBlocker(ContentParent* aParent) {
+void PreallocatedProcessManager::AddBlocker(const nsAString& aRemoteType,
+                                            ContentParent* aParent) {
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("AddBlocker: %s %p (sNumBlockers=%d)",
+           NS_ConvertUTF16toUTF8(aRemoteType).get(), aParent,
+           PreallocatedProcessManagerImpl::sNumBlockers));
   GetPPMImpl()->AddBlocker(aParent);
 }
 
 /* static */
-void PreallocatedProcessManager::RemoveBlocker(ContentParent* aParent) {
+void PreallocatedProcessManager::RemoveBlocker(const nsAString& aRemoteType,
+                                               ContentParent* aParent) {
+  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+          ("RemoveBlocker: %s %p (sNumBlockers=%d)",
+           NS_ConvertUTF16toUTF8(aRemoteType).get(), aParent,
+           PreallocatedProcessManagerImpl::sNumBlockers));
   GetPPMImpl()->RemoveBlocker(aParent);
 }
 
 /* static */
-already_AddRefed<ContentParent> PreallocatedProcessManager::Take() {
-  return GetPPMImpl()->Take();
+already_AddRefed<ContentParent> PreallocatedProcessManager::Take(
+    const nsAString& aRemoteType) {
+  return GetPPMImpl()->Take(aRemoteType);
 }
 
 /* static */
 bool PreallocatedProcessManager::Provide(ContentParent* aParent) {
   return GetPPMImpl()->Provide(aParent);
 }
 
+/* static */
+void PreallocatedProcessManager::Erase(ContentParent* aParent) {
+  GetPPMImpl()->Erase(aParent);
+}
+
 }  // namespace mozilla
--- a/dom/ipc/PreallocatedProcessManager.h
+++ b/dom/ipc/PreallocatedProcessManager.h
@@ -32,33 +32,40 @@ class PreallocatedProcessManager final {
   typedef mozilla::dom::ContentParent ContentParent;
 
  public:
   /**
    * Before first paint we don't want to allocate any processes in the
    * background. To avoid that, the PreallocatedProcessManager won't start up
    * any processes while there is a blocker active.
    */
-  static void AddBlocker(ContentParent* aParent);
-  static void RemoveBlocker(ContentParent* aParent);
+  static void AddBlocker(const nsAString& aRemoteType, ContentParent* aParent);
+  static void RemoveBlocker(const nsAString& aRemoteType,
+                            ContentParent* aParent);
 
   /**
-   * Take the preallocated process, if we have one.  If we don't have one, this
-   * returns null.
-   *
-   * If you call Take() twice in a row, the second call is guaranteed to return
-   * null.
+   * Take the preallocated process, if we have one, or a recycled
+   * process cached via Provide().  Currently we only cache
+   * DEFAULT_REMOTE_TYPE ('web') processes and only reuse them for that
+   * type.  If we don't have a process to return (cached or preallocated),
+   * this returns null.
    *
-   * After you Take() the preallocated process, you need to call one of the
-   * Allocate* functions (or change the dom.ipc.processPrelaunch pref from
-   * false to true) before we'll create a new process.
+   * If we use a preallocated process, it will schedule the start of
+   * another on Idle (AllocateOnIdle()).
    */
-  static already_AddRefed<ContentParent> Take();
+  static already_AddRefed<ContentParent> Take(const nsAString& aRemoteType);
 
+  /**
+   * Cache a process (currently only DEFAULT_REMOTE_TYPE) for reuse later
+   * via Take().  Returns true if we cached the process, and false if
+   * another process is already cached (so the caller knows to destroy it).
+   * This takes a reference to the ContentParent if it is cached.
+   */
   static bool Provide(ContentParent* aParent);
+  static void Erase(ContentParent* aParent);
 
  private:
   PreallocatedProcessManager();
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager);
 };
 
 }  // namespace mozilla
 
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -803,16 +803,18 @@ void ParticularProcessPriorityManager::T
   }
 
   ResetPriority();
 }
 
 void ParticularProcessPriorityManager::ShutDown() {
   MOZ_ASSERT(mContentParent);
 
+  LOGP("shutdown for %p (mContentParent %p)", this, mContentParent);
+
   UnregisterWakeLockObserver(this);
 
   if (mResetPriorityTimer) {
     mResetPriorityTimer->Cancel();
     mResetPriorityTimer = nullptr;
   }
 
   mContentParent = nullptr;
@@ -937,16 +939,17 @@ void ProcessPriorityManager::Init() {
   ProcessPriorityManagerImpl::StaticInit();
   ProcessPriorityManagerChild::StaticInit();
 }
 
 /* static */
 void ProcessPriorityManager::SetProcessPriority(ContentParent* aContentParent,
                                                 ProcessPriority aPriority) {
   MOZ_ASSERT(aContentParent);
+  MOZ_ASSERT(aContentParent->Pid() != -1);
 
   ProcessPriorityManagerImpl* singleton =
       ProcessPriorityManagerImpl::GetSingleton();
   if (singleton) {
     singleton->SetProcessPriority(aContentParent, aPriority);
   }
 }
 
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -1899,25 +1899,32 @@
 #endif
   mirror: always
 
 - name: dom.ipc.tabs.disabled
   type: bool
   value: false
   mirror: always
 
-# Process launch delay (im milliseconds).
+# Process launch delay (in milliseconds).
 - name: dom.ipc.processPrelaunch.delayMs
   type: uint32_t
 # This number is fairly arbitrary ... the intention is to put off
 # launching another app process until the last one has finished
 # loading its content, to reduce CPU/memory/IO contention.
   value: 1000
   mirror: always
 
+# Process preallocation cache
+# Only used in fission; in e10s we use 1 always
+- name: dom.ipc.processPrelaunch.fission.number
+  type: uint32_t
+  value: 3
+  mirror: always
+
 - name: dom.ipc.processPriorityManager.enabled
   type: bool
   value: false
   mirror: always
 
 - name: dom.ipc.processPriorityManager.testMode
   type: bool
   value: false
--- a/widget/ProcInfo.h
+++ b/widget/ProcInfo.h
@@ -13,17 +13,18 @@
 
 namespace mozilla {
 
 namespace ipc {
 class GeckoChildProcessHost;
 }
 
 // Process types. When updating this enum, please make sure to update
-// WebIDLProcType and ProcTypeToWebIDL to mirror the changes.
+// WebIDLProcType, ChromeUtils::RequestProcInfo and ProcTypeToWebIDL to
+// mirror the changes.
 enum class ProcType {
   // These must match the ones in ContentParent.h, and E10SUtils.jsm
   Web,
   WebIsolated,
   File,
   Extension,
   PrivilegedAbout,
   PrivilegedMozilla,
@@ -37,16 +38,17 @@ enum class ProcType {
   GPU,
   VR,
   RDD,
   Socket,
   RemoteSandboxBroker,
 #ifdef MOZ_ENABLE_FORKSERVER
   ForkServer,
 #endif
+  Preallocated,
   // Unknown type of process
   Unknown,
   Max = Unknown,
 };
 
 struct ThreadInfo {
   // Thread Id.
   base::ProcessId tid = 0;