This commit is contained in:
jeanlemotan
2024-07-02 18:10:39 +02:00
commit 48ab06b1d9
733 changed files with 321088 additions and 0 deletions
@@ -0,0 +1,189 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_barrier.h>
#include <eathread/eathread.h>
#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <new>
#ifdef EA_PLATFORM_WINDOWS
#pragma warning(push, 0)
#include <Windows.h> // Presumably we are using pthreads-win32.
#pragma warning(pop)
#endif
EABarrierData::EABarrierData()
: mCV(), mMutex(), mnHeight(0), mnCurrent(0), mnCycle(0), mbValid(false)
{}
EA::Thread::BarrierParameters::BarrierParameters(int height, bool bIntraProcess, const char* pName)
: mHeight(height), mbIntraProcess(bIntraProcess)
{
if(pName)
strncpy(mName, pName, sizeof(mName)-1);
else
mName[0] = 0;
}
EA::Thread::Barrier::Barrier(const BarrierParameters* pBarrierParameters, bool bDefaultParameters)
{
if(!pBarrierParameters && bDefaultParameters)
{
BarrierParameters parameters;
Init(&parameters);
}
else
Init(pBarrierParameters);
}
EA::Thread::Barrier::Barrier(int height)
{
BarrierParameters parameters(height);
Init(&parameters);
}
EA::Thread::Barrier::~Barrier()
{
if(mBarrierData.mbValid){
EAT_ASSERT(mBarrierData.mnCurrent == mBarrierData.mnHeight);
int result = pthread_mutex_destroy(&mBarrierData.mMutex);
EA_UNUSED(result);
EAT_ASSERT(result == 0);
result = pthread_cond_destroy(&mBarrierData.mCV);
EAT_ASSERT(result == 0);
EA_UNUSED( result ); //if compiling without asserts
}
}
bool EA::Thread::Barrier::Init(const BarrierParameters* pBarrierParameters)
{
if(pBarrierParameters && !mBarrierData.mbValid){
mBarrierData.mbValid = false;
mBarrierData.mnHeight = pBarrierParameters->mHeight;
mBarrierData.mnCurrent = pBarrierParameters->mHeight;
mBarrierData.mnCycle = 0;
int result = pthread_mutex_init(&mBarrierData.mMutex, NULL);
if(result == 0){
result = pthread_cond_init(&mBarrierData.mCV, NULL);
if(result == 0)
mBarrierData.mbValid = true;
else
pthread_mutex_destroy(&mBarrierData.mMutex);
}
return mBarrierData.mbValid;
}
return false;
}
EA::Thread::Barrier::Result EA::Thread::Barrier::Wait(const ThreadTime& timeoutAbsolute)
{
if(!mBarrierData.mbValid){
EAT_ASSERT(false);
return kResultError;
}
int result = pthread_mutex_lock(&mBarrierData.mMutex);
if(result != 0){
EAT_ASSERT(false);
return kResultError;
}
const unsigned long nCurrentCycle = (unsigned)mBarrierData.mnCycle;
bool bPrimary = false;
if(--mBarrierData.mnCurrent == 0){ // This is not an atomic operation. We are within a mutex lock.
// The last barrier can never time out, as its action is always immediate.
mBarrierData.mnCycle++;
mBarrierData.mnCurrent = mBarrierData.mnHeight;
result = pthread_cond_broadcast(&mBarrierData.mCV);
// The last thread into the barrier will return a result of
// kResultPrimary rather than kResultSecondary.
if(result == 0)
bPrimary = true;
//else leave result as an error value.
}
else{
// timeoutMilliseconds
// Wait with cancellation disabled, because pthreads barrier_wait
// should not be a cancellation point.
#if defined(PTHREAD_CANCEL_DISABLE)
int cancel;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel);
#endif
// Wait until the barrier's cycle changes, which means that
// it has been broadcast, and we don't want to wait anymore.
while(nCurrentCycle == mBarrierData.mnCycle){
do{
// Under SMP systems, pthread_cond_wait can return the success value 'spuriously'.
// This is by design and we must retest the predicate condition and if it has
// not true, we must go back to waiting.
result = pthread_cond_timedwait(&mBarrierData.mCV, &mBarrierData.mMutex, &timeoutAbsolute);
} while((result == 0) && (nCurrentCycle == mBarrierData.mnCycle));
if(result != 0)
break;
}
#if defined(PTHREAD_CANCEL_DISABLE)
int cancelTemp;
pthread_setcancelstate(cancel, &cancelTemp);
#endif
}
// We declare a new result2 value because the old one
// might have a special value from above in it.
const int result2 = pthread_mutex_unlock(&mBarrierData.mMutex); (void)result2;
EAT_ASSERT(result2 == 0);
if(result == 0)
return bPrimary ? kResultPrimary : kResultSecondary;
else if(result == ETIMEDOUT)
return kResultTimeout;
return kResultError;
}
EA::Thread::Barrier* EA::Thread::BarrierFactory::CreateBarrier()
{
EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator();
if(pAllocator)
return new(pAllocator->Alloc(sizeof(EA::Thread::Barrier))) EA::Thread::Barrier;
else
return new EA::Thread::Barrier;
}
void EA::Thread::BarrierFactory::DestroyBarrier(EA::Thread::Barrier* pBarrier)
{
EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator();
if(pAllocator)
{
pBarrier->~Barrier();
pAllocator->Free(pBarrier);
}
else
delete pBarrier;
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,269 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <string.h>
#include <pthread.h>
#include <eathread/eathread_storage.h>
#if EATHREAD_GLIBC_BACKTRACE_AVAILABLE
#include <signal.h>
#include <execinfo.h>
#endif
namespace EA
{
namespace Thread
{
///////////////////////////////////////////////////////////////////////////////
// GetInstructionPointer
//
EATHREADLIB_API void GetInstructionPointer(void*& pInstruction)
{
pInstruction = __builtin_return_address(0);
}
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstack
//
// Capture up to nReturnAddressArrayCapacity elements of the call stack,
// or the whole callstack, whichever is smaller.
//
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
EA_UNUSED(pContext);
#if EATHREAD_GLIBC_BACKTRACE_AVAILABLE
size_t count = 0;
// The pContext option is not currently supported.
if(pContext == NULL)
{
count = (size_t)backtrace(pReturnAddressArray, (int)nReturnAddressArrayCapacity);
if(count > 0)
{
--count; // Remove the first entry, because it refers to this function and by design we don't include this function.
memmove(pReturnAddressArray, pReturnAddressArray + 1, count * sizeof(void*));
}
}
return count;
#elif defined(__APPLE__) && defined(EA_PROCESSOR_X86)
// Apple's ABI defines a callstack frame system.
// http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/LowLevelABI/100-32-bit_PowerPC_Function_Calling_Conventions/32bitPowerPC.html#//apple_ref/doc/uid/TP40002438-SW20
// Apple x86 stack frame:
// struct StackFrame {
// StackFrame* mpParentStackFrame;
// void* mpParentCR;
// void* mpReturnPC;
// }
//
//struct StackFrame { To do: Re-write this in terms of StackFrame and pContext.
// StackFrame* mpParentStackFrame;
// void* mpParentCR;
// void* mpReturnPC;
//};
size_t index = 0;
if(nReturnAddressArrayCapacity)
{
void* fpParent = __builtin_frame_address(1); // frame pointer of caller.
void** fp;
pReturnAddressArray[index++] = __builtin_return_address(0);
fp = (void**)fpParent;
while(fp && *fp && (index < nReturnAddressArrayCapacity))
{
fpParent = *fp;
fp = fpParent;
if(*fp)
pReturnAddressArray[index++] = *(fp + 2);
}
}
return index;
#else
EA_UNUSED(pReturnAddressArray);
EA_UNUSED(nReturnAddressArrayCapacity);
return 0;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* pContext)
{
#if defined(EA_PROCESSOR_X86_64)
context.mRIP = pContext->Rip;
context.mRSP = pContext->Rsp;
context.mRBP = pContext->Rbp;
#elif defined(EA_PROCESSOR_X86)
context.mEIP = pContext->Eip;
context.mESP = pContext->Esp;
context.mEBP = pContext->Ebp;
#elif defined(EA_PROCESSOR_ARM)
context.mSP = pContext->mGpr[13];
context.mLR = pContext->mGpr[14];
context.mPC = pContext->mGpr[15];
#else
// To do.
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
EATHREADLIB_API size_t GetModuleFromAddress(const void* /*address*/, char* pModuleName, size_t /*moduleNameCapacity*/)
{
// Not currently implemented for the given platform.
pModuleName[0] = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* /*pAddress*/)
{
// Not currently implemented for the given platform.
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
// Under Windows, the threadId parameter is expected to be a thread HANDLE,
// which is different from a windows integer thread id.
// On Unix the threadId parameter is expected to be a pthread id.
//
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t /*threadId*/)
{
//if(threadId == pthread_self()) // Note that at least for MacOS, it's possible to get other threads' info.
{
#if defined(EA_PROCESSOR_X86)
context.mEIP = (uint32_t)__builtin_return_address(0);
context.mESP = (uint32_t)__builtin_frame_address(1);
context.mEBP = 0;
#else
// To do.
#endif
return true;
}
// Not currently implemented for the given platform.
memset(&context, 0, sizeof(context));
return false;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
return GetCallstackContext(context, sysThreadId);
}
// To do: Remove the usage of sStackBase for the platforms that it's not needed,
// as can be seen from the logic below. For example Mac OSX probably doesn't need it.
static EA::Thread::ThreadLocalStorage sStackBase;
///////////////////////////////////////////////////////////////////////////////
// SetStackBase
//
EATHREADLIB_API void SetStackBase(void* pStackBase)
{
if(pStackBase)
sStackBase.SetValue(pStackBase);
else
{
pStackBase = __builtin_frame_address(0);
if(pStackBase)
SetStackBase(pStackBase);
// Else failure; do nothing.
}
}
///////////////////////////////////////////////////////////////////////////////
// GetStackBase
//
EATHREADLIB_API void* GetStackBase()
{
#if defined(EA_PLATFORM_UNIX)
void* pBase;
if(GetPthreadStackInfo(&pBase, NULL))
return pBase;
#endif
// Else we require the user to have set this previously, usually via a call
// to SetStackBase() in the start function of this currently executing
// thread (or main for the main thread).
return sStackBase.GetValue();
}
///////////////////////////////////////////////////////////////////////////////
// GetStackLimit
//
EATHREADLIB_API void* GetStackLimit()
{
#if defined(EA_PLATFORM_UNIX)
void* pLimit;
if(GetPthreadStackInfo(NULL, &pLimit))
return pLimit;
#endif
// If this fails then we might have an issue where you are using GCC but not
// using the GCC standard library glibc. Or maybe glibc doesn't support
// __builtin_frame_address on this platform. Or maybe you aren't using GCC but
// rather a compiler that masquerades as GCC (common situation).
void* pStack = __builtin_frame_address(0);
return (void*)((uintptr_t)pStack & ~4095); // Round down to nearest page, as the stack grows downward.
}
} // namespace Thread
} // namespace EA
@@ -0,0 +1,146 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/internal/config.h>
#include <eathread/eathread_condition.h>
#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#ifdef EA_PLATFORM_WINDOWS
#pragma warning(push, 0)
#include <Windows.h> // Presumably we are using pthreads-win32.
#pragma warning(pop)
#endif
EAConditionData::EAConditionData()
{
memset(&mCV, 0, sizeof(mCV));
}
EA::Thread::ConditionParameters::ConditionParameters(bool bIntraProcess, const char* /*pName*/)
: mbIntraProcess(bIntraProcess)
{
// Empty
}
EA::Thread::Condition::Condition(const ConditionParameters* pConditionParameters, bool bDefaultParameters)
{
if(!pConditionParameters && bDefaultParameters)
{
ConditionParameters parameters;
Init(&parameters);
}
else
Init(pConditionParameters);
}
EA::Thread::Condition::~Condition()
{
pthread_cond_destroy(&mConditionData.mCV);
}
bool EA::Thread::Condition::Init(const ConditionParameters* pConditionParameters)
{
if(pConditionParameters)
{
#if defined(EA_PLATFORM_ANDROID) // Some platforms don't provide pthread_condattr_init, and pthread_condattr_t is just an int.
pthread_condattr_t cattr;
memset(&cattr, 0, sizeof(cattr));
const int result = pthread_cond_init(&mConditionData.mCV, &cattr);
return (result == 0);
#else
pthread_condattr_t cattr;
pthread_condattr_init(&cattr);
#if defined(PTHREAD_PROCESS_PRIVATE) // Some pthread implementations don't recognize this. PTHREAD_PROCESS_SHARED bugged on iphone
#if defined(EA_PLATFORM_IPHONE) || defined(EA_PLATFORM_OSX)
EAT_ASSERT( pConditionParameters->mbIntraProcess == true ); // shared conditions bugged on apple hardware
#else
if(pConditionParameters->mbIntraProcess)
pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);
else
pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
#endif
#endif
const int result = pthread_cond_init(&mConditionData.mCV, &cattr);
pthread_condattr_destroy(&cattr);
return (result == 0);
#endif
}
return false;
}
EA::Thread::Condition::Result EA::Thread::Condition::Wait(Mutex* pMutex, const ThreadTime& timeoutAbsolute)
{
int result;
pthread_mutex_t* pMutex_t;
EAMutexData* pMutexData;
EAT_ASSERT(pMutex);
// We have a small problem here in that if we are using the pMutex argument,
// the pthread_cond_wait call will unlock the mutex via the internal mutex data and
// not without calling the Mutex::Lock function. The result is that the Mutex doesn't
// have its lock count value reduced by one and so other threads will see the lock
// count as being 1 when in fact it should be zero. So we account for that here
// by manually maintaining the lock count, which we can do because we have the lock.
EAT_ASSERT(pMutex->GetLockCount() == 1);
pMutexData = (EAMutexData*)pMutex->GetPlatformData();
pMutexData->SimulateLock(false);
pMutex_t = &pMutexData->mMutex;
if(timeoutAbsolute == kTimeoutNone)
result = pthread_cond_wait(&mConditionData.mCV, pMutex_t);
else
result = pthread_cond_timedwait(&mConditionData.mCV, pMutex_t, &timeoutAbsolute);
pMutexData->SimulateLock(true);
EAT_ASSERT(!pMutex || (pMutex->GetLockCount() == 1));
if(result != 0)
{
if(result == ETIMEDOUT)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
return kResultOK;
}
bool EA::Thread::Condition::Signal(bool bBroadcast)
{
if(bBroadcast)
return (pthread_cond_broadcast(&mConditionData.mCV) == 0);
return (pthread_cond_signal(&mConditionData.mCV) == 0);
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,221 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/internal/config.h>
#include <eathread/eathread_mutex.h>
#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include <errno.h>
#include <string.h>
#ifdef EA_PLATFORM_WINDOWS
#pragma warning(push, 0)
#include <Windows.h> // Presumably we are using pthreads-win32.
#pragma warning(pop)
#ifdef CreateMutex
#undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW.
#endif
#endif
EAMutexData::EAMutexData()
: mMutex(), mnLockCount(0)
{
#if EAT_ASSERT_ENABLED
mThreadId = EA::Thread::kThreadIdInvalid;
#endif
::memset(&mMutex, 0, sizeof(mMutex));
}
void EAMutexData::SimulateLock(bool bLock)
{
if(bLock)
{
++mnLockCount;
#if EAT_ASSERT_ENABLED
mThreadId = EA::Thread::GetThreadId();
#endif
}
else
{
--mnLockCount;
#if EAT_ASSERT_ENABLED
mThreadId = EA::Thread::kThreadIdInvalid;
#endif
}
}
EA::Thread::MutexParameters::MutexParameters(bool bIntraProcess, const char* /*pName*/)
: mbIntraProcess(bIntraProcess)
{
// Empty
}
EA::Thread::Mutex::Mutex(const MutexParameters* pMutexParameters, bool bDefaultParameters)
{
if(!pMutexParameters && bDefaultParameters)
{
MutexParameters parameters;
Init(&parameters);
}
else
Init(pMutexParameters);
}
EA::Thread::Mutex::~Mutex()
{
EAT_ASSERT(mMutexData.mnLockCount == 0);
pthread_mutex_destroy(&mMutexData.mMutex);
}
bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters)
{
if(pMutexParameters)
{
mMutexData.mnLockCount = 0;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
#if defined(PTHREAD_PROCESS_PRIVATE) // Some pthread implementations don't recognize this.
#if defined(PTHREAD_PROCESS_SHARED)
if (pMutexParameters->mbIntraProcess)
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
else
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
#else
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
#endif
#endif
const int result = pthread_mutex_init(&mMutexData.mMutex, &attr);
pthread_mutexattr_destroy(&attr);
EAT_ASSERT(result != -1);
return (result != -1);
}
return false;
}
int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute)
{
int result;
EAT_ASSERT(mMutexData.mnLockCount < 100000);
if(timeoutAbsolute == kTimeoutNone)
{
result = pthread_mutex_lock(&mMutexData.mMutex);
if(result != 0)
{
EAT_ASSERT(false);
return kResultError;
}
}
else if(timeoutAbsolute == kTimeoutImmediate)
{
result = pthread_mutex_trylock(&mMutexData.mMutex);
if(result != 0)
{
if(result == EBUSY)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
}
else
{
#if (defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_WINDOWS)) && !defined(__CYGWIN__) && !defined(EA_PLATFORM_ANDROID)
const timespec* pTimeSpec = &timeoutAbsolute;
result = pthread_mutex_timedlock(&mMutexData.mMutex, const_cast<timespec*>(pTimeSpec)); // Some pthread implementations use non-const timespec, so cast for them.
if(result != 0)
{
if(result == ETIMEDOUT)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
#else // OSX, BSD
// Some Posix systems don't have pthread_mutex_timedlock. In these
// cases we fall back to a polling mechanism. However, polling really
// isn't proper because the polling thread might be at a greater
// priority level than the lock-owning thread and thus this code
// might not work as well as desired.
while(((result = pthread_mutex_trylock(&mMutexData.mMutex)) != 0) && (GetThreadTime() < timeoutAbsolute))
ThreadSleep(1);
if(result != 0)
{
if(result == EBUSY)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
#endif
}
EAT_ASSERT(mMutexData.mThreadId = EA::Thread::GetThreadId()); // Intentionally '=' here and not '=='.
EAT_ASSERT(mMutexData.mnLockCount >= 0);
return ++mMutexData.mnLockCount; // This is safe to do because we have the lock.
}
int EA::Thread::Mutex::Unlock()
{
EAT_ASSERT(mMutexData.mThreadId == EA::Thread::GetThreadId());
EAT_ASSERT(mMutexData.mnLockCount > 0);
const int nReturnValue(--mMutexData.mnLockCount); // This is safe to do because we have the lock.
if(pthread_mutex_unlock(&mMutexData.mMutex) != 0)
{
EAT_ASSERT(false);
return nReturnValue + 1;
}
return nReturnValue;
}
int EA::Thread::Mutex::GetLockCount() const
{
return mMutexData.mnLockCount;
}
bool EA::Thread::Mutex::HasLock() const
{
#if EAT_ASSERT_ENABLED
return (mMutexData.mnLockCount > 0) && (mMutexData.mThreadId == GetThreadId());
#else
return (mMutexData.mnLockCount > 0); // This is the best we can do.
#endif
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,140 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <stdlib.h>
#if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_APPLE)
#include <pthread.h>
#endif
namespace EA
{
namespace Thread
{
#if defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_APPLE) || defined(EA_PLATFORM_SONY)
// With some implementations of pthread, the stack base is returned by pthread as NULL if it's the main thread,
// or possibly if it's a thread you created but didn't call pthread_attr_setstack manually to provide your
// own stack. It's impossible for us to tell here whether will be such a NULL return value, so we just do what
// we can and the user nees to beware that a NULL return value means that the system doesn't provide the
// given information for the current thread. This function returns false and sets pBase and pLimit to NULL in
// the case that the thread base and limit weren't returned by the system or were returned as NULL.
#if defined(EA_PLATFORM_APPLE)
bool GetPthreadStackInfo(void** pBase, void** pLimit)
{
pthread_t thread = pthread_self();
void* pBaseTemp = pthread_get_stackaddr_np(thread);
size_t stackSize = pthread_get_stacksize_np(thread);
if(pBase)
*pBase = pBaseTemp;
if(pLimit)
{
if(pBaseTemp)
*pLimit = (void*)((size_t)pBaseTemp - stackSize);
else
*pLimit = NULL;
}
return (pBaseTemp != NULL);
}
#elif defined(EA_PLATFORM_SONY)
bool GetPthreadStackInfo(void** pBase, void** pLimit)
{
bool returnValue = false;
size_t stackSize;
void* pBaseTemp = NULL;
void* pLimitTemp = NULL;
ScePthreadAttr attr;
scePthreadAttrInit(&attr);
int result = scePthreadAttrGet(scePthreadSelf(), &attr);
if(result == 0) // SCE_OK (=0)
{
result = scePthreadAttrGetstack(&attr, &pLimitTemp, &stackSize);
if((result == 0) && (pLimitTemp != NULL)) // If success...
{
pBaseTemp = (void*)((uintptr_t)pLimitTemp + stackSize); // p is returned by pthread_attr_getstack as the lowest address in the stack, and not the stack base.
returnValue = true;
}
else
{
pBaseTemp = NULL;
pLimitTemp = NULL;
}
}
scePthreadAttrDestroy(&attr);
if(pBase)
*pBase = pBaseTemp;
if(pLimit)
*pLimit = pLimitTemp;
return returnValue;
}
#else
bool GetPthreadStackInfo(void** pBase, void** pLimit)
{
bool returnValue = false;
void* pBaseTemp = NULL;
void* pLimitTemp = NULL;
pthread_attr_t attr;
pthread_attr_init(&attr);
#if defined(EA_PLATFORM_LINUX)
int result = pthread_getattr_np(pthread_self(), &attr);
#else
int result = pthread_attr_get_np(pthread_self(), &attr); // __BSD__ or __FreeBSD__
#endif
if(result == 0)
{
// The pthread_attr_getstack() function returns the stack address and stack size
// attributes of the thread attributes object referred to by attr in the buffers
// pointed to by stackaddr and stacksize, respectively. According to the documentation,
// the stack address reported is the lowest memory address and not the stack 'base'.
// http://pubs.opengroup.org/onlinepubs/007904975/functions/pthread_attr_setstack.html
size_t stackSize;
result = pthread_attr_getstack(&attr, &pLimitTemp, &stackSize);
if((result == 0) && (pLimitTemp != NULL)) // If success...
{
pBaseTemp = (void*)((uintptr_t)pLimitTemp + stackSize); // p is returned by pthread_attr_getstack as the lowest address in the stack, and not the stack base.
returnValue = true;
}
else
{
pBaseTemp = NULL;
pLimitTemp = NULL;
}
}
pthread_attr_destroy(&attr);
if(pBase)
*pBase = pBaseTemp;
if(pLimit)
*pLimit = pLimitTemp;
return returnValue;
}
#endif
#endif
} // namespace Callstack
} // namespace EA
@@ -0,0 +1,261 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_semaphore.h>
#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include <time.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#ifdef EA_PLATFORM_WINDOWS
#pragma warning(push, 0)
#include <pthread.h>
#include <Windows.h> // Presumably we are using pthreads-win32.
#pragma warning(pop)
#ifdef CreateSemaphore
#undef CreateSemaphore // Windows #defines CreateSemaphore to CreateSemaphoreA or CreateSemaphoreW.
#endif
#endif
EASemaphoreData::EASemaphoreData()
: mnCount(0), mnMaxCount(INT_MAX)
{
memset(&mSemaphore, 0, sizeof(mSemaphore));
}
EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* /*pName*/)
: mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess)
{
}
EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters)
{
if(!pSemaphoreParameters && bDefaultParameters)
{
SemaphoreParameters parameters;
Init(&parameters);
}
else
Init(pSemaphoreParameters);
}
EA::Thread::Semaphore::Semaphore(int initialCount)
{
SemaphoreParameters parameters(initialCount);
Init(&parameters);
}
EA::Thread::Semaphore::~Semaphore()
{
#if defined(EA_PLATFORM_ANDROID)
sem_destroy(&mSemaphoreData.mSemaphore); // Android's sem_destroy is broken. http://code.google.com/p/android/issues/detail?id=3106
#else
int result = -1;
for(;;)
{
result = sem_destroy(&mSemaphoreData.mSemaphore);
if((result == -1) && (errno == EBUSY)) // If another thread or process is blocked on this semaphore...
ThreadSleep(kTimeoutYield); // Yield. If we don't yield, it's possible we could block other other threads or processes from running, on some systems.
else
break;
}
EAT_ASSERT(result != -1);
#endif
}
bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
{
if(pSemaphoreParameters)
{
mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
if(mSemaphoreData.mnCount < 0)
mSemaphoreData.mnCount = 0;
mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess;
int result = sem_init(&mSemaphoreData.mSemaphore, mSemaphoreData.mbIntraProcess ? 1 : 0, (unsigned)mSemaphoreData.mnCount);
// To consider: Remove this fallback and simply return false if the first attempt failed.
if((result == -1) && mSemaphoreData.mbIntraProcess)
{
result = sem_init(&mSemaphoreData.mSemaphore, 0, (unsigned)mSemaphoreData.mnCount);
if(result == -1)
{
EAT_ASSERT(false);
memset(&mSemaphoreData.mSemaphore, 0, sizeof(mSemaphoreData.mSemaphore));
}
else
mSemaphoreData.mbIntraProcess = false;
}
return (result != -1);
}
return false;
}
int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
{
int result;
if(timeoutAbsolute == kTimeoutNone)
{
// We retry waits that were interrupted by signals. Should we instead require
// the user to deal with this and return an error value? Or should we require
// the user to disable the appropriate signal interruptions?
while(((result = sem_wait(&mSemaphoreData.mSemaphore)) == -1) && (errno == EINTR))
continue;
if(result == -1)
{
EAT_ASSERT(false); // This is an error condition.
return kResultError;
}
}
else if(timeoutAbsolute == kTimeoutImmediate)
{
// The sem_trywait() and sem_wait() functions shall return zero if the calling process successfully
// performed the semaphore lock operation on the semaphore designated by sem. If the call was
// unsuccessful, the state of the semaphore shall be unchanged, and the function shall return a
// value of -1 and set errno to indicate the error.
int trywaitResult = sem_trywait(&mSemaphoreData.mSemaphore);
if(trywaitResult == -1)
{
if(errno == EAGAIN) // The sem_* family of functions are different from pthreads because they set errno instead of returning an error value.
return kResultTimeout;
#ifdef EA_PLATFORM_WINDOWS // On Windows, the errno mechanism doesn't work unless you
if(mSemaphoreData.mnCount == 0) // are using the C runtime library as a shared dll between
return kResultTimeout; // the app and pthreads.dll. We try to account for the existence
#endif // of this problem here in a somewhat conservative way.
return kResultError;
}
// Android sem_trywait is broken and in earlier versions returns EAGAIN instead of setting
// errno to EAGAIN. http://source-android.frandroid.com/bionic/libc/docs/CHANGES.TXT
#if defined(EA_PLATFORM_ANDROID)
if(trywaitResult == EAGAIN)
return kResultTimeout;
#endif
}
else
{
// Some systems don't have a sem_timedwait. In these cases we
// fall back to a polling mechanism. However, polling really
// isn't proper because the polling thread might be at a greater
// priority level than the lock-owning thread and thus this code
// might not work as well as desired.
#if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_WINDOWS)
// We retry waits that were interrupted by signals. Should we instead require
// the user to deal with this and return an error value? Or should we require
// the user to disable the appropriate signal interruptions?
while(((result = sem_timedwait(&mSemaphoreData.mSemaphore, &timeoutAbsolute)) == -1) && (errno == EINTR))
continue;
if(result == -1)
{
if(errno == ETIMEDOUT) // The sem_* family of functions are different from pthreads because they set errno instead of returning an error value.
return kResultTimeout;
#ifdef EA_PLATFORM_WINDOWS // On Windows, the errno mechanism doesn't work unless you
if(mSemaphoreData.mnCount == 0) // are using the C runtime library as a shared dll between
return kResultTimeout; // the app and pthreads.dll. We try to account for the existence
#endif // of this problem here in a somewhat conservative way.
return kResultError;
}
#else
// BSD family of Unixes usually lack sem_trywait as of this writing.
// This is a major problem, as a polling solution doesn't work under some circumstances.
while(((result = sem_trywait(&mSemaphoreData.mSemaphore)) == -1) && (errno == EAGAIN || errno == EINTR) && (GetThreadTime() < timeoutAbsolute))
ThreadSleep(1);
if(result == -1)
{
if(errno == EAGAIN)
return kResultTimeout;
return kResultError;
}
#endif
}
EAT_ASSERT(mSemaphoreData.mnCount > 0);
return (int)mSemaphoreData.mnCount.Decrement(); // AtomicInt32 operation. Note that the value of the semaphore count could change from the returned value by the time the caller reads it. This is fine but the user should understand this.
}
int EA::Thread::Semaphore::Post(int count)
{
// Some systems have a sem_post_multiple which we could take advantage
// of here to atomically post multiple times.
EAT_ASSERT(mSemaphoreData.mnCount >= 0);
// It's hard to correctly implement mnMaxCount here, given that it
// may be modified by multiple threads during this execution. So if you want
// to use max-count with an IntraProcess semaphore safely then you need to
// post only from a single thread, or at least a single thread at a time.
int currentCount = mSemaphoreData.mnCount;
// If count would cause an overflow exit early
if ((mSemaphoreData.mnMaxCount - count) < currentCount)
return kResultError;
currentCount += count;
while(count-- > 0)
{
++mSemaphoreData.mnCount; // AtomicInt32 operation.
if(sem_post(&mSemaphoreData.mSemaphore) == -1)
{
--mSemaphoreData.mnCount; // AtomicInt32 operation.
EAT_ASSERT(false);
return kResultError;
}
}
// If all count posts occurred...
return currentCount; // It's possible that another thread may have modified this value since we changed it, but that's not important.
}
int EA::Thread::Semaphore::GetCount() const
{
return (int)mSemaphoreData.mnCount;
}
#endif // EA_PLATFORM_XXX
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,715 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <EABase/eahave.h>
#include <eathread/eathread.h>
#include <eathread/eathread_thread.h>
#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include <pthread.h>
#include <sched.h>
#include <string.h>
#ifdef EA_PLATFORM_WINDOWS
#pragma warning(push, 0)
#include <Windows.h> // Presumably we are using pthreads-win32.
#pragma warning(pop)
#include <time.h>
#else
#include <unistd.h>
#if defined(_YVALS)
#include <time.h>
#else
#include <sys/time.h>
#endif
#if defined(EA_PLATFORM_OSX) || defined(EA_PLATFORM_BSD)
#include <sys/sysctl.h>
#include <sys/param.h>
#endif
#if defined(EA_PLATFORM_LINUX)
#include <sys/prctl.h>
#endif
#if defined(EA_PLATFORM_APPLE) || defined(__APPLE__)
#include <dlfcn.h>
#endif
#if defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(__FreeBSD__)
#include <sys/param.h>
#include <pthread_np.h>
#endif
#if defined(EA_PLATFORM_ANDROID)
#include <sys/syscall.h>
#endif
#endif
namespace EA
{
namespace Thread
{
// Assertion variables.
EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL;
void* gpAssertionFailureContext = NULL;
}
}
EA::Thread::ThreadId EA::Thread::GetThreadId()
{
return pthread_self();
}
EA::Thread::ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id)
{
EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mThreadId;
}
return EA::Thread::kThreadIdInvalid;
}
int EA::Thread::GetThreadPriority()
{
int policy;
sched_param param;
ThreadId currentThreadId = pthread_self();
int result = pthread_getschedparam(currentThreadId, &policy, &param);
if(result == 0)
{
#if defined(EA_PLATFORM_LINUX) && !defined(__CYGWIN__)
return kThreadPriorityDefault + param.sched_priority; // This works for both SCHED_OTHER, SCHED_RR, and SCHED_FIFO.
#else
#if defined(EA_PLATFORM_WINDOWS)
if(param.sched_priority == THREAD_PRIORITY_NORMAL)
return kThreadPriorityDefault;
#elif !(defined(__CYGWIN__) || defined(CS_UNDEFINED_STRING))
if(policy == SCHED_OTHER)
return 0; // 0 is the only native priority permitted with the SCHED_OTHER scheduling scheme.
#endif
// The following needs to be tested on a Unix-by-Unix case.
const int nMin = sched_get_priority_min(policy);
const int nMax = sched_get_priority_max(policy);
// Some implementations of Pthreads associate higher priorities with smaller
// integer values. We hide this. To the user, a higher value must always
// indicate higher priority.
const int adjustDir = (nMin < nMax) ? 1 : -1;
const int nativeBasePriority = (nMin + nMax) / 2;
// EAThread_user_priority = +/-(native_priority - EAThread_native_priority_default)
return adjustDir * (param.sched_priority - nativeBasePriority);
#endif
}
return kThreadPriorityDefault;
}
bool EA::Thread::SetThreadPriority(int nPriority)
{
ThreadId currentThreadId = pthread_self();
int policy;
sched_param param;
int result = -1;
EAT_ASSERT(nPriority != kThreadPriorityUnknown);
#if defined(EA_PLATFORM_LINUX) && !defined(__CYGWIN__)
// We are assuming Kernel 2.6 and later behavior, but perhaps we should dynamically detect.
// Linux supports three scheduling policies SCHED_OTHER, SCHED_RR, and SCHED_FIFO.
// The process needs to be run with superuser privileges to use SCHED_RR or SCHED_FIFO.
// Thread priorities for SCHED_OTHER do not exist; there is only one allowed thread priority: 0.
// Thread priorities for SCHED_RR and SCHED_FIFO are limited to the range of [1, 99] (verified with Linux 2.6.17),
// despite documentation on the Internet that refers to ranges of 0-99, 1-100, 1-140, etc.
// Higher values in this range mean higher priority.
// All of the SCHED_RR and SCHED_FIFO privileges are higher than anything running at SCHED_OTHER,
// as they are considered to be real-time scheduling. A result of this is that there is no
// such thing as having a thread of lower priority than normal; there are only higher real-time priorities.
if(nPriority <= kThreadPriorityDefault)
{
policy = SCHED_OTHER;
param.sched_priority = 0;
}
else
{
policy = SCHED_RR;
param.sched_priority = (nPriority - kThreadPriorityDefault);
}
result = pthread_setschedparam(currentThreadId, policy, &param);
#else
// The following needs to be tested on a Unix-by-Unix case.
result = pthread_getschedparam(currentThreadId, &policy, &param);
if(result == 0)
{
// Cygwin does not support any scheduling policy other than SCHED_OTHER.
#if !defined(__CYGWIN__)
if(policy == SCHED_OTHER)
policy = SCHED_FIFO;
#endif
int nMin = sched_get_priority_min(policy);
int nMax = sched_get_priority_max(policy);
int adjustDir = 1;
// Some implementations of pthreads associate higher priorities with smaller integer values.
// To the EAThread user, a higher value indicates a higher priority.
if (nMin > nMax)
{
adjustDir = nMax;
nMax = nMin;
nMin = adjustDir;
adjustDir = -1; // Translate user's desire for higher priority into a native lower value.
}
// native_priority = EAThread_native_priority_default +/- EAThread_user_priority.
// This calculation sets the default to be in the middle of low and high, which might not be so for all platforms in practice.
param.sched_priority = ((nMin + nMax) / 2) + (adjustDir * nPriority);
// Clamp to min/max as appropriate for current scheduling policy
if(param.sched_priority < nMin)
param.sched_priority = nMin;
else if(param.sched_priority > nMax)
param.sched_priority = nMax;
result = pthread_setschedparam(currentThreadId, policy, &param);
}
#endif
return (result == 0);
}
void* EA::Thread::GetThreadStackBase()
{
#if defined(EA_PLATFORM_APPLE)
pthread_t threadId = pthread_self();
return pthread_get_stackaddr_np(threadId);
#elif (EA_PLATFORM_SOLARIS)
stack_t s;
thr_stksegment(&s);
return s.ss_sp; // Note that this is not the sp pointer (which would refer to the a location low in the stack address space). When returned by thr_stksegment(), ss_sp refers to the top (base) of the stack.
#elif defined(__CYGWIN__)
// Cygwin reserves pthread_attr_getstackaddr and pthread_attr_getstacksize for future use.
// The solution here is probably to use the Windows implementation of this here.
return 0;
#else // Other Unix
void* stackLow = NULL;
size_t stackSize = 0;
pthread_t threadId = pthread_self();
pthread_attr_t sattr;
pthread_attr_init(&sattr);
#if defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(__FreeBSD__)
pthread_attr_get_np(threadId, &sattr);
#elif defined(EA_HAVE_pthread_getattr_np_DECL)
// Note: this function is non-portable; various Unix systems may have different np alternatives
pthread_getattr_np(threadId, &sattr);
#else
EA_UNUSED(threadId);
// What to do?
#endif
// See http://www.opengroup.org/onlinepubs/009695399/functions/pthread_attr_getstack.html
// stackLow is a constant. It is not the current low location but rather is the lowest allowed location.
pthread_attr_getstack(&sattr, &stackLow, &stackSize);
pthread_attr_destroy(&sattr);
return (char*)stackLow + stackSize;
#endif
}
void EA::Thread::SetThreadProcessor(int nProcessor)
{
// Posix threading doesn't have the ability to set the processor.
#if defined(EA_PLATFORM_WINDOWS)
static int nProcessorCount = 0; // This doesn't really need to be an atomic integer.
if(nProcessorCount == 0)
{
SYSTEM_INFO systemInfo;
memset(&systemInfo, 0, sizeof(systemInfo));
GetSystemInfo(&systemInfo);
nProcessorCount = (int)systemInfo.dwNumberOfProcessors;
}
DWORD dwThreadAffinityMask;
if((nProcessor < 0) || (nProcessor >= nProcessorCount))
dwThreadAffinityMask = 0xffffffff;
else
dwThreadAffinityMask = 1 << nProcessor;
SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinityMask);
#elif (defined(EA_PLATFORM_LINUX) && !defined(EA_PLATFORM_ANDROID)) || defined(CS_UNDEFINED_STRING)
cpu_set_t cpus;
CPU_ZERO(&cpus);
CPU_SET(nProcessor, &cpus);
for (int c = 0; c < EA::Thread::GetProcessorCount(); c++, nProcessor >>= 1)
{
if (nProcessor & 1)
{
CPU_SET(c, &cpus);
}
}
// To consider: Make it so we return a value.
/*int result =*/ pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus);
// We don't assert on the success, as that could be very noisy for some users.
#else
// Other Unix platforms don't provide a means to specify what processor a thread runs on.
// You have no choice but to let the OS schedule threads for you.
EA_UNUSED(nProcessor);
#endif
}
#if defined(EA_PLATFORM_WINDOWS) && defined(EA_PROCESSOR_X86) && defined(MSC_VER) && (MSC_VER >= 1400)
int GetCurrentProcessorNumberXP()
{
_asm { mov eax, 1 }
_asm { cpuid }
_asm { shr ebx, 24 }
_asm { mov eax, ebx }
}
#endif
int EA::Thread::GetThreadProcessor()
{
#if defined(EA_PLATFORM_WINDOWS)
// We are using Posix threading on Windows. It happens to be mapped to Windows threading and
// so we can use Windows facilities to tell what processor the thread is running on.
// Only Windows Vista and later provides GetCurrentProcessorNumber.
// So we must dynamically link to this function.
static EA_THREAD_LOCAL bool bInitialized = false;
static EA_THREAD_LOCAL DWORD (WINAPI *pfnGetCurrentProcessorNumber)() = NULL;
if(!bInitialized)
{
HMODULE hKernel32 = GetModuleHandle("KERNEL32.DLL");
if(hKernel32)
pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)())GetProcAddress(hKernel32, "GetCurrentProcessorNumber");
bInitialized = true;
}
if(pfnGetCurrentProcessorNumber)
return (int)(unsigned)pfnGetCurrentProcessorNumber();
#if defined(EA_PLATFORM_WINDOWS) && defined(EA_PROCESSOR_X86) && defined(MSC_VER) && (MSC_VER >= 1400)
return GetCurrentProcessorNumberXP();
#else
return 0;
#endif
#elif defined(EA_PLATFORM_ANDROID)
// return zero until Google provides a alternative to smp_processor_id()
return 0;
#elif EA_VALGRIND_ENABLED
// Valgrind does not support the sched_getcpu() vsyscall. It causes it to detect a segfault in the program and stop it.
// https://bugs.kde.org/show_bug.cgi?id=187043
// http://git.dorsal.polymtl.ca/?p=ust.git;a=commitdiff_plain;h=8f09cb9340387a52b483752c5d2d6c36035b26bc
return 0;
#elif (defined(EA_PLATFORM_LINUX) && (defined(EATHREAD_GLIBC_VERSION) && (EATHREAD_GLIBC_VERSION > 2005)))
// http://www.kernel.org/doc/man-pages/online/pages/man3/sched_getcpu.3.html
// http://www.kernel.org/doc/man-pages/online/pages/man2/getcpu.2.html
// Another solution is to use the cpuid instruction like we do for Windows.
int cpu = sched_getcpu();
if(cpu < 0)
cpu = 0;
if(cpu >= 0)
return cpu;
// Ideally we would never need to execute the following code:
cpu_set_t cpus;
CPU_ZERO(&cpus);
pthread_getaffinity_np(pthread_self(), sizeof(cpus), &cpus);
for(int i = 0; i < CPU_SETSIZE; i++)
{
if(CPU_ISSET(i, &cpus))
return i;
}
return 0;
#else
return 0;
#endif
}
EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
pTDD->mnThreadAffinityMask = nAffinityMask;
#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
cpu_set_t cpuSetMask;
memset(&cpuSetMask, 0, sizeof(cpu_set_t));
for (int c = 0; c < EA::Thread::GetProcessorCount(); c++, nAffinityMask >>= 1)
{
if (nAffinityMask & 1)
{
CPU_SET(c, &cpuSetMask);
}
}
sched_setaffinity(pTDD->mThreadPid, sizeof(cpu_set_t), &cpuSetMask);
#endif
}
}
EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
// Internal SetThreadName API's so we don't repeat the implementations
namespace Internal
{
// This function is not currently used if the thread name can be set from any other thread
#if !EATHREAD_OTHER_THREAD_NAMING_SUPPORTED
void SetCurrentThreadName(const char8_t* pName)
{
#if defined(EA_PLATFORM_LINUX)
// http://manpages.courier-mta.org/htmlman2/prctl.2.html
// The Linux documentation says PR_SET_NAME sets the process name, but that
// documentation is wrong and instead it sets the current thread name.
// Also: http://0pointer.de/blog/projects/name-your-threads.html
// Stefan Kost recently pointed me to the fact that the Linux system call prctl(PR_SET_NAME)
// does not in fact change the process name, but the task name (comm field) -- in contrast
// to what the man page suggests. That makes it very useful for naming threads, since you
// can read back the name you set with PR_SET_NAME earlier from the /proc file system
// (/proc/$PID/task/$TID/comm on newer kernels, /proc/$PID/task/$TID/stat's second field
// on older kernels), and hence distinguish which thread might be responsible for the high
// CPU load or similar problems.
char8_t nameBuf[16]; // Limited to 16 bytes, null terminated if < 16 bytes
strncpy(nameBuf, pName, sizeof(nameBuf));
nameBuf[15] = 0;
prctl(PR_SET_NAME, (unsigned long)nameBuf, 0, 0, 0);
#elif defined(EA_PLATFORM_APPLE) || defined(__APPLE__)
// http://src.chromium.org/viewvc/chrome/trunk/src/base/platform_thread_mac.mm?revision=49465&view=markup&pathrev=49465
// "There's a non-portable function for doing this: pthread_setname_np.
// It's supported by OS X >= 10.6 and the Xcode debugger will show the thread
// names if they're provided."
// On OSX the return value is always -1 on error; use errno to tell the error value.
typedef int (*pthread_setname_np_type)(const char*);
pthread_setname_np_type pthread_setname_np_ptr = (pthread_setname_np_type)(uintptr_t)dlsym(RTLD_DEFAULT, "pthread_setname_np");
if(pthread_setname_np_ptr)
{
// Mac OS X does not expose the length limit of the name, so hardcode it.
char8_t nameBuf[63]; // It is not clear what the size limit actually is, though 63 is known to work because it was seen on the Internet.
strncpy(nameBuf, pName, sizeof(nameBuf));
nameBuf[62] = 0;
pthread_setname_np_ptr(nameBuf);
}
#elif defined(EA_PLATFORM_BSD) || defined(EA_PLATFORM_CONSOLE_BSD) || defined(__FreeBSD__)
// http://www.unix.com/man-page/freebsd/3/PTHREAD_SET_NAME_NP/
pthread_set_name_np(pthread_self(), pName);
#endif
}
#endif
EA::Thread::ThreadId GetId(EAThreadDynamicData* pTDD)
{
if(pTDD)
return pTDD->mThreadId;
return EA::Thread::kThreadIdInvalid;
}
void SetThreadName(EAThreadDynamicData* pTDD)
{
#if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_APPLE)
EAT_COMPILETIME_ASSERT(EATHREAD_OTHER_THREAD_NAMING_SUPPORTED == 0);
// http://stackoverflow.com/questions/2369738/can-i-set-the-name-of-a-thread-in-pthreads-linux
// Under some Unixes you can name only the current thread, so we apply the naming
// only if the currently executing thread is the one that is associated with
// this class object.
if(GetId(pTDD) == EA::Thread::GetThreadId())
SetCurrentThreadName(pTDD->mName);
#elif defined(EA_PLATFORM_BSD)
EAT_COMPILETIME_ASSERT(EATHREAD_OTHER_THREAD_NAMING_SUPPORTED == 1);
// http://www.unix.com/man-page/freebsd/3/PTHREAD_SET_NAME_NP/
if(GetId(pTDD) != EA::Thread::kThreadIdInvalid)
pthread_set_name_np(GetId(pTDD), pTDD->mName);
#endif
}
} // namespace Internal
EATHREADLIB_API void EA::Thread::SetThreadName(const char* pName) { SetThreadName(GetThreadId(), pName); }
EATHREADLIB_API const char* EA::Thread::GetThreadName() { return GetThreadName(GetThreadId()); }
EATHREADLIB_API void EA::Thread::SetThreadName(const EA::Thread::ThreadId& id, const char* pName)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
if(pTDD->mName != pName) // self-assignment check
{
strncpy(pTDD->mName, pName, EATHREAD_NAME_SIZE);
pTDD->mName[EATHREAD_NAME_SIZE - 1] = 0;
}
Internal::SetThreadName(pTDD);
}
}
EATHREADLIB_API const char* EA::Thread::GetThreadName(const EA::Thread::ThreadId& id)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
return pTDD ? pTDD->mName : "";
}
int EA::Thread::GetProcessorCount()
{
#if defined(EA_PLATFORM_WINDOWS)
static int nProcessorCount = 0; // This doesn't really need to be an atomic integer.
if(nProcessorCount == 0)
{
// A better function to use would possibly be KeQueryActiveProcessorCount
// (NTKERNELAPI ULONG KeQueryActiveProcessorCount(PKAFFINITY ActiveProcessors))
SYSTEM_INFO systemInfo;
memset(&systemInfo, 0, sizeof(systemInfo));
GetSystemInfo(&systemInfo);
nProcessorCount = (int)systemInfo.dwNumberOfProcessors;
}
return nProcessorCount;
#elif defined(EA_PLATFORM_OSX) || defined(EA_PLATFORM_BSD)
// http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man3/sysctlbyname.3.html
// We can use:
// int sysctl(int* name, u_int namelen, void* oldp, size_t* oldlenp, void* newp, size_t newlen);
// int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
#ifdef EA_PLATFORM_BSD
int mib[4] = { CTL_HW, HW_NCPU, 0, 0 };
#else
int mib[4] = { CTL_HW, HW_AVAILCPU, 0, 0 };
#endif
int cpuCount = 0; // Unfortunately, Apple's documentation fails to clarify if this needs to be 'int' or 'long'.
size_t len = sizeof(cpuCount);
sysctl(mib, 2, &cpuCount, &len, NULL, 0);
if(cpuCount < 1)
{
mib[1] = HW_NCPU;
sysctl(mib, 2, &cpuCount, &len, NULL, 0);
if(cpuCount < 1)
cpuCount = 1;
}
return cpuCount;
// Maybe simpler, should try it out to make sure it works:
//
// int cpuCount = 0;
// size_t len = sizeof(cpuCount);
// if(sysctlbyname("hw.ncpu", &cpuCount, &len, NULL, 0) != 0)
// cpuCount = 1;
// return cpuCount;
#else
// Posix doesn't provide a means to get this information.
// Some Unixes provide sysconf() with the _SC_NPROCESSORS_ONLN or _SC_NPROCESSORS_CONF option.
// Another option is to count the number of entries in /proc/cpuinfo
#ifdef _SC_NPROCESSORS_ONLN
return (int)sysconf(_SC_NPROCESSORS_ONLN);
#else
return 1;
#endif
#endif
}
#if defined(EA_PLATFORM_WINDOWS)
extern "C" __declspec(dllimport) void __stdcall Sleep(unsigned long dwMilliseconds);
#endif
void EA::Thread::ThreadSleep(const ThreadTime& timeRelative)
{
#if defined(EA_PLATFORM_WINDOWS)
// There is no nanosleep on Windows, but there is Sleep.
if(timeRelative == kTimeoutImmediate)
Sleep(0);
else
Sleep((unsigned)((timeRelative.tv_sec * 1000) + (((timeRelative.tv_nsec % 1000) * 1000000))));
#else
if(timeRelative == kTimeoutImmediate)
{
sched_yield();
}
else
{
#if defined(EA_HAVE_nanosleep_DECL)
nanosleep(&timeRelative, 0);
#else
// What to do?
#endif
}
#endif
}
namespace EA
{
namespace Thread
{
EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId);
}
}
void EA::Thread::ThreadEnd(intptr_t threadReturnValue)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(GetThreadId());
if(pTDD)
{
pTDD->mnStatus = Thread::kStatusEnded;
pTDD->mnReturnValue = threadReturnValue;
pTDD->mRunMutex.Unlock();
pTDD->Release();
}
pthread_exit((void*)threadReturnValue);
}
#if defined(EA_PLATFORM_APPLE)
EA::Thread::SysThreadId EA::Thread::GetSysThreadId(ThreadId id)
{
return pthread_mach_thread_np(id);
}
EA::Thread::SysThreadId EA::Thread::GetSysThreadId()
{
return pthread_mach_thread_np(pthread_self()); // There isn't a self-specific version of pthread_mach_thread_np.
}
#endif
EA::Thread::ThreadTime EA::Thread::GetThreadTime()
{
#if defined(EA_PLATFORM_WINDOWS) && !defined(__CYGWIN__)
// We use this code instead of GetTickCount or similar because pthreads under
// Win32 uses the 'system file time' definition (e.g. GetSystemTimeAsFileTime())
// for current time. The implementation here is just like that in the
// pthreads-Win32 ptw32_timespec.c file.
int64_t ft;
ThreadTime threadTime;
GetSystemTimeAsFileTime((FILETIME*)&ft); // nTime64 is in intervals of 100ns.
#define PTW32_TIMESPEC_TO_FILETIME_OFFSET (((int64_t)27111902 << 32) + (int64_t)3577643008)
threadTime.tv_sec = (int)((ft - PTW32_TIMESPEC_TO_FILETIME_OFFSET) / 10000000);
threadTime.tv_nsec = (int)((ft - PTW32_TIMESPEC_TO_FILETIME_OFFSET - ((int64_t)threadTime.tv_sec * (int64_t)10000000)) * 100);
return threadTime;
// Alternative which will likely be slower:
//#include <sys/timeb.h>
//ThreadTime threadTime;
//_timeb fTime; _ftime(&fTime);
//threadTime.tv_sec = (long)fTime.time;
//threadTime.tv_nsec = fTime.millitm * 1000000;
//return threadTime;
#else
// For some systems we may need to use gettimeofday() instead of clock_gettime().
#if defined(EA_PLATFORM_LINUX) || defined(__CYGWIN__) || (_POSIX_TIMERS > 0)
ThreadTime threadTime;
clock_gettime(CLOCK_REALTIME, &threadTime); // If you get a linker error about clock_getttime, you need to link librt.a (specify -lrt to the linker).
return threadTime;
#else
timeval temp;
gettimeofday(&temp, NULL);
return ThreadTime((ThreadTime::seconds_t)temp.tv_sec, (ThreadTime::nseconds_t)temp.tv_usec * 1000);
#endif
#endif
}
void EA::Thread::SetAssertionFailureFunction(EA::Thread::AssertionFailureFunction pAssertionFailureFunction, void* pContext)
{
gpAssertionFailureFunction = pAssertionFailureFunction;
gpAssertionFailureContext = pContext;
}
void EA::Thread::AssertionFailure(const char* pExpression)
{
if(gpAssertionFailureFunction)
gpAssertionFailureFunction(pExpression, gpAssertionFailureContext);
else
{
#if EAT_ASSERT_ENABLED
#ifdef EA_PLATFORM_WINDOWS
OutputDebugStringA("EA::Thread::AssertionFailure: ");
OutputDebugStringA(pExpression);
OutputDebugStringA("\n");
#else
printf("EA::Thread::AssertionFailure: ");
printf("%s", pExpression);
printf("\n");
fflush(stdout);
fflush(stderr);
#endif
EATHREAD_DEBUG_BREAK();
#endif
}
}
#endif // defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE