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
--- 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(¤t);
- 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} \