Bug 1602757: Fix e10s process reuse cache and add debugs r=nika
☠☠ backed out by 7aaac87105b7 ☠ ☠
authorRandell Jesup <rjesup@wgate.com>
Wed, 20 May 2020 22:38:09 +0000
changeset 531339 d9b4b2cffaa55e510a9901951baa665aa64114bb
parent 531338 33515632a7dbf17e868ee383a9c0b0919b4abbef
child 531340 7f114ce6b98d43c4dfd31cc509b115a0896b8e3d
push id37438
push userabutkovits@mozilla.com
push dateThu, 21 May 2020 09:36:57 +0000
treeherdermozilla-central@2d00a1a6495c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
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: Fix e10s process reuse cache and add debugs r=nika Differential Revision: https://phabricator.services.mozilla.com/D72553
dom/ipc/ContentParent.cpp
dom/ipc/PreallocatedProcessManager.cpp
dom/ipc/PreallocatedProcessManager.h
dom/ipc/ProcessPriorityManager.cpp
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -480,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();
@@ -763,46 +767,74 @@ uint32_t ContentParent::GetMaxProcessCou
 
 /*static*/
 bool ContentParent::IsMaxProcessCountReached(
     const nsAString& aContentProcessType) {
   return GetPoolSize(aContentProcessType) >=
          GetMaxProcessCount(aContentProcessType);
 }
 
+// Really more ReleaseUnneededProcesses()
 /*static*/
 void ContentParent::ReleaseCachedProcesses() {
+  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, so we need to release all the types of content processes
+#ifdef DEBUG
+  int num = 0;
   for (auto iter = sBrowserContentParents->Iter(); !iter.Done(); iter.Next()) {
     nsTArray<ContentParent*>* contentParents = iter.Data().get();
-    nsTArray<ContentParent*> toRelease;
+    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;
+  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->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) {
-      // 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();
-    }
+  }
+
+  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();
   }
 }
 
 /*static*/
 already_AddRefed<ContentParent> ContentParent::MinTabSelect(
     const nsTArray<ContentParent*>& aContentParents, ContentParent* aOpener,
     int32_t aMaxContentParents) {
   uint32_t maxSelectable =
@@ -905,23 +937,26 @@ already_AddRefed<ContentParent> ContentP
                              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 for type %s",
-             preallocated ? "preallocated" : "reused web",
+            ("Adopted %s process %p for type %s",
+             preallocated ? "preallocated" : "reused web", p.get(),
              NS_ConvertUTF16toUTF8(aRemoteType).get()));
     p->mOpener = aOpener;
-    aContentParents.AppendElement(p);
     p->mActivateTS = TimeStamp::Now();
     if (preallocated) {
+      nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
+      // Store this process for future reuse.
+      contentParents.AppendElement(p);
+
       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));
     }
@@ -943,28 +978,32 @@ ContentParent::GetNewOrUsedBrowserProces
           ("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", contentParent.get()));
     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()));
@@ -980,16 +1019,18 @@ ContentParent::GetNewOrUsedBrowserProces
 
   // 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.
   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,
@@ -1477,16 +1518,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
 
@@ -1619,16 +1662,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);
@@ -1782,20 +1827,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();
   }
@@ -1821,40 +1877,52 @@ 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 process with lifespan %f seconds",
-           (TimeStamp::Now() - mActivateTS).ToSeconds()));
+  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(mRemoteType, 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));
+    return retval;
+  }
   return true;
 }
 
 bool ContentParent::HasActiveWorkerOrJSPlugin() {
   if (IsForJSPlugin()) {
     return true;
   }
 
@@ -1924,16 +1992,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();
@@ -1968,18 +2038,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());
 }
 
@@ -2347,16 +2420,20 @@ 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!");
@@ -2371,22 +2448,28 @@ ContentParent::~ContentParent() {
   // 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;
 
   nsCOMPtr<nsIIOService> io(do_GetIOService());
@@ -3474,16 +3557,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));
@@ -6094,18 +6181,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/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -35,16 +35,17 @@ class PreallocatedProcessManagerImpl fin
   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(const nsAString& aRemoteType);
   bool Provide(ContentParent* aParent);
+  void Erase(ContentParent* aParent);
 
  private:
   static const char* const kObserverTopics[];
 
   static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
 
   PreallocatedProcessManagerImpl();
   ~PreallocatedProcessManagerImpl();
@@ -209,42 +210,53 @@ already_AddRefed<ContentParent> Prealloc
     }
   }
   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);
   }
   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 && !mPreallocatedE10SProcess) {
+    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
+            ("Store for reuse " DEFAULT_REMOTE_TYPE " process %p", aParent));
     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 == mPreallocatedE10SProcess;
 }
 
+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();
@@ -331,19 +343,20 @@ void PreallocatedProcessManagerImpl::All
         if (CanAllocate()) {
           // 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",
-                   mPreallocatedProcesses.size(), mNumberPreallocs));
+          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);
         }
@@ -365,16 +378,21 @@ void PreallocatedProcessManagerImpl::Dis
 
 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;
@@ -417,14 +435,18 @@ void PreallocatedProcessManager::RemoveB
 
 /* static */
 already_AddRefed<ContentParent> PreallocatedProcessManager::Take(
     const nsAString& aRemoteType) {
   return GetPPMImpl()->Take(aRemoteType);
 }
 
 /* static */
-bool PreallocatedProcessManager::Provide(const nsAString& aRemoteType,
-                                         ContentParent* aParent) {
+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
@@ -54,17 +54,18 @@ class PreallocatedProcessManager final {
   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(const nsAString& aRemoteType, ContentParent* aParent);
+  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);
   }
 }