Bug 1479035: Part 2 - Get rid of PRThread to nsThread map. r?froydnj draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 27 Jul 2018 15:26:08 -0700
changeset 823811 0cbd46770f37a269bd7bd0be1431dbb9c9d3b9d8
parent 823810 063cfc1af22ff9822f5f7d3c561708594b090914
push id117794
push usermaglione.k@gmail.com
push dateSat, 28 Jul 2018 18:51:57 +0000
reviewersfroydnj
bugs1479035
milestone63.0a1
Bug 1479035: Part 2 - Get rid of PRThread to nsThread map. r?froydnj These maps hold strong references which complicate nsThread lifetime handling considerably, and only have a couple of fringe uses. We have a linked list of active threads that the thread manager can use for its internal enumeration purposes, and the external uses are easily done away with, so there doesn't seem to be much reason to keep the map around. MozReview-Commit-ID: x7dsj6C4x8
dom/workers/WorkerPrivate.cpp
storage/test/gtest/storage_test_harness.h
xpcom/threads/nsIThreadManager.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadManager.cpp
xpcom/threads/nsThreadManager.h
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -5348,26 +5348,17 @@ bool
 WorkerPrivate::IsOnWorkerThread() const
 {
   // This is much more complicated than it needs to be but we can't use mThread
   // because it must be protected by mMutex and sometimes this method is called
   // when mMutex is already locked. This method should always work.
   MOZ_ASSERT(mPRThread,
              "AssertIsOnWorkerThread() called before a thread was assigned!");
 
-  nsCOMPtr<nsIThread> thread;
-  nsresult rv =
-    nsThreadManager::get().GetThreadFromPRThread(mPRThread,
-                                                 getter_AddRefs(thread));
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-  MOZ_ASSERT(thread);
-
-  bool current;
-  rv = thread->IsOnCurrentThread(&current);
-  return NS_SUCCEEDED(rv) && current;
+  return mPRThread == PR_GetCurrentThread();
 }
 
 #ifdef DEBUG
 void
 WorkerPrivate::AssertIsOnWorkerThread() const
 {
   MOZ_ASSERT(IsOnWorkerThread());
 }
--- a/storage/test/gtest/storage_test_harness.h
+++ b/storage/test/gtest/storage_test_harness.h
@@ -220,29 +220,28 @@ PRThread *watched_thread = nullptr;
  * Ugly hack to let us figure out what a connection's async thread is.  If we
  * were MOZILLA_INTERNAL_API and linked as such we could just include
  * mozStorageConnection.h and just ask Connection directly.  But that turns out
  * poorly.
  *
  * When the thread a mutex is invoked on isn't watched_thread we save it to this
  * variable.
  */
