diff options
Diffstat (limited to 'xpcom/threads/nsThreadManager.cpp')
-rw-r--r-- | xpcom/threads/nsThreadManager.cpp | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp new file mode 100644 index 000000000..d1eb84b8f --- /dev/null +++ b/xpcom/threads/nsThreadManager.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsTArray.h" +#include "nsAutoPtr.h" +#include "mozilla/ThreadLocal.h" +#ifdef MOZ_CANARY +#include <fcntl.h> +#include <unistd.h> +#endif + +#include "MainThreadIdlePeriod.h" + +using namespace mozilla; + +static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; + +bool +NS_IsMainThread() +{ + return sTLSIsMainThread.get(); +} + +void +NS_SetMainThread() +{ + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + MOZ_ASSERT(NS_IsMainThread()); +} + +typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray; + +//----------------------------------------------------------------------------- + +static void +ReleaseObject(void* aData) +{ + static_cast<nsISupports*>(aData)->Release(); +} + +// statically allocated instance +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::AddRef() +{ + return 2; +} +NS_IMETHODIMP_(MozExternalRefCountType) +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) + +//----------------------------------------------------------------------------- + +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; + } + + if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } + + +#ifdef MOZ_CANARY + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + char* env_var_flag = getenv("MOZ_KILL_CANARIES"); + sCanaryOutputFD = + env_var_flag ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : + STDERR_FILENO) : + 0; +#endif + + // Setup "main" thread + mMainThread = new nsThread(nsThread::MAIN_THREAD, 0); + + nsresult rv = mMainThread->InitCurrentThread(); + if (NS_FAILED(rv)) { + mMainThread = nullptr; + return rv; + } + + { + nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod(); + mMainThread->RegisterIdlePeriod(idlePeriod.forget()); + } + + // We need to keep a pointer to the current thread, so we can satisfy + // GetIsMainThread calls that occur post-Shutdown. + mMainThread->GetPRThread(&mMainPRThread); + + mInitialized = true; + return NS_OK; +} + +void +nsThreadManager::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); + + // Prevent further access to the thread manager (no more new threads!) + // + // What happens if shutdown happens before NewThread completes? + // We Shutdown() the new thread, and return error if we've started Shutdown + // between when NewThread started, and when the thread finished initializing + // and registering with ThreadManager. + // + 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(); + } + } + + // 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(); + } + } + + // 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); + mMainThread->ClearObservers(); + + // Release main thread object. + mMainThread = nullptr; + + // Remove the TLS entry for the main thread. + PR_SetThreadPrivate(mCurThreadIndex, nullptr); +} + +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 + PR_SetThreadPrivate(mCurThreadIndex, &aThread); +} + +void +nsThreadManager::UnregisterCurrentThread(nsThread& aThread) +{ + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + OffTheBooksMutexAutoLock lock(mLock); + + --mCurrentNumberOfThreads; + mThreadsByPRThread.Remove(aThread.GetPRThread()); + + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + // Ref-count balanced via ReleaseObject +} + +nsThread* +nsThreadManager::GetCurrentThread() +{ + // read thread local storage + void* data = PR_GetThreadPrivate(mCurThreadIndex); + if (data) { + return static_cast<nsThread*>(data); + } + + if (!mInitialized) { + return nullptr; + } + + // OK, that's fine. We'll dynamically create one :-) + RefPtr<nsThread> thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +NS_IMETHODIMP +nsThreadManager::NewThread(uint32_t aCreationFlags, + uint32_t aStackSize, + nsIThread** aResult) +{ + // Note: can be called from arbitrary threads + + // No new threads during Shutdown + if (NS_WARN_IF(!mInitialized)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<nsThread> thr = new nsThread(nsThread::NOT_MAIN_THREAD, aStackSize); + nsresult rv = thr->Init(); // Note: blocks until the new thread has been set up + if (NS_FAILED(rv)) { + return rv; + } + + // At this point, we expect that the thread has been registered in mThreadByPRThread; + // however, it is possible that it could have also been replaced by now, so + // we cannot really assert that it was added. Instead, kill it if we entered + // Shutdown() during/before Init() + + if (NS_WARN_IF(!mInitialized)) { + if (thr->ShutdownRequired()) { + thr->Shutdown(); // ok if it happens multiple times + } + 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; +} + +NS_IMETHODIMP +nsThreadManager::GetCurrentThread(nsIThread** aResult) +{ + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = GetCurrentThread(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetIsMainThread(bool* aResult) +{ + // This method may be called post-Shutdown + + *aResult = (PR_GetCurrentThread() == mMainPRThread); + return NS_OK; +} + +uint32_t +nsThreadManager::GetHighestNumberOfThreads() +{ + OffTheBooksMutexAutoLock lock(mLock); + return mHighestNumberOfThreads; +} |