Bug 1425559 - Implement nsIThreadManager::spinEventLoopUntilOrShutdown, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Sun, 28 Jan 2018 08:41:36 +0100
changeset 456215 21e24506cb0e2a4cb7bd78e85962f9cb8c8b9537
parent 456199 db980507149b41f59b4c9c242b61ba1caee7eb8c
child 456216 939a770548c4fe90f166ab668c14a46a5a14e057
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1425559
milestone60.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 1425559 - Implement nsIThreadManager::spinEventLoopUntilOrShutdown, r=smaug Currently nsIThreadManager::spinEventLoopUntil doesn't monitor the shutting down. Firefox shutting down can be blocked by a 'broken' use of nsIThreadManager::spinEventLoopUntil. nsIThreadManager::spinEventLoopUntilOrShutdown should be used instead.
layout/build/nsLayoutStatics.cpp
xpcom/threads/moz.build
xpcom/threads/nsIThreadManager.idl
xpcom/threads/nsThreadManager.cpp
xpcom/threads/nsThreadManager.h
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -117,16 +117,17 @@
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
 #include "mozilla/StylePrefs.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamStorage.h"
 #include "mozilla/dom/U2FTokenManager.h"
 #include "mozilla/dom/PointerEventHandler.h"
 #include "nsHostObjectProtocolHandler.h"
+#include "nsThreadManager.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
 
@@ -316,16 +317,18 @@ nsLayoutStatics::Initialize()
   mozilla::dom::U2FTokenManager::Initialize();
 
   if (XRE_IsParentProcess()) {
     // On content process we initialize DOMPrefs when PContentChild is fully
     // initialized.
     mozilla::dom::DOMPrefs::Initialize();
   }
 
