Bug 1423261: Don't allow child processes to delay shutdown endlessly when a shutdown message is received from within a nested event loop. r=jimm
authorStephen A Pohl <spohl.mozilla.bugs@gmail.com>
Wed, 21 Mar 2018 19:22:57 -0400
changeset 409412 ff30955a00d2f6cbba9288b3ba2b7b749a577628
parent 409411 9185d9c69f0ed9ccc1a1d5eacf5beb8df38af060
child 409413 17c6a17df94a52888a467a1e23d939174e21c37b
push id33687
push userapavel@mozilla.com
push dateThu, 22 Mar 2018 09:31:48 +0000
treeherdermozilla-central@7771df14ea18 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1423261
milestone61.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 1423261: Don't allow child processes to delay shutdown endlessly when a shutdown message is received from within a nested event loop. r=jimm
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -539,30 +539,37 @@ StaticAutoPtr<ContentChild::ShutdownCana
 ContentChild::ContentChild()
  : mID(uint64_t(-1))
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
  , mMainChromeTid(0)
  , mMsaaID(0)
 #endif
  , mIsAlive(true)
  , mShuttingDown(false)
+ , mShutdownTimeout(0)
 {
   // This process is a content process, so it's clearly running in
   // multiprocess mode!
   nsDebugImpl::SetMultiprocessMode("Child");
 
   // When ContentChild is created, the observer service does not even exist.
   // When ContentChild::RecvSetXPCOMProcessAttributes is called (the first
   // IPDL call made on this object), shutdown may have already happened. Thus
   // we create a canary here that relies upon getting cleared if shutdown
   // happens without requiring the observer service at this time.
   if (!sShutdownCanary) {
     sShutdownCanary = new ShutdownCanary();
     ClearOnShutdown(&sShutdownCanary, ShutdownPhase::Shutdown);
   }
+  // If a shutdown message is received from within a nested event loop, we set
+  // the timeout for the nested event loop to half the ForceKillTimer timeout
+  // (in ms) to leave enough time to send the FinishShutdown message to the
+  // parent.
+  mShutdownTimeout =
+    Preferences::GetInt("dom.ipc.tabs.shutdownTimeoutSecs", 5) * 1000 / 2;
 }
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4722) /* Silence "destructor never returns" warning */
 #endif
 
 ContentChild::~ContentChild()
@@ -3025,38 +3032,42 @@ ContentChild::RecvShutdown()
 
   ShutdownInternal();
   return IPC_OK();
 }
 
 void
 ContentChild::ShutdownInternal()
 {
+  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCShutdownState"),
+                                     NS_LITERAL_CSTRING("RecvShutdown"));
+
   // If we receive the shutdown message from within a nested event loop, we want
   // to wait for that event loop to finish. Otherwise we could prematurely
   // terminate an "unload" or "pagehide" event handler (which might be doing a
-  // sync XHR, for example).
-  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCShutdownState"),
-                                     NS_LITERAL_CSTRING("RecvShutdown"));
-
-  MOZ_ASSERT(NS_IsMainThread());
-  RefPtr<nsThread> mainThread = nsThreadManager::get().GetCurrentThread();
+  // sync XHR, for example). However, we need to strike a balance and shut down
+  // within a reasonable amount of time (mShutdownTimeout) or the ForceKillTimer
+  // in the parent will execute and kill us hard.
   // Note that we only have to check the recursion count for the current
   // cooperative thread. Since the Shutdown message is not labeled with a
   // SchedulerGroup, there can be no other cooperative threads doing work while
   // we're running.
-  if (mainThread && mainThread->RecursionDepth() > 1) {
+  MOZ_ASSERT(NS_IsMainThread());
+  RefPtr<nsThread> mainThread = nsThreadManager::get().GetCurrentThread();
+  if (mainThread && mainThread->RecursionDepth() > 1 && mShutdownTimeout > 0) {
     // We're in a nested event loop. Let's delay for an arbitrary period of
     // time (100ms) in the hopes that the event loop will have finished by
     // then.
+    int32_t delay = 100;
     MessageLoop::current()->PostDelayedTask(
       NewRunnableMethod(
         "dom::ContentChild::RecvShutdown", this,
         &ContentChild::ShutdownInternal),
-      100);
+      delay);
+    mShutdownTimeout -= delay;
     return;
   }
 
   mShuttingDown = true;
 
 #ifdef NIGHTLY_BUILD
   HangMonitor::UnregisterAnnotator(PendingInputEventHangAnnotator::sSingleton);
 #endif
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -823,16 +823,17 @@ private:
   // Hashtable to keep track of the pending file creation.
   // These items are removed when RecvFileCreationResponse is received.
   nsRefPtrHashtable<nsIDHashKey, FileCreatorHelper> mFileCreationPending;
 
 
   nsClassHashtable<nsUint64HashKey, AnonymousTemporaryFileCallback> mPendingAnonymousTemporaryFiles;
 
   mozilla::Atomic<bool> mShuttingDown;
+  int32_t mShutdownTimeout;
 
 #ifdef NIGHTLY_BUILD
   // NOTE: This member is atomic because it can be accessed from off-main-thread.
   mozilla::Atomic<uint32_t> mPendingInputEvents;
 #endif
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };