/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsThread.h" #include "prmem.h" #include "prlog.h" #include "nsAutoLock.h" PRUintn nsThread::kIThreadSelfIndex = 0; static nsIThread *gMainThread = 0; #if defined(PR_LOGGING) // // Log module for nsIThread logging... // // To enable logging (see prlog.h for full details): // // set NSPR_LOG_MODULES=nsIThread:5 // set NSPR_LOG_FILE=nspr.log // // this enables PR_LOG_DEBUG level information and places all output in // the file nspr.log // // gSocketLog is defined in nsSocketTransport.cpp // PRLogModuleInfo* nsIThreadLog = nsnull; #endif /* PR_LOGGING */ //////////////////////////////////////////////////////////////////////////////// nsThread::nsThread() : mThread(nsnull), mDead(PR_FALSE), mStartLock(nsnull) { #if defined(PR_LOGGING) // // Initialize the global PRLogModule for nsIThread logging // if necessary... // if (nsIThreadLog == nsnull) { nsIThreadLog = PR_NewLogModule("nsIThread"); } #endif /* PR_LOGGING */ // enforce matching of constants to enums in prthread.h NS_ASSERTION(int(nsIThread::PRIORITY_LOW) == int(PR_PRIORITY_LOW) && int(nsIThread::PRIORITY_NORMAL) == int(PRIORITY_NORMAL) && int(nsIThread::PRIORITY_HIGH) == int(PRIORITY_HIGH) && int(nsIThread::PRIORITY_URGENT) == int(PRIORITY_URGENT) && int(nsIThread::SCOPE_LOCAL) == int(PR_LOCAL_THREAD) && int(nsIThread::SCOPE_GLOBAL) == int(PR_GLOBAL_THREAD) && int(nsIThread::STATE_JOINABLE) == int(PR_JOINABLE_THREAD) && int(nsIThread::STATE_UNJOINABLE) == int(PR_UNJOINABLE_THREAD), "Bad constant in nsIThread!"); } nsThread::~nsThread() { if (mStartLock) PR_DestroyLock(mStartLock); PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p destroyed\n", this)); // This code used to free the nsIThreadLog loginfo stuff // Don't do that; loginfo structures are owned by nspr // and would be freed if we ever called PR_Cleanup() // see bug 142072 } void nsThread::Main(void* arg) { nsThread* self = (nsThread*)arg; self->WaitUntilReadyToStartMain(); nsresult rv = NS_OK; rv = self->RegisterThreadSelf(); NS_ASSERTION(rv == NS_OK, "failed to set thread self"); PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p start run %p\n", self, self->mRunnable.get())); rv = self->mRunnable->Run(); NS_ASSERTION(NS_SUCCEEDED(rv), "runnable failed"); #ifdef DEBUG // Because a thread can die after gMainThread dies and takes nsIThreadLog with it, // we need to check for it being null so that we don't crash on shutdown. if (nsIThreadLog) { PRThreadState state; rv = self->GetState(&state); PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p end run %p\n", self, self->mRunnable.get())); } #endif // explicitly drop the runnable now in case there are circular references // between it and the thread object self->mRunnable = nsnull; } void nsThread::Exit(void* arg) { nsThread* self = (nsThread*)arg; if (self->mDead) { NS_ERROR("attempt to Exit() thread twice"); return; } self->mDead = PR_TRUE; #if defined(PR_LOGGING) if (nsIThreadLog) { PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p exited\n", self)); } #endif NS_RELEASE(self); } NS_METHOD nsThread::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) { nsThread* thread = new nsThread(); if (!thread) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = thread->QueryInterface(aIID, aResult); if (NS_FAILED(rv)) delete thread; return rv; } NS_IMPL_THREADSAFE_ISUPPORTS1(nsThread, nsIThread) NS_IMETHODIMP nsThread::Join() { // don't check for mDead here because nspr calls Exit (cleaning up // thread-local storage) before they let us join with the thread PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p start join\n", this)); if (!mThread) return NS_ERROR_NOT_INITIALIZED; PRStatus status = PR_JoinThread(mThread); // XXX can't use NS_RELEASE here because the macro wants to set // this to null (bad c++) PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p end join\n", this)); if (status == PR_SUCCESS) { NS_RELEASE_THIS(); // most likely the final release of this thread return NS_OK; } else return NS_ERROR_FAILURE; } NS_IMETHODIMP nsThread::GetPriority(PRThreadPriority *result) { if (mDead) return NS_ERROR_FAILURE; if (!mThread) return NS_ERROR_NOT_INITIALIZED; *result = PR_GetThreadPriority(mThread); return NS_OK; } NS_IMETHODIMP nsThread::SetPriority(PRThreadPriority value) { if (mDead) return NS_ERROR_FAILURE; if (!mThread) return NS_ERROR_NOT_INITIALIZED; PR_SetThreadPriority(mThread, value); return NS_OK; } NS_IMETHODIMP nsThread::Interrupt() { if (mDead) return NS_ERROR_FAILURE; if (!mThread) return NS_ERROR_NOT_INITIALIZED; PRStatus status = PR_Interrupt(mThread); return status == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsThread::GetScope(PRThreadScope *result) { if (mDead) return NS_ERROR_FAILURE; if (!mThread) return NS_ERROR_NOT_INITIALIZED; *result = PR_GetThreadScope(mThread); return NS_OK; } NS_IMETHODIMP nsThread::GetState(PRThreadState *result) { if (mDead) return NS_ERROR_FAILURE; if (!mThread) return NS_ERROR_NOT_INITIALIZED; *result = PR_GetThreadState(mThread); return NS_OK; } NS_IMETHODIMP nsThread::GetPRThread(PRThread* *result) { if (mDead) { *result = nsnull; return NS_ERROR_FAILURE; } *result = mThread; return NS_OK; } NS_IMETHODIMP nsThread::Init(nsIRunnable* runnable, PRUint32 stackSize, PRThreadPriority priority, PRThreadScope scope, PRThreadState state) { NS_ENSURE_ARG_POINTER(runnable); mRunnable = runnable; NS_ADDREF_THIS(); // released in nsThread::Exit if (state == PR_JOINABLE_THREAD) NS_ADDREF_THIS(); // released in nsThread::Join mStartLock = PR_NewLock(); if (mStartLock == nsnull) return NS_ERROR_OUT_OF_MEMORY; PR_Lock(mStartLock); mThread = PR_CreateThread(PR_USER_THREAD, Main, this, priority, scope, state, stackSize); PR_Unlock(mStartLock); PR_LOG(nsIThreadLog, PR_LOG_DEBUG, ("nsIThread %p created\n", this)); if (mThread == nsnull) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } /* readonly attribute nsIThread currentThread; */ NS_IMETHODIMP nsThread::GetCurrentThread(nsIThread * *aCurrentThread) { return GetIThread(PR_GetCurrentThread(), aCurrentThread); } /* void sleep (in PRUint32 msec); */ NS_IMETHODIMP nsThread::Sleep(PRUint32 msec) { if (PR_GetCurrentThread() != mThread) return NS_ERROR_FAILURE; if (PR_Sleep(PR_MillisecondsToInterval(msec)) != PR_SUCCESS) return NS_ERROR_FAILURE; return NS_OK; } NS_COM nsresult NS_NewThread(nsIThread* *result, nsIRunnable* runnable, PRUint32 stackSize, PRThreadState state, PRThreadPriority priority, PRThreadScope scope) { nsresult rv; nsThread* thread = new nsThread(); if (thread == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(thread); rv = thread->Init(runnable, stackSize, priority, scope, state); if (NS_FAILED(rv)) { NS_RELEASE(thread); return rv; } *result = thread; return NS_OK; } NS_COM nsresult NS_NewThread(nsIThread* *result, PRUint32 stackSize, PRThreadState state, PRThreadPriority priority, PRThreadScope scope) { nsThread* thread = new nsThread(); if (thread == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(thread); *result = thread; return NS_OK; } //////////////////////////////////////////////////////////////////////////////// nsresult nsThread::RegisterThreadSelf() { PRStatus status; if (kIThreadSelfIndex == 0) { status = PR_NewThreadPrivateIndex(&kIThreadSelfIndex, Exit); if (status != PR_SUCCESS) return NS_ERROR_FAILURE; } status = PR_SetThreadPrivate(kIThreadSelfIndex, this); if (status != PR_SUCCESS) return NS_ERROR_FAILURE; return NS_OK; } void nsThread::WaitUntilReadyToStartMain() { PR_Lock(mStartLock); PR_Unlock(mStartLock); PR_DestroyLock(mStartLock); mStartLock = nsnull; } NS_COM nsresult nsIThread::GetCurrent(nsIThread* *result) { return GetIThread(PR_GetCurrentThread(), result); } NS_COM nsresult nsIThread::GetIThread(PRThread* prthread, nsIThread* *result) { PRStatus status; nsThread* thread; if (nsThread::kIThreadSelfIndex == 0) { status = PR_NewThreadPrivateIndex(&nsThread::kIThreadSelfIndex, nsThread::Exit); if (status != PR_SUCCESS) return NS_ERROR_FAILURE; } thread = (nsThread*)PR_GetThreadPrivate(nsThread::kIThreadSelfIndex); if (thread == nsnull) { // if the current thread doesn't have an nsIThread associated // with it, make one thread = new nsThread(); if (thread == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(thread); // released by Exit thread->SetPRThread(prthread); nsresult rv = thread->RegisterThreadSelf(); if (NS_FAILED(rv)) return rv; } NS_ADDREF(thread); *result = thread; return NS_OK; } NS_COM nsresult nsIThread::SetMainThread() { // strictly speaking, it could be set twice. but practically speaking, // it's almost certainly an error if it is if (gMainThread != 0) { NS_ERROR("Setting main thread twice?"); return NS_ERROR_FAILURE; } return GetCurrent(&gMainThread); } NS_COM nsresult nsIThread::GetMainThread(nsIThread **result) { NS_ASSERTION(result, "bad result pointer"); if (gMainThread == 0) return NS_ERROR_FAILURE; *result = gMainThread; NS_ADDREF(gMainThread); return NS_OK; } NS_COM PRBool nsIThread::IsMainThread() { if (gMainThread == 0) return PR_TRUE; PRThread *theMainThread; gMainThread->GetPRThread(&theMainThread); return theMainThread == PR_GetCurrentThread(); } void nsThread::Shutdown() { if (gMainThread) { // XXX nspr doesn't seem to be calling the main thread's destructor // callback, so let's help it out: nsThread::Exit(NS_STATIC_CAST(nsThread*, gMainThread)); nsrefcnt cnt; NS_RELEASE2(gMainThread, cnt); NS_WARN_IF_FALSE(cnt == 0, "Main thread being held past XPCOM shutdown."); gMainThread = nsnull; kIThreadSelfIndex = 0; } } ////////////////////////////////////////////////////////////////////////////////