-PRThread *last_non_watched_thread = nullptr;
+nsIThread* last_non_watched_thread = nullptr;
 
 /**
  * Set a flag if the mutex is used on the thread we are watching, but always
  * call the real mutex function.
  */
 extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex)
 {
-  PRThread *curThread = ::PR_GetCurrentThread();
-  if (curThread == watched_thread)
+  if (PR_GetCurrentThread() == watched_thread)
     mutex_used_on_watched_thread = true;
   else
-    last_non_watched_thread = curThread;
+    last_non_watched_thread = NS_GetCurrentThread();
   orig_mutex_methods.xMutexEnter(mutex);
 }
 
 extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex)
 {
   if (::PR_GetCurrentThread() == watched_thread)
     mutex_used_on_watched_thread = true;
   return orig_mutex_methods.xMutexTry(mutex);
@@ -346,26 +345,20 @@ get_conn_async_thread(mozIStorageConnect
   // - statement with nothing to bind
   nsCOMPtr<mozIStorageAsyncStatement> stmt;
   db->CreateAsyncStatement(
     NS_LITERAL_CSTRING("SELECT 1"),
     getter_AddRefs(stmt));
   blocking_async_execute(stmt);
   stmt->Finalize();
 
-  nsCOMPtr<nsIThreadManager> threadMan =
-    do_GetService("@mozilla.org/thread-manager;1");
-  nsCOMPtr<nsIThread> asyncThread;
-  threadMan->GetThreadFromPRThread(last_non_watched_thread,
-                                   getter_AddRefs(asyncThread));
+  nsCOMPtr<nsIThread> asyncThread = last_non_watched_thread;
 
   // Additionally, check that the thread we get as the background thread is the
   // same one as the one we report from getInterface.
   nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
   nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
-  PRThread *allegedPRThread;
-  (void)allegedAsyncThread->GetPRThread(&allegedPRThread);
-  do_check_eq(allegedPRThread, last_non_watched_thread);
+  do_check_eq(allegedAsyncThread, asyncThread);
   return asyncThread.forget();
 }
 
 #endif // storage_test_harness_h__
 
--- a/xpcom/threads/nsIThreadManager.idl
+++ b/xpcom/threads/nsIThreadManager.idl
@@ -80,29 +80,16 @@ interface nsIThreadManager : nsISupports
    *   default.
    *
    * @returns
    *   The newly created nsIThread object.
    */
   [noscript] nsIThread newNamedThread(in ACString name, [optional] in unsigned long stackSize);
 
   /**
-   * Get the nsIThread object (if any) corresponding to the given PRThread.
-   * This method returns null if there is no corresponding nsIThread.
-   *
-   * @param prthread
-   *   The PRThread of the nsIThread being requested.
-   *
-   * @returns
-   *   The nsIThread object corresponding to the given PRThread or null if no
-   *   such nsIThread exists.
-   */
-  [noscript] nsIThread getThreadFromPRThread(in PRThread prthread);
-
-  /**
    * Get the main thread.
    */
   readonly attribute nsIThread mainThread;
 
   /**
    * Get the current thread.  If the calling thread does not already have a
    * nsIThread associated with it, then a new nsIThread will be created and
    * associated with the current PRThread.
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -413,16 +413,53 @@ nsThread::ClearThreadList()
 }
 
 /* static */ nsThreadEnumerator
 nsThread::Enumerate()
 {
   return {};
 }
 
+/* static */ uint32_t
+nsThread::MaxActiveThreads()
+{
+  OffTheBooksMutexAutoLock mal(ThreadListMutex());
+  return sMaxActiveThreads;
+}
+
+uint32_t nsThread::sActiveThreads;
+uint32_t nsThread::sMaxActiveThreads;
+
+void
+nsThread::AddToThreadList()
+{
+  OffTheBooksMutexAutoLock mal(ThreadListMutex());
+  MOZ_ASSERT(!isInList());
+
+  sActiveThreads++;
+  sMaxActiveThreads = std::max(sActiveThreads, sMaxActiveThreads);
+
+  ThreadList().insertBack(this);
+}
+
+void
+nsThread::MaybeRemoveFromThreadList()
+{
+  // We shouldn't need to lock before checking isInList at this point. We're
+  // destroying the last reference to this object, so there's no way for anyone
+  // else to remove it in the middle of our check. And the not-in-list state is
+  // determined the element's next and previous members pointing to itself, so a
+  // non-atomic update to an adjacent member won't affect the outcome either.
+  if (isInList()) {
+    OffTheBooksMutexAutoLock mal(ThreadListMutex());
+    sActiveThreads--;
+    removeFrom(ThreadList());
+  }
+}
+
 /*static*/ void
 nsThread::ThreadFunc(void* aArg)
 {
   using mozilla::ipc::BackgroundChild;
 
   ThreadInitData* initData = static_cast<ThreadInitData*>(aArg);
   nsThread* self = initData->thread;  // strong reference
 
@@ -585,18 +622,17 @@ nsThread::InitCommon()
       ULONG_PTR stackBottom, stackTop;
       sGetStackLimits(&stackBottom, &stackTop);
       mStackBase = reinterpret_cast<void*>(stackBottom);
       mStackSize = stackTop - stackBottom;
     }
 #endif
   }
 
-  OffTheBooksMutexAutoLock mal(ThreadListMutex());
-  ThreadList().insertBack(this);
+  AddToThreadList();
 }
 
 //-----------------------------------------------------------------------------
 
 // Tell the crash reporter to save a memory report if our heuristics determine
 // that an OOM failure is likely to occur soon.
 // Memory usage will not be checked more than every 30 seconds or saved more
 // than every 3 minutes
