Apart from just really cleaning things up and moving everything into one class regarding thread IDs (ie, is_main_thread(), comparing ID's etc), this also fixes an obscure bug where LL was casting thread ID's to U32 and then compared those to find out if it the same thread. It's theoretically possible that such fails on a 64bit OS. By generalizing the interface, I adopted the use of a thread-local cache for the current thread ID as used by LLMutex et al, so now all code benefits from that. The idea was even extended to now also be used for is_main_thread() tests and even resetting a thread ID to the ID of the current thread.
486 lines
11 KiB
C++
486 lines
11 KiB
C++
/**
|
|
* @file llthread.cpp
|
|
*
|
|
* $LicenseInfo:firstyear=2004&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2004-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#ifdef __GNUC__
|
|
// Generate code for inlines from llthread.h (needed for is_main_thread()).
|
|
#pragma implementation "llthread.h"
|
|
#endif
|
|
|
|
#include "linden_common.h"
|
|
#include "llapr.h"
|
|
|
|
#include "apr_portable.h"
|
|
|
|
#include "llthread.h"
|
|
|
|
#include "lltimer.h"
|
|
|
|
#if LL_LINUX || LL_SOLARIS
|
|
#include <sched.h>
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Usage:
|
|
// void run_func(LLThread* thread)
|
|
// {
|
|
// }
|
|
// LLThread* thread = new LLThread();
|
|
// thread->run(run_func);
|
|
// ...
|
|
// thread->setQuitting();
|
|
// while(!timeout)
|
|
// {
|
|
// if (thread->isStopped())
|
|
// {
|
|
// delete thread;
|
|
// break;
|
|
// }
|
|
// }
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
LLAtomicS32 LLThread::sCount = 0;
|
|
LLAtomicS32 LLThread::sRunning = 0;
|
|
|
|
LL_COMMON_API void assert_main_thread()
|
|
{
|
|
if (!AIThreadID::in_main_thread())
|
|
{
|
|
llerrs << "Illegal execution outside main thread." << llendl;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handed to the APR thread creation function
|
|
//
|
|
void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap)
|
|
{
|
|
#ifdef CWDEBUG
|
|
debug::init_thread();
|
|
#endif
|
|
|
|
LLThread *threadp = (LLThread *)datap;
|
|
|
|
// Initialize thread-local cache of current thread ID (if supported).
|
|
AIThreadID::set_current_thread_id();
|
|
|
|
// Create a thread local data.
|
|
LLThreadLocalData::create(threadp);
|
|
|
|
// Run the user supplied function
|
|
threadp->run();
|
|
|
|
// Setting mStatus to STOPPED is done non-thread-safe, so it's
|
|
// possible that the thread is deleted by another thread at
|
|
// the moment it happens... therefore make a copy here.
|
|
char const* volatile name = threadp->mName.c_str();
|
|
|
|
// Always make sure that sRunning <= number of threads with status RUNNING,
|
|
// so do this before changing mStatus (meaning that once we see that we
|
|
// are STOPPED, then sRunning is also up to date).
|
|
--sRunning;
|
|
|
|
// We're done with the run function, this thread is done executing now.
|
|
threadp->mStatus = STOPPED;
|
|
|
|
// Only now print this info [doing that before setting mStatus
|
|
// to STOPPED makes it much more likely that another thread runs
|
|
// after the LLCurl::Multi::run() function exits and we actually
|
|
// change this variable (which really SHOULD have been inside
|
|
// the critical area of the mSignal lock)].
|
|
lldebugs << "LLThread::staticRun() Exiting: " << name << llendl;
|
|
|
|
--sRunning; // Would be better to do this after joining with the thread, but we don't join :/
|
|
return NULL;
|
|
}
|
|
|
|
|
|
LLThread::LLThread(std::string const& name) :
|
|
mPaused(false),
|
|
mName(name),
|
|
mAPRThreadp(NULL),
|
|
mStatus(STOPPED),
|
|
mThreadLocalData(NULL)
|
|
{
|
|
sCount++;
|
|
llassert(sCount <= 50);
|
|
mRunCondition = new LLCondition;
|
|
}
|
|
|
|
|
|
LLThread::~LLThread()
|
|
{
|
|
shutdown();
|
|
}
|
|
|
|
void LLThread::shutdown()
|
|
{
|
|
// Warning! If you somehow call the thread destructor from itself,
|
|
// the thread will die in an unclean fashion!
|
|
if (mAPRThreadp)
|
|
{
|
|
if (!isStopped())
|
|
{
|
|
// The thread isn't already stopped
|
|
// First, set the flag that indicates that we're ready to die
|
|
setQuitting();
|
|
|
|
llinfos << "LLThread::shutdown() Killing thread " << mName << " Status: " << mStatus << llendl;
|
|
// Now wait a bit for the thread to exit
|
|
// It's unclear whether I should even bother doing this - this destructor
|
|
// should netver get called unless we're already stopped, really...
|
|
S32 counter = 0;
|
|
const S32 MAX_WAIT = 600;
|
|
while (counter < MAX_WAIT)
|
|
{
|
|
if (isStopped())
|
|
{
|
|
break;
|
|
}
|
|
// Sleep for a tenth of a second
|
|
ms_sleep(100);
|
|
yield();
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
if (!isStopped())
|
|
{
|
|
// This thread just wouldn't stop, even though we gave it time
|
|
llwarns << "LLThread::shutdown() exiting thread before clean exit!" << llendl;
|
|
// Put a stake in its heart.
|
|
apr_thread_exit(mAPRThreadp, -1);
|
|
return;
|
|
}
|
|
mAPRThreadp = NULL;
|
|
}
|
|
--sCount;
|
|
delete mRunCondition;
|
|
mRunCondition = 0;
|
|
}
|
|
|
|
void LLThread::start()
|
|
{
|
|
llassert(isStopped());
|
|
|
|
// Set thread state to running
|
|
mStatus = RUNNING;
|
|
sRunning++;
|
|
|
|
apr_status_t status =
|
|
apr_thread_create(&mAPRThreadp, NULL, staticRun, (void *)this, tldata().mRootPool());
|
|
|
|
if(status == APR_SUCCESS)
|
|
{
|
|
// We won't bother joining
|
|
apr_thread_detach(mAPRThreadp);
|
|
}
|
|
else
|
|
{
|
|
--sRunning;
|
|
mStatus = STOPPED;
|
|
llwarns << "failed to start thread " << mName << llendl;
|
|
ll_apr_warn_status(status);
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
// Called from MAIN THREAD.
|
|
|
|
// Request that the thread pause/resume.
|
|
// The thread will pause when (and if) it calls checkPause()
|
|
void LLThread::pause()
|
|
{
|
|
if (!mPaused)
|
|
{
|
|
// this will cause the thread to stop execution as soon as checkPause() is called
|
|
mPaused = true; // Does not need to be atomic since this is only set/unset from the main thread
|
|
}
|
|
}
|
|
|
|
void LLThread::unpause()
|
|
{
|
|
if (mPaused)
|
|
{
|
|
mPaused = false;
|
|
}
|
|
|
|
wake(); // wake up the thread if necessary
|
|
}
|
|
|
|
// virtual predicate function -- returns true if the thread should wake up, false if it should sleep.
|
|
bool LLThread::runCondition(void)
|
|
{
|
|
// by default, always run. Handling of pause/unpause is done regardless of this function's result.
|
|
return true;
|
|
}
|
|
|
|
//============================================================================
|
|
// Called from run() (CHILD THREAD).
|
|
// Stop thread execution if requested until unpaused.
|
|
void LLThread::checkPause()
|
|
{
|
|
mRunCondition->lock();
|
|
|
|
// This is in a while loop because the pthread API allows for spurious wakeups.
|
|
while(shouldSleep())
|
|
{
|
|
mRunCondition->wait(); // unlocks mRunCondition
|
|
// mRunCondition is locked when the thread wakes up
|
|
}
|
|
|
|
mRunCondition->unlock();
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
void LLThread::setQuitting()
|
|
{
|
|
mRunCondition->lock();
|
|
if (mStatus == RUNNING)
|
|
{
|
|
mStatus = QUITTING;
|
|
}
|
|
mRunCondition->unlock();
|
|
wake();
|
|
}
|
|
|
|
// static
|
|
void LLThread::yield()
|
|
{
|
|
#if LL_LINUX || LL_SOLARIS
|
|
sched_yield(); // annoyingly, apr_thread_yield is a noop on linux...
|
|
#else
|
|
apr_thread_yield();
|
|
#endif
|
|
}
|
|
|
|
void LLThread::wake()
|
|
{
|
|
mRunCondition->lock();
|
|
if(!shouldSleep())
|
|
{
|
|
mRunCondition->signal();
|
|
}
|
|
mRunCondition->unlock();
|
|
}
|
|
|
|
void LLThread::wakeLocked()
|
|
{
|
|
if(!shouldSleep())
|
|
{
|
|
mRunCondition->signal();
|
|
}
|
|
}
|
|
|
|
// The thread private handle to access the LLThreadLocalData instance.
|
|
apr_threadkey_t* LLThreadLocalData::sThreadLocalDataKey;
|
|
|
|
LLThreadLocalData::LLThreadLocalData(char const* name) : mCurlMultiHandle(NULL), mCurlErrorBuffer(NULL), mName(name)
|
|
{
|
|
}
|
|
|
|
LLThreadLocalData::~LLThreadLocalData()
|
|
{
|
|
delete mCurlMultiHandle;
|
|
delete [] mCurlErrorBuffer;
|
|
}
|
|
|
|
//static
|
|
void LLThreadLocalData::init(void)
|
|
{
|
|
// Only do this once.
|
|
if (sThreadLocalDataKey)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// This function is called by the main thread (these values are also needed in the next line).
|
|
AIThreadID::set_main_thread_id();
|
|
AIThreadID::set_current_thread_id();
|
|
|
|
apr_status_t status = apr_threadkey_private_create(&sThreadLocalDataKey, &LLThreadLocalData::destroy, LLAPRRootPool::get()());
|
|
ll_apr_assert_status(status); // Or out of memory, or system-imposed limit on the
|
|
// total number of keys per process {PTHREAD_KEYS_MAX}
|
|
// has been exceeded.
|
|
|
|
// Create the thread-local data for the main thread (this function is called by the main thread).
|
|
LLThreadLocalData::create(NULL);
|
|
}
|
|
|
|
// This is called once for every thread when the thread is destructed.
|
|
//static
|
|
void LLThreadLocalData::destroy(void* thread_local_data)
|
|
{
|
|
delete static_cast<LLThreadLocalData*>(thread_local_data);
|
|
}
|
|
|
|
//static
|
|
void LLThreadLocalData::create(LLThread* threadp)
|
|
{
|
|
LLThreadLocalData* new_tld = new LLThreadLocalData(threadp ? threadp->mName.c_str() : "main thread");
|
|
if (threadp)
|
|
{
|
|
threadp->mThreadLocalData = new_tld;
|
|
}
|
|
apr_status_t status = apr_threadkey_private_set(new_tld, sThreadLocalDataKey);
|
|
llassert_always(status == APR_SUCCESS);
|
|
}
|
|
|
|
//static
|
|
LLThreadLocalData& LLThreadLocalData::tldata(void)
|
|
{
|
|
if (!sThreadLocalDataKey)
|
|
{
|
|
LLThreadLocalData::init();
|
|
}
|
|
|
|
void* data;
|
|
apr_status_t status = apr_threadkey_private_get(&data, sThreadLocalDataKey);
|
|
llassert_always(status == APR_SUCCESS);
|
|
return *static_cast<LLThreadLocalData*>(data);
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
LLCondition::LLCondition(LLAPRPool& parent) : LLMutex(parent)
|
|
{
|
|
apr_thread_cond_create(&mAPRCondp, mPool());
|
|
}
|
|
|
|
|
|
LLCondition::~LLCondition()
|
|
{
|
|
apr_thread_cond_destroy(mAPRCondp);
|
|
mAPRCondp = NULL;
|
|
}
|
|
|
|
void LLCondition::wait()
|
|
{
|
|
apr_thread_cond_wait(mAPRCondp, mAPRMutexp);
|
|
}
|
|
|
|
void LLCondition::signal()
|
|
{
|
|
apr_thread_cond_signal(mAPRCondp);
|
|
}
|
|
|
|
void LLCondition::broadcast()
|
|
{
|
|
apr_thread_cond_broadcast(mAPRCondp);
|
|
}
|
|
|
|
//============================================================================
|
|
LLMutexBase::LLMutexBase() :
|
|
mLockingThread(AIThreadID::sNone),
|
|
mCount(0)
|
|
{
|
|
}
|
|
|
|
bool LLMutexBase::isSelfLocked() const
|
|
{
|
|
return mLockingThread.equals_current_thread();
|
|
}
|
|
|
|
void LLMutexBase::lock()
|
|
{
|
|
if (mLockingThread.equals_current_thread())
|
|
{ //redundant lock
|
|
mCount++;
|
|
return;
|
|
}
|
|
|
|
apr_thread_mutex_lock(mAPRMutexp);
|
|
|
|
mLockingThread.reset();
|
|
}
|
|
|
|
bool LLMutexBase::tryLock()
|
|
{
|
|
if (mLockingThread.equals_current_thread())
|
|
{ //redundant lock
|
|
mCount++;
|
|
return true;
|
|
}
|
|
bool success = !APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp));
|
|
if (success)
|
|
{
|
|
mLockingThread.reset();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// non-blocking, but does do a lock/unlock so not free
|
|
bool LLMutexBase::isLocked() const
|
|
{
|
|
if (mLockingThread.equals_current_thread())
|
|
return false; // A call to lock() won't block.
|
|
if (APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp)))
|
|
return true;
|
|
apr_thread_mutex_unlock(mAPRMutexp);
|
|
return false;
|
|
}
|
|
|
|
void LLMutexBase::unlock()
|
|
{
|
|
if (mCount > 0)
|
|
{ //not the root unlock
|
|
mCount--;
|
|
return;
|
|
}
|
|
mLockingThread = AIThreadID::sNone;
|
|
|
|
apr_thread_mutex_unlock(mAPRMutexp);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
LLThreadSafeRefCount::LLThreadSafeRefCount() :
|
|
mRef(0)
|
|
{
|
|
}
|
|
|
|
LLThreadSafeRefCount::~LLThreadSafeRefCount()
|
|
{
|
|
if (mRef != 0)
|
|
{
|
|
llerrs << "deleting non-zero reference" << llendl;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
LLResponder::~LLResponder()
|
|
{
|
|
}
|
|
|
|
//============================================================================
|