RetroZilla/xpcom/threads/nsThread.cpp
2015-10-20 23:03:22 -04:00

494 lines
14 KiB
C++

/* -*- 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)
return NS_ERROR_FAILURE;
NS_RELEASE_THIS(); // most likely the final release of this thread
return NS_OK;
}
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);
if (mRunnable)
return NS_ERROR_ALREADY_INITIALIZED;
mRunnable = runnable;
if (mStartLock)
return NS_ERROR_ALREADY_INITIALIZED;
mStartLock = PR_NewLock();
if (mStartLock == nsnull) {
mRunnable = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF_THIS(); // released in nsThread::Exit
if (state == PR_JOINABLE_THREAD)
NS_ADDREF_THIS(); // released in nsThread::Join
PR_Lock(mStartLock);
mDead = PR_FALSE;
mThread = PR_CreateThread(PR_USER_THREAD, Main, this,
priority, scope, state, stackSize);
/* As soon as we PR_Unlock(mStartLock), if mThread was successfully
* created, it could run and exit very quickly. In which case, it
* would null mThread and therefore if we check if (mThread) we could
* confuse a successfully created, yet already exited thread with
* OOM - failure to create the thread. So instead we store a local thr
* which we check to see if we really failed to create the thread.
*/
PRThread *thr = mThread;
PR_Unlock(mStartLock);
if (thr == nsnull) {
mDead = PR_TRUE; // otherwise cleared in nsThread::Exit
mRunnable = nsnull; // otherwise cleared in nsThread::Main(when done)
PR_DestroyLock(mStartLock);
mStartLock = nsnull;
NS_RELEASE_THIS(); // otherwise released in nsThread::Exit
if (state == PR_JOINABLE_THREAD)
NS_RELEASE_THIS(); // otherwise released in nsThread::Join
return NS_ERROR_OUT_OF_MEMORY;
}
PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
("nsIThread %p created\n", this));
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) {
// In most recent versions of NSPR the main thread's destructor
// callback will get called.
// In older versions of NSPR it will not get called,
// (unless we call PR_Cleanup).
// Because of that we:
// - call the function ourselves
// - set the data pointer to NULL to ensure the function will
// not get called again by NSPR
// The PR_SetThreadPrivate call does both of these.
// See also bugs 379550, 362768.
PR_SetThreadPrivate(kIThreadSelfIndex, NULL);
nsrefcnt cnt;
NS_RELEASE2(gMainThread, cnt);
NS_WARN_IF_FALSE(cnt == 0, "Main thread being held past XPCOM shutdown.");
gMainThread = nsnull;
kIThreadSelfIndex = 0;
}
}
////////////////////////////////////////////////////////////////////////////////