@@ -707,25 +743,17 @@ nsThread::nsThread()
   MOZ_ASSERT(!NS_IsMainThread());
 }
 
 nsThread::~nsThread()
 {
   NS_ASSERTION(mRequestedShutdownContexts.IsEmpty(),
                "shouldn't be waiting on other threads to shutdown");
 
-  // We shouldn't need to lock before checking isInList at this point. We're
-  // destroying the last reference to this object, so there's no way for anyone
-  // else to remove it in the middle of our check. And the not-in-list state is
-  // determined the element's next and previous members pointing to itself, so a
-  // non-atomic update to an adjacent member won't affect the outcome either.
-  if (isInList()) {
-    OffTheBooksMutexAutoLock mal(ThreadListMutex());
-    removeFrom(ThreadList());
-  }
+  MaybeRemoveFromThreadList();
 
 #ifdef DEBUG
   // We deliberately leak these so they can be tracked by the leak checker.
   // If you're having nsThreadShutdownContext leaks, you can set:
   //   XPCOM_MEM_LOG_CLASSES=nsThreadShutdownContext
   // during a test run and that will at least tell you what thread is
   // requesting shutdown on another, which can be helpful for diagnosing
   // the leak.
@@ -883,22 +911,17 @@ nsThread::ShutdownInternal(bool aSync)
     return nullptr;
   }
 
   // Prevent multiple calls to this method
   if (!mShutdownRequired.compareExchange(true, false)) {
     return nullptr;
   }
 
-  {
-    OffTheBooksMutexAutoLock mal(ThreadListMutex());
-    if (isInList()) {
-      removeFrom(ThreadList());
-    }
-  }
+  MaybeRemoveFromThreadList();
 
   NotNull<nsThread*> currentThread =
     WrapNotNull(nsThreadManager::get().GetCurrentThread());
 
   nsAutoPtr<nsThreadShutdownContext>& context =
     *currentThread->mRequestedShutdownContexts.AppendElement();
   context = new nsThreadShutdownContext(WrapNotNull(this), currentThread, aSync);
 
