Bug 1602757: add preallocation cache for webIsolated (fission) processes r=nika,smaug
☠☠ backed out by f519fea12dfc ☠ ☠
authorRandell Jesup <rjesup@wgate.com>
Thu, 28 May 2020 21:37:03 +0000
changeset 596595 5134aaa3278ebfb0e20bf35cc9f5b3aedec628f8
parent 596594 4a1540970cae5d08ee4e93711a84deeff32c4a25
child 596596 a34288a6f60c6e99af1b9c28bdb474291372381b
push id13186
push userffxbld-merge
push dateMon, 01 Jun 2020 09:52:46 +0000
treeherdermozilla-beta@3e7c70a1e4a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika, smaug
bugs1602757
milestone78.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 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"
@@ -2540,31 +2541,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; }
 
--- 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>
@@ -344,16 +347,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)
 
@@ -470,16 +478,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();
@@ -627,18 +639,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
@@ -707,16 +721,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));
 }
@@ -724,17 +740,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);
@@ -749,37 +765,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();
   }
@@ -834,92 +880,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,
@@ -1407,16 +1515,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
 
@@ -1549,16 +1659,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);
@@ -1712,20 +1824,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();
   }
@@ -1749,44 +1872,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;
@@ -1834,16 +1994,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();
@@ -1880,18 +2042,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());
 }
 
@@ -2086,28 +2251,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 =
@@ -2203,16 +2381,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()),
@@ -2245,46 +2424,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?");
 
@@ -2860,17 +3058,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)
@@ -3358,16 +3559,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));
@@ -5988,18 +6193,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(
--- 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;
@@ -156,16 +157,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.
@@ -782,16 +785,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.
@@ -1338,16 +1346,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;