michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsThreadManager.h" michael@0: #include "nsThread.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIClassInfoImpl.h" michael@0: #include "nsTArray.h" michael@0: #include "nsAutoPtr.h" michael@0: #ifdef MOZ_CANARY michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: DWORD gTLSThreadIDIndex = TlsAlloc(); michael@0: #elif defined(NS_TLS) michael@0: NS_TLS mozilla::threads::ID gTLSThreadID = mozilla::threads::Generic; michael@0: #endif michael@0: michael@0: typedef nsTArray< nsRefPtr > nsThreadArray; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static void michael@0: ReleaseObject(void *data) michael@0: { michael@0: static_cast(data)->Release(); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: AppendAndRemoveThread(PRThread *key, nsRefPtr &thread, void *arg) michael@0: { michael@0: nsThreadArray *threads = static_cast(arg); michael@0: threads->AppendElement(thread); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: // statically allocated instance michael@0: NS_IMETHODIMP_(MozExternalRefCountType) nsThreadManager::AddRef() { return 2; } michael@0: NS_IMETHODIMP_(MozExternalRefCountType) nsThreadManager::Release() { return 1; } michael@0: NS_IMPL_CLASSINFO(nsThreadManager, nullptr, michael@0: nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, michael@0: NS_THREADMANAGER_CID) michael@0: NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) michael@0: NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsThreadManager::Init() michael@0: { michael@0: // Child processes need to initialize the thread manager before they michael@0: // initialize XPCOM in order to set up the crash reporter. This leads to michael@0: // situations where we get initialized twice. michael@0: if (mInitialized) michael@0: return NS_OK; michael@0: michael@0: if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mLock = new Mutex("nsThreadManager.mLock"); michael@0: michael@0: #ifdef MOZ_CANARY michael@0: const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; michael@0: const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; michael@0: char* env_var_flag = getenv("MOZ_KILL_CANARIES"); michael@0: sCanaryOutputFD = env_var_flag ? (env_var_flag[0] ? michael@0: open(env_var_flag, flags, mode) : michael@0: STDERR_FILENO) : 0; michael@0: #endif michael@0: michael@0: // Setup "main" thread michael@0: mMainThread = new nsThread(nsThread::MAIN_THREAD, 0); michael@0: michael@0: nsresult rv = mMainThread->InitCurrentThread(); michael@0: if (NS_FAILED(rv)) { michael@0: mMainThread = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: // We need to keep a pointer to the current thread, so we can satisfy michael@0: // GetIsMainThread calls that occur post-Shutdown. michael@0: mMainThread->GetPRThread(&mMainPRThread); michael@0: michael@0: #ifdef XP_WIN michael@0: TlsSetValue(gTLSThreadIDIndex, (void*) mozilla::threads::Main); michael@0: #elif defined(NS_TLS) michael@0: gTLSThreadID = mozilla::threads::Main; michael@0: #endif michael@0: michael@0: mInitialized = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsThreadManager::Shutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); michael@0: michael@0: // Prevent further access to the thread manager (no more new threads!) michael@0: // michael@0: // XXX What happens if shutdown happens before NewThread completes? michael@0: // Fortunately, NewThread is only called on the main thread for now. michael@0: // michael@0: mInitialized = false; michael@0: michael@0: // Empty the main thread event queue before we begin shutting down threads. michael@0: NS_ProcessPendingEvents(mMainThread); michael@0: michael@0: // We gather the threads from the hashtable into a list, so that we avoid michael@0: // holding the hashtable lock while calling nsIThread::Shutdown. michael@0: nsThreadArray threads; michael@0: { michael@0: MutexAutoLock lock(*mLock); michael@0: mThreadsByPRThread.Enumerate(AppendAndRemoveThread, &threads); michael@0: } michael@0: michael@0: // It's tempting to walk the list of threads here and tell them each to stop michael@0: // accepting new events, but that could lead to badness if one of those michael@0: // threads is stuck waiting for a response from another thread. To do it michael@0: // right, we'd need some way to interrupt the threads. michael@0: // michael@0: // Instead, we process events on the current thread while waiting for threads michael@0: // to shutdown. This means that we have to preserve a mostly functioning michael@0: // world until such time as the threads exit. michael@0: michael@0: // Shutdown all threads that require it (join with threads that we created). michael@0: for (uint32_t i = 0; i < threads.Length(); ++i) { michael@0: nsThread *thread = threads[i]; michael@0: if (thread->ShutdownRequired()) michael@0: thread->Shutdown(); michael@0: } michael@0: michael@0: // In case there are any more events somehow... michael@0: NS_ProcessPendingEvents(mMainThread); michael@0: michael@0: // There are no more background threads at this point. michael@0: michael@0: // Clear the table of threads. michael@0: { michael@0: MutexAutoLock lock(*mLock); michael@0: mThreadsByPRThread.Clear(); michael@0: } michael@0: michael@0: // Normally thread shutdown clears the observer for the thread, but since the michael@0: // main thread is special we do it manually here after we're sure all events michael@0: // have been processed. michael@0: mMainThread->SetObserver(nullptr); michael@0: mMainThread->ClearObservers(); michael@0: michael@0: // Release main thread object. michael@0: mMainThread = nullptr; michael@0: mLock = nullptr; michael@0: michael@0: // Remove the TLS entry for the main thread. michael@0: PR_SetThreadPrivate(mCurThreadIndex, nullptr); michael@0: } michael@0: michael@0: void michael@0: nsThreadManager::RegisterCurrentThread(nsThread *thread) michael@0: { michael@0: MOZ_ASSERT(thread->GetPRThread() == PR_GetCurrentThread(), "bad thread"); michael@0: michael@0: MutexAutoLock lock(*mLock); michael@0: michael@0: ++mCurrentNumberOfThreads; michael@0: if (mCurrentNumberOfThreads > mHighestNumberOfThreads) { michael@0: mHighestNumberOfThreads = mCurrentNumberOfThreads; michael@0: } michael@0: michael@0: mThreadsByPRThread.Put(thread->GetPRThread(), thread); // XXX check OOM? michael@0: michael@0: NS_ADDREF(thread); // for TLS entry michael@0: PR_SetThreadPrivate(mCurThreadIndex, thread); michael@0: } michael@0: michael@0: void michael@0: nsThreadManager::UnregisterCurrentThread(nsThread *thread) michael@0: { michael@0: MOZ_ASSERT(thread->GetPRThread() == PR_GetCurrentThread(), "bad thread"); michael@0: michael@0: MutexAutoLock lock(*mLock); michael@0: michael@0: --mCurrentNumberOfThreads; michael@0: mThreadsByPRThread.Remove(thread->GetPRThread()); michael@0: michael@0: PR_SetThreadPrivate(mCurThreadIndex, nullptr); michael@0: // Ref-count balanced via ReleaseObject michael@0: } michael@0: michael@0: nsThread * michael@0: nsThreadManager::GetCurrentThread() michael@0: { michael@0: // read thread local storage michael@0: void *data = PR_GetThreadPrivate(mCurThreadIndex); michael@0: if (data) michael@0: return static_cast(data); michael@0: michael@0: if (!mInitialized) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // OK, that's fine. We'll dynamically create one :-) michael@0: nsRefPtr thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0); michael@0: if (!thread || NS_FAILED(thread->InitCurrentThread())) michael@0: return nullptr; michael@0: michael@0: return thread.get(); // reference held in TLS michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadManager::NewThread(uint32_t creationFlags, michael@0: uint32_t stackSize, michael@0: nsIThread **result) michael@0: { michael@0: // No new threads during Shutdown michael@0: if (NS_WARN_IF(!mInitialized)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsThread *thr = new nsThread(nsThread::NOT_MAIN_THREAD, stackSize); michael@0: if (!thr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(thr); michael@0: michael@0: nsresult rv = thr->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(thr); michael@0: return rv; michael@0: } michael@0: michael@0: // At this point, we expect that the thread has been registered in mThread; michael@0: // however, it is possible that it could have also been replaced by now, so michael@0: // we cannot really assert that it was added. michael@0: michael@0: *result = thr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadManager::GetThreadFromPRThread(PRThread *thread, nsIThread **result) michael@0: { michael@0: // Keep this functioning during Shutdown michael@0: if (NS_WARN_IF(!mMainThread)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: if (NS_WARN_IF(!thread)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsRefPtr temp; michael@0: { michael@0: MutexAutoLock lock(*mLock); michael@0: mThreadsByPRThread.Get(thread, getter_AddRefs(temp)); michael@0: } michael@0: michael@0: NS_IF_ADDREF(*result = temp); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadManager::GetMainThread(nsIThread **result) michael@0: { michael@0: // Keep this functioning during Shutdown michael@0: if (NS_WARN_IF(!mMainThread)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: NS_ADDREF(*result = mMainThread); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadManager::GetCurrentThread(nsIThread **result) michael@0: { michael@0: // Keep this functioning during Shutdown michael@0: if (NS_WARN_IF(!mMainThread)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: *result = GetCurrentThread(); michael@0: if (!*result) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadManager::GetIsMainThread(bool *result) michael@0: { michael@0: // This method may be called post-Shutdown michael@0: michael@0: *result = (PR_GetCurrentThread() == mMainPRThread); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsThreadManager::GetHighestNumberOfThreads() michael@0: { michael@0: MutexAutoLock lock(*mLock); michael@0: return mHighestNumberOfThreads; michael@0: }