@@ -918,22 +941,17 @@ nsThread::ShutdownInternal(bool aSync)
 void
 nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext)
 {
   MOZ_ASSERT(mEvents);
   MOZ_ASSERT(mEventTarget);
   MOZ_ASSERT(mThread);
   MOZ_ASSERT(aContext->mTerminatingThread == this);
 
-  {
-    OffTheBooksMutexAutoLock mal(ThreadListMutex());
-    if (isInList()) {
-      removeFrom(ThreadList());
-    }
-  }
+  MaybeRemoveFromThreadList();
 
   if (aContext->mAwaitingShutdownAck) {
     // We're in a synchronous shutdown, so tell whatever is up the stack that
     // we're done and unwind the stack so it can call us again.
     aContext->mAwaitingShutdownAck = false;
     return;
   }
 
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -151,16 +151,18 @@ public:
   // Returns the size of this object, its PRThread, and its shutdown contexts,
   // but excluding its event queues.
   size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   static nsThreadEnumerator Enumerate();
 
+  static uint32_t MaxActiveThreads();
+
 private:
   void DoMainThreadSpecificProcessing(bool aReallyWait);
 
 protected:
   friend class nsThreadShutdownEvent;
 
   friend class nsThreadEnumerator;
 
@@ -179,16 +181,24 @@ protected:
   struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
 
   friend class nsThreadManager;
 
   static mozilla::OffTheBooksMutex& ThreadListMutex();
   static mozilla::LinkedList<nsThread>& ThreadList();
   static void ClearThreadList();
 
+  // The current number of active threads.
+  static uint32_t sActiveThreads;
+  // The maximum current number of active threads we've had in this session.
+  static uint32_t sMaxActiveThreads;
+
+  void AddToThreadList();
+  void MaybeRemoveFromThreadList();
+
   RefPtr<mozilla::SynchronizedEventQueue> mEvents;
   RefPtr<mozilla::ThreadEventTarget> mEventTarget;
 
   // The shutdown contexts for any other threads we've asked to shut down.
   nsTArray<nsAutoPtr<struct nsThreadShutdownContext>> mRequestedShutdownContexts;
   // The shutdown context for ourselves.
   struct nsThreadShutdownContext* mShutdownContext;
 
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -104,18 +104,16 @@ nsThreadManager::ReleaseThread(void* aDa
   if (sShutdownComplete) {
     // We've already completed shutdown and released the references to all or
     // our TLS wrappers. Don't try to release them again.
     return;
   }
 
   auto* thread = static_cast<nsThread*>(aData);
 
-  get().UnregisterCurrentThread(*thread, true);
-
   if (thread->mHasTLSEntry) {
     thread->mHasTLSEntry = false;
     thread->Release();
   }
 }
 
 // statically allocated instance
 NS_IMETHODIMP_(MozExternalRefCountType)
@@ -316,62 +314,50 @@ nsThreadManager::Shutdown()
   //
   mInitialized = false;
 
   // Empty the main thread event queue before we begin shutting down threads.
   NS_ProcessPendingEvents(mMainThread);
 
   {
     // We gather the threads from the hashtable into a list, so that we avoid
-    // holding the hashtable lock while calling nsIThread::Shutdown.
-    nsThreadArray threads;
-    {
-      OffTheBooksMutexAutoLock lock(mLock);
-      for (auto iter = mThreadsByPRThread.Iter(); !iter.Done(); iter.Next()) {
-        RefPtr<nsThread>& thread = iter.Data();
-        threads.AppendElement(WrapNotNull(thread));
-        iter.Remove();
+    // holding the enumerator lock while calling nsIThread::Shutdown.
+    nsTArray<RefPtr<nsThread>> threads;
+    for (auto* thread : nsThread::Enumerate()) {
+      if (thread->ShutdownRequired()) {
+        threads.AppendElement(thread);
       }
     }
 
     // It's tempting to walk the list of threads here and tell them each to stop
     // accepting new events, but that could lead to badness if one of those
     // threads is stuck waiting for a response from another thread.  To do it
     // right, we'd need some way to interrupt the threads.
     //
     // Instead, we process events on the current thread while waiting for threads
     // to shutdown.  This means that we have to preserve a mostly functioning
     // world until such time as the threads exit.
 
     // Shutdown all threads that require it (join with threads that we created).
-    for (uint32_t i = 0; i < threads.Length(); ++i) {
-      NotNull<nsThread*> thread = threads[i];
-      if (thread->ShutdownRequired()) {
-        thread->Shutdown();
-      }
+    for (auto& thread : threads) {
+      thread->Shutdown();
     }
   }
 
   // NB: It's possible that there are events in the queue that want to *start*
   // an asynchronous shutdown. But we have already shutdown the threads above,
   // so there's no need to worry about them. We only have to wait for all
   // in-flight asynchronous thread shutdowns to complete.
   mMainThread->WaitForAllAsynchronousShutdowns();
 
   // In case there are any more events somehow...
   NS_ProcessPendingEvents(mMainThread);
 
   // There are no more background threads at this point.
 
-  // Clear the table of threads.
-  {
-    OffTheBooksMutexAutoLock lock(mLock);
-    mThreadsByPRThread.Clear();
-  }
-
   // Normally thread shutdown clears the observer for the thread, but since the
   // main thread is special we do it manually here after we're sure all events
   // have been processed.
   mMainThread->SetObserver(nullptr);
 
   // Release main thread object.
   mMainThread = nullptr;
 
@@ -397,46 +383,26 @@ nsThreadManager::Shutdown()
   sShutdownComplete = true;
 }
 
 void
 nsThreadManager::RegisterCurrentThread(nsThread& aThread)
 {
   MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
 
-  OffTheBooksMutexAutoLock lock(mLock);
-
-  ++mCurrentNumberOfThreads;
-  if (mCurrentNumberOfThreads > mHighestNumberOfThreads) {
-    mHighestNumberOfThreads = mCurrentNumberOfThreads;
-  }
-
-  mThreadsByPRThread.Put(aThread.GetPRThread(), &aThread);  // XXX check OOM?
-
   aThread.AddRef();  // for TLS entry
   aThread.mHasTLSEntry = true;
   PR_SetThreadPrivate(mCurThreadIndex, &aThread);
 }
 
 void
-nsThreadManager::UnregisterCurrentThread(nsThread& aThread, bool aIfExists)
+nsThreadManager::UnregisterCurrentThread(nsThread& aThread)
 {
   MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
 
-  {
-    OffTheBooksMutexAutoLock lock(mLock);
-
-    if (aIfExists && !mThreadsByPRThread.GetWeak(aThread.GetPRThread())) {
-      return;
-    }
-
-    --mCurrentNumberOfThreads;
-    mThreadsByPRThread.Remove(aThread.GetPRThread());
-  }
-
   PR_SetThreadPrivate(mCurThreadIndex, nullptr);
   // Ref-count balanced via ReleaseThread
 }
 
 nsThread*
 nsThreadManager::CreateCurrentThread(SynchronizedEventQueue* aQueue,
                                      nsThread::MainThreadFlag aMainThread)
 {
@@ -529,37 +495,16 @@ nsThreadManager::NewNamedThread(const ns
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   thr.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsThreadManager::GetThreadFromPRThread(PRThread* aThread, nsIThread** aResult)
-{
-  // Keep this functioning during Shutdown
-  if (NS_WARN_IF(!mMainThread)) {
-    return NS_ERROR_NOT_INITIALIZED;
-  }
-  if (NS_WARN_IF(!aThread)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  RefPtr<nsThread> temp;
-  {
-    OffTheBooksMutexAutoLock lock(mLock);
-    mThreadsByPRThread.Get(aThread, getter_AddRefs(temp));
-  }
-
-  NS_IF_ADDREF(*aResult = temp);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsThreadManager::GetMainThread(nsIThread** aResult)
 {
   // Keep this functioning during Shutdown
   if (NS_WARN_IF(!mMainThread)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   NS_ADDREF(*aResult = mMainThread);
   return NS_OK;
@@ -651,18 +596,17 @@ nsThreadManager::GetSystemGroupEventTarg
   nsCOMPtr<nsIEventTarget> target = SystemGroup::EventTargetFor(TaskCategory::Other);
   target.forget(aTarget);
   return NS_OK;
 }
 
 uint32_t
 nsThreadManager::GetHighestNumberOfThreads()
 {
-  OffTheBooksMutexAutoLock lock(mLock);
-  return mHighestNumberOfThreads;
+  return nsThread::MaxActiveThreads();
 }
 
 NS_IMETHODIMP
 nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent, uint32_t aPriority)
 {
   // Note: C++ callers should instead use NS_DispatchToMainThread.
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/xpcom/threads/nsThreadManager.h
+++ b/xpcom/threads/nsThreadManager.h
@@ -31,17 +31,17 @@ public:
   void Shutdown();
 
   // Called by nsThread to inform the ThreadManager it exists.  This method
   // must be called when the given thread is the current thread.
   void RegisterCurrentThread(nsThread& aThread);
 
   // Called by nsThread to inform the ThreadManager it is going away.  This
   // method must be called when the given thread is the current thread.
-  void UnregisterCurrentThread(nsThread& aThread, bool aIfExists = false);
+  void UnregisterCurrentThread(nsThread& aThread);
 
   // Returns the current thread.  Returns null if OOM or if ThreadManager isn't
   // initialized.  Creates the nsThread if one does not exist yet.
   nsThread* GetCurrentThread();
 
   // Returns true iff the currently running thread has an nsThread associated
   // with it (ie; whether this is a thread that we can dispatch runnables to).
   bool IsNSThread() const;
@@ -69,42 +69,33 @@ public:
   void FlushInputEventPrioritization();
   void SuspendInputEventPrioritization();
   void ResumeInputEventPrioritization();
 
 private:
   nsThreadManager()
     : mCurThreadIndex(0)
     , mMainPRThread(nullptr)
-    , mLock("nsThreadManager.mLock")
     , mInitialized(false)
-    , mCurrentNumberOfThreads(1)
-    , mHighestNumberOfThreads(1)
   {
   }
 
   nsresult
   SpinEventLoopUntilInternal(nsINestedEventLoopCondition* aCondition,
                              bool aCheckingShutdown);
 
   static void ReleaseThread(void* aData);
 
   nsRefPtrHashtable<nsPtrHashKey<PRThread>, nsThread> mThreadsByPRThread;
   unsigned            mCurThreadIndex;  // thread-local-storage index
   RefPtr<nsThread>  mMainThread;
   PRThread*         mMainPRThread;
-  mozilla::OffTheBooksMutex mLock;  // protects tables
   mozilla::Atomic<bool,
                   mozilla::SequentiallyConsistent,
                   mozilla::recordreplay::Behavior::DontPreserve> mInitialized;
-
-  // The current number of threads
-  uint32_t            mCurrentNumberOfThreads;
-  // The highest number of threads encountered so far during the session
-  uint32_t            mHighestNumberOfThreads;
 };
 
 #define NS_THREADMANAGER_CID                       \
 { /* 7a4204c6-e45a-4c37-8ebb-6709a22c917c */       \
   0x7a4204c6,                                      \
   0xe45a,                                          \
   0x4c37,                                          \
   {0x8e, 0xbb, 0x67, 0x09, 0xa2, 0x2c, 0x91, 0x7c} \