+  nsThreadManager::InitializeShutdownObserver();
+
   return NS_OK;
 }
 
 void
 nsLayoutStatics::Shutdown()
 {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -26,16 +26,17 @@ EXPORTS += [
     'MainThreadUtils.h',
     'nsICancelableRunnable.h',
     'nsIIdleRunnable.h',
     'nsILabelableRunnable.h',
     'nsMemoryPressure.h',
     'nsProcess.h',
     'nsProxyRelease.h',
     'nsThread.h',
+    'nsThreadManager.h',
     'nsThreadUtils.h',
 ]
 
 EXPORTS.mozilla += [
     'AbstractEventQueue.h',
     'AbstractThread.h',
     'BlockingResourceBase.h',
     'CondVar.h',
--- a/xpcom/threads/nsIThreadManager.idl
+++ b/xpcom/threads/nsIThreadManager.idl
@@ -116,16 +116,25 @@ interface nsIThreadManager : nsISupports
    * If condition.isDone() throws, this function will throw as well.
    *
    * C++ code should not use this function, instead preferring
    * mozilla::SpinEventLoopUntil.
    */
   void spinEventLoopUntil(in nsINestedEventLoopCondition condition);
 
   /**
+   * Similar to the previous method, but the spinning of the event loop
+   * terminates when the shutting down starts.
+   *
+   * C++ code should not use this function, instead preferring
+   * mozilla::SpinEventLoopUntil.
+   */
+  void spinEventLoopUntilOrShutdown(in nsINestedEventLoopCondition condition);
+
+  /**
    * Spin the current thread's event loop until there are no more pending
    * events.  This could be done with spinEventLoopUntil, but that would
    * require access to the current thread from JavaScript, which we are
    * moving away from.
    */
   void spinEventLoopUntilEmpty();
 
   /**
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -9,20 +9,22 @@
 #include "nsThreadUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsXULAppAPI.h"
 #include "LabeledEventQueue.h"
 #include "MainThreadQueue.h"
 #include "mozilla/AbstractThread.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventQueue.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Scheduler.h"
 #include "mozilla/SystemGroup.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/ThreadEventQueue.h"
 #include "mozilla/ThreadLocal.h"
 #include "PrioritizedEventQueue.h"
 #ifdef MOZ_CANARY
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
@@ -98,25 +100,117 @@ nsThreadManager::Release()
   return 1;
 }
 NS_IMPL_CLASSINFO(nsThreadManager, nullptr,
                   nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
                   NS_THREADMANAGER_CID)
 NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager)
 NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager)
 
+namespace {
+
+// Simple observer to monitor the beginning of the shutdown.
+class ShutdownObserveHelper final : public nsIObserver
+                                  , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  static nsresult
+  Create(ShutdownObserveHelper** aObserver)
+  {
+    MOZ_ASSERT(aObserver);
+
+    RefPtr<ShutdownObserveHelper> observer = new ShutdownObserveHelper();
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (NS_WARN_IF(!obs)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsresult rv = obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = obs->AddObserver(observer, "content-child-will-shutdown", true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    observer.forget(aObserver);
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  Observe(nsISupports* aSubject, const char* aTopic,
+          const char16_t* aData) override
+  {
+    if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
+        !strcmp(aTopic, "content-child-will-shutdown")) {
+      mShuttingDown = true;
+      return NS_OK;
+    }
+
+    return NS_OK;
+  }
+
+  bool
+  ShuttingDown() const
+  {
+    return mShuttingDown;
+  }
+
+private:
+  explicit ShutdownObserveHelper()
+    : mShuttingDown(false)
+  {}
+
+  ~ShutdownObserveHelper() = default;
+
+  bool mShuttingDown;
+};
+
+NS_INTERFACE_MAP_BEGIN(ShutdownObserveHelper)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(ShutdownObserveHelper)
+NS_IMPL_RELEASE(ShutdownObserveHelper)
+
+StaticRefPtr<ShutdownObserveHelper> gShutdownObserveHelper;
+
+} // anonymous
+
 //-----------------------------------------------------------------------------
 
 /*static*/ nsThreadManager&
 nsThreadManager::get()
 {
   static nsThreadManager sInstance;
   return sInstance;
 }
 
+/* static */ void
+nsThreadManager::InitializeShutdownObserver()
+{
+  MOZ_ASSERT(!gShutdownObserveHelper);
+
+  RefPtr<ShutdownObserveHelper> observer;
+  nsresult rv = ShutdownObserveHelper::Create(getter_AddRefs(observer));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  gShutdownObserveHelper = observer;
+  ClearOnShutdown(&gShutdownObserveHelper);
+}
+
 nsresult
 nsThreadManager::Init()
 {
   // Child processes need to initialize the thread manager before they
   // initialize XPCOM in order to set up the crash reporter. This leads to
   // situations where we get initialized twice.
   if (mInitialized) {
     return NS_OK;
@@ -420,20 +514,47 @@ nsThreadManager::GetCurrentThread(nsIThr
   }
   NS_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadManager::SpinEventLoopUntil(nsINestedEventLoopCondition* aCondition)
 {
+  return SpinEventLoopUntilInternal(aCondition, false);
+}
+
+NS_IMETHODIMP
+nsThreadManager::SpinEventLoopUntilOrShutdown(nsINestedEventLoopCondition* aCondition)
+{
+  return SpinEventLoopUntilInternal(aCondition, true);
+}
+
+nsresult
+nsThreadManager::SpinEventLoopUntilInternal(nsINestedEventLoopCondition* aCondition,
+                                            bool aCheckingShutdown)
+{
   nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
   nsresult rv = NS_OK;
 
+  // Nothing to do if already shutting down. Note that gShutdownObserveHelper is
+  // nullified on shutdown.
+  if (aCheckingShutdown &&
+      (!gShutdownObserveHelper || gShutdownObserveHelper->ShuttingDown())) {
+    return NS_OK;
+  }
+
   if (!mozilla::SpinEventLoopUntil([&]() -> bool {
+        // Shutting down is started.
+        if (aCheckingShutdown &&
+            (!gShutdownObserveHelper ||
+             gShutdownObserveHelper->ShuttingDown())) {
+          return true;
+        }
+
         bool isDone = false;
         rv = condition->IsDone(&isDone);
         // JS failure should be unusual, but we need to stop and propagate
         // the error back to the caller.
         if (NS_FAILED(rv)) {
           return true;
         }
 
--- a/xpcom/threads/nsThreadManager.h
+++ b/xpcom/threads/nsThreadManager.h
@@ -17,16 +17,18 @@ class nsIRunnable;
 class nsThreadManager : public nsIThreadManager
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITHREADMANAGER
 
   static nsThreadManager& get();
 
+  static void InitializeShutdownObserver();
+
   nsresult Init();
 
   // Shutdown all threads.  This function should only be called on the main
   // thread of the application process.
   void Shutdown();
 
   // Called by nsThread to inform the ThreadManager it exists.  This method
   // must be called when the given thread is the current thread.
@@ -70,16 +72,20 @@ private:
     , mMainPRThread(nullptr)
     , mLock("nsThreadManager.mLock")
     , mInitialized(false)
     , mCurrentNumberOfThreads(1)
     , mHighestNumberOfThreads(1)
   {
   }
 
+  nsresult
+  SpinEventLoopUntilInternal(nsINestedEventLoopCondition* aCondition,
+                             bool aCheckingShutdown);
+
   nsRefPtrHashtable<nsPtrHashKey<PRThread>, nsThread> mThreadsByPRThread;
   unsigned            mCurThreadIndex;  // thread-local-storage index
   RefPtr<nsThread>  mMainThread;
   PRThread*         mMainPRThread;
   mozilla::OffTheBooksMutex mLock;  // protects tables
   mozilla::Atomic<bool> mInitialized;
 
   // The current number of threads