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,21 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ea_EAThread_EAThread */
#ifndef _Included_com_ea_EAThread_EAThread
#define _Included_com_ea_EAThread_EAThread
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_ea_EAThread_EAThread
* Method: Init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_ea_EAThread_EAThread_Init
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,10 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread.h>
@@ -0,0 +1,84 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
/// Pseudo implementation of 64 bit primitives modelled after Android's internals.
/// Return values and semantics are intended to be the same as 32 bit versions.
///
/// Basically just does a mutex lock around the operation. Rather than just
/// one global lock, uses a fixed set of mutexes to lock based on incoming
/// address to reduce contention.
///
/// Abuses the fact that the initializer for a pthread_mutex_t in Android is
/// simply "{0}" on a volatile int to avoid requiring global initialization of
/// these mutexes.
#include <EABase/eabase.h>
#if defined(EA_PLATFORM_ANDROID)
#include <pthread.h>
namespace EA
{
namespace Thread
{
#define EAT_FAKE_ATOMIC_SWAP_LOCK_COUNT 32U
static pthread_mutex_t sFakeAtomic64SwapLocks[EAT_FAKE_ATOMIC_SWAP_LOCK_COUNT];
#define EAT_SWAP_LOCK(addr) &sFakeAtomic64SwapLocks[((unsigned)(void*)(addr) >> 3U) % EAT_FAKE_ATOMIC_SWAP_LOCK_COUNT]
int64_t android_fake_atomic_swap_64(int64_t value, volatile int64_t* addr)
{
int64_t oldValue;
pthread_mutex_t* lock = EAT_SWAP_LOCK(addr);
pthread_mutex_lock(lock);
oldValue = *addr;
*addr = value;
pthread_mutex_unlock(lock);
return oldValue;
}
int android_fake_atomic_cmpxchg_64(int64_t oldvalue, int64_t newvalue, volatile int64_t* addr)
{
int ret;
pthread_mutex_t* lock = EAT_SWAP_LOCK(addr);
pthread_mutex_lock(lock);
if (*addr == oldvalue)
{
*addr = newvalue;
ret = 0;
}
else
{
ret = 1;
}
pthread_mutex_unlock(lock);
return ret;
}
int64_t android_fake_atomic_read_64(volatile int64_t* addr)
{
int64_t ret;
pthread_mutex_t* lock = EAT_SWAP_LOCK(addr);
pthread_mutex_lock(lock);
ret = *addr;
pthread_mutex_unlock(lock);
return ret;
}
} // namespace Thread
} // namespace EA
#endif // #if defined(EA_PLATFORM_ANDROID)
@@ -0,0 +1,226 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_eabase_H
#include <EABase/eabase.h>
#endif
#ifndef EATHREAD_EATHREAD_SEMAPHORE_H
#include <eathread/eathread_semaphore.h>
#endif
#if defined(EA_PLATFORM_ANDROID)
#include <time.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <sys/errno.h>
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()
{
sem_destroy(&mSemaphoreData.mSemaphore);
// Can't use the following because Android's sem_destroy is broken. http://code.google.com/p/android/issues/detail?id=3106
//
// 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);
}
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;
// TODO intraprocess not supported on Android. Assert? Fail?
mSemaphoreData.mbIntraProcess = false;
int result = sem_init(&mSemaphoreData.mSemaphore, 0, (unsigned int)mSemaphoreData.mnCount);
if(result != 0)
{
EAT_ASSERT(false);
memset(&mSemaphoreData.mSemaphore, 0, sizeof(mSemaphoreData.mSemaphore));
}
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)) != 0) && (errno == EINTR))
{
continue;
}
int val;
sem_getvalue(&mSemaphoreData.mSemaphore, &val);
if(result != 0)
{
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;
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.
// 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)) != 0) && (errno == EINTR))
{
continue;
}
if(result != 0)
{
if(errno == ETIMEDOUT)
return kResultTimeout;
return kResultError;
}
}
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) != 0)
{
--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
@@ -0,0 +1,829 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread.h>
#include <eathread/eathread_futex.h>
#include <eathread/eathread_storage.h>
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/apple/eathread_callstack_apple.h>
#include <mach/thread_act.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <dlfcn.h>
#include <new>
#if EATHREAD_APPLE_GETMODULEINFO_ENABLED
#include <mach-o/dyld_images.h> //dyld_all_image_infos
#include <mach-o/dyld.h> //segment_command(_64)
#include <mach/task.h> //task_info
#if defined(EA_PLATFORM_IPHONE)
//On iPhone, this gets pulled in dynamically through libproc.dylib
extern "C" int proc_regionfilename(int pid, uint64_t address, void * buffer, uint32_t buffersize);
#else
#include <libproc.h> //proc_regionfilename
#endif
#endif
#if defined(__LP64__)
typedef struct mach_header_64 MachHeader;
typedef struct segment_command_64 SegmentCommand;
typedef struct section_64 Section;
#define kLCSegment LC_SEGMENT_64
#else
typedef struct mach_header MachHeader;
typedef struct segment_command SegmentCommand;
typedef struct section Section;
#define kLCSegment LC_SEGMENT
#endif
#if EACALLSTACK_GLIBC_BACKTRACE_AVAILABLE
#include <signal.h>
#include <execinfo.h>
#endif
namespace EA
{
namespace Thread
{
///////////////////////////////////////////////////////////////////////////////
// gModuleInfoApple
//
// We keep a cached array of the module info. It's possible that the module
// info could change at runtime, though for our purposes the changes don't
// usually matter. Nevertheless that's a limitation of this scheme and we
// may need to do something about it in the future.
// This global array is freed in ShutdownCallstack.
// Currently this array is stored per-DLL when EAThread is built as a DLL.
//
static ModuleInfoApple* gModuleInfoAppleArray = NULL;
static size_t gModuleInfoAppleArrayCount = 0;
static Futex* gCallstackFutex = NULL;
///////////////////////////////////////////////////////////////////////////////
// ReallocModuleInfoApple
//
// This is not a fully generic Realloc function. It currently reallocs only
// to a greater size or to zero, which is fine for our purposes. The caller
// of this function needs to be aware that the Realloc may fail and should
// use gModuleInfoAppleArrayCount as the array count and not the value passed
// to this function.
//
static ModuleInfoApple* ReallocModuleInfoApple(size_t newCount)
{
if(gCallstackFutex)
gCallstackFutex->Lock();
EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator();
EAT_ASSERT_MSG(pAllocator != NULL, "EA::Thread::SetAllocator needs to be called on app startup.");
if(pAllocator)
{
if(newCount > gModuleInfoAppleArrayCount) // If increasing in size...
{
size_t allocSize = sizeof(ModuleInfoApple) * newCount;
void* allocMemory = pAllocator->Alloc(allocSize);
if(allocMemory)
{
ModuleInfoApple* pNew = new(allocMemory) ModuleInfoApple[newCount]; // Placement new always succeeds.
if(gModuleInfoAppleArray && gModuleInfoAppleArray)
{
// gModuleInfoAppleArrayCount is guaranteed to be < newCount for this memcpy.
memcpy(pNew, gModuleInfoAppleArray, sizeof(ModuleInfoApple) * gModuleInfoAppleArrayCount);
pAllocator->Free(gModuleInfoAppleArray);
}
gModuleInfoAppleArray = pNew;
gModuleInfoAppleArrayCount = newCount;
}
// Else fall through and use the existing gModuleInfoAppleArray.
}
else if(newCount == 0) // If freeing...
{
if(gModuleInfoAppleArray)
{
pAllocator->Free(gModuleInfoAppleArray);
gModuleInfoAppleArray = NULL;
gModuleInfoAppleArrayCount = 0;
}
}
// Else we do nothing for the case of requesting a newCount < gModuleInfoAppleArrayCount.
}
if(gCallstackFutex)
gCallstackFutex->Unlock();
return gModuleInfoAppleArray; // gModuleInfoAppleArrayCount indicates the capacity of this array.
}
#if EATHREAD_APPLE_GETMODULEINFO_ENABLED
// This fills a moduleInfoApple object with the information from all of the segments listed in the given mach_header's segments, starting at the given currentSegmentPos. It also puts the pModulePath info the moduleInfoApple object, which is then push_back on the given array.
//
// The results are appended to pModuleInfoAppleArray up to its capacity
// pTypeFilter is used to filter out segment types
// pModulePath is the path corresponding to the given pMachHeader. It is assumed it is NullTerminated
// currentSegmentPos is the starting segment we are iterating over
// pMachHeader is the mach_header with all the segment information
void CreateModuleInfoApple(ModuleInfoApple* pModuleInfoAppleArray, size_t arrayCapacity, size_t& requiredArraySize, size_t& arraySize,
const char* pTypeFilter, const char* pModulePath, uintptr_t currentSegmentPos, const MachHeader* pMachHeader, intptr_t offset)
{
for(uint32_t i = 0; i < pMachHeader->ncmds; i++) // Look at each command, paying attention to LC_SEGMENT/LC_SEGMENT_64 (segment_command) commands.
{
const SegmentCommand* pSegmentCommand = reinterpret_cast<const SegmentCommand*>(currentSegmentPos); // This won't actually be a segment_command unless the type is kLCSegment
if(pSegmentCommand != NULL && pSegmentCommand->cmd == kLCSegment) // If this really is a segment_command... (otherwise it is some other kind of command)
{
const size_t segnameBufferLen = sizeof(pSegmentCommand->segname) + 1;
char segnameBuffer[segnameBufferLen];
memcpy(segnameBuffer, pSegmentCommand->segname, sizeof(pSegmentCommand->segname));
segnameBuffer[segnameBufferLen-1] = '\0'; // Incase segname was not 0-terminated
if(!pTypeFilter || strncmp(segnameBuffer, pTypeFilter, sizeof(segnameBuffer)))
{
requiredArraySize++;
if (arraySize < arrayCapacity)
{
ModuleInfoApple& info = pModuleInfoAppleArray[arraySize++];
uint64_t uOffset = (uint64_t)offset;
info.mBaseAddress = (uint64_t)(pSegmentCommand->vmaddr + uOffset);
// info.mModuleHandle = reinterpret_cast<ModuleHandle>((uintptr_t)info.mBaseAddress);
info.mSize = (uint64_t)pSegmentCommand->vmsize;
// Copy modulePath to info.mPath.
strlcpy(info.mPath, pModulePath, EAArrayCount(info.mPath));
// Get the beginning of the file name within modulePath and copy the file name to info.mName.
const char* pDirSeparator = strrchr(pModulePath, '/');
if(pDirSeparator)
pDirSeparator++;
else
pDirSeparator = pModulePath;
strlcpy(info.mName, pDirSeparator, EAArrayCount(info.mName));
info.mPermissions[0] = (pSegmentCommand->initprot & VM_PROT_READ) ? 'r' : '-';
info.mPermissions[1] = (pSegmentCommand->initprot & VM_PROT_WRITE) ? 'w' : '-';
info.mPermissions[2] = (pSegmentCommand->initprot & VM_PROT_EXECUTE) ? 'x' : '-';
info.mPermissions[3] = '/';
info.mPermissions[4] = (pSegmentCommand->maxprot & VM_PROT_READ) ? 'r' : '-';
info.mPermissions[5] = (pSegmentCommand->maxprot & VM_PROT_WRITE) ? 'w' : '-';
info.mPermissions[6] = (pSegmentCommand->maxprot & VM_PROT_EXECUTE) ? 'x' : '-';
info.mPermissions[7] = '\0';
strlcpy(info.mType,pSegmentCommand->segname,EAArrayCount(info.mType));
//**********************************************************************************
//For Debugging Purposes
//__TEXT 0000000100000000-0000000100001000 [ 4K] r-x/rwx SM=COW /Build/Products/Debug/TestProject.app/Contents/MacOS/TestProject
//printf("%20s %llx-%llx %s %s\n", segnameBuffer, (unsigned long long)info.mBaseAddress, (unsigned long long)(info.mBaseAddress + pSegmentCommand->vmsize), info.mPermissions, pModulePath);
//**********************************************************************************/
}
}
}
currentSegmentPos += pSegmentCommand->cmdsize;
}
}
#endif // EATHREAD_APPLE_GETMODULEINFO_ENABLED
#if EATHREAD_APPLE_GETMODULEINFO_ENABLED
// GetModuleInfoApple
//
// This function exists for the purpose of being a central module/VM map info collecting function,
// used by a couple functions within EACallstack.
//
// We used to use vmmap and parse the output
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/vmmap.1.html
// But starting ~osx 10.9 vmmap can not be called due to new security restrictions
//
// I tried using ::mach_vm_region, but I was unable to find the type of the segments (__TEXT, etc.)
// and system libraries addresses were given, but their name/modulePath was not.
// ::mach_vm_region_recurse did not solve this problem either.
//
// Replaced _dyld_get_all_image_infos() call with task_info() call as the old call is no longer available
// with osx 10.13 (High Sierra).
size_t GetModuleInfoApple(ModuleInfoApple* pModuleInfoAppleArray, size_t arrayCapacity,
const char* pTypeFilter, bool bEnableCache)
{
// The following is present to handle the case that the user forgot to call EA::Thread::InitCallstack().
// We don't match this with a ShutdownCallstack, and so the user might see memory leaks in that case.
// The user should call EA::Thread::InitCallstack on app startup and EA::Thread::ShutdownCallstack on
// app shutdown, at least if the user wants to use this function.
if(!gCallstackFutex)
InitCallstack();
size_t requiredArraySize = 0;
size_t arraySize = 0;
if(bEnableCache)
{
if(gCallstackFutex)
gCallstackFutex->Lock();
if(gModuleInfoAppleArrayCount == 0) // If nothing is cached...
{
// Call ourselves recursively, for the sole purpose of getting the required size and filling the cache.
// We call GetModuleInfoApple with a NULL filter (get all results). This may result in a required size that's
// greater than the size needed for the user's possibly supplied filter. Thus we have a variable here
// called maxRequiredArraySize.
const size_t maxRequiredArraySize = GetModuleInfoApple(NULL, 0, NULL, false);
ReallocModuleInfoApple(maxRequiredArraySize); // If the realloc fails, the code below deals with it safely.
// Call ourselves recursively, for the purpose of filling in the cache.
GetModuleInfoApple(gModuleInfoAppleArray, gModuleInfoAppleArrayCount, NULL, false);
}
// Copy our cache to the user's supplied array, while applying the filter and updating requiredArraySize.
for(size_t i = 0, iEnd = gModuleInfoAppleArrayCount; i != iEnd; i++)
{
const ModuleInfoApple& mia = gModuleInfoAppleArray[i];
if(!pTypeFilter || strstr(mia.mType, pTypeFilter)) // If the filter matches...
{
requiredArraySize++;
if(arraySize < arrayCapacity) // If there is room in the user-supplied array...
{
ModuleInfoApple& miaUser = pModuleInfoAppleArray[arraySize++];
memcpy(&miaUser, &mia, sizeof(ModuleInfoApple));
}
}
}
if(gCallstackFutex)
gCallstackFutex->Unlock();
}
else
{
struct task_dyld_info t_info;
uint32_t t_info_count = TASK_DYLD_INFO_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&t_info, &t_info_count);
if (kr != KERN_SUCCESS)
{
EAT_ASSERT_FORMATTED(false, "GetModuleInfoApple: task_info() returned %d", kr);
return 0;
}
const struct dyld_all_image_infos* pAllImageInfos = (const struct dyld_all_image_infos *)t_info.all_image_info_addr;
for(uint32_t i = 0; i < pAllImageInfos->infoArrayCount; i++)
{
const char* pModulePath = pAllImageInfos->infoArray[i].imageFilePath;
if(pModulePath != NULL && strncmp(pModulePath, "", PATH_MAX) != 0)
{
uintptr_t currentSegmentPos = (uintptr_t)pAllImageInfos->infoArray[i].imageLoadAddress;
const MachHeader* pMachHeader = reinterpret_cast<const MachHeader*>(currentSegmentPos);
EAT_ASSERT(pMachHeader != NULL);
currentSegmentPos += sizeof(*pMachHeader);
// The system library addresses we obtain are the linker address.
// So we need to get the get the dynamic loading offset
// The offset is also stored in pAllImageInfos->sharedCacheSlide, but there is no way
// to know whether or not it should get used on each image. (dyld and our executable images do not slide)
// http://lists.apple.com/archives/darwin-kernel/2012/Apr/msg00012.html
intptr_t offset = _dyld_get_image_vmaddr_slide(i);
CreateModuleInfoApple(pModuleInfoAppleArray, arrayCapacity, requiredArraySize, arraySize,
pTypeFilter, pModulePath, currentSegmentPos, pMachHeader, offset);
}
}
// Iterating on dyld_all_image_infos->infoArray[] does not give us entries for /usr/lib/dyld.
// We use the mach_header to get /usr/lib/dyld
const MachHeader* pMachHeader = (const MachHeader*)pAllImageInfos->dyldImageLoadAddress;
uintptr_t currentSegmentPos = (uintptr_t)pMachHeader + sizeof(*pMachHeader);
char modulePath[PATH_MAX] = "";
pid_t pid = getpid();
int filenameLen = proc_regionfilename((int)pid,currentSegmentPos,modulePath,(uint32_t)sizeof(modulePath));
EAT_ASSERT(filenameLen > 0 && modulePath != NULL && strncmp(modulePath,"",sizeof(modulePath)) != 0);
if(filenameLen > 0)
{
CreateModuleInfoApple(pModuleInfoAppleArray, arrayCapacity, requiredArraySize, arraySize,
pTypeFilter, modulePath, currentSegmentPos, pMachHeader, 0); // offset is 0 because dyld is already loaded
}
// Use this to compare results
// printf("vmmap -w %lld", (int64_t)pid);
}
return requiredArraySize;
}
#endif // EATHREAD_APPLE_GETMODULEINFO_ENABLED
///////////////////////////////////////////////////////////////////////////////
// GetInstructionPointer
//
EATHREADLIB_API void GetInstructionPointer(void*& pInstruction)
{
pInstruction = __builtin_return_address(0); // Works for all Apple platforms and compilers (gcc and clang).
}
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator();
EAT_ASSERT_MSG(pAllocator != NULL, "EA::Thread::SetAllocator needs to be called on app startup.");
if(pAllocator)
gCallstackFutex = new(pAllocator->Alloc(sizeof(Futex))) Futex;
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
EA::Thread::Allocator* pAllocator = EA::Thread::GetAllocator();
EAT_ASSERT_MSG(pAllocator != NULL, "EAThread requires an allocator to be available between InitCallstack and ShutdownCallstack.");
if(pAllocator)
{
if(gModuleInfoAppleArray)
ReallocModuleInfoApple(0);
if(gCallstackFutex)
{
pAllocator->Free(gCallstackFutex);
gCallstackFutex = NULL;
}
}
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstack
//
// Capture up to nReturnAddressArrayCapacity elements of the call stack,
// or the whole callstack, whichever is smaller.
//
// ARM
// Apple defines a different ABI than the ARM eabi used by Linux and the ABI used
// by Microsoft. It implements a predictable stack frame system using r7 as the
// frame pointer. Documentation:
// http://developer.apple.com/library/ios/#documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html
//
// Basically, Apple uses r7 as a frame pointer. So for any function you are
// executing, r7 + 4 is the LR passed to us by the caller and is the PC of
// the parent. And r7 + 0 is a pointer to the parent's r7.
// x86/x64
// The ABI is similar except using the different registers from the different CPU.
//
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
#if defined(EA_DEBUG)
memset(pReturnAddressArray, 0, nReturnAddressArrayCapacity * sizeof(void*));
#endif
#if defined(EA_PROCESSOR_ARM) || defined(EA_PROCESSOR_X86) || defined(EA_PROCESSOR_X86_64)
struct StackFrame {
StackFrame* mpParentStackFrame;
void* mpReturnPC;
};
StackFrame* pStackFrame;
void* pInstruction;
size_t index = 0;
if(pContext)
{
#if defined(EA_PROCESSOR_ARM32)
pStackFrame = (StackFrame*)pContext->mFP;
pInstruction = (void*) pContext->mPC;
#define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0x1) == 0)
#elif defined(EA_PROCESSOR_ARM64)
pStackFrame = (StackFrame*)pContext->mFP;
pInstruction = (void*) pContext->mPC;
#define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 0)
#elif defined(EA_PROCESSOR_X86_64)
pStackFrame = (StackFrame*)pContext->mRBP;
pInstruction = (void*) pContext->mRIP;
#define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 0)
#elif defined(EA_PROCESSOR_X86)
pStackFrame = (StackFrame*)pContext->mEBP;
pInstruction = (void*) pContext->mEIP;
#define FrameIsAligned(pStackFrame) ((((uintptr_t)pStackFrame) & 0xf) == 8)
#endif
// Write the instruction to pReturnAddressArray. In this case we have this thread
// reading the callstack from another thread.
if(index < nReturnAddressArrayCapacity)
pReturnAddressArray[index++] = pInstruction;
}
else // Else get the current values...
{
pStackFrame = (StackFrame*)__builtin_frame_address(0);
GetInstructionPointer(pInstruction); // Intentionally don't call EAGetInstructionPointer, because it won't set the Thumb bit if this is Thumb code.
// Don't write pInstruction to pReturnAddressArray, as pInstruction refers to the code in *this* function, whereas we want to start with caller's call frame.
}
// We can do some range validation if we have a pthread id.
StackFrame* pStackBase;
StackFrame* pStackLimit;
const bool bThreadIsCurrent = (pContext == NULL); // To do: allow this to also tell if the thread is current for the case that pContext is non-NULL. We can do that by reading the current frame address and walking it backwards a few times and seeing if any value matches pStackFrame.
if(bThreadIsCurrent)
{
pthread_t pthread = pthread_self(); // This makes the assumption that the current thread is a pthread and not just a kernel thread.
pStackBase = reinterpret_cast<StackFrame*>(pthread_get_stackaddr_np(pthread));
pStackLimit = pStackBase - (pthread_get_stacksize_np(pthread) / sizeof(StackFrame));
}
else
{ // Make a conservative guess.
pStackBase = pStackFrame + ((1024 * 1024) / sizeof(StackFrame));
pStackLimit = pStackFrame - ((1024 * 1024) / sizeof(StackFrame));
}
// To consider: Do some validation of the PC. We can validate it by making sure it's with 20 MB
// of our PC and also verify that the instruction before it (be it Thumb or ARM) is a BL or BLX
// function call instruction (in the case of EA_PROCESSOR_ARM).
// To consider: Verify that each successive pStackFrame is at a higher address than the last,
// as otherwise the data must be corrupt.
if((index < nReturnAddressArrayCapacity) && pStackFrame && FrameIsAligned(pStackFrame))
{
pReturnAddressArray[index++] = pStackFrame->mpReturnPC; // Should happen to be equal to pContext->mLR.
while(pStackFrame && pStackFrame->mpReturnPC && (index < nReturnAddressArrayCapacity))
{
pStackFrame = pStackFrame->mpParentStackFrame;
if(pStackFrame && FrameIsAligned(pStackFrame) && pStackFrame->mpReturnPC && (pStackFrame < pStackBase) && (pStackFrame > pStackLimit))
pReturnAddressArray[index++] = pStackFrame->mpReturnPC;
else
break;
}
}
return index;
#elif EACALLSTACK_GLIBC_BACKTRACE_AVAILABLE // Mac OS X with GlibC
// One way to get the callstack of another thread, via signal handling:
// https://github.com/albertz/openlierox/blob/0.59/src/common/Debug_GetCallstack.cpp
size_t count = 0;
// The pContext option is not currently supported.
if(pContext == NULL) // To do: || pContext refers to this thread.
{
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*));
}
}
// else fall through to code that manually reads stack frames?
return count;
#else
EA_UNUSED(pReturnAddressArray);
EA_UNUSED(nReturnAddressArrayCapacity);
EA_UNUSED(pContext);
return 0;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
// Convert a full Context to a CallstackContext (subset of context).
//
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_ARM32)
context.mFP = pContext->mGpr[7]; // Apple uses R7 for the frame pointer in both ARM and Thumb CPU modes.
context.mSP = pContext->mGpr[13];
context.mLR = pContext->mGpr[14];
context.mPC = pContext->mGpr[15];
#elif defined(EA_PROCESSOR_ARM64)
context.mFP = pContext->mGpr[29];
context.mSP = pContext->mGpr[31];
context.mLR = pContext->mGpr[30];
context.mPC = pContext->mPC;
#else
EAT_FAIL_MSG("Platform unsupported");
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
// Returns the required strlen of pModuleName.
//
EATHREADLIB_API size_t GetModuleFromAddress(const void* pCodeAddress, char* pModuleName, size_t moduleNameCapacity)
{
if(moduleNameCapacity > 0)
pModuleName[0] = 0;
#if EATHREAD_APPLE_GETMODULEINFO_ENABLED
Dl_info dlInfo; memset(&dlInfo, 0, sizeof(dlInfo)); // Just memset because dladdr sometimes leaves dli_fname untouched.
int result = dladdr(pCodeAddress, &dlInfo);
if((result != 0) && dlInfo.dli_fname) // It seems that usually this fails.
return strlcpy(pModuleName, dlInfo.dli_fname, moduleNameCapacity);
// To do: Make this be dynamically resized as needed.
const size_t kCapacity = 64;
ModuleInfoApple moduleInfoAppleArray[kCapacity];
size_t requiredCapacity = GetModuleInfoApple(moduleInfoAppleArray, kCapacity, "__TEXT", true); // To consider: Make this true (use cache) configurable.
uint64_t codeAddress = (uint64_t)(uintptr_t)pCodeAddress;
if(requiredCapacity > kCapacity)
requiredCapacity = kCapacity;
for(size_t i = 0; i < requiredCapacity; i++)
{
const ModuleInfoApple& miaUser = moduleInfoAppleArray[i];
if((miaUser.mBaseAddress < codeAddress) && (codeAddress < (miaUser.mBaseAddress + miaUser.mSize)))
return strlcpy(pModuleName, miaUser.mPath, moduleNameCapacity);
}
#endif
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* pCodeAddress)
{
#if EATHREAD_APPLE_GETMODULEINFO_ENABLED
Dl_info dlInfo; memset(&dlInfo, 0, sizeof(dlInfo)); // Just memset because dladdr sometimes leaves fields untouched.
int result = dladdr(pCodeAddress, &dlInfo);
if(result != 0)
return dlInfo.dli_fbase; // Is the object load base the same as the module handle?
// Try using GetModuleInfoApple to get the information.
// To do: Make this be dynamically resized as needed.
const size_t kCapacity = 256;
ModuleInfoApple moduleInfoAppleArray[kCapacity];
size_t requiredCapacity = GetModuleInfoApple(moduleInfoAppleArray, kCapacity, "__TEXT", true); // To consider: Make this true (use cache) configurable.
uint64_t codeAddress = (uint64_t)(uintptr_t)pCodeAddress;
if(requiredCapacity > kCapacity)
requiredCapacity = kCapacity;
for(size_t i = 0; i < requiredCapacity; i++)
{
ModuleInfoApple& miaUser = moduleInfoAppleArray[i];
if((miaUser.mBaseAddress < codeAddress) && (codeAddress < (miaUser.mBaseAddress + miaUser.mSize)))
return (ModuleHandle)miaUser.mBaseAddress;
}
#endif
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId)
{
// For Apple pthread_t is typedef'd to an internally defined _opaque_pthread_t*.
bool threadIsSelf = (threadId == (intptr_t)EA::Thread::kThreadIdInvalid) || // Due to a specification mistake, this function
(threadId == (intptr_t)EA::Thread::kThreadIdCurrent) || // accepts kThreadInvalid to mean current.
(threadId == (intptr_t)pthread_self());
if(threadIsSelf)
{
bool result = true;
context.mStackBase = (uintptr_t)GetStackBase();
context.mStackLimit = (uintptr_t)GetStackLimit();
#if defined(EA_PROCESSOR_ARM32)
void* p;
EAGetInstructionPointer(p);
context.mPC = (uint32_t)p;
context.mFP = (uint32_t)__builtin_frame_address(0); // This data isn't exactly right. We want to return the registers as they
context.mSP = (uint32_t)__builtin_frame_address(0); // are for the caller, not for us. Without doing that we end up reporting
context.mLR = (uint32_t)__builtin_return_address(0); // an extra frame (this one) on the top of callstacks.
#elif defined(EA_PROCESSOR_ARM64)
void* p;
EAGetInstructionPointer(p);
context.mPC = (uint64_t)p;
context.mFP = (uint64_t)__builtin_frame_address(0);
context.mSP = (uint64_t)__builtin_frame_address(0);
context.mLR = (uint64_t)__builtin_return_address(0);
#elif defined(EA_PROCESSOR_X86_64)
context.mRIP = (uint64_t)__builtin_return_address(0);
context.mRSP = 0;
context.mRBP = (uint64_t)__builtin_frame_address(1);
#elif defined(EA_PROCESSOR_X86)
context.mEIP = (uint32_t)__builtin_return_address(0);
context.mESP = 0;
context.mEBP = (uint32_t)__builtin_frame_address(1);
#else
// platform not supported
result = false;
#endif
return result;
}
else
{
// Pause the thread, get its state, unpause it.
//
// Question: Is it truly necessary to suspend a thread in Apple platforms in order to read
// their state? It is usually so for other platforms doing the same kind of thing.
//
// Question: Is it dangerous to suspend an arbitrary thread? Often such a thing is dangerous
// because that other thread might for example have some kernel mutex locked that we need.
// We'll have to see, as it's a great benefit for us to be able to read callstack contexts.
// Another solution would be to inject a signal handler into the thread and signal it and
// have the handler read context information, if that can be useful. There's example code
// on the Internet for that.
// Some documentation:
// http://www.linuxselfhelp.com/gnu/machinfo/html_chapter/mach_7.html
mach_port_t thread = pthread_mach_thread_np((pthread_t)threadId); // Convert pthread_t to kernel thread id.
kern_return_t result = thread_suspend(thread);
if(result == KERN_SUCCESS)
{
#if defined(EA_PROCESSOR_ARM32)
arm_thread_state_t threadState; memset(&threadState, 0, sizeof(threadState));
mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;
result = thread_get_state(thread, MACHINE_THREAD_STATE, (natural_t*)(uintptr_t)&threadState, &stateCount);
context.mFP = threadState.__r[7]; // Apple uses R7 for the frame pointer in both ARM and Thumb CPU modes.
context.mPC = threadState.__pc;
context.mSP = threadState.__sp;
context.mLR = threadState.__lr;
#elif defined(EA_PROCESSOR_ARM64)
__darwin_arm_thread_state64 threadState; memset(&threadState, 0, sizeof(threadState));
mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;
result = thread_get_state(thread, MACHINE_THREAD_STATE, (natural_t*)(uintptr_t)&threadState, &stateCount);
context.mFP = threadState.__fp;
context.mPC = threadState.__pc;
context.mSP = threadState.__sp;
context.mLR = threadState.__lr;
#elif defined(EA_PROCESSOR_X86_64)
// Note: This is yielding gibberish data for me, despite everything seemingly being done correctly.
x86_thread_state_t threadState; memset(&threadState, 0, sizeof(threadState));
mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;
result = thread_get_state(thread, MACHINE_THREAD_STATE, (natural_t*)(uintptr_t)&threadState, &stateCount);
context.mRIP = threadState.uts.ts64.__rip;
context.mRSP = threadState.uts.ts64.__rsp;
context.mRBP = threadState.uts.ts64.__rbp;
#elif defined(EA_PROCESSOR_X86)
// Note: This is yielding gibberish data for me, despite everything seemingly being done correctly.
x86_thread_state_t threadState; memset(&threadState, 0, sizeof(threadState));
mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;
result = thread_get_state(thread, MACHINE_THREAD_STATE, (natural_t*)(uintptr_t)&threadState, &stateCount);
context.mEIP = threadState.uts.ts32.__eip;
context.mESP = threadState.uts.ts32.__esp;
context.mEBP = threadState.uts.ts32.__ebp;
#endif
thread_resume(thread);
return (result == KERN_SUCCESS);
}
}
// Not currently implemented for the given platform.
memset(&context, 0, sizeof(context));
return false;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
pthread_t pthread = pthread_from_mach_thread_np((mach_port_t)sysThreadId);
return GetCallstackContext(context, (intptr_t)pthread);
}
// 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) || defined(EA_PLATFORM_APPLE)
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) || defined(EA_PLATFORM_APPLE)
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.
}
} // namespace Thread
} // namespace EA
@@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_semaphore.h>
#if defined(EA_PLATFORM_APPLE)
#include <mach/task.h>
#include <mach/mach_init.h>
#include <mach/kern_return.h>
#include <semaphore.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>
EASemaphoreData::EASemaphoreData()
: mSemaphore(), mnCount(0), mnMaxCount(INT_MAX)
{
}
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()
{
const kern_return_t result = semaphore_destroy(mach_task_self(), mSemaphoreData.mSemaphore); (void)result;
EAT_ASSERT(KERN_SUCCESS == result);
}
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;
// Todo, Jaap Suter, December 2009, do we care about actually supporting this?
mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess;
const kern_return_t result = semaphore_create(mach_task_self(), &mSemaphoreData.mSemaphore, SYNC_POLICY_FIFO, static_cast<int>(mSemaphoreData.mnCount)); (void)result;
EAT_ASSERT(KERN_SUCCESS == result);
return true;
}
return false;
}
int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
{
kern_return_t result = KERN_SUCCESS;
if(timeoutAbsolute == kTimeoutNone)
{
result = semaphore_wait(mSemaphoreData.mSemaphore);
if(result != KERN_SUCCESS)
{
EAT_ASSERT(false); // This is an error condition.
return kResultError;
}
}
else
{
for (;;)
{
ThreadTime timeoutRelative = kTimeoutImmediate;
if (timeoutAbsolute != kTimeoutImmediate)
{
ThreadTime timeCurrent = GetThreadTime();
timeoutRelative = (timeoutAbsolute > timeCurrent) ? (timeoutAbsolute - timeCurrent) : kTimeoutImmediate;
}
mach_timespec_t machTimeoutRelative = { (unsigned int)timeoutRelative.tv_sec, (clock_res_t)timeoutRelative.tv_nsec };
result = semaphore_timedwait(mSemaphoreData.mSemaphore, machTimeoutRelative);
if (result == KERN_SUCCESS)
break;
if (result == KERN_OPERATION_TIMED_OUT)
return kResultTimeout;
// printf("semaphore_timedwait other error: %d\n", result);
}
}
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);
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(semaphore_signal(mSemaphoreData.mSemaphore) != KERN_SUCCESS)
{
--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 // #if defined(EA_PLATFORM_APPLE)
@@ -0,0 +1,412 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/eathread_storage.h>
#include <string.h>
#if EATHREAD_DEBUG_DETAIL_ENABLED
#include <EAStdC/EASprintf.h>
#endif
#if defined(EA_PLATFORM_WINDOWS) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
#pragma warning(push, 0)
#include <Windows.h>
#include <winternl.h>
#pragma warning(pop)
#endif
#if defined(EA_PLATFORM_UNIX)
#include <pthread.h>
#include <eathread/eathread.h>
#endif
#if defined(EA_COMPILER_CLANG)
#include <unwind.h>
#endif
namespace EA
{
namespace Thread
{
#if defined(EA_PLATFORM_WINDOWS) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
EATHREADLIB_API void GetInstructionPointer(void*& pInstruction)
{
CONTEXT context;
// Apparently there is no need to memset the context struct.
context.ContextFlags = CONTEXT_ALL;
RtlCaptureContext(&context);
// Possibly use the __emit intrinsic. http://msdn.microsoft.com/en-us/library/ms933778.aspx
pInstruction = (void*)(uintptr_t)context.___; // To do.
}
#elif defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG)
EATHREADLIB_API void GetInstructionPointer(void*& pInstruction)
{
// __builtin_return_address returns the address with the Thumb bit set
// if it's a return to Thumb code. We intentionally preserve this and
// don't try to mask it away.
pInstruction = (void*)(uintptr_t)__builtin_return_address(0);
}
#else
EATHREADLIB_API void GetInstructionPointer(void*& /*pInstruction*/)
{
//Un-implemented
}
#endif
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
}
#if defined(EA_PLATFORM_APPLE)
// Apple defines a different ABI than the ARM eabi used by Linux and the ABI used
// by Microsoft. It implements a predictable stack frame system using r7 as the
// frame pointer. Documentation:
// http://developer.apple.com/library/ios/#documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html
//
// Apple ARM stack frame:
// struct StackFrame {
// StackFrame* mpParentStackFrame;
// void* mpReturnPC;
// }
//
// Basically, Apple uses r7 as a frame pointer. So for any function you are
// executing, r7 + 4 is the LR passed to us by the caller and is the PC of
// the parent. And r7 + 0 is a pointer to the parent's r7.
//
static size_t GetCallstackARMApple(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
struct StackFrame {
StackFrame* mpParentStackFrame;
void* mpReturnPC;
};
size_t index = 0;
if(nReturnAddressArrayCapacity && pContext->mFP) // To consider: Do some basic validation of mFP if it refers to this same thread.
{
StackFrame* pStackFrame = static_cast<StackFrame*>((void*)pContext->mFP); // Points to the GetCallstack frame pointer.
pReturnAddressArray[index++] = pStackFrame->mpReturnPC; // Should happen to be equal to pContext->mLR.
while(pStackFrame && pStackFrame->mpReturnPC && (index < nReturnAddressArrayCapacity)) // To consider: do some validation of the PC. We can validate it by making sure it's with 20 MB of our PC and also verify that the instruction before it (be it Thumb or ARM) is a BL or BLX function call instruction.
{
pStackFrame = pStackFrame->mpParentStackFrame;
if(pStackFrame && pStackFrame->mpReturnPC)
pReturnAddressArray[index++] = pStackFrame->mpReturnPC;
}
}
return index;
}
#endif
#if defined(EA_COMPILER_CLANG)
struct CallstackState
{
void** current;
void** end;
};
static _Unwind_Reason_Code UnwindCallback(struct _Unwind_Context* context, void* arg)
{
CallstackState* state = static_cast<CallstackState*>(arg);
uintptr_t pc = _Unwind_GetIP(context);
if (pc)
{
if (state->current == state->end)
{
return _URC_END_OF_STACK;
}
else
{
*state->current++ = reinterpret_cast<void*>(pc);
}
}
return _URC_NO_REASON;
}
#endif
///////////////////////////////////////////////////////////////////////////////
// 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)
{
void* p;
CallstackContext context;
size_t entryCount = 0;
if(pContext)
context = *pContext;
else
{
#if defined(__ARMCC_VERSION)
context.mFP = 0; // We don't currently have a simple way to read fp (which is r7 (Thumb) or r11 (ARM)).
context.mSP = (uintptr_t)__current_sp();
context.mLR = (uintptr_t)__return_address();
GetInstructionPointer(p); // Intentionally don't call __current_pc() or EAGetInstructionPointer, because these won't set the Thumb bit it this is Thumb code.
context.mPC = (uintptr_t)p;
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG) // Including Apple iOS.
void* spAddress = &context.mSP;
void* sp;
asm volatile(
"add %0, sp, #0\n"
"str %0, [%1, #0]\n"
: "=r"(sp), "+r"(spAddress) :: "memory");
context.mFP = (uintptr_t)__builtin_frame_address(0);
context.mLR = (uintptr_t)__builtin_return_address(0);
GetInstructionPointer(p); // Intentionally don't call EAGetInstructionPointer, because it won't set the Thumb bit it this is Thumb code.
context.mPC = (uintptr_t)p;
#elif defined(EA_PLATFORM_WINDOWS) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
// Possibly use the __emit intrinsic. Do this by making a __declspec(naked) function that
// does nothing but return r14 (move r14 to r0). Need to know the opcode for that.
// http://msdn.microsoft.com/en-us/library/ms933778.aspx
#error Need to complete this somehow.
context.mFP = 0;
context.mLR = 0;
context.mSP = 0;
GetInstructionPointer(p); // Intentionally don't call EAGetInstructionPointer, because it won't set the Thumb bit it this is Thumb code.
context.mPC = (uintptr_t)p;
#endif
}
#if defined(__APPLE__)
// We have reason to believe that the following should be reliable. But if it's not then we should
// just call the code below.
entryCount = GetCallstackARMApple(pReturnAddressArray, nReturnAddressArrayCapacity, &context);
if(entryCount >= 3) // If GetCallstackARMApple seems to have been successful, use it. Else fall through to the more complicated code below.
return entryCount;
#elif defined(EA_COMPILER_CLANG)
CallstackState state = { pReturnAddressArray, pReturnAddressArray + nReturnAddressArrayCapacity };
_Unwind_Backtrace(UnwindCallback, &state);
entryCount = state.current - pReturnAddressArray;
#else
EA_UNUSED(pReturnAddressArray);
EA_UNUSED(nReturnAddressArrayCapacity);
EA_UNUSED(context);
#endif
EA_UNUSED(p);
return entryCount;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* pContext)
{
context.mSP = pContext->mGpr[13];
context.mLR = pContext->mGpr[14];
context.mPC = pContext->mGpr[15];
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
EATHREADLIB_API size_t GetModuleFromAddress(const void* /*address*/, char* pModuleName, size_t /*moduleNameCapacity*/)
{
pModuleName[0] = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* /*pAddress*/)
{
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)
{
memset(&context, 0, sizeof(context));
// True Linux-based ARM platforms (usually tablets and phones) can use pthread_attr_getstack.
#if defined(EA_PLATFORM_ANDROID) || defined(EA_PLATFORM_IPHONE)
if((threadId == (intptr_t)kThreadIdInvalid) ||
(threadId == (intptr_t)kThreadIdCurrent) ||
(threadId == (intptr_t)EA::Thread::GetThreadId()))
{
void* p;
// TODO: make defines of this so that the implementation between us and GetCallstack remains the same
#if defined(__ARMCC_VERSION)
context.mSP = (uint32_t)__current_sp();
context.mLR = (uint32_t)__return_address();
context.mPC = (uint32_t)__current_pc();
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
// register uintptr_t current_sp asm ("sp");
p = __builtin_frame_address(0);
context.mSP = (uintptr_t)p;
p = __builtin_return_address(0);
context.mLR = (uint32_t)p;
EAGetInstructionPointer(p);
context.mPC = reinterpret_cast<uintptr_t>(p);
#elif defined(_MSC_VER)
context.mSP = 0;
#error EACallstack::GetCallstack: Need a way to get the return address (register 14)
// Possibly use the __emit intrinsic. Do this by making a __declspec(naked) function that
// does nothing but return r14 (move r14 to r0). Need to know the opcode for that.
// http://msdn.microsoft.com/en-us/library/ms933778.aspx
context.mLR = 0;
EAGetInstructionPointer(p);
context.mPC = reinterpret_cast<uintptr_t>(p);
#endif
context.mStackBase = (uintptr_t)GetStackBase();
context.mStackLimit = (uintptr_t)GetStackLimit();
context.mStackPointer = context.mSP;
return true;
}
// Else haven't implemented getting the stack info for other threads
#else
// Not currently implemented for the given platform.
EA_UNUSED(threadId);
#endif
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 iPhone probably doesn't need it.
EA::Thread::ThreadLocalStorage sStackBase;
///////////////////////////////////////////////////////////////////////////////
// SetStackBase
//
EATHREADLIB_API void SetStackBase(void* pStackBase)
{
if(pStackBase)
sStackBase.SetValue(pStackBase);
else
{
// Can't call GetStackLimit() because doing so would disturb the stack.
// As of this writing, we don't have an EAGetStackTop macro which could do this.
// So we implement it inline here.
#if defined(__ARMCC_VERSION)
pStackBase = (void*)__current_sp();
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
pStackBase = __builtin_frame_address(0);
#endif
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 defined(__ARMCC_VERSION)
void* pStack = (void*)__current_sp();
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
void* pStack = __builtin_frame_address(0);
#else
void* pStack = NULL; // TODO: determine fix.
pStack = &pStack;
#endif
return (void*)((uintptr_t)pStack & ~4095); // Round down to nearest page, as the stack grows downward.
}
} // namespace Thread
} // namespace EA
@@ -0,0 +1,217 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include "eathread/eathread.h"
#include "eathread/eathread_thread.h"
#include <cstring>
#include <sstream>
#include <type_traits>
namespace EA
{
namespace Thread
{
EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL;
void* gpAssertionFailureContext = NULL;
EATHREADLIB_API ThreadId EA::Thread::GetThreadId()
{
return std::this_thread::get_id();
}
EATHREADLIB_API ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id)
{
EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mpComp->mThread.get_id();
}
return EA::Thread::kThreadIdInvalid;
}
EATHREADLIB_API SysThreadId EA::Thread::GetSysThreadId(ThreadId threadId)
{
EAThreadDynamicData* tdd = EA::Thread::FindThreadDynamicData(threadId);
if (tdd && tdd->mpComp)
return tdd->mpComp->mThread.native_handle();
ThreadId threadIdCurrent = GetThreadId();
if(threadId == threadIdCurrent)
{
#if defined(EA_PLATFORM_MICROSOFT)
std::thread::id stdId = std::this_thread::get_id();
EAT_COMPILETIME_ASSERT(sizeof(_Thrd_t) == sizeof(std::thread::id));
return ((_Thrd_t&)stdId)._Hnd;
#elif EA_POSIX_THREADS_AVAILABLE && defined(_YVALS)
std::thread::id stdId = std::this_thread::get_id();
EAT_COMPILETIME_ASSERT(sizeof(_Thrd_t) == sizeof(std::thread::id));
return reinterpret_cast<_Thrd_t>(stdId);
#else
#error Platform not supported yet.
#endif
}
EAT_ASSERT_MSG(false, "Failed to find associated EAThreadDynamicData for this thread.\n");
return SysThreadId();
}
EATHREADLIB_API SysThreadId EA::Thread::GetSysThreadId()
{
// There currently isn't a means to directly get the current SysThreadId, so we do it indirectly:
return GetSysThreadId(std::this_thread::get_id());
}
EATHREADLIB_API ThreadTime EA::Thread::GetThreadTime()
{
using namespace std::chrono;
auto nowMs = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
return nowMs.count();
}
EATHREADLIB_API int GetThreadPriority()
{
// No way to query or set thread priority through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs
return kThreadPriorityDefault;
}
EATHREADLIB_API bool SetThreadPriority(int nPriority)
{
// No way to query or set thread priority through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs
return false;
}
EATHREADLIB_API void SetThreadProcessor(int nProcessor)
{
// No way to query or set thread processor through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs
}
EATHREADLIB_API int GetThreadProcessor()
{
// No way to query or set thread processor through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs
return 0;
}
EATHREADLIB_API int GetProcessorCount()
{
return static_cast<int>(std::thread::hardware_concurrency());
}
EATHREADLIB_API void ThreadSleep(const ThreadTime& timeRelative)
{
std::this_thread::sleep_for(std::chrono::milliseconds(timeRelative));
}
void ThreadEnd(intptr_t threadReturnValue)
{
// No way to end a thread through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs
EAT_ASSERT_MSG(false, "ThreadEnd is not implemented for C++11 threads.\n");
}
EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask)
{
// Update the affinity mask in the thread dynamic data cache.
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
pTDD->mnThreadAffinityMask = nAffinityMask;
}
#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
// Call the Windows library function.
#endif
}
EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id)
{
// Update the affinity mask in the thread dynamic data cache.
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
EATHREADLIB_API void SetAssertionFailureFunction(AssertionFailureFunction pAssertionFailureFunction, void* pContext)
{
gpAssertionFailureFunction = pAssertionFailureFunction;
gpAssertionFailureContext = pContext;
}
EATHREADLIB_API void AssertionFailure(const char* pExpression)
{
if(gpAssertionFailureFunction)
gpAssertionFailureFunction(pExpression, gpAssertionFailureContext);
}
void* GetThreadStackBase()
{
return nullptr;
}
// This can be removed once all remaining synchronization primitives are implemented in terms of C++11 APIs
uint32_t EA::Thread::RelativeTimeoutFromAbsoluteTimeout(ThreadTime timeoutAbsolute)
{
EAT_ASSERT((timeoutAbsolute == kTimeoutImmediate) || (timeoutAbsolute > EATHREAD_MIN_ABSOLUTE_TIME)); // Assert that the user didn't make the mistake of treating time as relative instead of absolute.
DWORD timeoutRelative = 0;
if (timeoutAbsolute == kTimeoutNone)
{
timeoutRelative = 0xffffffff;
}
else if (timeoutAbsolute == kTimeoutImmediate)
{
timeoutRelative = 0;
}
else
{
ThreadTime timeCurrent(GetThreadTime());
timeoutRelative = (timeoutAbsolute > timeCurrent) ? static_cast<DWORD>(timeoutAbsolute - timeCurrent) : 0;
}
EAT_ASSERT((timeoutRelative == 0xffffffff) || (timeoutRelative < 100000000)); // Assert that the timeout is a sane value and didn't wrap around.
return timeoutRelative;
}
// Implement native_handle_type comparison as a memcmp() - may need platform specific implementations on some future platforms.
bool Equals(const SysThreadId& a, const SysThreadId& b)
{
static_assert((std::is_fundamental<SysThreadId>::value || std::is_pointer<SysThreadId>::value || std::is_pod<SysThreadId>::value),
"SysThreadId should be comparable using memcmp()");
return memcmp(&a, &b, sizeof(SysThreadId)) == 0;
}
namespace detail
{
// Override the default EAThreadToString implementation
#define EAThreadIdToString_CUSTOM_IMPLEMENTATION
ThreadIdToStringBuffer::ThreadIdToStringBuffer(EA::Thread::ThreadId threadId)
{
std::stringstream formatStream;
formatStream << threadId;
strncpy(mBuf, formatStream.str().c_str(), BufSize - 1);
mBuf[BufSize - 1] = '\0';
}
SysThreadIdToStringBuffer::SysThreadIdToStringBuffer(EA::Thread::SysThreadId sysThreadId)
{
strncpy(mBuf, "Unknown", BufSize - 1);
mBuf[BufSize - 1] = '\0';
}
}
}
}
@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "eathread/eathread_mutex.h"
EAMutexData::EAMutexData() : mnLockCount(0) {}
EA::Thread::MutexParameters::MutexParameters(bool /*bIntraProcess*/, const char* pName)
{
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
{
mName[0] = 0;
}
}
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);
}
bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters)
{
if (pMutexParameters)
{
mMutexData.mnLockCount = 0;
return true;
}
return false;
}
int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute)
{
if (timeoutAbsolute == kTimeoutNone)
{
mMutexData.mMutex.lock();
}
else
{
std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute);
std::chrono::time_point<std::chrono::system_clock> timeout_time(timeoutAbsoluteMs);
if (!mMutexData.mMutex.try_lock_until(timeout_time))
{
return kResultTimeout;
}
}
EAT_ASSERT((mMutexData.mThreadId = EA::Thread::GetThreadId()) != kThreadIdInvalid);
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.
mMutexData.mMutex.unlock();
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 == EA::Thread::GetThreadId());
#else
return (mMutexData.mnLockCount > 0); // This is the best we can do, though it is of limited use, since it doesn't tell you if you are the thread with the lock.
#endif
}
@@ -0,0 +1,5 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "eathread/eathread_semaphore.h"
@@ -0,0 +1,488 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "eathread/eathread_thread.h"
#include "eathread/eathread.h"
#include "eathread/eathread_sync.h"
#include "eathread/eathread_callstack.h"
#include "eathread/internal/eathread_global.h"
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
static AtomicInt32 nLastProcessor = 0;
const size_t kMaxThreadDynamicDataCount = 128;
struct EAThreadGlobalVars
{
char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)];
AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount];
Mutex gThreadDynamicMutex;
};
EATHREAD_GLOBALVARS_CREATE_INSTANCE;
EAThreadDynamicData* AllocateThreadDynamicData()
{
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
{
if (EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0))
return (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
}
// This is a safety fallback mechanism. In practice it won't be used in almost all situations.
if (gpAllocator)
return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData));
return nullptr;
}
void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData)
{
pEAThreadDynamicData->~EAThreadDynamicData();
if ((pEAThreadDynamicData >= (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount)))
{
EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0);
}
else
{
// Assume the data was allocated via the fallback mechanism.
if (gpAllocator)
{
gpAllocator->Free(pEAThreadDynamicData);
}
}
}
EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId)
{
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if (pTDD->mpComp && pTDD->mpComp->mThread.get_id() == threadId)
return pTDD;
}
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
}
EAThreadDynamicData* FindThreadDynamicData(EA::Thread::ThreadUniqueId threadId)
{
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if (pTDD->mUniqueThreadId == threadId)
return pTDD;
}
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
}
EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId)
{
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if (pTDD->mpComp && pTDD->mpComp->mThread.native_handle() == sysThreadId)
return pTDD;
}
// NOTE: This function does not support finding externally created threads due to limitations in the CPP11 std::thread API.
// At the time of writing, it is not possible to retrieve the thread object of a thread not created by the CPP11 API.
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
}
}
}
EA_DISABLE_VC_WARNING(4355) // this used in base member initializer list - should be safe in this context
EAThreadDynamicData::EAThreadDynamicData(void* userFunc, void* userContext, void* userWrapperFunc, ThreadFunc threadFunc) :
mnRefCount(2), // Init ref count to 2, one corresponding release happens on threadFunc exit and the other when Thread class is destroyed or Begin is called again
mStatus(EA::Thread::Thread::kStatusNone),
mpComp(nullptr)
{
mpComp = new EAThreadComposite();
if(mpComp)
mpComp->mThread = std::thread(threadFunc, this, userFunc, userContext, userWrapperFunc); // This doesn't spawn CPP11 threads when created within the EAThreadComposite constructor.
}
EAThreadDynamicData::EAThreadDynamicData(EA::Thread::ThreadUniqueId uniqueThreadId, const char* pThreadName) :
mnRefCount(2), // Init ref count to 2, one corresponding release happens on threadFunc exit and the other when Thread class is destroyed or Begin is called again
mStatus(EA::Thread::Thread::kStatusNone),
mpComp(nullptr),
mUniqueThreadId(uniqueThreadId)
{
strncpy(mName, pThreadName, EATHREAD_NAME_SIZE);
mName[EATHREAD_NAME_SIZE - 1] = 0;
}
EA_RESTORE_VC_WARNING()
EAThreadDynamicData::~EAThreadDynamicData()
{
if (mpComp->mThread.joinable())
mpComp->mThread.detach();
if(mpComp)
delete mpComp;
mpComp = nullptr;
// the threads, promises, and futures in this class will
// allocate memory with the Concurrency runtime new/delete operators.
// If you're crashing in here with access violations on process exit,
// then you likely have a static instance of EA::Thread::Thread somewhere
// that's being destructed after your memory system is uninitialized
// leaving dangling pointers to bad memory. Attempt to change
// these static instances to be constructed/destructed with the scope
// of normal app operation.
}
void EAThreadDynamicData::AddRef()
{
mnRefCount.Increment();
}
void EAThreadDynamicData::Release()
{
if(mnRefCount.Decrement() == 0)
EA::Thread::FreeThreadDynamicData(this);
}
namespace EA
{
namespace Thread
{
ThreadParameters::ThreadParameters() :
mpStack(NULL),
mnStackSize(0),
mnPriority(kThreadPriorityDefault),
mnProcessor(kProcessorDefault),
mpName(""),
mbDisablePriorityBoost(false)
{
}
RunnableFunctionUserWrapper Thread::sGlobalRunnableFunctionUserWrapper = NULL;
RunnableClassUserWrapper Thread::sGlobalRunnableClassUserWrapper = NULL;
AtomicInt32 Thread::sDefaultProcessor = kProcessorAny;
RunnableFunctionUserWrapper Thread::GetGlobalRunnableFunctionUserWrapper()
{
return sGlobalRunnableFunctionUserWrapper;
}
void Thread::SetGlobalRunnableFunctionUserWrapper(RunnableFunctionUserWrapper pUserWrapper)
{
if (sGlobalRunnableFunctionUserWrapper != NULL)
{
// Can only be set once in entire game.
EAT_ASSERT(false);
}
else
{
sGlobalRunnableFunctionUserWrapper = pUserWrapper;
}
}
RunnableClassUserWrapper Thread::GetGlobalRunnableClassUserWrapper()
{
return sGlobalRunnableClassUserWrapper;
}
void Thread::SetGlobalRunnableClassUserWrapper(RunnableClassUserWrapper pUserWrapper)
{
if (sGlobalRunnableClassUserWrapper != NULL)
{
// Can only be set once in entire game.
EAT_ASSERT(false);
}
else
{
sGlobalRunnableClassUserWrapper = pUserWrapper;
}
}
Thread::Thread()
{
mThreadData.mpData = NULL;
}
Thread::Thread(const Thread& t) :
mThreadData(t.mThreadData)
{
if (mThreadData.mpData)
mThreadData.mpData->AddRef();
}
Thread& Thread::operator=(const Thread& t)
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if (t.mThreadData.mpData)
t.mThreadData.mpData->AddRef();
if (mThreadData.mpData)
mThreadData.mpData->Release();
mThreadData = t.mThreadData;
return *this;
}
Thread::~Thread()
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if (mThreadData.mpData)
mThreadData.mpData->Release();
}
static void RunnableFunctionInternal(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc)
{
tdd->mStatus = Thread::kStatusRunning;
tdd->mpStackBase = EA::Thread::GetStackBase();
RunnableFunction pFunction = (RunnableFunction)userFunc;
if (userWrapperFunc)
{
RunnableFunctionUserWrapper pWrapperFunction = (RunnableFunctionUserWrapper)userWrapperFunc;
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
tdd->mpComp->mReturnPromise.set_value(pWrapperFunction(pFunction, userContext));
}
else
{
tdd->mpComp->mReturnPromise.set_value(pFunction(userContext));
}
tdd->mStatus = Thread::kStatusEnded;
tdd->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor
}
ThreadId Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper)
{
// Check there is an entry for the current thread context in our ThreadDynamicData array.
ThreadUniqueId threadUniqueId;
EAThreadGetUniqueId(threadUniqueId);
if(!FindThreadDynamicData(threadUniqueId))
{
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData(threadUniqueId, "external");
if(pData)
{
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
// Do no AddRef for thread execution because this is not an EAThread managed thread.
}
}
if (mThreadData.mpData)
mThreadData.mpData->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor
// C++11 Threads don't support user-supplied stacks. A user-supplied stack pointer
// here would be a waste of user memory, and so we assert that mpStack == NULL.
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pDataAddr = AllocateThreadDynamicData();
EAT_ASSERT(pDataAddr != nullptr);
EAThreadDynamicData* pData = new(pDataAddr) EAThreadDynamicData(pFunction, pContext, pUserWrapper, RunnableFunctionInternal); // Note that we use a special new here which doesn't use the heap.
EAT_ASSERT(pData != nullptr);
mThreadData.mpData = pData;
if (pTP)
SetName(pTP->mpName);
return pData->mpComp->mThread.get_id();
}
static void RunnableObjectInternal(EAThreadDynamicData* tdd, void* userFunc, void* userContext, void* userWrapperFunc)
{
tdd->mStatus = Thread::kStatusRunning;
IRunnable* pRunnable = (IRunnable*)userFunc;
if (userWrapperFunc)
{
RunnableClassUserWrapper pWrapperFunction = (RunnableClassUserWrapper)userWrapperFunc;
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
tdd->mpComp->mReturnPromise.set_value(pWrapperFunction(pRunnable, userContext));
}
else
{
tdd->mpComp->mReturnPromise.set_value(pRunnable->Run(userContext));
}
tdd->mStatus = Thread::kStatusEnded;
tdd->Release(); // Matches implicit AddRef in EAThreadDynamicData constructor
}
ThreadId Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper)
{
if (mThreadData.mpData)
mThreadData.mpData->Release(); // Matches an implicit AddRef in EAThreadDynamicData constructor
// C++11 Threads don't support user-supplied stacks. A user-supplied stack pointer
// here would be a waste of user memory, and so we assert that mpStack == NULL.
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pDataAddr = AllocateThreadDynamicData();
EAT_ASSERT(pDataAddr != nullptr);
EAThreadDynamicData* pData = new(pDataAddr) EAThreadDynamicData(pRunnable, pContext, pUserWrapper, RunnableObjectInternal); // Note that we use a special new here which doesn't use the heap.
EAT_ASSERT(pData != nullptr);
mThreadData.mpData = pData;
if (pTP)
SetName(pTP->mpName);
EAT_ASSERT(pData && pData->mpComp);
return pData->mpComp->mThread.get_id();
}
Thread::Status Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue)
{
// The mThreadData memory is shared between threads and when
// reading it we must be synchronized.
EAReadWriteBarrier();
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
// Todo: Consider that there may be a subtle race condition here if
// the user immediately calls WaitForEnd right after calling Begin.
if (mThreadData.mpData && mThreadData.mpData->mpComp)
{
// We must not call WaitForEnd from the thread we are waiting to end. That would result in a deadlock.
EAT_ASSERT(mThreadData.mpData->mpComp->mThread.get_id() != GetThreadId());
std::chrono::milliseconds timeoutAbsoluteMs(timeoutAbsolute);
std::chrono::time_point<std::chrono::system_clock> timeoutTime(timeoutAbsoluteMs);
if (mThreadData.mpData->mpComp->mReturnFuture.wait_until(timeoutTime) == std::future_status::timeout)
{
return kStatusRunning;
}
if (pThreadReturnValue)
{
mThreadData.mpData->mReturnValue = mThreadData.mpData->mpComp->mReturnFuture.get();
*pThreadReturnValue = mThreadData.mpData->mReturnValue;
}
mThreadData.mpData->mpComp->mThread.join();
return kStatusEnded; // A thread was created, so it must have ended.
}
else
{
// Else the user hasn't started the thread yet, so we wait until the user starts it.
// Ideally, what we really want to do here is wait for some kind of signal.
// Instead for the time being we do a polling loop.
while ((!mThreadData.mpData) && (GetThreadTime() < timeoutAbsolute))
{
ThreadSleep(1);
}
if (mThreadData.mpData)
return WaitForEnd(timeoutAbsolute);
}
return kStatusNone; // No thread has been started.
}
Thread::Status Thread::GetStatus(intptr_t* pThreadReturnValue) const
{
if (mThreadData.mpData && mThreadData.mpData->mpComp)
{
auto status = static_cast<Thread::Status>(mThreadData.mpData->mStatus.GetValue());
if (pThreadReturnValue && status == kStatusEnded)
{
if (mThreadData.mpData->mpComp->mGetStatusFuture.valid())
mThreadData.mpData->mReturnValue = mThreadData.mpData->mpComp->mGetStatusFuture.get();
*pThreadReturnValue = mThreadData.mpData->mReturnValue;
}
return status;
}
return kStatusNone;
}
int Thread::GetPriority() const
{
// No way to query or set thread priority through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs using native_handle()
return kThreadPriorityDefault;
}
bool Thread::SetPriority(int nPriority)
{
// No way to query or set thread priority through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs using native_handle()
return false;
}
void Thread::SetProcessor(int nProcessor)
{
// No way to query or set thread priority through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs using native_handle()
}
void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask)
{
if(mThreadData.mpData)
{
EA::Thread::SetThreadAffinityMask(nAffinityMask);
}
}
EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask()
{
if(mThreadData.mpData)
{
return mThreadData.mpData->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
void Thread::Wake()
{
// No way to wake a thread through standard C++11 thread library.
// On some platforms this could be implemented through platform specific APIs using native_handle()
}
const char* Thread::GetName() const
{
if (mThreadData.mpData)
return mThreadData.mpData->mName;
return "";
}
void Thread::SetName(const char* pName)
{
if (mThreadData.mpData && pName)
{
strncpy(mThreadData.mpData->mName, pName, EATHREAD_NAME_SIZE);
mThreadData.mpData->mName[EATHREAD_NAME_SIZE - 1] = 0;
}
}
ThreadId Thread::GetId() const
{
if (mThreadData.mpData && mThreadData.mpData->mpComp)
return mThreadData.mpData->mpComp->mThread.get_id();
return kThreadIdInvalid;
}
}
}
+254
View File
@@ -0,0 +1,254 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/internal/config.h>
#include <eathread/eathread.h>
#include <stdarg.h>
#include <stdio.h>
namespace EA
{
namespace Thread
{
EA::Thread::Allocator* gpAllocator = NULL;
EATHREADLIB_API void SetAllocator(Allocator* pEAThreadAllocator)
{
gpAllocator = pEAThreadAllocator;
}
EATHREADLIB_API Allocator* GetAllocator()
{
return gpAllocator;
}
// Currently we take advantage of the fact that ICoreAllocator
// is a binary mapping to EA::Thread::Allocator.
// To do: We need to come up with a better solution that this,
// as it is not future-safe and not even guaranteed to
// be portable. The problem is that we can't make this
// package dependent on the CoreAllocator package without
// breaking users who aren't using it.
EATHREADLIB_API void SetAllocator(EA::Allocator::ICoreAllocator* pCoreAllocator)
{
gpAllocator = (EA::Thread::Allocator*)(uintptr_t)pCoreAllocator;
}
EATHREADLIB_API void SetThreadAffinityMask(ThreadAffinityMask nAffinityMask)
{
EA::Thread::SetThreadAffinityMask(GetThreadId(), nAffinityMask);
}
EATHREADLIB_API ThreadAffinityMask GetThreadAffinityMask()
{
return GetThreadAffinityMask(GetThreadId());
}
}
}
#if !EA_THREADS_AVAILABLE
// Do nothing
#elif EA_USE_CPP11_CONCURRENCY
#include "cpp11/eathread_cpp11.cpp"
#elif defined(EA_PLATFORM_SONY)
#include "kettle/eathread_kettle.cpp"
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include "unix/eathread_unix.cpp"
#elif defined(EA_PLATFORM_MICROSOFT)
#include "pc/eathread_pc.cpp"
#endif
namespace EA
{
namespace Thread
{
namespace detail
{
#if !defined(EAThreadIdToString_CUSTOM_IMPLEMENTATION)
ThreadIdToStringBuffer::ThreadIdToStringBuffer(EA::Thread::ThreadId threadId)
{
sprintf(mBuf, "%d", (int)(intptr_t)threadId);
}
SysThreadIdToStringBuffer::SysThreadIdToStringBuffer(EA::Thread::SysThreadId sysThreadId)
{
sprintf(mBuf, "%d", (int)(intptr_t)sysThreadId);
}
#endif
}
}
}
#if defined(EA_PLATFORM_ANDROID)
#if EATHREAD_C11_ATOMICS_AVAILABLE == 0
#include "android/eathread_fake_atomic_64.cpp"
#endif
#endif
#if !defined(EAT_ASSERT_SNPRINTF)
#if defined(EA_PLATFORM_MICROSOFT)
#define EAT_ASSERT_SNPRINTF _vsnprintf
#else
#define EAT_ASSERT_SNPRINTF snprintf
#endif
#endif
void EA::Thread::AssertionFailureV(const char* pFormat, ...)
{
const size_t kBufferSize = 512;
char buffer[kBufferSize];
va_list arguments;
va_start(arguments, pFormat);
const int nReturnValue = EAT_ASSERT_SNPRINTF(buffer, kBufferSize, pFormat, arguments);
va_end(arguments);
if(nReturnValue > 0)
{
buffer[kBufferSize - 1] = 0;
AssertionFailure(buffer);
}
}
///////////////////////////////////////////////////////////////////////////////
// non-threaded implementation
///////////////////////////////////////////////////////////////////////////////
#if !EA_THREADS_AVAILABLE
#include <stdio.h>
#if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include <sched.h>
#include <sys/time.h>
#elif defined(EA_PLATFORM_WINDOWS)
extern "C" __declspec(dllimport) void __stdcall Sleep(unsigned long dwMilliseconds);
#endif
namespace EA
{
namespace Thread
{
// Assertion variables.
EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL;
void* gpAssertionFailureContext = NULL;
}
}
EA::Thread::ThreadId EA::Thread::GetThreadId()
{
return 1;
}
int EA::Thread::GetThreadPriority()
{
return kThreadPriorityDefault;
}
bool EA::Thread::SetThreadPriority(int nPriority)
{
return true;
}
void* EA::Thread::GetThreadStackBase()
{
return NULL;
}
void EA::Thread::SetThreadProcessor(int /*nProcessor*/)
{
}
int EA::Thread::GetThreadProcessor()
{
return 0;
}
int EA::Thread::GetProcessorCount()
{
return 1;
}
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))));
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
if(timeRelative == kTimeoutImmediate)
sched_yield();
else
nanosleep(&timeRelative, 0);
#endif
}
void EA::Thread::ThreadEnd(intptr_t /*threadReturnValue*/)
{
// We could possibly call exit here.
}
EA::Thread::ThreadTime EA::Thread::GetThreadTime()
{
#if defined(EA_PLATFORM_WINDOWS)
return (ThreadTime)GetTickCount();
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#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(temp.tv_sec, 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
printf("EA::Thread::AssertionFailure: %s\n", pExpression);
#endif
}
}
#endif // EA_THREADS_AVAILABLE
@@ -0,0 +1,194 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/internal/config.h>
EA_DISABLE_VC_WARNING(4574)
#include <new>
EA_RESTORE_VC_WARNING()
#if defined(EA_PLATFORM_SONY)
#include "kettle/eathread_barrier_kettle.cpp"
#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE
// Posix already defines a barrier (via condition variables or directly with pthread_barrier).
#include "unix/eathread_barrier_unix.cpp"
#else // All other platforms
#include <eathread/eathread_barrier.h>
#include <string.h>
EABarrierData::EABarrierData()
: mnCurrent(0), mnHeight(0), mnIndex(0), mSemaphore0(NULL, false), mSemaphore1(NULL, false)
{
// Leave mSemaphores alone for now. We leave them constructed but not initialized.
}
EA::Thread::BarrierParameters::BarrierParameters(int height, bool bIntraProcess, const char* pName)
: mHeight(height), mbIntraProcess(bIntraProcess)
{
if(pName)
{
EA_DISABLE_VC_WARNING(4996); // This function or variable may be unsafe / deprecated.
strncpy(mName, pName, sizeof(mName)-1);
EA_RESTORE_VC_WARNING();
mName[sizeof(mName)-1] = 0;
}
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()
{
// Nothing to do.
}
bool EA::Thread::Barrier::Init(const BarrierParameters* pBarrierParameters)
{
// You cannot set the height after it's already been set.
EAT_ASSERT((mBarrierData.mnHeight == 0) && (mBarrierData.mnCurrent == 0));
if(pBarrierParameters && (mBarrierData.mnHeight == 0))
{
mBarrierData.mnHeight = pBarrierParameters->mHeight; // We don't put mutex lock around this as it is only to be ever set once, before use.
mBarrierData.mnCurrent = pBarrierParameters->mHeight;
SemaphoreParameters sp(0, pBarrierParameters->mbIntraProcess);
mBarrierData.mSemaphore0.Init(&sp);
mBarrierData.mSemaphore1.Init(&sp);
return true;
}
return false;
}
EA::Thread::Barrier::Result EA::Thread::Barrier::Wait(const ThreadTime& timeoutAbsolute)
{
int result;
const int nCurrentIndex = (int)mBarrierData.mnIndex;
// Question: What do we do if a fifth thread calls Wait on a barrier with height
// of four after the fourth thread has decremented the current count below?
EAT_ASSERT(mBarrierData.mnCurrent > 0); // If this assert fails then it means that more threads are waiting on the barrier than the barrier height.
const int32_t nCurrent = mBarrierData.mnCurrent.Decrement(); // atomic integer operation.
if(nCurrent == 0) // If the barrier has been breached...
{
mBarrierData.mnCurrent = mBarrierData.mnHeight;
if(mBarrierData.mnHeight > 1) // If there are threads other than us...
{
// We don't have a potential race condition here because we use alternating
// semaphores and since we are here, all other threads are waiting on the
// current semaphore below. And if they haven't started waiting on the
// semaphore yet, they'll succeed anyway because we Post all directly below.
Semaphore* const pSemaphore = (nCurrentIndex == 0 ? &mBarrierData.mSemaphore0 : &mBarrierData.mSemaphore1);
result = pSemaphore->Post(mBarrierData.mnHeight - 1); // Upon success, the return value will in practice be >= 1, but semaphore defines success as >= 0.
}
else // Else we are the only thead.
result = 0;
}
else
{
Semaphore* const pSemaphore = (nCurrentIndex == 0 ? &mBarrierData.mSemaphore0 : &mBarrierData.mSemaphore1);
result = pSemaphore->Wait(timeoutAbsolute);
if(result == Semaphore::kResultTimeout)
return kResultTimeout;
}
if(result >= 0) // If the result wasn't an error such as Semaphore::kResultError or Semaphore::kResultTimeout.
{
// Use an atomic operation to change the index, which conveniently gives us a thread to designate as primary.
EAT_ASSERT((unsigned)nCurrentIndex <= 1);
if(mBarrierData.mnIndex.SetValueConditional(1 - nCurrentIndex, nCurrentIndex)) // Toggle value between 0 and 1.
return kResultPrimary;
return kResultSecondary;
}
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;
}
size_t EA::Thread::BarrierFactory::GetBarrierSize()
{
return sizeof(EA::Thread::Barrier);
}
EA::Thread::Barrier* EA::Thread::BarrierFactory::ConstructBarrier(void* pMemory)
{
return new(pMemory) EA::Thread::Barrier;
}
void EA::Thread::BarrierFactory::DestructBarrier(EA::Thread::Barrier* pBarrier)
{
pBarrier->~Barrier();
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,36 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#if defined(EA_PLATFORM_WIN32) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
#include "pc/eathread_callstack_win32.cpp"
#elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64)
#include "pc/eathread_callstack_win64.cpp"
#elif defined(EA_PLATFORM_SONY)
#include "kettle/eathread_callstack_kettle.cpp"
#include "kettle/eathread_pthread_stack_info.cpp"
#elif defined(EA_PLATFORM_ANDROID) && defined(EA_PROCESSOR_X86)
#include "x86/eathread_callstack_x86.cpp"
#include "unix/eathread_pthread_stack_info.cpp"
#elif defined(EA_PLATFORM_ANDROID)
#include "libunwind/eathread_callstack_libunwind.cpp"
#include "unix/eathread_pthread_stack_info.cpp"
#elif defined(EA_PLATFORM_APPLE) // OSX, iPhone, iPhone Simulator
#include "apple/eathread_callstack_apple.cpp"
#include "unix/eathread_pthread_stack_info.cpp"
#elif defined(EA_PROCESSOR_ARM)
#include "arm/eathread_callstack_arm.cpp"
#if !defined(EA_PLATFORM_MICROSOFT)
#include "unix/eathread_pthread_stack_info.cpp"
#endif
#elif (defined(EA_PLATFORM_LINUX) || defined(__CYGWIN__)) && (defined(EA_PROCESSOR_X86) || defined(EA_PROCESSOR_X86_64))
#include "x86/eathread_callstack_x86.cpp"
#include "unix/eathread_pthread_stack_info.cpp"
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
#include "unix/eathread_callstack_glibc.cpp"
#include "unix/eathread_pthread_stack_info.cpp"
#else
#include "null/eathread_callstack_null.cpp"
#endif
@@ -0,0 +1,265 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/internal/config.h>
EA_DISABLE_VC_WARNING(4574)
#include <new>
EA_RESTORE_VC_WARNING()
#if defined(EA_PLATFORM_SONY)
// Posix already defines a Condition (via condition variables).
#include "kettle/eathread_condition_kettle.cpp"
#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EA_THREADS_AVAILABLE
// Posix already defines a Condition (via condition variables).
#include "unix/eathread_condition_unix.cpp"
#else // All other platforms
#include <eathread/eathread_condition.h>
#include <string.h>
EAConditionData::EAConditionData()
: mnWaitersBlocked(0), mnWaitersToUnblock(0), mnWaitersDone(0),
mSemaphoreBlockQueue(NULL, false), // We will be initializing these ourselves specifically below.
mSemaphoreBlockLock(NULL, false),
mUnblockLock(NULL, false)
{
// Empty
}
EA::Thread::ConditionParameters::ConditionParameters(bool bIntraProcess, const char* pName)
: mbIntraProcess(bIntraProcess)
{
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
}
EA::Thread::Condition::Condition(const ConditionParameters* pConditionParameters, bool bDefaultParameters)
{
if(!pConditionParameters && bDefaultParameters)
{
ConditionParameters parameters;
Init(&parameters);
}
else
Init(pConditionParameters);
}
EA::Thread::Condition::~Condition()
{
// Empty
}
bool EA::Thread::Condition::Init(const ConditionParameters* pConditionParameters)
{
if(pConditionParameters)
{
// We have a problem with naming here. We implement our Condition variable with two semaphores and a mutex.
// It's not possible to have them all have the same name, since the OS will think you want them to be
// shared instances. What we really need is an explicit debug name that is separate from the OS name.
// And the ConditionParameters::mName should be that debug name only and not be applied to the child primitives.
const SemaphoreParameters sp1(0, pConditionParameters->mbIntraProcess, NULL); // Set the name to NULL, regardless of what pConditionParameters->mName is.
const SemaphoreParameters sp2(1, pConditionParameters->mbIntraProcess, NULL);
const MutexParameters mp(pConditionParameters->mbIntraProcess, NULL);
if(mConditionData.mSemaphoreBlockQueue.Init(&sp1) &&
mConditionData.mSemaphoreBlockLock .Init(&sp2) &&
mConditionData.mUnblockLock.Init(&mp))
{
return true;
}
}
return false;
}
EA::Thread::Condition::Result EA::Thread::Condition::Wait(Mutex* pMutex, const ThreadTime& timeoutAbsolute)
{
int lockResult, result;
EAT_ASSERT(pMutex); // The user is required to pass a valid Mutex pointer.
++mConditionData.mnWaitersBlocked; // Note that this is an atomic operation.
EAT_ASSERT(pMutex->GetLockCount() == 1);
lockResult = pMutex->Unlock();
if(lockResult < 0)
return (Result)lockResult;
result = mConditionData.mSemaphoreBlockQueue.Wait(timeoutAbsolute);
EAT_ASSERT(result != EA::Thread::Semaphore::kResultError);
// Regardless of the result of the above error, we must press on with the code below.
mConditionData.mUnblockLock.Lock();
const int nWaitersToUnblock = mConditionData.mnWaitersToUnblock;
if(nWaitersToUnblock != 0)
--mConditionData.mnWaitersToUnblock;
else if(++mConditionData.mnWaitersDone == (INT_MAX / 2)) // This is not an atomic operation. We are within a mutex lock.
{
// Normally this doesn't happen, but can happen under very
// unusual circumstances, such as spurious semaphore signals
// or cases whereby many many threads are timing out.
EAT_ASSERT(false);
mConditionData.mSemaphoreBlockLock.Wait();
mConditionData.mnWaitersBlocked -= mConditionData.mnWaitersDone;
mConditionData.mSemaphoreBlockLock.Post();
mConditionData.mnWaitersDone = 0;
}
mConditionData.mUnblockLock.Unlock();
if(nWaitersToUnblock == 1) // If we were the last...
mConditionData.mSemaphoreBlockLock.Post();
// We cannot apply a timeout here. The caller always expects to have the
// lock upon return, even in the case of a wait timeout. Similarly, we
// may or may not want the result of the lock attempt to be propogated
// back to the caller. In this case, we do if it is an error.
lockResult = pMutex->Lock();
if(lockResult == Mutex::kResultError)
return kResultError;
else if(result >= 0)
return kResultOK;
return (Result)result; // This is the result of the wait call above.
}
bool EA::Thread::Condition::Signal(bool bBroadcast)
{
int result;
int nSignalsToIssue;
result = mConditionData.mUnblockLock.Lock();
if(result < 0)
return false;
if(mConditionData.mnWaitersToUnblock)
{
if(mConditionData.mnWaitersBlocked == 0)
{
mConditionData.mUnblockLock.Unlock();
return true;
}
if(bBroadcast)
{
nSignalsToIssue = (int)mConditionData.mnWaitersBlocked.SetValue(0);
mConditionData.mnWaitersToUnblock += nSignalsToIssue;
}
else
{
nSignalsToIssue = 1;
mConditionData.mnWaitersToUnblock++;
mConditionData.mnWaitersBlocked--;
}
}
else if(mConditionData.mnWaitersBlocked > mConditionData.mnWaitersDone)
{
if(mConditionData.mSemaphoreBlockLock.Wait() == EA::Thread::Semaphore::kResultError)
{
mConditionData.mUnblockLock.Unlock();
return false;
}
if(mConditionData.mnWaitersDone != 0)
{
mConditionData.mnWaitersBlocked -= mConditionData.mnWaitersDone;
mConditionData.mnWaitersDone = 0;
}
if(bBroadcast)
{
nSignalsToIssue = mConditionData.mnWaitersToUnblock = (int)mConditionData.mnWaitersBlocked.SetValue(0);
}
else
{
nSignalsToIssue = mConditionData.mnWaitersToUnblock = 1;
mConditionData.mnWaitersBlocked--;
}
}
else
{
mConditionData.mUnblockLock.Unlock();
return true;
}
mConditionData.mUnblockLock.Unlock();
mConditionData.mSemaphoreBlockQueue.Post(nSignalsToIssue);
return true;
}
#endif // EA_PLATFORM_XXX
EA::Thread::Condition* EA::Thread::ConditionFactory::CreateCondition()
{
Allocator* pAllocator = GetAllocator();
if(pAllocator)
return new(pAllocator->Alloc(sizeof(EA::Thread::Condition))) EA::Thread::Condition;
else
return new EA::Thread::Condition;
}
void EA::Thread::ConditionFactory::DestroyCondition(EA::Thread::Condition* pCondition)
{
Allocator* pAllocator = GetAllocator();
if(pAllocator)
{
pCondition->~Condition();
pAllocator->Free(pCondition);
}
else
delete pCondition;
}
size_t EA::Thread::ConditionFactory::GetConditionSize()
{
return sizeof(EA::Thread::Condition);
}
EA::Thread::Condition* EA::Thread::ConditionFactory::ConstructCondition(void* pMemory)
{
return new(pMemory) EA::Thread::Condition;
}
void EA::Thread::ConditionFactory::DestructCondition(EA::Thread::Condition* pCondition)
{
pCondition->~Condition();
}
@@ -0,0 +1,335 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_futex.h>
#include <new>
#if defined(EA_THREAD_NONTHREADED_FUTEX) && EA_THREAD_NONTHREADED_FUTEX
void EA::Thread::Futex::CreateFSemaphore()
{
mSemaphore.mnCount = 0;
}
void EA::Thread::Futex::DestroyFSemaphore()
{
// Do nothing;
}
void EA::Thread::Futex::SignalFSemaphore()
{
mSemaphore.mnCount++;
}
void EA::Thread::Futex::WaitFSemaphore()
{
while(mSemaphore.mnCount <= 0)
EA_THREAD_DO_SPIN();
mSemaphore.mnCount--;
}
bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime&)
{
WaitFSemaphore();
return true;
}
#elif defined(__APPLE__) && EATHREAD_MANUAL_FUTEX_ENABLED
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <libkern/OSAtomic.h>
void EA::Thread::Futex::CreateFSemaphore()
{
mSemaphore.Init(0);
}
void EA::Thread::Futex::DestroyFSemaphore()
{
// Do nothing;
}
void EA::Thread::Futex::SignalFSemaphore()
{
mSemaphore.Post();
}
void EA::Thread::Futex::WaitFSemaphore()
{
mSemaphore.Wait();
}
bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime& timeoutAbsolute)
{
return (mSemaphore.Wait(timeoutAbsolute) >= 0);
}
#elif defined(EA_PLATFORM_SONY) && !EATHREAD_MANUAL_FUTEX_ENABLED
#include <kernel.h>
#include <eathread/eathread_atomic.h>
EA::Thread::Futex::Futex()
: mSpinCount(EATHREAD_FUTEX_SPIN_COUNT)
{
}
EA::Thread::Futex::~Futex()
{
}
void EA::Thread::Futex::Lock()
{
Uint spinCount(mSpinCount);
while(--spinCount)
{
if(TryLock())
return;
}
mMutex.Lock();
}
void EA::Thread::Futex::Unlock()
{
mMutex.Unlock();
}
bool EA::Thread::Futex::TryLock()
{
if(mMutex.Lock(EA::Thread::kTimeoutImmediate) > 0) // This calls scePthreadMutexTrylock
return true;
return false;
}
int EA::Thread::Futex::Lock(const ThreadTime& timeoutAbsolute)
{
return mMutex.Lock(timeoutAbsolute);
}
int EA::Thread::Futex::GetLockCount() const
{
return mMutex.GetLockCount();
}
bool EA::Thread::Futex::HasLock() const
{
return mMutex.HasLock();
}
void EA::Thread::Futex::SetSpinCount(Uint spinCount)
{
mSpinCount = spinCount;
}
#elif defined(EA_PLATFORM_SONY) && EATHREAD_MANUAL_FUTEX_ENABLED
#include <kernel/semaphore.h>
#include <sceerror.h>
void EA::Thread::Futex::CreateFSemaphore()
{
// To consider: Copy the Futex name into this semaphore name.
int result = sceKernelCreateSema(&mSemaphore, "Futex", SCE_KERNEL_SEMA_ATTR_TH_FIFO, 0, 100000, NULL);
EA_UNUSED(result);
EAT_ASSERT(result == SCE_OK);
}
void EA::Thread::Futex::DestroyFSemaphore()
{
int result = sceKernelDeleteSema(mSemaphore);
EA_UNUSED(result);
EAT_ASSERT(result == SCE_OK);
}
void EA::Thread::Futex::SignalFSemaphore()
{
int result = sceKernelSignalSema(mSemaphore, 1);
EA_UNUSED(result);
EAT_ASSERT(result == SCE_OK);
}
void EA::Thread::Futex::WaitFSemaphore()
{
int result = sceKernelWaitSema(mSemaphore, 1, NULL);
EA_UNUSED(result);
EAT_ASSERT(result == SCE_OK);
}
bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime& timeoutAbsolute)
{
SceKernelUseconds timeoutRelativeUs = static_cast<SceKernelUseconds>(RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(timeoutRelativeUs < 1)
timeoutRelativeUs = 1;
return (sceKernelWaitSema(mSemaphore, 1, &timeoutRelativeUs) == SCE_OK);
}
#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && EATHREAD_MANUAL_FUTEX_ENABLED
#include <semaphore.h>
#include <errno.h>
void EA::Thread::Futex::CreateFSemaphore()
{
const int result = sem_init(&mSemaphore, 0, 0);
(void)result;
EAT_ASSERT(result != -1);
}
void EA::Thread::Futex::DestroyFSemaphore()
{
#if defined (__APPLE__)
sem_close(&mSemaphore);
#elif defined(EA_PLATFORM_ANDROID)
sem_destroy(&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(&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
}
void EA::Thread::Futex::SignalFSemaphore()
{
sem_post(&mSemaphore);
}
void EA::Thread::Futex::WaitFSemaphore()
{
// We don't have much choice but to retry interrupted waits,
// as there is no lock failure return value.
while((sem_wait(&mSemaphore) == -1) && (errno == EINTR))
continue;
}
bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime&)
{
WaitFSemaphore();
return true;
}
#elif defined(EA_PLATFORM_MICROSOFT) && !EA_USE_CPP11_CONCURRENCY && !EATHREAD_MANUAL_FUTEX_ENABLED
#pragma warning(push, 0)
#include <Windows.h>
#pragma warning(pop)
// Validate what we assume to be invariants.
EAT_COMPILETIME_ASSERT(sizeof(CRITICAL_SECTION) <= (EA::Thread::FUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t) * sizeof(uint64_t)));
#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64)
EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, RecursionCount) == (3 * sizeof(int)));
EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, OwningThread) == (4 * sizeof(int)));
#elif defined(EA_PLATFORM_WIN32)
EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, RecursionCount) == (2 * sizeof(int)));
EAT_COMPILETIME_ASSERT(offsetof(CRITICAL_SECTION, OwningThread) == (3 * sizeof(int)));
#else
EAT_FAIL_MSG("Need to verify offsetof.");
#endif
#elif defined(EA_PLATFORM_MICROSOFT) && EATHREAD_MANUAL_FUTEX_ENABLED
#if defined(EA_PLATFORM_WINDOWS)
#pragma warning(push, 0)
#include <Windows.h>
#pragma warning(pop)
#endif
void EA::Thread::Futex::CreateFSemaphore()
{
mSemaphore = CreateSemaphoreA(NULL, 0, INT_MAX / 2, NULL);
EAT_ASSERT(mSemaphore != 0);
}
void EA::Thread::Futex::DestroyFSemaphore()
{
if(mSemaphore)
CloseHandle(mSemaphore);
}
void EA::Thread::Futex::SignalFSemaphore()
{
ReleaseSemaphore(mSemaphore, 1, NULL);
}
void EA::Thread::Futex::WaitFSemaphore()
{
WaitForSingleObject(mSemaphore, INFINITE);
}
bool EA::Thread::Futex::WaitFSemaphore(const ThreadTime& timeoutAbsolute)
{
int64_t timeoutRelativeMS = (int64_t)(timeoutAbsolute - GetThreadTime());
if(timeoutRelativeMS < 1)
timeoutRelativeMS = 1;
return WaitForSingleObject(mSemaphore, (DWORD)timeoutRelativeMS) == WAIT_OBJECT_0;
}
#endif
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::Futex* EA::Thread::FutexFactory::CreateFutex()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::Futex))) EA::Thread::Futex;
else
return new EA::Thread::Futex;
}
void EA::Thread::FutexFactory::DestroyFutex(EA::Thread::Futex* pFutex)
{
if(gpAllocator)
{
pFutex->~Futex();
gpAllocator->Free(pFutex);
}
else
delete pFutex;
}
size_t EA::Thread::FutexFactory::GetFutexSize()
{
return sizeof(EA::Thread::Futex);
}
EA::Thread::Futex* EA::Thread::FutexFactory::ConstructFutex(void* pMemory)
{
return new(pMemory) EA::Thread::Futex;
}
void EA::Thread::FutexFactory::DestructFutex(EA::Thread::Futex* pFutex)
{
pFutex->~Futex();
}
@@ -0,0 +1,144 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/internal/config.h>
EA_DISABLE_VC_WARNING(4574)
#include <string.h>
#include <new>
EA_RESTORE_VC_WARNING()
#if !EA_THREADS_AVAILABLE
#include <eathread/eathread_mutex.h>
#elif EA_USE_CPP11_CONCURRENCY
#include "cpp11/eathread_mutex_cpp11.cpp"
#if defined(CreateMutex)
#undef CreateMutex
#endif
#elif defined(EA_PLATFORM_SONY)
#include "kettle/eathread_mutex_kettle.cpp"
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include "unix/eathread_mutex_unix.cpp"
#elif defined(EA_PLATFORM_MICROSOFT)
#include "pc/eathread_mutex_pc.cpp"
#endif
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::Mutex* EA::Thread::MutexFactory::CreateMutex()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::Mutex))) EA::Thread::Mutex;
else
return new EA::Thread::Mutex;
}
void EA::Thread::MutexFactory::DestroyMutex(EA::Thread::Mutex* pMutex)
{
if(gpAllocator)
{
pMutex->~Mutex();
gpAllocator->Free(pMutex);
}
else
delete pMutex;
}
size_t EA::Thread::MutexFactory::GetMutexSize()
{
return sizeof(EA::Thread::Mutex);
}
EA::Thread::Mutex* EA::Thread::MutexFactory::ConstructMutex(void* pMemory)
{
return new(pMemory) EA::Thread::Mutex;
}
void EA::Thread::MutexFactory::DestructMutex(EA::Thread::Mutex* pMutex)
{
pMutex->~Mutex();
}
///////////////////////////////////////////////////////////////////////////////
// non-threaded implementation
///////////////////////////////////////////////////////////////////////////////
#if defined(EA_THREAD_NONTHREADED_MUTEX) && EA_THREAD_NONTHREADED_MUTEX
EAMutexData::EAMutexData()
: mnLockCount(0)
{
// Empty
}
EA::Thread::MutexParameters::MutexParameters(bool /*bIntraProcess*/, const char* /*pName*/)
: mbIntraProcess(true)
{
}
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);
}
bool EA::Thread::Mutex::Init(const MutexParameters* /*pMutexParameters*/)
{
// Possibly copy pMutexParameters->mName to mMutexData.mName
return true;
}
int EA::Thread::Mutex::Lock(const ThreadTime& /*timeoutAbsolute*/)
{
EAT_ASSERT(mMutexData.mnLockCount < 100000);
return ++mMutexData.mnLockCount;
}
int EA::Thread::Mutex::Unlock()
{
EAT_ASSERT(mMutexData.mnLockCount > 0);
return --mMutexData.mnLockCount;
}
int EA::Thread::Mutex::GetLockCount() const
{
return mMutexData.mnLockCount;
}
bool EA::Thread::Mutex::HasLock() const
{
return (mMutexData.mnLockCount > 0);
}
#endif // EA_THREAD_NONTHREADED_MUTEX
@@ -0,0 +1,711 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/internal/config.h>
#include <eathread/eathread_pool.h>
#include <eathread/eathread_sync.h>
#include <string.h>
#include <new>
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 6011) // Dereferencing NULL pointer 'gpAllocator'
#pragma warning(disable: 6211) // Leaking memory 'pThreadInfo' due to an exception.
#pragma warning(disable: 6326) // Potential comparison of a constant with another constant
#endif
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::ThreadPoolParameters::ThreadPoolParameters()
: mnMinCount(EA::Thread::ThreadPool::kDefaultMinCount),
mnMaxCount(EA::Thread::ThreadPool::kDefaultMaxCount),
mnInitialCount(EA::Thread::ThreadPool::kDefaultInitialCount),
mnIdleTimeoutMilliseconds(EA::Thread::ThreadPool::kDefaultIdleTimeout), // This is a relative time, not an absolute time. Can be a millisecond value or Thread::kTimeoutNone or Thread::kTimeoutImmediate.
mnProcessorMask(0xffffffff),
mDefaultThreadParameters()
{
// Empty
}
EA::Thread::ThreadPool::Job::Job()
: mpRunnable(NULL), mpFunction(NULL), mpContext(NULL)
{
// Empty
}
EA::Thread::ThreadPool::ThreadInfo::ThreadInfo()
: mbActive(false),
mbQuit(false),
//mbPersistent(false),
mpThread(NULL),
mpThreadPool(NULL),
mCurrentJob()
{
// Empty
}
EA::Thread::ThreadPool::ThreadPool(const ThreadPoolParameters* pThreadPoolParameters, bool bDefaultParameters)
: mbInitialized(false),
mnMinCount(kDefaultMinCount),
mnMaxCount(kDefaultMaxCount),
mnCurrentCount(0),
mnActiveCount(0),
mnIdleTimeoutMilliseconds(kDefaultIdleTimeout),
mnProcessorMask((unsigned)kDefaultProcessorMask),
mnProcessorCount(0),
mnNextProcessor(0),
mnPauseCount(0),
mnLastJobID(0),
mDefaultThreadParameters(),
mThreadCondition(NULL, false), // Explicitly don't initialize.
mThreadMutex(NULL, false), // Explicitly don't initialize.
mThreadInfoList(),
mJobList()
{
if(!pThreadPoolParameters && bDefaultParameters)
{
ThreadPoolParameters parameters;
Init(&parameters);
}
else
Init(pThreadPoolParameters);
}
EA::Thread::ThreadPool::~ThreadPool()
{
Shutdown(kJobWaitAll, kTimeoutNone);
EAT_ASSERT(mJobList.empty() && mThreadInfoList.empty() && (mnCurrentCount == 0) && (mnActiveCount == 0) && (mThreadMutex.GetLockCount() == 0));
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4296 4706) // '>=' : expression is always true and assignment within conditional expression (in the assert)
#endif
#if EAT_ASSERT_ENABLED
template <class T>
inline bool EATIsUnsigned(T)
{ return (((T)(-1) >> 1) != (T)(-1)); }
#endif
// If mDefaultThreadParameters.mnProcessor is set to kThreadPoolParametersProcessorDefault,
// then the ThreadPool controls what processors the thread executes on. Otherwise ThreadPool
// doesn't set the thread affinity itself.
static const int kThreadPoolParametersProcessorDefault = -1;
bool EA::Thread::ThreadPool::Init(const ThreadPoolParameters* pThreadPoolParameters)
{
if(!mbInitialized)
{
if(pThreadPoolParameters && (mnCurrentCount == 0))
{
mbInitialized = true;
mnMinCount = pThreadPoolParameters->mnMinCount;
mnMaxCount = pThreadPoolParameters->mnMaxCount;
mnCurrentCount = (int)pThreadPoolParameters->mnInitialCount;
mnIdleTimeoutMilliseconds = pThreadPoolParameters->mnIdleTimeoutMilliseconds;
mnProcessorMask = pThreadPoolParameters->mnProcessorMask;
mDefaultThreadParameters = pThreadPoolParameters->mDefaultThreadParameters;
mnProcessorCount = (uint32_t)EA::Thread::GetProcessorCount(); // We currently assume this value is constant at runtime.
// Do bounds checking.
//if(mnMinCount < 0) // This check is unnecessary because mnMinCount is of an
// mnMinCount = 0; // unsigned data type. We assert for this unsigned-ness below.
EAT_ASSERT(EATIsUnsigned(mnMinCount));
if(mnMaxCount > EA_THREAD_POOL_MAX_SIZE)
mnMaxCount = EA_THREAD_POOL_MAX_SIZE;
if(mnCurrentCount < (int)mnMinCount)
mnCurrentCount = (int)mnMinCount;
if(mnCurrentCount > (int)mnMaxCount)
mnCurrentCount = (int)mnMaxCount;
// Make sure the processor mask refers to existing processors.
const int processorMask = (1 << mnProcessorCount) - 1; // So for a processor count of 8 we have a mask of 11111111 (255)
if((mnProcessorMask & processorMask) == 0)
mnProcessorMask = 0xffffffff;
mDefaultThreadParameters.mpStack = NULL; // You can't specify a default stack location, as every thread needs a unique one.
if(mDefaultThreadParameters.mnProcessor != EA::Thread::kProcessorAny) // If the user hasn't set threads to execute on any processor chosen by the OS...
mDefaultThreadParameters.mnProcessor = kThreadPoolParametersProcessorDefault; // then use our default processing, which is for us to currently round-robin the processor used.
ConditionParameters mnp;
mThreadCondition.Init(&mnp);
MutexParameters mtp;
mThreadMutex.Init(&mtp);
mThreadMutex.Lock();
const int nDesiredCount((int)mnCurrentCount);
mnCurrentCount = 0;
AdjustThreadCount((unsigned int)nDesiredCount);
mThreadMutex.Unlock();
return true;
}
}
return false;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
bool EA::Thread::ThreadPool::Shutdown(JobWait jobWait, const ThreadTime& timeoutAbsolute)
{
int nResult;
if(mbInitialized)
{
mbInitialized = false;
nResult = WaitForJobCompletion(-1, jobWait, timeoutAbsolute);
mThreadMutex.Lock();
// If jobWait is kJobWaitNone, then we nuke all existing jobs.
if(jobWait == kJobWaitNone)
mJobList.clear();
// Leave a message to tell the thread to quit.
for(ThreadInfoList::iterator it(mThreadInfoList.begin()), itEnd(mThreadInfoList.end()); it != itEnd; )
{
ThreadInfo* const pThreadInfo = *it;
pThreadInfo->mbQuit = true;
//pThreadInfo->mbPersistent = false;
// If somehow the thread isn't running (possibly because it never started), manually remove it.
if(pThreadInfo->mpThread->GetStatus() != EA::Thread::Thread::kStatusRunning)
it = mThreadInfoList.erase(it);
else
++it;
}
// Wake up any threads that may be blocked on a condition variable wait.
mThreadCondition.Signal(true);
// Make sure we unlock after we signal, lest there be a certain kind of race condition.
mThreadMutex.Unlock();
// Wait for any existing threads to quit.
// Todo: Replace this poor polling loop with Thread::Wait calls.
// Doing so requires a little finessing with the thread
// objects in the list. Possibly make ThreadInfo ref-counted.
while(!mThreadInfoList.empty())
{
ThreadSleep(1);
EAReadBarrier();
}
mThreadMutex.Lock();
mnPauseCount = 0;
mThreadMutex.Unlock();
}
else
nResult = kResultOK;
return (nResult == kResultOK);
}
intptr_t EA::Thread::ThreadPool::ThreadFunction(void* pContext)
{
ThreadInfo* const pThreadInfo = reinterpret_cast<ThreadInfo*>(pContext);
ThreadPool* const pThreadPool = pThreadInfo->mpThreadPool;
Condition* const pCondition = &pThreadPool->mThreadCondition;
Mutex* const pMutex = &pThreadPool->mThreadMutex;
pMutex->Lock();
while(!pThreadInfo->mbQuit)
{
if(!pThreadPool->mJobList.empty())
{
pThreadInfo->mCurrentJob = pThreadPool->mJobList.front();
pThreadPool->mJobList.pop_front();
pThreadInfo->mbActive = true;
++pThreadPool->mnActiveCount; // Atomic integer operation.
pMutex->Unlock();
// Do the job here. It's important that we keep the mutex unlocked while doing the job.
if(pThreadInfo->mCurrentJob.mpRunnable)
pThreadInfo->mCurrentJob.mpRunnable->Run(pThreadInfo->mCurrentJob.mpContext);
else if(pThreadInfo->mCurrentJob.mpFunction)
pThreadInfo->mCurrentJob.mpFunction(pThreadInfo->mCurrentJob.mpContext);
else
pThreadInfo->mbQuit = true; // Tell ourself to quit.
// Problem: We are not paying attention to the pThreadInfo->mbPersistent variable.
// We don't have an easy way of dealing with it because we don't have a means for
// the ThreadPool to direct quit commands to individual threads. For now we don't
// pay attention to mbPersistent and require that persistence be controlled by
// the min/max thread count settings.
pMutex->Lock();
--pThreadPool->mnActiveCount; // Atomic integer operation.
pThreadInfo->mbActive = false;
}
else
{
// The wait call here will unlock the condition variable and will re-lock it upon return.
EA::Thread::ThreadTime timeoutAbsolute = (GetThreadTime() + pThreadPool->mnIdleTimeoutMilliseconds);
if (pThreadPool->mnIdleTimeoutMilliseconds == kTimeoutNone)
timeoutAbsolute = kTimeoutNone;
else if(pThreadPool->mnIdleTimeoutMilliseconds == kTimeoutImmediate)
timeoutAbsolute = kTimeoutImmediate;
else if(timeoutAbsolute == kTimeoutNone) // If it coincidentally is the magic kTimeoutNone value...
timeoutAbsolute -= 1;
const Condition::Result result = pCondition->Wait(pMutex, timeoutAbsolute);
if(result != Condition::kResultOK) // If result is an error then what do we do? Is there a
pThreadInfo->mbQuit = true; // specific reason to quit? There's no good solution here,
} // but on the other hand this should never happen in practice.
}
pThreadPool->RemoveThread(pThreadInfo);
pMutex->Unlock();
return 0;
}
EA::Thread::ThreadPool::Result EA::Thread::ThreadPool::QueueJob(const Job& job, Thread** ppThread, bool /*bEnableDeferred*/)
{
if(mbInitialized){
mThreadMutex.Lock();
// If there are other threads busy with jobs or other threads soon to be busy with jobs and if the thread count is less than the maximum allowable, bump up the thread count by one.
EAT_ASSERT(mnActiveCount <= mnCurrentCount);
if((((int)mnActiveCount >= mnCurrentCount) || !mJobList.empty()) && (mnCurrentCount < (int)mnMaxCount))
AdjustThreadCount((unsigned)(mnCurrentCount + 1));
mJobList.push_back(job);
FixThreads();
if(mnPauseCount == 0)
mThreadCondition.Signal(false); // Wake up one thread to work on this.
mThreadMutex.Unlock();
if(ppThread){
// In this case the caller wants to know what thread got the job.
// So we wait until we know what caller got the job.
// Todo: Complete this.
*ppThread = NULL;
}
return kResultDeferred;
}
return kResultError;
}
int EA::Thread::ThreadPool::Begin(IRunnable* pRunnable, void* pContext, Thread** ppThread, bool bEnableDeferred)
{
Job job;
job.mnJobID = mnLastJobID.Increment();
job.mpRunnable = pRunnable;
job.mpFunction = NULL;
job.mpContext = pContext;
if(QueueJob(job, ppThread, bEnableDeferred) != kResultError)
return job.mnJobID;
return kResultError;
}
int EA::Thread::ThreadPool::Begin(RunnableFunction pFunction, void* pContext, Thread** ppThread, bool bEnableDeferred)
{
Job job;
job.mnJobID = mnLastJobID.Increment();
job.mpRunnable = NULL;
job.mpFunction = pFunction;
job.mpContext = pContext;
if(QueueJob(job, ppThread, bEnableDeferred) != kResultError)
return job.mnJobID;
return kResultError;
}
int EA::Thread::ThreadPool::WaitForJobCompletion(int nJob, JobWait jobWait, const ThreadTime& timeoutAbsolute)
{
int nResult = kResultError;
if(nJob == -1){
// We have a problem here in that we need to wait for all threads to finish
// but the only way to wait for them to finish is to use the Thread::WaitForEnd
// function. But when the thread exits, it destroys the Thread object rendering
// it unsafe for us to use that object in any safe way here. We can rearrange
// things to allow this to work more cleanly, but in the meantime we spin and
// sleep, which is not a good solution if the worker threads are of a lower
// priority than this sleeping thread, as this thread will steal their time.
if(jobWait == kJobWaitNone){
// Do nothing.
nResult = kResultOK;
}
else if(jobWait == kJobWaitCurrent){
// Wait for currently running jobs to complete.
while((mnActiveCount != 0) && (GetThreadTime() < timeoutAbsolute))
ThreadSleep(10);
if(mnActiveCount == 0)
nResult = kResultOK;
else
nResult = kResultTimeout;
}
else{ // jobWait == kJobWaitAll
// Wait for all current and queued jobs to complete.
bool shouldContinue = true;
while(shouldContinue)
{
mThreadMutex.Lock();
shouldContinue = (((mnActiveCount != 0) || !mJobList.empty()) && (GetThreadTime() < timeoutAbsolute));
mThreadMutex.Unlock();
if(shouldContinue)
ThreadSleep(10);
}
mThreadMutex.Lock();
if((mnActiveCount == 0) && mJobList.empty())
nResult = kResultOK;
else
nResult = kResultTimeout;
mThreadMutex.Unlock();
}
}
else{
// Like above we do the wait via polling. Ideally we want to set up a
// mechanism whereby we sleep until an alarm wakes us. This can perhaps
// be done by setting a flag in the job which causes the job to signal
// the alarm when complete. In the meantime we will follow the simpler
// behaviour we have here.
bool bJobExists;
for(;;){
bJobExists = false;
mThreadMutex.Lock();
// Search the list of jobs yet to become active to see if the job exists in there.
for(JobList::iterator it(mJobList.begin()); it != mJobList.end(); ++it){
const Job& job = *it;
if(job.mnJobID == nJob){ // If the user's job was found...
bJobExists = true;
nResult = kResultTimeout;
}
}
// Search the list of jobs actively executing as well.
for(ThreadInfoList::iterator it(mThreadInfoList.begin()); it != mThreadInfoList.end(); ++it){
const ThreadInfo* const pThreadInfo = *it;
const Job& job = pThreadInfo->mCurrentJob;
// Note the thread must be active for the Job assigned to it be valid.
if(pThreadInfo->mbActive && job.mnJobID == nJob){ // If the user's job was found...
bJobExists = true;
nResult = kResultTimeout;
}
}
mThreadMutex.Unlock();
if(!bJobExists || (GetThreadTime() >= timeoutAbsolute))
break;
ThreadSleep(10);
}
if(!bJobExists)
nResult = kResultOK;
}
return nResult;
}
void EA::Thread::ThreadPool::Pause(bool bPause)
{
if(bPause)
++mnPauseCount;
else{
if(mnPauseCount.Decrement() == 0){
mThreadMutex.Lock();
if(!mJobList.empty())
mThreadCondition.Signal(true);
mThreadMutex.Unlock();
}
}
}
void EA::Thread::ThreadPool::Lock()
{
mThreadMutex.Lock();
}
void EA::Thread::ThreadPool::Unlock()
{
mThreadMutex.Unlock();
}
void EA::Thread::ThreadPool::SetupThreadParameters(EA::Thread::ThreadParameters& tp)
{
if(tp.mnProcessor == kThreadPoolParametersProcessorDefault) // If we are to manipulate tp.mnProcessor...
{
if(mnProcessorMask != 0xffffffff) // If we are not using the default...
{
// We round-robin mnNextProcessor within our mnProcessorMask.
while(((1 << mnNextProcessor) & mnProcessorMask) == 0)
++mnNextProcessor;
mnNextProcessor %= mnProcessorCount;
tp.mnProcessor = (int)mnNextProcessor++;
}
}
}
EA::Thread::ThreadPool::ThreadInfo* EA::Thread::ThreadPool::AddThread(const EA::Thread::ThreadParameters& tp, bool bBeginThread)
{
ThreadInfo* const pThreadInfo = CreateThreadInfo();
EAT_ASSERT(pThreadInfo != NULL);
if(pThreadInfo)
{
AddThread(pThreadInfo);
if(bBeginThread)
{
ThreadParameters tpUsed(tp);
SetupThreadParameters(tpUsed); // This function sets tpUsed.mnProcessor
pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &tpUsed);
}
}
return pThreadInfo;
}
// Gets the ThreadInfo for the nth Thread identified by index.
// You must call this function within a Lock/Unlock pair on the thread pool.
EA::Thread::ThreadPool::ThreadInfo* EA::Thread::ThreadPool::GetThreadInfo(int index)
{
EA::Thread::AutoMutex autoMutex(mThreadMutex);
int i = 0;
for(ThreadInfoList::iterator it = mThreadInfoList.begin(); it != mThreadInfoList.end(); ++it)
{
if(i == index)
{
ThreadInfo* pThreadInfo = *it;
return pThreadInfo;
}
++i;
}
return NULL;
}
// Unless you call this function while the Pool is locked (via Lock), the return
// value may be out of date by the time you read it.
int EA::Thread::ThreadPool::GetThreadCount()
{
EA::Thread::AutoMutex autoMutex(mThreadMutex);
return (int)mThreadInfoList.size();
}
EA::Thread::ThreadPool::ThreadInfo* EA::Thread::ThreadPool::CreateThreadInfo()
{
// Currently we assume that allocation never fails.
ThreadInfo* const pThreadInfo = gpAllocator ? new(gpAllocator->Alloc(sizeof(ThreadInfo))) ThreadInfo : new ThreadInfo;
if(pThreadInfo)
{
pThreadInfo->mbActive = false;
pThreadInfo->mbQuit = false;
pThreadInfo->mpThreadPool = this;
pThreadInfo->mpThread = gpAllocator ? new(gpAllocator->Alloc(sizeof(Thread))) Thread : new Thread;
}
return pThreadInfo;
}
void EA::Thread::ThreadPool::AdjustThreadCount(unsigned nDesiredCount)
{
// This function doesn't read mnMinCount/mnMaxCount, as it expects the caller to do so.
// Assumes that condition variable is locked.
int nAdjustment = (int)(nDesiredCount - mnCurrentCount);
while(nAdjustment > 0) // If we are to create threads...
{
ThreadInfo* const pThreadInfo = CreateThreadInfo();
EAT_ASSERT(pThreadInfo != NULL);
AddThread(pThreadInfo);
ThreadParameters tpUsed(mDefaultThreadParameters);
SetupThreadParameters(tpUsed); // This function sets tpUsed.mnProcessor
pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &tpUsed);
nAdjustment--;
}
while(nAdjustment < 0) // If we are to quit threads...
{
// An empty job is a signal for a thread to quit.
QueueJob(Job(), NULL, true);
nAdjustment++;
}
FixThreads(); // Makes sure that mnCurrentCount really does match the number of threads waiting for work.
}
void EA::Thread::ThreadPool::AddThread(ThreadInfo* pThreadInfo)
{
// Assumes that condition variable is locked.
mThreadInfoList.push_back(pThreadInfo);
++mnCurrentCount;
}
void EA::Thread::ThreadPool::RemoveThread(ThreadInfo* pThreadInfo)
{
// Assumes that condition variable is locked.
ThreadInfoList::iterator it = mThreadInfoList.find(pThreadInfo);
EAT_ASSERT(it != mThreadInfoList.end());
if(it != mThreadInfoList.end())
{
if(gpAllocator)
{
pThreadInfo->mpThread->~Thread();
gpAllocator->Free(pThreadInfo->mpThread);
}
else
delete pThreadInfo->mpThread;
pThreadInfo->mpThread = NULL;
mThreadInfoList.erase(it);
if(gpAllocator)
{
pThreadInfo->~ThreadInfo();
gpAllocator->Free(pThreadInfo);
}
else
delete pThreadInfo;
--mnCurrentCount;
}
}
// FixThreads
// We have a small in problem in that the system allows threads to explicitly exit at any time without
// returning to the caller. Many operating systems with thread support don't have a mechanism to enable
// you to tell you via a callback when a thread has exited. Due to this latter problem, it is possible
// that threads could exit without us ever finding out about it. So we poll the threads to catch up
// to their state in such cases here.
void EA::Thread::ThreadPool::FixThreads()
{
// Assumes that condition variable is locked.
for(ThreadInfoList::iterator it(mThreadInfoList.begin()), itEnd(mThreadInfoList.end()); it != itEnd; ++it)
{
ThreadInfo* const pThreadInfo = *it;
// Fix any threads which have exited via a thread exit and not by simply returning to the caller.
const EA::Thread::Thread::Status status = pThreadInfo->mpThread->GetStatus();
if(status == EA::Thread::Thread::kStatusEnded)
pThreadInfo->mpThread->Begin(ThreadFunction, pThreadInfo, &mDefaultThreadParameters);
}
}
EA::Thread::ThreadPool* EA::Thread::ThreadPoolFactory::CreateThreadPool()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::ThreadPool))) EA::Thread::ThreadPool;
else
return new EA::Thread::ThreadPool;
}
void EA::Thread::ThreadPoolFactory::DestroyThreadPool(EA::Thread::ThreadPool* pThreadPool)
{
if(gpAllocator)
{
pThreadPool->~ThreadPool();
gpAllocator->Free(pThreadPool);
}
else
delete pThreadPool;
}
size_t EA::Thread::ThreadPoolFactory::GetThreadPoolSize()
{
return sizeof(EA::Thread::ThreadPool);
}
EA::Thread::ThreadPool* EA::Thread::ThreadPoolFactory::ConstructThreadPool(void* pMemory)
{
return new(pMemory) EA::Thread::ThreadPool;
}
void EA::Thread::ThreadPoolFactory::DestructThreadPool(EA::Thread::ThreadPool* pThreadPool)
{
pThreadPool->~ThreadPool();
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
@@ -0,0 +1,263 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#if defined(_MSC_VER)
#pragma warning(disable: 4985) // 'ceil': attributes not present on previous declaration.1> C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\INCLUDE\intrin.h(142) : see declaration of 'ceil'
#endif
#include <eathread/internal/config.h>
#include <eathread/eathread_rwmutex.h>
#include <eathread/eathread.h>
#include <new> // include new for placement new operator
#include <string.h>
#ifdef _MSC_VER
#pragma warning(disable : 4996) // This function or variable may be unsafe / deprecated.
#endif
EARWMutexData::EARWMutexData()
: mnReadWaiters(0),
mnWriteWaiters(0),
mnReaders(0),
mThreadIdWriter(EA::Thread::kThreadIdInvalid),
mMutex(NULL, false),
mReadCondition(NULL, false),
mWriteCondition(NULL, false)
{
// Empty
}
EA::Thread::RWMutexParameters::RWMutexParameters(bool bIntraProcess, const char* pName)
: mbIntraProcess(bIntraProcess)
{
(void)pName; // Suppress possible warnings.
#ifdef EA_PLATFORM_WINDOWS
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
#endif
}
EA::Thread::RWMutex::RWMutex(const RWMutexParameters* pRWMutexParameters, bool bDefaultParameters)
{
if(!pRWMutexParameters && bDefaultParameters)
{
RWMutexParameters parameters;
Init(&parameters);
}
else
Init(pRWMutexParameters);
}
EA::Thread::RWMutex::~RWMutex()
{
// Possibly do asserts here.
}
bool EA::Thread::RWMutex::Init(const RWMutexParameters* pRWMutexParameters)
{
if(pRWMutexParameters)
{
#if EATHREAD_MULTIPROCESSING_OS
EAT_ASSERT(pRWMutexParameters->mbIntraProcess); // We don't currently have support for intra-process RWMutex on these platforms (and any multi-process platform).
#endif
MutexParameters mup(pRWMutexParameters->mbIntraProcess);
mRWMutexData.mMutex.Init(&mup);
ConditionParameters mop(pRWMutexParameters->mbIntraProcess);
mRWMutexData.mReadCondition.Init(&mop);
mRWMutexData.mWriteCondition.Init(&mop);
return true;
}
return false;
}
int EA::Thread::RWMutex::Lock(LockType lockType, const ThreadTime& timeoutAbsolute)
{
int result = 0;
mRWMutexData.mMutex.Lock(); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily.
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
// We cannot obtain a write lock recursively, else we will deadlock.
// Alternatively, we can build a bunch of extra logic to deal with this.
EAT_ASSERT(mRWMutexData.mThreadIdWriter != GetThreadId());
// Assert that there aren't both readers and writers at the same time.
EAT_ASSERT(!((mRWMutexData.mThreadIdWriter != kThreadIdInvalid) && mRWMutexData.mnReaders));
if(lockType == kLockTypeRead)
{
while(mRWMutexData.mThreadIdWriter != kThreadIdInvalid)
{
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
mRWMutexData.mnReadWaiters++;
const Condition::Result mresult = mRWMutexData.mReadCondition.Wait(&mRWMutexData.mMutex, timeoutAbsolute);
mRWMutexData.mnReadWaiters--;
EAT_ASSERT(mresult != EA::Thread::Condition::kResultError);
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
if(mresult == Condition::kResultTimeout)
{
mRWMutexData.mMutex.Unlock();
return kResultTimeout;
}
}
result = ++mRWMutexData.mnReaders; // This is not an atomic operation. We are within a mutex lock.
}
else if(lockType == kLockTypeWrite)
{
while((mRWMutexData.mnReaders > 0) || (mRWMutexData.mThreadIdWriter != kThreadIdInvalid))
{
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
mRWMutexData.mnWriteWaiters++;
const Condition::Result mresult = mRWMutexData.mWriteCondition.Wait(&mRWMutexData.mMutex, timeoutAbsolute);
mRWMutexData.mnWriteWaiters--;
EAT_ASSERT(mresult != EA::Thread::Condition::kResultError);
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
if(mresult == Condition::kResultTimeout)
{
mRWMutexData.mMutex.Unlock();
return kResultTimeout;
}
}
result = 1;
mRWMutexData.mThreadIdWriter = GetThreadId();
}
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
mRWMutexData.mMutex.Unlock();
return result;
}
int EA::Thread::RWMutex::Unlock()
{
mRWMutexData.mMutex.Lock(); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily.
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
if(mRWMutexData.mThreadIdWriter != kThreadIdInvalid)
{
EAT_ASSERT(mRWMutexData.mThreadIdWriter == GetThreadId());
//Possibly enable this if we want some runtime error checking at some cost.
//if(mRWMutexData.mThreadIdWriter == GetThreadId()){
// mRWMutexData.mMutex.Unlock();
// return kResultError;
//}
mRWMutexData.mThreadIdWriter = kThreadIdInvalid;
}
else
{
EAT_ASSERT(mRWMutexData.mnReaders >= 1);
//Possibly enable this if we want some runtime error checking at some cost.
//if(mRWMutexData.mnReaders < 1){
// mRWMutexData.mMutex.Unlock();
// return kResultError;
//}
const int nNewReaders = --mRWMutexData.mnReaders; // This is not an atomic operation. We are within a mutex lock.
if(nNewReaders > 0)
{
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
mRWMutexData.mMutex.Unlock();
return nNewReaders;
}
}
if(mRWMutexData.mnWriteWaiters > 0)
mRWMutexData.mWriteCondition.Signal(false);
else if(mRWMutexData.mnReadWaiters > 0)
mRWMutexData.mReadCondition.Signal(true);
EAT_ASSERT(mRWMutexData.mMutex.GetLockCount() == 1);
mRWMutexData.mMutex.Unlock();
return 0;
}
int EA::Thread::RWMutex::GetLockCount(LockType lockType)
{
if(lockType == kLockTypeRead)
return mRWMutexData.mnReaders;
else if((lockType == kLockTypeWrite) && (mRWMutexData.mThreadIdWriter != kThreadIdInvalid))
return 1;
return 0;
}
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::RWMutex* EA::Thread::RWMutexFactory::CreateRWMutex()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::RWMutex))) EA::Thread::RWMutex;
else
return new EA::Thread::RWMutex;
}
void EA::Thread::RWMutexFactory::DestroyRWMutex(EA::Thread::RWMutex* pRWMutex)
{
if(gpAllocator)
{
pRWMutex->~RWMutex();
gpAllocator->Free(pRWMutex);
}
else
delete pRWMutex;
}
size_t EA::Thread::RWMutexFactory::GetRWMutexSize()
{
return sizeof(EA::Thread::RWMutex);
}
EA::Thread::RWMutex* EA::Thread::RWMutexFactory::ConstructRWMutex(void* pMemory)
{
return new(pMemory) EA::Thread::RWMutex;
}
void EA::Thread::RWMutexFactory::DestructRWMutex(EA::Thread::RWMutex* pRWMutex)
{
pRWMutex->~RWMutex();
}
@@ -0,0 +1,361 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/internal/config.h>
#include <eathread/eathread_rwmutex_ip.h>
#include <new> // include new for placement new operator
#include <string.h>
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
///////////////////////////////////////////////////////////////////////////
// EARWMutexIPData
///////////////////////////////////////////////////////////////////////////
EA::Thread::EARWMutexIPData::EARWMutexIPData()
: mSharedData(), // This still needs to be Init-ed.
mMutex(NULL),
mReadSemaphore(NULL),
mWriteSemaphore(NULL)
{
}
EA::Thread::EARWMutexIPData::~EARWMutexIPData()
{
// mSharedData.Shutdown(); // This shouldn't be necessary, as the SharedData dtor will do this itself.
}
bool EA::Thread::EARWMutexIPData::Init(const char* pName)
{
char mutexName[256];
mutexName[0] = '\0';
if(pName)
strcpy(mutexName, pName);
strcat(mutexName, ".Mutex");
mMutex = CreateMutexA(NULL, FALSE, mutexName);
char readSemaphoreName[256];
readSemaphoreName[0] = '\0';
if(pName)
strcpy(readSemaphoreName, pName);
strcat(readSemaphoreName, ".SemR");
mReadSemaphore = CreateSemaphoreA(NULL, 0, 9999, readSemaphoreName);
char writeSemaphoreName[256];
writeSemaphoreName[0] = '\0';
if(pName)
strcpy(writeSemaphoreName, pName);
strcat(writeSemaphoreName, ".SemW");
mWriteSemaphore = CreateSemaphoreA(NULL, 0, 9999, writeSemaphoreName);
return mSharedData.Init(pName);
}
void EA::Thread::EARWMutexIPData::Shutdown()
{
if(mMutex)
{
CloseHandle(mMutex);
mMutex = NULL;
}
if(mReadSemaphore)
{
CloseHandle(mReadSemaphore);
mReadSemaphore = NULL;
}
if(mWriteSemaphore)
{
CloseHandle(mWriteSemaphore);
mWriteSemaphore = NULL;
}
mSharedData.Shutdown();
}
///////////////////////////////////////////////////////////////////////////
// RWMutexIPParameters
///////////////////////////////////////////////////////////////////////////
EA::Thread::RWMutexIPParameters::RWMutexIPParameters(bool bIntraProcess, const char* pName)
: mbIntraProcess(bIntraProcess)
{
#ifdef EA_PLATFORM_WINDOWS
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
#else
(void)pName; // Suppress possible warnings.
#endif
}
///////////////////////////////////////////////////////////////////////////
// RWMutexIP
///////////////////////////////////////////////////////////////////////////
EA::Thread::RWMutexIP::RWMutexIP(const RWMutexIPParameters* pRWMutexIPParameters, bool bDefaultParameters)
{
if(!pRWMutexIPParameters && bDefaultParameters)
{
RWMutexIPParameters parameters;
Init(&parameters);
}
else
Init(pRWMutexIPParameters);
}
EA::Thread::RWMutexIP::~RWMutexIP()
{
}
bool EA::Thread::RWMutexIP::Init(const RWMutexIPParameters* pRWMutexIPParameters)
{
if(pRWMutexIPParameters)
{
// Must provide a valid name for inter-process RWMutex.
EAT_ASSERT(pRWMutexIPParameters->mbIntraProcess || pRWMutexIPParameters->mName[0]);
return mRWMutexIPData.Init(pRWMutexIPParameters->mName);
}
return false;
}
int EA::Thread::RWMutexIP::Lock(LockType lockType, const ThreadTime& /*timeoutAbsolute*/)
{
int result = 0;
WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily.
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
// We cannot obtain a write lock recursively, else we will deadlock.
// Alternatively, we can build a bunch of extra logic to deal with this.
EAT_ASSERT(mRWMutexIPData.mSharedData->mThreadIdWriter != ::GetCurrentThreadId());
// Assert that there aren't both readers and writers at the same time.
EAT_ASSERT(!((mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid) && mRWMutexIPData.mSharedData->mnReaders));
if(lockType == kLockTypeRead)
{
while(mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid)
{
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
mRWMutexIPData.mSharedData->mnReadWaiters++;
ReleaseMutex(mRWMutexIPData.mMutex);
DWORD dwResult = WaitForSingleObject(mRWMutexIPData.mReadSemaphore, INFINITE); // To do: support timeoutAbsolute
WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE);
mRWMutexIPData.mSharedData->mnReadWaiters--;
EAT_ASSERT(dwResult != WAIT_FAILED);
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
if(dwResult == WAIT_TIMEOUT)
{
ReleaseMutex(mRWMutexIPData.mMutex);
return kResultTimeout;
}
}
result = ++mRWMutexIPData.mSharedData->mnReaders; // This is not an atomic operation. We are within a mutex lock.
}
else if(lockType == kLockTypeWrite)
{
while((mRWMutexIPData.mSharedData->mnReaders > 0) || // While somebody has the read lock or
(mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid)) // somebody has the write lock... go back to waiting.
{
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
mRWMutexIPData.mSharedData->mnWriteWaiters++;
ReleaseMutex(mRWMutexIPData.mMutex);
DWORD dwResult = WaitForSingleObject(mRWMutexIPData.mWriteSemaphore, INFINITE); // To do: support timeoutAbsolute
WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE);
mRWMutexIPData.mSharedData->mnWriteWaiters--;
EAT_ASSERT(dwResult != WAIT_FAILED);
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
if(dwResult == WAIT_TIMEOUT)
{
ReleaseMutex(mRWMutexIPData.mMutex);
return kResultTimeout;
}
}
result = 1;
mRWMutexIPData.mSharedData->mThreadIdWriter = ::GetCurrentThreadId();
}
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
ReleaseMutex(mRWMutexIPData.mMutex);
return result;
}
int EA::Thread::RWMutexIP::Unlock()
{
WaitForSingleObject(mRWMutexIPData.mMutex, INFINITE); // This lock should always be fast, as it belongs to us and we only hold onto it very temporarily.
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
if(mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid) // If we have a write lock...
{
EAT_ASSERT(mRWMutexIPData.mSharedData->mThreadIdWriter == ::GetCurrentThreadId());
mRWMutexIPData.mSharedData->mThreadIdWriter = kSysThreadIdInvalid;
}
else // Else we have a read lock...
{
EAT_ASSERT(mRWMutexIPData.mSharedData->mnReaders >= 1);
const int nNewReaders = --mRWMutexIPData.mSharedData->mnReaders; // This is not an atomic operation. We are within a mutex lock.
if(nNewReaders > 0)
{
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
ReleaseMutex(mRWMutexIPData.mMutex);
return nNewReaders;
}
}
if(mRWMutexIPData.mSharedData->mnWriteWaiters > 0) // We ignore the possibility that
{
ReleaseSemaphore(mRWMutexIPData.mWriteSemaphore, 1, NULL);
// We rely on the released write waiter to decrement mnWriteWaiters.
// If the released write waiter doesn't wake up for a while, it's possible that the ReleaseMutex below
// will be called and another read unlocker will execute this code and release the semaphore again and
// we will have two writers that are released. But this isn't a problem because the released writers
// must still lock our mMutex and contend for the write lock, and one of the two will fail and go back
// to waiting on the semaphore.
}
else if(mRWMutexIPData.mSharedData->mnReadWaiters > 0)
{
// I'm a little concerned about this signal here. We release mnReadWaiters, though it's possible
// that a reader could timeout before this function completes and not all the semaphore count
// will be claimed by waiters. However, the read wait code in the Lock function above does
// seem to be able to handle this case, as it does do a check to make sure it can hold the read
// lock before it claims it.
ReleaseSemaphore(mRWMutexIPData.mReadSemaphore, mRWMutexIPData.mSharedData->mnReadWaiters, NULL);
}
//EAT_ASSERT(mRWMutexIPData.mMutex.GetLockCount() == 1);
ReleaseMutex(mRWMutexIPData.mMutex);
return 0;
}
int EA::Thread::RWMutexIP::GetLockCount(LockType lockType)
{
if(lockType == kLockTypeRead)
return mRWMutexIPData.mSharedData->mnReaders;
else if((lockType == kLockTypeWrite) && (mRWMutexIPData.mSharedData->mThreadIdWriter != kSysThreadIdInvalid))
return 1;
return 0;
}
#else
EA::Thread::RWMutexIPParameters::RWMutexIPParameters(bool /*bIntraProcess*/, const char* /*pName*/)
{
}
EA::Thread::RWMutexIP::RWMutexIP(const RWMutexIPParameters* /*pRWMutexIPParameters*/, bool /*bDefaultParameters*/)
{
}
EA::Thread::RWMutexIP::~RWMutexIP()
{
}
bool EA::Thread::RWMutexIP::Init(const RWMutexIPParameters* /*pRWMutexIPParameters*/)
{
return false;
}
int EA::Thread::RWMutexIP::Lock(LockType /*lockType*/, const ThreadTime& /*timeoutAbsolute*/)
{
return 0;
}
int EA::Thread::RWMutexIP::Unlock()
{
return 0;
}
int EA::Thread::RWMutexIP::GetLockCount(LockType /*lockType*/)
{
return 0;
}
#endif // EA_PLATFORM_XXX
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::RWMutexIP* EA::Thread::RWMutexIPFactory::CreateRWMutexIP()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::RWMutexIP))) EA::Thread::RWMutexIP;
else
return new EA::Thread::RWMutexIP;
}
void EA::Thread::RWMutexIPFactory::DestroyRWMutexIP(EA::Thread::RWMutexIP* pRWMutexIP)
{
if(gpAllocator)
{
pRWMutexIP->~RWMutexIP();
gpAllocator->Free(pRWMutexIP);
}
else
delete pRWMutexIP;
}
size_t EA::Thread::RWMutexIPFactory::GetRWMutexIPSize()
{
return sizeof(EA::Thread::RWMutexIP);
}
EA::Thread::RWMutexIP* EA::Thread::RWMutexIPFactory::ConstructRWMutexIP(void* pMemory)
{
return new(pMemory) EA::Thread::RWMutexIP;
}
void EA::Thread::RWMutexIPFactory::DestructRWMutexIP(EA::Thread::RWMutexIP* pRWMutexIP)
{
pRWMutexIP->~RWMutexIP();
}
@@ -0,0 +1,351 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/internal/config.h>
#include <eathread/eathread_semaphore.h>
EA_DISABLE_VC_WARNING(4574)
#include <string.h>
#include <new>
EA_RESTORE_VC_WARNING()
#if !EA_THREADS_AVAILABLE
#include <eathread/eathread_semaphore.h>
#elif EATHREAD_USE_SYNTHESIZED_SEMAPHORE
// Fall through.
#elif 0 //EA_USE_CPP11_CONCURRENCY
#include "cpp11/eathread_semaphore_cpp11.cpp"
#elif defined(__APPLE__)
#include "apple/eathread_semaphore_apple.cpp"
#elif defined(EA_PLATFORM_ANDROID)
#include "android/eathread_semaphore_android.cpp"
#elif defined(EA_PLATFORM_SONY)
#include "kettle/eathread_semaphore_kettle.cpp"
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include "unix/eathread_semaphore_unix.cpp"
#elif defined(EA_PLATFORM_MICROSOFT)
#include "pc/eathread_semaphore_pc.cpp"
#endif
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::Semaphore* EA::Thread::SemaphoreFactory::CreateSemaphore()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::Semaphore))) EA::Thread::Semaphore;
else
return new EA::Thread::Semaphore;
}
void EA::Thread::SemaphoreFactory::DestroySemaphore(EA::Thread::Semaphore* pSemaphore)
{
if(gpAllocator)
{
pSemaphore->~Semaphore();
gpAllocator->Free(pSemaphore);
}
else
delete pSemaphore;
}
size_t EA::Thread::SemaphoreFactory::GetSemaphoreSize()
{
return sizeof(EA::Thread::Semaphore);
}
EA::Thread::Semaphore* EA::Thread::SemaphoreFactory::ConstructSemaphore(void* pMemory)
{
return new(pMemory) EA::Thread::Semaphore;
}
void EA::Thread::SemaphoreFactory::DestructSemaphore(EA::Thread::Semaphore* pSemaphore)
{
pSemaphore->~Semaphore();
}
#if EATHREAD_USE_SYNTHESIZED_SEMAPHORE
EASemaphoreData::EASemaphoreData()
: mCV(),
mMutex(),
mnCount(0),
mnMaxCount(INT_MAX),
mbValid(false)
{
// Empty
}
EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName)
: mInitialCount(initialCount),
mMaxCount(INT_MAX),
mbIntraProcess(bIntraProcess)
{
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
}
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()
{
EAT_ASSERT(!mSemaphoreData.mMutex.HasLock()); // The mMutex destructor will also assert this, but here it makes it more obvious this mutex is ours.
}
bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
{
if(pSemaphoreParameters && (!mSemaphoreData.mbValid))
{
mSemaphoreData.mbValid = true; // It's not really true unless our member mCV and mMutex init OK. To do: Added functions to our classes that verify they are OK.
mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
if(mSemaphoreData.mnCount < 0)
mSemaphoreData.mnCount = 0;
return mSemaphoreData.mbValid;
}
return false;
}
int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
{
int nReturnValue = kResultError;
int result = mSemaphoreData.mMutex.Lock(); // This mutex is owned by us and will be unlocked immediately in the mCV.Wait call, so we don't apply timeoutAbsolute. To consider: Maybe we should do so, though it's less efficient.
if(result > 0) // If success...
{
if(timeoutAbsolute == kTimeoutImmediate)
{
if(mSemaphoreData.mnCount.GetValue() >= 1)
nReturnValue = mSemaphoreData.mnCount.Decrement();
else
nReturnValue = kResultTimeout;
}
else
{
if(mSemaphoreData.mnCount.GetValue() >= 1) // If we can decrement it immediately...
nReturnValue = mSemaphoreData.mnCount.Decrement();
else // Else we need to wait.
{
Condition::Result cResult;
do{
cResult = mSemaphoreData.mCV.Wait(&mSemaphoreData.mMutex, timeoutAbsolute);
} while((cResult == Condition::kResultOK) && (mSemaphoreData.mnCount.GetValue() < 1)); // Always need to check the condition and retry if not matched. In rare cases two threads could return from Wait.
if(cResult == Condition::kResultOK) // If apparent success...
nReturnValue = mSemaphoreData.mnCount.Decrement();
else if(cResult == Condition::kResultTimeout)
nReturnValue = kResultTimeout;
else
{
// We return immediately here because mCV.Wait has not locked the mutex for
// us and so we don't want to fall through and unlock it below. Also, it would
// be inefficient for us to lock here and fall through only to unlock it below.
return nReturnValue;
}
}
}
result = mSemaphoreData.mMutex.Unlock();
EAT_ASSERT(result >= 0);
if(result < 0)
nReturnValue = kResultError; // This Semaphore is now considered dead and unusable.
}
return nReturnValue;
}
int EA::Thread::Semaphore::Post(int count)
{
EAT_ASSERT(mSemaphoreData.mnCount >= 0);
int newValue = kResultError;
int result = mSemaphoreData.mMutex.Lock();
if(result > 0)
{
// Set the new value to be whatever the current value is.
newValue = mSemaphoreData.mnCount.GetValue();
if((mSemaphoreData.mnMaxCount - count) < newValue) // If count would cause an overflow...
return kResultError; // We do what most OS implementations of max-count do. count = (mSemaphoreData.mnMaxCount - newValue);
newValue = mSemaphoreData.mnCount.Add(count);
bool bResult = mSemaphoreData.mCV.Signal(true); // Signal broadcast (the true arg) because semaphores could have multiple counts and multiple threads waiting for them. There's a potential "thundering herd" problem here.
EAT_ASSERT(bResult);
EA_UNUSED(bResult);
result = mSemaphoreData.mMutex.Unlock(); // Important that we lock after the mCV.Signal.
EAT_ASSERT(result >= 0);
if(result < 0)
newValue = kResultError; // This Semaphore is now considered dead and unusable.
}
return newValue;
}
int EA::Thread::Semaphore::GetCount() const
{
return mSemaphoreData.mnCount.GetValue();
}
#elif !EA_THREADS_AVAILABLE
///////////////////////////////////////////////////////////////////////////////
// non-threaded implementation
///////////////////////////////////////////////////////////////////////////////
EASemaphoreData::EASemaphoreData()
: mnCount(0),
mnMaxCount(INT_MAX)
{
// Empty
}
EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName)
: mInitialCount(initialCount),
mMaxCount(INT_MAX),
mbIntraProcess(bIntraProcess)
{
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
}
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()
{
}
bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
{
if(pSemaphoreParameters)
{
mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
return true;
}
return false;
}
int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
{
if(timeoutAbsolute == kTimeoutNone)
{
while(mSemaphoreData.mnCount <= 0)
ThreadSleep(1);
--mSemaphoreData.mnCount;
}
else if(timeoutAbsolute == 0)
{
if(mSemaphoreData.mnCount)
--mSemaphoreData.mnCount;
else
return kResultTimeout;
}
else
{
while((mSemaphoreData.mnCount <= 0) && (GetThreadTime() < timeoutAbsolute))
ThreadSleep(1);
if(mSemaphoreData.mnCount <= 0)
return kResultTimeout;
}
return mSemaphoreData.mnCount;
}
int EA::Thread::Semaphore::Post(int count)
{
EAT_ASSERT(mSemaphoreData.mnCount >= 0);
// Ideally, what we would do is account for the number of waiters in
// this overflow calculation. If max-count = 4, count = 6, waiters = 8,
// we would release 6 waiters and leave the semaphore at 2.
// The problem is that some of those 6 waiters might time out while we
// are doing this and leave ourselves with count greater than max-count.
if((mSemaphoreData.mnMaxCount - count) < mSemaphoreData.mnCount) // If count would cause an overflow...
return kResultError; // We do what most OS implementations of max-count do. // count = (mSemaphoreData.mnMaxCount - nLastCount);
return (mSemaphoreData.mnCount += count);
}
int EA::Thread::Semaphore::GetCount() const
{
return mSemaphoreData.mnCount;
}
#endif // !EA_THREADS_AVAILABLE
@@ -0,0 +1,354 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_storage.h>
#include <eathread/eathread.h>
EA_DISABLE_VC_WARNING(4574)
#include <new>
EA_RESTORE_VC_WARNING()
#if defined(EA_PLATFORM_SONY)
#include <kernel.h>
EA::Thread::ThreadLocalStorage::ThreadLocalStorage()
: mTLSData()
{
// To consider: Support the specification of a destructor instead of just passing NULL.
mTLSData.mResult = scePthreadKeyCreate(&mTLSData.mKey, NULL);
EAT_ASSERT(mTLSData.mResult == 0);
}
EA::Thread::ThreadLocalStorage::~ThreadLocalStorage()
{
if(mTLSData.mResult == 0)
scePthreadKeyDelete(mTLSData.mKey);
}
void* EA::Thread::ThreadLocalStorage::GetValue()
{
return scePthreadGetspecific(mTLSData.mKey);
}
bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData)
{
if(scePthreadSetspecific(mTLSData.mKey, pData) == 0)
return true;
return false;
}
#elif (defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE) && !defined(CS_UNDEFINED_STRING)
#if defined(EA_PLATFORM_UNIX)
#include <unistd.h>
#elif defined(EA_PLATFORM_WINDOWS)
#pragma warning(push, 0)
#include <Windows.h>
#pragma warning(pop)
#endif
EA::Thread::ThreadLocalStorage::ThreadLocalStorage()
: mTLSData()
{
// To consider: Support the specification of a destructor instead of just passing NULL.
mTLSData.mResult = pthread_key_create(&mTLSData.mKey, NULL);
EAT_ASSERT(mTLSData.mResult == 0);
}
EA::Thread::ThreadLocalStorage::~ThreadLocalStorage()
{
if(mTLSData.mResult == 0)
pthread_key_delete(mTLSData.mKey);
}
void* EA::Thread::ThreadLocalStorage::GetValue()
{
return pthread_getspecific(mTLSData.mKey);
}
bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData)
{
if(pthread_setspecific(mTLSData.mKey, pData) == 0)
return true;
return false;
}
#elif defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_WINDOWS_PHONE) && !(defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP))
#pragma warning(push, 0)
#include <Windows.h>
#pragma warning(pop)
EA::Thread::ThreadLocalStorage::ThreadLocalStorage()
: mTLSData(TlsAlloc())
{
EAT_ASSERT(mTLSData != TLS_OUT_OF_INDEXES);
}
EA::Thread::ThreadLocalStorage::~ThreadLocalStorage()
{
if(mTLSData != TLS_OUT_OF_INDEXES)
TlsFree(mTLSData);
}
void* EA::Thread::ThreadLocalStorage::GetValue()
{
return TlsGetValue(mTLSData);
}
bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData)
{
if(TlsSetValue(mTLSData, (void*)pData))
return true;
return false;
}
#elif (!EA_THREADS_AVAILABLE || defined(EA_PLATFORM_CONSOLE)) && !defined(CS_UNDEFINED_STRING)
#include <string.h>
#if !EA_THREADS_AVAILABLE
#define OSEnableInterrupts()
#define OSDisableInterrupts()
#else
#error Need to define EnableInterrupts/DisableInterrupts for the given platform.
#endif
EAThreadLocalStorageData::ThreadToDataPair* EAThreadLocalStorageData::GetTLSEntry(bool bCreateIfNotFound)
{
const int kArraySize = (sizeof(mDataArray) / sizeof(mDataArray[0]));
ThreadToDataPair* pCurrent, *pEnd;
EA::Thread::ThreadUniqueId nCurrentThreadID;
EAThreadGetUniqueId(nCurrentThreadID);
// The code below is likely to execute very quickly and never transfers
// execution outside the function, so we can very briefly disable interrupts
// for the period needed to do the logic below.
OSDisableInterrupts();
// We make the assumption that there are likely to be less than 10 threads most of
// the time. Thus, instead of maintaining a sorted array and do a binary search
// within that array, we do a linear search. An improvement would be to make the
// array be sorted if it goes above some preset size, such as 20.
for(pCurrent = mDataArray, pEnd = mDataArray + mDataArrayCount; pCurrent < pEnd; ++pCurrent)
{
if(pCurrent->mThreadID == nCurrentThreadID)
{
OSEnableInterrupts();
return pCurrent;
}
}
if((pCurrent >= pEnd) && ((mDataArrayCount + 1) < kArraySize) && bCreateIfNotFound) // If we didn't find it above and there is more room and we should create if not found...
{
pCurrent = mDataArray + mDataArrayCount++;
pCurrent->mThreadID = nCurrentThreadID;
}
else
pCurrent = NULL;
OSEnableInterrupts();
return pCurrent;
}
EA::Thread::ThreadLocalStorage::ThreadLocalStorage()
{
memset(mTLSData.mDataArray, 0, sizeof(mTLSData.mDataArray));
mTLSData.mDataArrayCount = 0;
}
EA::Thread::ThreadLocalStorage::~ThreadLocalStorage()
{
// Nothing to do.
}
void* EA::Thread::ThreadLocalStorage::GetValue()
{
EAThreadLocalStorageData::ThreadToDataPair* const pTDP = mTLSData.GetTLSEntry(false);
if(pTDP)
return (void*)pTDP->mpData;
return NULL;
}
bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData)
{
if(pData == NULL)
{ // We remove it from the container so that the container can have room for others.
EAThreadLocalStorageData::ThreadToDataPair* pTDP = mTLSData.GetTLSEntry(false);
if(pTDP)
{
OSDisableInterrupts(); // Briefly disable interrupts for the duration of the logic below.
const EAThreadLocalStorageData::ThreadToDataPair* const pTDPEnd = mTLSData.mDataArray + mTLSData.mDataArrayCount;
while(++pTDP <= pTDPEnd) // What we do here is move all the other values downward. This is an O(n) operation,
pTDP[-1] = pTDP[0]; // but the number of unique threads usinug us is likely to be pretty small.
mTLSData.mDataArrayCount = (int)(pTDPEnd - mTLSData.mDataArray - 1);
OSEnableInterrupts();
}
return true;
}
EAThreadLocalStorageData::ThreadToDataPair* const pTDP = mTLSData.GetTLSEntry(true);
if(pTDP)
pTDP->mpData = pData;
return (pTDP != NULL);
}
#else
// Use reference std::map implementation.
EA_DISABLE_VC_WARNING(4574)
#include <map>
EA_RESTORE_VC_WARNING()
#include <eathread/eathread_futex.h>
void** EAThreadLocalStorageData::GetTLSEntry(bool bCreateIfNotFound)
{
EA::Thread::ThreadUniqueId nThreadID;
EAThreadGetUniqueId(nThreadID);
EA::Thread::AutoFutex autoFutex(mFutex);
if(bCreateIfNotFound) // We expect this to be true most of the time.
{
// Create as needed
if (mThreadToDataMap == NULL)
{
mThreadToDataMap = new std::map<EA::Thread::ThreadUniqueId, const void*>;
}
return (void**)(char*)&((*mThreadToDataMap)[nThreadID]); // Dereferencing a std::map value by index inserts the value if it is not present.
}
if (mThreadToDataMap == NULL)
{
return NULL;
}
std::map<EA::Thread::ThreadUniqueId, const void*>::iterator it(mThreadToDataMap->find(nThreadID));
if(it != mThreadToDataMap->end())
{
std::map<EA::Thread::ThreadUniqueId, const void*>::value_type& value = *it;
return (void**)(char*)&value.second;
}
return NULL;
}
EA::Thread::ThreadLocalStorage::ThreadLocalStorage()
{
}
EA::Thread::ThreadLocalStorage::~ThreadLocalStorage()
{
// Nothing to do.
}
void* EA::Thread::ThreadLocalStorage::GetValue()
{
void** const ppData = mTLSData.GetTLSEntry(false);
if(ppData)
return *ppData;
return NULL;
}
bool EA::Thread::ThreadLocalStorage::SetValue(const void* pData)
{
if(pData == NULL)
{
ThreadUniqueId nThreadID;
EAThreadGetUniqueId(nThreadID);
EA::Thread::AutoFutex autoFutex(mTLSData.mFutex);
if (mTLSData.mThreadToDataMap)
{
std::map<EA::Thread::ThreadUniqueId, const void*>::iterator it(mTLSData.mThreadToDataMap->find(nThreadID));
if(it != mTLSData.mThreadToDataMap->end())
mTLSData.mThreadToDataMap->erase(it);
}
return true;
}
void** const ppData = mTLSData.GetTLSEntry(true);
if(ppData)
*ppData = (void*)pData;
return (*ppData != NULL);
}
#endif
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::ThreadLocalStorage* EA::Thread::ThreadLocalStorageFactory::CreateThreadLocalStorage()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::ThreadLocalStorage))) EA::Thread::ThreadLocalStorage;
else
return new EA::Thread::ThreadLocalStorage;
}
void EA::Thread::ThreadLocalStorageFactory::DestroyThreadLocalStorage(EA::Thread::ThreadLocalStorage* pThreadLocalStorage)
{
if(gpAllocator)
{
pThreadLocalStorage->~ThreadLocalStorage();
gpAllocator->Free(pThreadLocalStorage);
}
else
delete pThreadLocalStorage;
}
size_t EA::Thread::ThreadLocalStorageFactory::GetThreadLocalStorageSize()
{
return sizeof(EA::Thread::ThreadLocalStorage);
}
EA::Thread::ThreadLocalStorage* EA::Thread::ThreadLocalStorageFactory::ConstructThreadLocalStorage(void* pMemory)
{
return new(pMemory) EA::Thread::ThreadLocalStorage;
}
void EA::Thread::ThreadLocalStorageFactory::DestructThreadLocalStorage(EA::Thread::ThreadLocalStorage* pThreadLocalStorage)
{
pThreadLocalStorage->~ThreadLocalStorage();
}
#undef OSEnableInterrupts
#undef OSDisableInterrupts
@@ -0,0 +1,262 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/internal/config.h>
#include "eathread/internal/eathread_global.h"
#include <eathread/eathread_thread.h>
#include <eathread/eathread_mutex.h>
#include <new> // include new for placement new operator
#if !EA_THREADS_AVAILABLE
// Do nothing
#elif EA_USE_CPP11_CONCURRENCY
#include "cpp11/eathread_thread_cpp11.cpp"
#elif defined(EA_PLATFORM_SONY)
#include "kettle/eathread_thread_kettle.cpp"
#elif defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
#include "unix/eathread_thread_unix.cpp"
#elif defined(EA_PLATFORM_MICROSOFT)
#include "pc/eathread_thread_pc.cpp"
#endif
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
}
}
EA::Thread::Thread* EA::Thread::ThreadFactory::CreateThread()
{
if(gpAllocator)
return new(gpAllocator->Alloc(sizeof(EA::Thread::Thread))) EA::Thread::Thread;
else
return new EA::Thread::Thread;
}
void EA::Thread::ThreadFactory::DestroyThread(EA::Thread::Thread* pThread)
{
if(gpAllocator)
{
pThread->~Thread();
gpAllocator->Free(pThread);
}
else
delete pThread;
}
size_t EA::Thread::ThreadFactory::GetThreadSize()
{
return sizeof(EA::Thread::Thread);
}
EA::Thread::Thread* EA::Thread::ThreadFactory::ConstructThread(void* pMemory)
{
return new(pMemory) EA::Thread::Thread;
}
void EA::Thread::ThreadFactory::DestructThread(EA::Thread::Thread* pThread)
{
pThread->~Thread();
}
EA::Thread::ThreadEnumData::ThreadEnumData()
: mpThreadDynamicData(NULL)
{
}
EA::Thread::ThreadEnumData::~ThreadEnumData()
{
Release();
}
void EA::Thread::ThreadEnumData::Release()
{
if(mpThreadDynamicData)
{
mpThreadDynamicData->Release();
mpThreadDynamicData = NULL;
}
}
extern const size_t kMaxThreadDynamicDataCount;
EATHREAD_GLOBALVARS_EXTERN_INSTANCE;
///////////////////////////////////////////////////////////////////////////////
//
size_t EA::Thread::EnumerateThreads(ThreadEnumData* pDataArray, size_t dataArrayCapacity)
{
size_t requiredCount = 0;
if(dataArrayCapacity > EA::Thread::kMaxThreadDynamicDataCount)
dataArrayCapacity = EA::Thread::kMaxThreadDynamicDataCount;
EATHREAD_GLOBALVARS.gThreadDynamicMutex.Lock();
for(size_t i(0); i < EA::Thread::kMaxThreadDynamicDataCount; i++)
{
if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].GetValue() != 0)
{
if(requiredCount < dataArrayCapacity)
{
pDataArray[requiredCount].mpThreadDynamicData = (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
pDataArray[requiredCount].mpThreadDynamicData->AddRef();
}
requiredCount++;
}
}
EATHREAD_GLOBALVARS.gThreadDynamicMutex.Unlock();
return requiredCount;
}
///////////////////////////////////////////////////////////////////////////////
// non-threaded implementation
///////////////////////////////////////////////////////////////////////////////
#if !EA_THREADS_AVAILABLE
// If mulitithreading support is not available, we can't implement anything
// here that works. All we do is define a null implementation that links
// but fails all operations.
EA::Thread::ThreadParameters::ThreadParameters()
: mpStack(NULL),
mnStackSize(0),
mnPriority(kThreadPriorityDefault),
mnProcessor(kProcessorDefault),
mpName(""),
mbDisablePriorityBoost(false)
{
}
EA::Thread::Thread::Thread()
{
mThreadData.mpData = NULL;
}
EA::Thread::Thread::Thread(const Thread& /*t*/)
{
}
EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& /*t*/)
{
return *this;
}
EA::Thread::Thread::~Thread()
{
}
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL;
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL;
EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny;
EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff);
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper()
{
return sGlobalRunnableFunctionUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper)
{
if (sGlobalRunnableFunctionUserWrapper != NULL)
{
// Can only be set once in entire game.
EAT_ASSERT(false);
}
else
sGlobalRunnableFunctionUserWrapper = pUserWrapper;
}
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper()
{
return sGlobalRunnableClassUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper)
{
if (sGlobalRunnableClassUserWrapper != NULL)
{
// Can only be set once in entire game.
EAT_ASSERT(false);
}
else
sGlobalRunnableClassUserWrapper = pUserWrapper;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction /*pFunction*/, void* /*pContext*/, const ThreadParameters* /*pTP*/, RunnableFunctionUserWrapper /*pUserWrapper*/)
{
return kThreadIdInvalid;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* /*pRunnable*/, void* /*pContext*/, const ThreadParameters* /*pTP*/, RunnableClassUserWrapper /*pUserWrapper*/)
{
return kThreadIdInvalid;
}
EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& /*timeoutAbsolute*/, intptr_t* /*pThreadReturnValue*/)
{
return kStatusNone;
}
EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* /*pThreadReturnValue*/) const
{
return kStatusNone;
}
EA::Thread::ThreadId EA::Thread::Thread::GetId() const
{
return (ThreadId)kThreadIdInvalid;
}
int EA::Thread::Thread::GetPriority() const
{
return kThreadPriorityUnknown;
}
bool EA::Thread::Thread::SetPriority(int /*nPriority*/)
{
return false;
}
void EA::Thread::Thread::SetProcessor(int /*nProcessor*/)
{
}
void EA::Thread::Thread::Wake()
{
}
const char* EA::Thread::Thread::GetName() const
{
return "";
}
void EA::Thread::Thread::SetName(const char* /*pName*/)
{
}
#endif // !EA_THREADS_AVAILABLE
@@ -0,0 +1,178 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_barrier.h>
#include <eathread/eathread.h>
#include <kernel.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <new>
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 = scePthreadMutexDestroy(&mBarrierData.mMutex);
EA_UNUSED(result);
EAT_ASSERT(result == 0);
result = scePthreadCondDestroy(&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 = scePthreadMutexInit(&mBarrierData.mMutex, NULL, pBarrierParameters->mName);
if(result == 0){
result = scePthreadCondInit(&mBarrierData.mCV, NULL, pBarrierParameters->mName);
if(result == 0)
mBarrierData.mbValid = true;
else
scePthreadMutexDestroy(&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 = scePthreadMutexLock(&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 = scePthreadCondBroadcast(&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(SCE_PTHREAD_CANCEL_DISABLE)
int cancel;
scePthreadSetcancelstate(SCE_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 = scePthreadCondTimedwait(&mBarrierData.mCV, &mBarrierData.mMutex, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
} while((result == 0) && (nCurrentCycle == mBarrierData.mnCycle));
if(result != 0)
break;
}
#if defined(SCE_PTHREAD_CANCEL_DISABLE)
int cancelTemp;
scePthreadSetcancelstate(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 = scePthreadMutexUnlock(&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;
}
@@ -0,0 +1,557 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread.h>
#include <eathread/eathread_atomic.h>
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/eathread_storage.h>
#include <string.h>
#include <sys/signal.h>
#include <machine/signal.h>
#include <sdk_version.h>
#include <unistd.h>
// EATHREAD_PTHREAD_SIGACTION_SUPPORTED
//
// Defined as 0 or 1.
//
#if !defined(EATHREAD_PTHREAD_SIGACTION_SUPPORTED)
//#if EATHREAD_SCEDBG_ENABLED || defined(EA_DEBUG)
// #define EATHREAD_PTHREAD_SIGACTION_SUPPORTED 1
//#else
// #define EATHREAD_PTHREAD_SIGACTION_SUPPORTED 0
//#endif
// Disabling due to syscall crashing on SDK 1.6.
#define EATHREAD_PTHREAD_SIGACTION_SUPPORTED 0
#endif
#if EATHREAD_PTHREAD_SIGACTION_SUPPORTED
// Until Sony provides a declaration for this or an alternative scheme, we declare this ourselves.
__BEGIN_DECLS
// User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9.
// The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9, which is what matters to us below.
// http://www.ibm.com/developerworks/library/l-ia/index.html
// A system-call is done via the syscall instruction. The kernel destroys registers %rcx and %r11.
// The number of the syscall has to be passed in register %rax.
// System-calls are limited to six arguments, no argument is passed directly on the stack.
// Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is -errno.
// Only values of class INTEGER or class MEMORY are passed to the kernel.
// Relevant BSD source code: https://bitbucket.org/freebsd/freebsd-head/src/36b017c6a0f817439d40abfd790238dfa13e2be3/lib/libthr/thread?at=default
// The BSD pthread struct: https://bitbucket.org/freebsd/freebsd-head/src/36b017c6a0f817439d40abfd790238dfa13e2be3/lib/libthr/thread/thr_private.h?at=default
// Some NetBSD pthread source: http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libpthread/pthread.c?rev=1.134&content-type=text/x-cvsweb-markup&only_with_tag=MAIN
static int sigaction(int sig, const struct sigaction * __restrict act, struct sigaction * __restrict oact)
{
int result;
__asm__ __volatile__(
"mov %%rcx, %%r10\n\t"
"syscall\n\t"
: "=a"(result) : "a"(416), "D"(sig), "S"(act), "d"(oact));
return result;
}
// #define SYS_thr_kill 433
// typedef long thread_t
// pthread_t is an opaque typedef for struct pthread. struct pthread looks like so:
// struct pthread {
// long tid; // Kernel thread id.
// . . . // Many other members.
// }
// Thus you can directly reinterpret_cast pthread to a pointer to a kernel thread id.
#if !defined(GetTidFromPthread)
#define GetTidFromPthread(pthreadId) *reinterpret_cast<long*>(pthreadId)
#endif
static int thr_kill(long thread, int sig)
{
int result;
__asm__ __volatile__(
"mov %%rcx, %%r10\n\t"
"syscall\n\t"
: "=a"(result) : "a"(433), "D"(thread), "S"(sig));
return result;
}
static int pthread_kill(pthread_t pthreadId, int sig)
{
long tid = GetTidFromPthread(pthreadId);
thr_kill(tid, sig);
return 0;
}
const size_t kBacktraceSignalHandlerIgnoreCount = 2; // It's unclear what this value should be. On one machine it was 4, but on another it was 2. Going with a lower number is more conservative. Possibly a debug/opt thing?
__END_DECLS
#endif
// Sony may remove this header in the future, so we use the clang __has_include feature to detect if and when that occurs.
// NOTE: Use of unwind.h is disabled on PS4 due to syscall hangs in the kernel
// experienced by Frostbite when overloadiing user_malloc to generate a
// callstack. In addition, Sony recommends the use of __builtin_frame_address
// / __builtin_return_address over _Unwind_Backtrace as it is more performant
// due to the frame pointers being included by default in all builds.
// Thread that stats performance of __builtin_frame_pointer is better.
// https://ps4.scedev.net/forums/thread/2267/
// Open support ticket for syscall hang:
// https://ps4.scedev.net/forums/thread/52687/
#if __has_include(<unwind.h>) && !defined(EA_PLATFORM_SONY)
#include <unwind.h>
#if !defined(EA_HAVE_UNWIND_H)
#define EA_HAVE_UNWIND_H 1
#endif
#else
#if !defined(EA_NO_HAVE_UNWIND_H)
#define EA_NO_HAVE_UNWIND_H 1
#endif
#endif
namespace EA
{
namespace Thread
{
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
// Nothing needed.
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
// Nothing needed.
}
EATHREADLIB_API void GetInstructionPointer(void*& p)
{
p = __builtin_return_address(0);
}
#if defined(EA_HAVE_UNWIND_H)
// This is a callback function which libunwind calls, once per callstack entry.
struct UnwindCallbackContext
{
void** mpReturnAddressArray;
size_t mReturnAddressArrayCapacity;
size_t mReturnAddressArrayIndex;
};
static _Unwind_Reason_Code UnwindCallback(_Unwind_Context* pUnwindContext, void* pUnwindCallbackContextVoid)
{
UnwindCallbackContext* pUnwindCallbackContext = (UnwindCallbackContext*)pUnwindCallbackContextVoid;
if(pUnwindCallbackContext->mReturnAddressArrayIndex < pUnwindCallbackContext->mReturnAddressArrayCapacity)
{
uintptr_t ip = _Unwind_GetIP(pUnwindContext);
pUnwindCallbackContext->mpReturnAddressArray[pUnwindCallbackContext->mReturnAddressArrayIndex++] = (void*)ip;
return _URC_NO_REASON;
}
return _URC_NORMAL_STOP;
}
#endif
#if EATHREAD_PTHREAD_SIGACTION_SUPPORTED
namespace Local
{
enum EAThreadBacktraceState
{
// Positive thread lwp ids are here implicitly.
EATHREAD_BACKTRACE_STATE_NONE = -1,
EATHREAD_BACKTRACE_STATE_DUMPING = -2,
EATHREAD_BACKTRACE_STATE_DONE = -3,
EATHREAD_BACKTRACE_STATE_CANCEL = -4
};
struct ThreadBacktraceState
{
EA::Thread::AtomicInt32 mState; // One of enum EAThreadBacktraceState or (initially) the thread id of the thread we are targeting.
void** mCallstack; // Output param
size_t mCallstackCapacity; // Input param, refers to array capacity of mCallstack.
size_t mCallstackCount; // Output param
ScePthread mPthread; // Output param
ThreadBacktraceState() : mState(EATHREAD_BACKTRACE_STATE_NONE), mCallstackCapacity(0), mCallstackCount(0), mPthread(NULL){}
};
static ScePthreadMutex gThreadBacktraceMutex = SCE_PTHREAD_MUTEX_INITIALIZER;
static ThreadBacktraceState gThreadBacktraceState; // Protected by gThreadBacktraceMutex.
static void gThreadBacktraceSignalHandler(int /*sigNum*/, siginfo_t* /*pSigInfo*/, void* pSigContextVoid)
{
int32_t lwpSelf = *(int32_t*)scePthreadSelf();
if(gThreadBacktraceState.mState.SetValueConditional(EATHREAD_BACKTRACE_STATE_DUMPING, lwpSelf))
{
gThreadBacktraceState.mPthread = scePthreadSelf();
if(gThreadBacktraceState.mCallstackCapacity)
{
gThreadBacktraceState.mCallstackCount = GetCallstack(gThreadBacktraceState.mCallstack, gThreadBacktraceState.mCallstackCapacity, (const CallstackContext*)NULL);
// At this point we need to remove the top N entries and insert an entry for where the thread's instruction pointer is.
// We originally had code like the following, but it's returning a signal
// handling address now that we are using our own pthread_kill function:
//if(gThreadBacktraceState.mCallstackCount >= kBacktraceSignalHandlerIgnoreCount) // This should always be true.
//{
// gThreadBacktraceState.mCallstackCount -= (kBacktraceSignalHandlerIgnoreCount - 1);
// memmove(&gThreadBacktraceState.mCallstack[1], &gThreadBacktraceState.mCallstack[kBacktraceSignalHandlerIgnoreCount], (gThreadBacktraceState.mCallstackCount - 1) * sizeof(void*));
//}
//else
// gThreadBacktraceState.mCallstackCount = 1;
//gThreadBacktraceState.mCallstack[0] = pSigContextVoid ? reinterpret_cast<void*>(reinterpret_cast<sigcontext*>((uintptr_t)pSigContextVoid + 48)->sc_rip) : NULL;
// New code that's working for our own pthread_kill function usage:
if(gThreadBacktraceState.mCallstackCount >= kBacktraceSignalHandlerIgnoreCount) // This should always be true.
{
gThreadBacktraceState.mCallstackCount -= kBacktraceSignalHandlerIgnoreCount;
memmove(&gThreadBacktraceState.mCallstack[0], &gThreadBacktraceState.mCallstack[kBacktraceSignalHandlerIgnoreCount], gThreadBacktraceState.mCallstackCount * sizeof(void*));
}
}
else
gThreadBacktraceState.mCallstackCount = 0;
gThreadBacktraceState.mState.SetValue(EATHREAD_BACKTRACE_STATE_DONE);
}
// else this thread received an unexpected SIGURG. This can happen if it was so delayed that
// we timed out waiting for it to happen and moved on.
}
}
#endif
/// GetCallstack
///
/// This is a version of GetCallstack which gets the callstack of a thread based on its thread id as opposed to
/// its register state. It works by injecting a signal handler into the given thread and reading the self callstack
/// then exiting from the signal handler. The GetCallstack function sets this up, generates the signal for the
/// other thread, then waits for it to complete. It uses the SIGURG signal for this.
///
/// Primary causes of failure:
/// The target thread has SIGURG explicitly ignored.
/// The target thread somehow is getting too little CPU time to respond to the signal.
///
/// To do: Change this function to take a ThreadInfo as a last parameter instead of pthread_t. And have the
/// ThreadInfo return additional basic thread information. Or maybe even change this function to be a
/// GetThreadInfo function instead of GetCallstack.
///
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, EA::Thread::ThreadId& pthread)
{
size_t callstackCount = 0;
#if EATHREAD_PTHREAD_SIGACTION_SUPPORTED
using namespace Local;
if(pthread)
{
ScePthread pthreadSelf = scePthreadSelf();
int32_t lwp = *(int32_t*)pthread;
int32_t lwpSelf = *(int32_t*)pthreadSelf;
if(lwp == lwpSelf) // This function can be called only for a thread other than self.
callstackCount = GetCallstack(pReturnAddressArray, nReturnAddressArrayCapacity, (const CallstackContext*)NULL);
else
{
struct sigaction act; memset(&act, 0, sizeof(act));
struct sigaction oact; memset(&oact, 0, sizeof(oact));
act.sa_sigaction = gThreadBacktraceSignalHandler;
act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
scePthreadMutexLock(&gThreadBacktraceMutex);
if(sigaction(SIGURG, &act, &oact) == 0)
{
gThreadBacktraceState.mCallstack = pReturnAddressArray;
gThreadBacktraceState.mCallstackCapacity = nReturnAddressArrayCapacity;
gThreadBacktraceState.mState.SetValue(lwp);
// Signal the specific thread that we want to dump.
int32_t stateTemp = lwp;
if(pthread_kill(pthread, SIGURG) == 0)
{
// Wait for the other thread to start dumping the stack, or time out.
for(int waitMS = 200; waitMS; waitMS--)
{
stateTemp = gThreadBacktraceState.mState.GetValue();
if(stateTemp != lwp)
break;
usleep(1000); // This sleep gives the OS the opportunity to execute the target thread, even if it's of a lower priority than this thread.
}
}
// else apparently failed to send SIGURG to the thread, or the thread was paused in a way that it couldn't receive it.
if(stateTemp == lwp) // If the operation timed out or seemingly never started...
{
if(gThreadBacktraceState.mState.SetValueConditional(EATHREAD_BACKTRACE_STATE_CANCEL, lwp)) // If the backtrace still didn't start, and we were able to stop it by setting the state to cancel...
stateTemp = EATHREAD_BACKTRACE_STATE_CANCEL;
else
stateTemp = gThreadBacktraceState.mState.GetValue(); // It looks like the backtrace thread did in fact get a late start and is now executing
}
// Wait indefinitely for the dump to finish or be canceled.
// We cannot apply a timeout here because the other thread is accessing state that
// is owned by this thread.
for(int waitMS = 100; (stateTemp == EATHREAD_BACKTRACE_STATE_DUMPING) && waitMS; waitMS--) // If the thread is (still) busy writing it out its callstack...
{
usleep(1000);
stateTemp = gThreadBacktraceState.mState.GetValue();
}
if(stateTemp == EATHREAD_BACKTRACE_STATE_DONE)
callstackCount = gThreadBacktraceState.mCallstackCount;
// Else give up on it. It's OK to just fall through.
// Restore the original SIGURG handler.
sigaction(SIGURG, &oact, NULL);
}
scePthreadMutexUnlock(&gThreadBacktraceMutex);
}
}
#endif
return callstackCount;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstack
//
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
#if defined(EA_HAVE_UNWIND_H)
// libunwind can only read the stack from the current thread.
// However, we can accomplish this for another thread by injecting a signal handler into that thread.
// See the EAThreadBacktrace() function source code above.
if(pContext == NULL) // If reading the current thread's context...
{
UnwindCallbackContext context = { pReturnAddressArray, nReturnAddressArrayCapacity, 0 };
_Unwind_Backtrace(&UnwindCallback, &context);
return context.mReturnAddressArrayIndex;
}
// We don't yet have a means to read another thread's context.
return 0;
#else
// This platform doesn't use glibc and so the backtrace() function isn't available.
// For debug builds we can follow the stack frame manually, as stack frames are usually available in debug builds.
EA_UNUSED(pReturnAddressArray);
EA_UNUSED(nReturnAddressArrayCapacity);
size_t index = 0;
void** sp = nullptr;
void** new_sp = nullptr;
const uintptr_t kPtrSanityCheckLimit = 1*1024*1024;
if (pContext == NULL)
{
// Arguments are passed in registers on x86-64, so we can't just offset from &pReturnAddressArray.
sp = (void**)__builtin_frame_address(0);
}
else
{
// On kettle it's not recommended to omit the frame pointer so we check that RBP is sane before use since
// it could have been omitted. From Sony Docs:
// "[omit frame pointer] will inhibit unwinding and ... the option may also increase code size since the
// encoding for stack-based addressing is often 1 byte longer then RBP-based (frame pointer) addressing.
// With PlayStation®4 Clang, frame pointer omission may not lead to improved performance.
// Performance analysis and code profiling are recommended before using this option"
sp = (void**)((pContext->mRBP - pContext->mRSP) > kPtrSanityCheckLimit ? pContext->mRSP : pContext->mRBP);
pReturnAddressArray[index++] = (void*)pContext->mRIP;
}
for(int count = 0; sp && (index < nReturnAddressArrayCapacity); sp = new_sp, ++count)
{
if(count > 0 || index != 0) // We skip the current frame if we haven't set it already above
pReturnAddressArray[index++] = *(sp + 1);
new_sp = (void**)*sp;
if((new_sp < sp) || (new_sp > (sp + kPtrSanityCheckLimit)))
break;
}
return index;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId)
{
ScePthread self = scePthreadSelf();
ScePthread pthread_Id = (ScePthread)threadId; // Requires that ScePthread is a pointer or integral type.
if(scePthreadEqual(pthread_Id, self))
{
void* pInstruction;
// This is some crazy GCC code that happens to work:
pInstruction = ({ __label__ label; label: &&label; });
context.mRIP = (uint64_t)pInstruction;
context.mRSP = (uint64_t)__builtin_frame_address(1);
context.mRBP = 0;
}
else
{
// There is currently no way to do this.
memset(&context, 0, sizeof(context));
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
// Assuming we are using pthreads, sysThreadId == threadId.
return GetCallstackContext(context, sysThreadId);
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* pContext)
{
context.mRIP = pContext->Rip;
context.mRSP = pContext->Rsp;
context.mRBP = pContext->Rbp;
}
///////////////////////////////////////////////////////////////////////////////
// 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;
}
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()
{
void* pBase;
if(GetPthreadStackInfo(&pBase, NULL))
return pBase;
// 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).
pBase = sStackBase.GetValue();
if(pBase == NULL)
pBase = (void*)(((uintptr_t)&pBase + 4095) & ~4095); // Make a guess, round up to next 4096.
return pBase;
}
///////////////////////////////////////////////////////////////////////////////
// GetStackLimit
//
EATHREADLIB_API void* GetStackLimit()
{
void* pLimit;
if(GetPthreadStackInfo(NULL, &pLimit))
return pLimit;
pLimit = __builtin_frame_address(0);
return (void*)((uintptr_t)pLimit & ~4095); // Round down to nearest page.
}
} // namespace Thread
} // namespace EA
@@ -0,0 +1,121 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/internal/config.h>
#include <eathread/eathread_condition.h>
#include <kernel.h>
#include <time.h>
#include <errno.h>
#include <string.h>
EAConditionData::EAConditionData()
{
memset(&mCV, 0, sizeof(mCV));
}
EA::Thread::ConditionParameters::ConditionParameters(bool bIntraProcess, const char* pName)
: mbIntraProcess(bIntraProcess)
{
if (pName)
{
strncpy(mName, pName, sizeof(mName) - 1);
mName[sizeof(mName) - 1] = 0;
}
else
mName[0] = 0;
}
EA::Thread::Condition::Condition(const ConditionParameters* pConditionParameters, bool bDefaultParameters)
{
if(!pConditionParameters && bDefaultParameters)
{
ConditionParameters parameters;
Init(&parameters);
}
else
Init(pConditionParameters);
}
EA::Thread::Condition::~Condition()
{
scePthreadCondDestroy(&mConditionData.mCV);
}
bool EA::Thread::Condition::Init(const ConditionParameters* pConditionParameters)
{
if(pConditionParameters)
{
ScePthreadCondattr cattr;
scePthreadCondattrInit(&cattr);
const int result = scePthreadCondInit(&mConditionData.mCV, &cattr, pConditionParameters->mName);
EAT_ASSERT(result == 0);
scePthreadCondattrDestroy(&cattr);
return (result == 0);
}
return false;
}
EA::Thread::Condition::Result EA::Thread::Condition::Wait(Mutex* pMutex, const ThreadTime& timeoutAbsolute)
{
int result;
ScePthreadMutex* 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 = scePthreadCondWait(&mConditionData.mCV, pMutex_t);
else
result = scePthreadCondTimedwait(&mConditionData.mCV, pMutex_t, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
pMutexData->SimulateLock(true);
EAT_ASSERT(!pMutex || (pMutex->GetLockCount() == 1));
if(result != 0)
{
if(result == SCE_KERNEL_ERROR_ETIMEDOUT)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
return kResultOK;
}
bool EA::Thread::Condition::Signal(bool bBroadcast)
{
if(bBroadcast)
return (scePthreadCondBroadcast(&mConditionData.mCV) == 0);
return (scePthreadCondSignal(&mConditionData.mCV) == 0);
}
@@ -0,0 +1,393 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread.h>
#include <eathread/eathread_thread.h>
#include <eathread/eathread_atomic.h>
#include <eathread/eathread_storage.h>
#include <sched.h>
#include <unistd.h>
#if defined(_YVALS)
#include <time.h>
#else
#include <sys/time.h>
#endif
#include <kernel.h>
#include <sceerror.h>
#include <sdk_version.h>
#include <cpuid.h>
#include <new>
#include <string.h>
namespace EA
{
namespace Thread
{
// Assertion variables.
EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL;
void* gpAssertionFailureContext = NULL;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
// Variables required for ThreadSleep
//
// TLS var for quicker lookups to our thread's data so we may grab the thread local EAThreadTimerQueue
static EA_THREAD_LOCAL EAThreadDynamicData* tpThreadDynamicData = nullptr;
// In the event a non-EAThread requires a timer queue we may supply the global instance
static EAThreadTimerQueue gThreadTimerQueue;
////////////////////////////////////////////////////////////////////////////////////////////////////////
EA::Thread::ThreadId EA::Thread::GetThreadId()
{
// https://ps4.scedev.net/forums/thread/12697/
// https://ps4.scedev.net/forums/thread/53323/
//
// ScePthread scePthreadSelf() does not return a integral thread id value. Instead it returns a ScePthread structure
// with is actually a pointer to a pthread structure (eg. pthread*). On other Sony platforms, an API like
// scePthreadGetthreadid was available for this use case but this isn't the case on the PS4. The above scedev.net
// threads indicate that the request for an additiona API to retrieve the kernel threadid has been submitted to
// Sony. Until this feature is shipped in a future SDK update we use the following technique to get a scalar thread
// id value that matches the threadid presented in the PS4 debugger.
const EA::Thread::ThreadId currentThreadId = *reinterpret_cast<EA::Thread::ThreadId*>(scePthreadSelf());
return currentThreadId;
}
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;
SysThreadId currentThreadId = scePthreadSelf();
int result = scePthreadGetschedparam(currentThreadId, &policy, &param);
if(result == SCE_OK)
{
// Kettle pthreads uses a reversed interpretation of sched_get_priority_min and sched_get_priority_max.
return -1 * (param.sched_priority - SCE_KERNEL_PRIO_FIFO_DEFAULT);
}
return kThreadPriorityDefault;
}
bool EA::Thread::SetThreadPriority(int nPriority)
{
SysThreadId currentThreadId = scePthreadSelf();
int policy;
SceKernelSchedParam param;
int result = -1;
EAT_ASSERT(nPriority != kThreadPriorityUnknown);
result = scePthreadGetschedparam(currentThreadId, &policy, &param);
if(result == SCE_OK)
{
// Kettle pthreads uses a reversed interpretation of sched_get_priority_min and sched_get_priority_max.
const int nMin = SCE_KERNEL_PRIO_FIFO_HIGHEST;
const int nMax = SCE_KERNEL_PRIO_FIFO_LOWEST;
param.sched_priority = (SCE_KERNEL_PRIO_FIFO_DEFAULT + (-1 * 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 = scePthreadSetprio(currentThreadId, param.sched_priority);
}
return (result == SCE_OK);
}
void* EA::Thread::GetThreadStackBase()
{
void* pStackAddr = NULL;
int result;
ScePthreadAttr attr;
result = scePthreadAttrInit(&attr);
EAT_ASSERT(SCE_OK == result);
result = scePthreadAttrGet(scePthreadSelf(), &attr);
EAT_ASSERT(SCE_OK == result);
result = scePthreadAttrGetstackaddr(&attr, &pStackAddr);
EAT_ASSERT(SCE_OK == result);
result = scePthreadAttrDestroy(&attr);
EAT_ASSERT(SCE_OK == result);
EA_UNUSED(result);
return pStackAddr;
}
namespace
{
SceKernelCpumask GetSceKernelAllCpuMask()
{
#if (SCE_ORBIS_SDK_VERSION >= 0x03000000u)
return (EA::Thread::GetProcessorCount() == 6) ? SCE_KERNEL_CPUMASK_6CPU_ALL : SCE_KERNEL_CPUMASK_7CPU_ALL;
#else
nAffinityMask &= 0x3f;
#endif
}
}
void EA::Thread::SetThreadProcessor(int nProcessor)
{
SceKernelCpumask mask = GetSceKernelAllCpuMask();
if (nProcessor >= 0)
mask = (SceKernelCpumask)(1 << nProcessor);
int result = scePthreadSetaffinity(scePthreadSelf(), mask);
EA_UNUSED(result);
EAT_ASSERT(SCE_OK == result);
}
int EA::Thread::GetThreadProcessor()
{
return sceKernelGetCurrentCpu();
}
EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask)
{
// Update the affinity mask in the thread dynamic data cache.
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
pTDD->mnThreadAffinityMask = nAffinityMask;
}
#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
nAffinityMask &= GetSceKernelAllCpuMask();
int res = scePthreadSetaffinity(GetSysThreadId(id), static_cast<SceKernelCpumask>(nAffinityMask));
EAT_ASSERT(SCE_OK == res);
EA_UNUSED(res);
#endif
}
EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id)
{
// Update the affinity mask in the thread dynamic data cache.
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
namespace Internal
{
void SetThreadName(EAThreadDynamicData* pTDD)
{
if(pTDD)
{
EAT_COMPILETIME_ASSERT(EATHREAD_NAME_SIZE == 32); // New name (up to 32 bytes including the NULL terminator), or NULL due to Sony OS constraint
char buf[EATHREAD_NAME_SIZE];
snprintf(buf, sizeof(buf), "%s", pTDD->mName);
buf[EATHREAD_NAME_SIZE - 1] = 0;
auto sceResult = scePthreadRename(pTDD->mSysThreadId, buf);
EA_UNUSED(sceResult);
EAT_ASSERT(SCE_OK == sceResult);
}
}
};
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)
{
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 (SCE_ORBIS_SDK_VERSION >= 0x03000000u)
return sceKernelGetCpumode() == SCE_KERNEL_CPUMODE_6CPU ? 6 : 7;
#else
return 6;
#endif
}
void EA::Thread::ThreadSleep(const ThreadTime& timeRelative)
{
if(timeRelative == kTimeoutImmediate)
{
scePthreadYield();
}
else
{
SceKernelTimespec ts;
static const double MILLISECONDS_TO_NANOSECONDS = 1000000.0;
static const uint64_t SECONDS_TO_NANOSECONDS = 1000000000;
// make sure we compute this with doubles then uint64_t or we will run out of bits precision
uint64_t timeNanoSeconds = (uint64_t)(MILLISECONDS_TO_NANOSECONDS * timeRelative);
ts.tv_sec = timeNanoSeconds / SECONDS_TO_NANOSECONDS;
ts.tv_nsec = static_cast<long>(timeNanoSeconds % SECONDS_TO_NANOSECONDS); // converting from milliseconds to nanoseconds.
// Determine which TimerQueue to use. Timer Queues are used to allow for higher resolution sleeps
EAThreadTimerQueue* pThreadTimerQueue = nullptr;
if (EA_UNLIKELY(tpThreadDynamicData == nullptr))
{
// This is either the first time an EAThread thread has called ThreadSleep or we are calling ThreadSleep
// from a non-eathread function. Find the ThreadDynamicData which houses the TimerQueue and if not present (we are
// using a non-eathread) grab the global instance instead.
tpThreadDynamicData = EA::Thread::FindThreadDynamicData(EA::Thread::GetThreadId());
if (tpThreadDynamicData)
{
pThreadTimerQueue = &tpThreadDynamicData->mThreadTimerQueue;
}
else
{
pThreadTimerQueue = &gThreadTimerQueue;
}
}
else
{
pThreadTimerQueue = &tpThreadDynamicData->mThreadTimerQueue;
}
// Timer queues may only accept sleep values between 100 microseconds and while we guarantee pThreadTimerQueue will
// not be null, we must ensure it has been enabled since it may fail in two uncommon ways:
// 1. The underlying Sony Queue failed to initialize (such as too many queues currently being created)
// 2. This function (ThreadSleep) is called during static initialization and due to static initialization order
// we haven't had a chance to initialize the global static EAThreadTimerQueue instance
if (EA_LIKELY((timeNanoSeconds < (SECONDS_TO_NANOSECONDS * 100)) && pThreadTimerQueue->mbEnabled))
{
const long kMinTimeForTimerEventNanoSeconds = 100000; // 100 microseconds represented in nanoseconds
ts.tv_nsec = EA_UNLIKELY((ts.tv_nsec < kMinTimeForTimerEventNanoSeconds) && (ts.tv_sec != 0)) ?
kMinTimeForTimerEventNanoSeconds : ts.tv_nsec;
// it's ok to submit negative ids to the queue in the event that mCurrentId wraps around
int result = sceKernelAddHRTimerEvent(pThreadTimerQueue->mTimerEventQueue, (int)pThreadTimerQueue->mCurrentId++, &ts, nullptr);
EA_UNUSED(result);
EAT_ASSERT_FORMATTED(result == SCE_OK, "sceKernelAddHRTimerEvent returned an error (0x%08x)", result);
int out;
SceKernelEvent ev;
result = sceKernelWaitEqueue(pThreadTimerQueue->mTimerEventQueue, &ev, 1, &out, nullptr);
EAT_ASSERT_FORMATTED(result == SCE_OK, "sceKernelWaitEqueue returned an error (0x%08x)", result);
}
else
{
int result = sceKernelNanosleep(&ts, 0);
EA_UNUSED(result);
EAT_ASSERT_MSG(result == SCE_OK, "sceKernelNanosleep returned an error");
}
}
}
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();
}
scePthreadExit((void*)threadReturnValue);
}
EA::Thread::ThreadTime EA::Thread::GetThreadTime()
{
SceKernelTimespec ts;
sceKernelClockGettime(SCE_KERNEL_CLOCK_MONOTONIC, &ts);
ThreadTime ret = EA_TIMESPEC_AS_DOUBLE_IN_MS(ts);
return ret;
}
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
// Todo.
#endif
}
}
EA::Thread::SysThreadId EA::Thread::GetSysThreadId(EA::Thread::ThreadId id)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if (pTDD)
{
return pTDD->mSysThreadId;
}
return kSysThreadIdInvalid;
}
EA::Thread::SysThreadId EA::Thread::GetSysThreadId()
{
return scePthreadSelf();
}
@@ -0,0 +1,199 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/internal/config.h>
#include <eathread/eathread_mutex.h>
#include <errno.h>
#include <string.h>
#include <sceerror.h>
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;
EAT_ASSERT((mThreadId = EA::Thread::GetThreadId()) || true); // Intentionally '=' here and not '=='.
}
else
{
--mnLockCount;
EAT_ASSERT((mThreadId = EA::Thread::kThreadIdInvalid) || true); // Intentionally '=' here and not '=='.
}
}
EA::Thread::MutexParameters::MutexParameters(bool bIntraProcess, const char* pName)
: mbIntraProcess(bIntraProcess)
{
mName[0] = '\0';
if (pName != nullptr)
{
strncpy(mName, pName, sizeof(mName) - 1);
mName[sizeof(mName) - 1] = '\0';
}
}
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);
scePthreadMutexDestroy(&mMutexData.mMutex);
}
bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters)
{
if(pMutexParameters)
{
mMutexData.mnLockCount = 0;
ScePthreadMutexattr attr;
scePthreadMutexattrInit(&attr);
scePthreadMutexattrSettype(&attr, SCE_PTHREAD_MUTEX_RECURSIVE);
#if defined(SCE_PTHREAD_PROCESS_PRIVATE) // Some pthread_disabled implementations don't recognize this.
if(pMutexParameters->mbIntraProcess)
scePthreadMutexattrSettype(&attr, SCE_PTHREAD_PROCESS_PRIVATE);
else
scePthreadMutexattrSettype(&attr, SCE_PTHREAD_PROCESS_PRIVATE);
#endif
// kettle mutex name is restricted to 32 bytes INCLUDING null character. See "scePthreadMutexInit"
char mutexNameCopy[32];
strncpy(mutexNameCopy, pMutexParameters->mName, sizeof(mutexNameCopy) - 1);
mutexNameCopy[sizeof(mutexNameCopy)-1] = '\0';
// Sony allocates memory for any length string which reduces the amount of active mutex allowed by the operating
// system. We only provide a string if it is non-zero in length.
int result = SCE_KERNEL_ERROR_EAGAIN;
if (pMutexParameters->mName[0] != '\0')
{
result = scePthreadMutexInit(&mMutexData.mMutex, &attr, mutexNameCopy);
}
if (result == SCE_KERNEL_ERROR_EAGAIN)
{
// We've hit the limit for named mutexes on PS4, so fallback to an unnamed mutex which has a much higher limit
result = scePthreadMutexInit(&mMutexData.mMutex, &attr, NULL);
}
scePthreadMutexattrDestroy(&attr);
EAT_ASSERT(SCE_OK == result);
return (SCE_OK == result);
}
return false;
}
int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute)
{
int result;
EAT_ASSERT(mMutexData.mnLockCount < 100000);
if(timeoutAbsolute == kTimeoutNone)
{
result = scePthreadMutexLock(&mMutexData.mMutex);
if(result != 0)
{
EAT_ASSERT(false);
return kResultError;
}
}
else if(timeoutAbsolute == kTimeoutImmediate)
{
result = scePthreadMutexTrylock(&mMutexData.mMutex);
if(result != 0)
{
if(result == SCE_KERNEL_ERROR_EBUSY)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
}
else
{
result = scePthreadMutexTimedlock(&mMutexData.mMutex, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(result != 0)
{
if(result == SCE_KERNEL_ERROR_ETIMEDOUT)
return kResultTimeout;
EAT_ASSERT(false);
return kResultError;
}
}
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(scePthreadMutexUnlock(&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
}
@@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <stdlib.h>
namespace EA
{
namespace Thread
{
// With some implementations of pthread_disabled, the stack base is returned by pthread_disabled as NULL if it's the main thread,
// or possibly if it's a thread you created but didn't call pthread_disabled_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.
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_disabled_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;
}
} // namespace Callstack
} // namespace EA
@@ -0,0 +1,177 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_semaphore.h>
#if defined(EA_PLATFORM_SONY)
#include <kernel/semaphore.h>
#include <sceerror.h>
EASemaphoreData::EASemaphoreData()
: mSemaphore(NULL), mnMaxCount(INT_MAX), mnCount(0)
{
}
EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName)
: mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess)
{
// Maximum lenght for the semaphore name on Kettle is 32 (including NULL terminator)
EAT_COMPILETIME_ASSERT(sizeof(mName) <= 32);
if (pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
{
mName[0] = 0;
}
}
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()
{
int result = sceKernelDeleteSema(mSemaphoreData.mSemaphore);
EAT_ASSERT(result == SCE_OK); EA_UNUSED(result);
}
bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
{
if (pSemaphoreParameters
&& pSemaphoreParameters->mInitialCount >= 0
&& pSemaphoreParameters->mMaxCount >= 0)
{
mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
int result = sceKernelCreateSema(
&mSemaphoreData.mSemaphore,
pSemaphoreParameters->mName,
SCE_KERNEL_SEMA_ATTR_TH_FIFO,
mSemaphoreData.mnCount,
mSemaphoreData.mnMaxCount,
NULL);
if (result == SCE_OK)
return true;
}
// Failure: could not create semaphore
return false;
}
int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
{
int result = 0;
// Convert timeout from absolute to relative (possibly losing some capacity)
SceKernelUseconds timeoutRelativeUs = static_cast<SceKernelUseconds>(RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
do
{
if (timeoutAbsolute == kTimeoutImmediate)
{
result = sceKernelPollSema(mSemaphoreData.mSemaphore, 1);
}
else
{
result = sceKernelWaitSema(mSemaphoreData.mSemaphore, 1, &timeoutRelativeUs);
}
if (result != SCE_OK)
{
// SCE_KERNEL_ERROR_ETIMEDOUT is the failure case for 'sceKernelWaitSema'
// SCE_KERNEL_ERROR_EBUSY is the failure case for 'sceKernelPollSema'
// We want to consume the SCE_KERNEL_ERROR_EBUSY error code from the polling interface
// users have a consistent error code to check against.
if (result == SCE_KERNEL_ERROR_ETIMEDOUT || result == SCE_KERNEL_ERROR_EBUSY)
{
if (timeoutAbsolute != kTimeoutNone)
return kResultTimeout;
}
else
{
EAT_FAIL_MSG("Semaphore::Wait: sceKernelWaitSema failure.");
return kResultError;
}
}
} while (result != SCE_OK);
// Success
EAT_ASSERT(mSemaphoreData.mnCount.GetValue() > 0);
return static_cast<int>(mSemaphoreData.mnCount.Decrement());
}
int EA::Thread::Semaphore::Post(int count)
{
EAT_ASSERT(count >= 0);
const int currentCount = mSemaphoreData.mnCount;
if (count > 0)
{
// If count would cause an overflow exit early
if ((mSemaphoreData.mnMaxCount - count) < currentCount)
return kResultError;
// We increment the count before we signal the semaphore so that any waken up
// thread will have the right count immediately
mSemaphoreData.mnCount.Add(count);
int result = sceKernelSignalSema(mSemaphoreData.mSemaphore, count);
if (result != SCE_OK)
{
// If not successful set the count back
mSemaphoreData.mnCount.Add(-count);
return kResultError;
}
}
return currentCount + count; // 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
{
// There is no way to query the semaphore for the resource count on Kettle,
// we need to rely on our external atomic counter
return mSemaphoreData.mnCount.GetValue();
}
#endif // EA_PLATFORM_SONY
@@ -0,0 +1,799 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_thread.h>
#include <eathread/eathread.h>
#include <eathread/eathread_sync.h>
#include <eathread/eathread_callstack.h>
#include <new>
#include <kernel.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sceerror.h>
#define EA_ALLOW_POSIX_THREADS_PRIORITIES 1
namespace
{
// We convert a an EAThread priority (higher value implies higher priority) to a native priority
// value, as some implementations of pthread_disableds use lower values to indicate higher priority.
void ConvertToNativePriority(int eathreadPriority, sched_param& param, int& policy)
{
using namespace EA::Thread;
policy = SCE_KERNEL_SCHED_RR;
const int nMin = SCE_KERNEL_PRIO_FIFO_HIGHEST;
const int nMax = SCE_KERNEL_PRIO_FIFO_LOWEST;
// Kettle pthread_disableds uses a reversed interpretation of sched_get_priority_min and sched_get_priority_max.
param.sched_priority = (SCE_KERNEL_PRIO_FIFO_DEFAULT + (-1 * eathreadPriority));
if(param.sched_priority < nMin)
param.sched_priority = nMin;
else if(param.sched_priority > nMax)
param.sched_priority = nMax;
}
// We convert a native priority value to an EAThread priority (higher value implies higher
// priority), as some implementations of pthread_disableds use lower values to indicate higher priority.
int ConvertFromNativePriority(const sched_param& param, int policy)
{
using namespace EA::Thread;
// 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.
// Kettle pthread_disableds uses a reversed interpretation of sched_get_priority_min and sched_get_priority_max.
return -1 * (param.sched_priority - SCE_KERNEL_PRIO_FIFO_DEFAULT);
}
// Setup stack and/or priority of a new thread
void SetupThreadAttributes(ScePthreadAttr& creationAttribs, const EA::Thread::ThreadParameters* pTP)
{
int result = 0;
EA_UNUSED( result ); //only used for assertions
// We create the thread as attached, and we'll call either pthread_disabled_join or pthread_disabled_detach,
// depending on whether WaitForEnd (pthread_disabled_join) is called or not (pthread_disabled_detach).
if(pTP)
{
// Set thread stack address and/or size
if(pTP->mpStack)
{
EAT_ASSERT(pTP->mnStackSize != 0);
result = scePthreadAttrSetstack(&creationAttribs, (void*)pTP->mpStack, pTP->mnStackSize);
EAT_ASSERT(result == 0);
}
else if(pTP->mnStackSize)
{
result = scePthreadAttrSetstacksize(&creationAttribs, pTP->mnStackSize);
EAT_ASSERT(result == 0);
}
// Set initial non-zero priority
// Even if pTP->mnPriority == kThreadPriorityDefault, we need to run this on some platforms, as the thread priority for new threads on them isn't the same as the thread priority for the main thread.
int policy = SCHED_OTHER;
sched_param param;
ConvertToNativePriority(pTP->mnPriority, param, policy);
result = scePthreadAttrSetschedpolicy(&creationAttribs, policy);
EAT_ASSERT(result == 0);
result = scePthreadAttrSetschedparam(&creationAttribs, &param);
EAT_ASSERT(result == 0);
// Unix doesn't let you specify thread CPU affinity via pthread_disabled attributes.
// Instead you need to call sched_setaffinity or pthread_setaffinity_np.
}
else
{
result = scePthreadAttrSetschedpolicy(&creationAttribs, SCE_KERNEL_SCHED_RR);
EAT_ASSERT(result == 0);
}
}
// 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)
{
EAT_COMPILETIME_ASSERT(EATHREAD_NAME_SIZE == 32); // New name (up to 32 bytes including the NULL terminator), or NULL
scePthreadRename(scePthreadSelf(), pName);
}
#endif
static void SetPlatformThreadAffinity(EAThreadDynamicData* pTDD)
{
if(pTDD->mThreadId != EA::Thread::kThreadIdInvalid) // If the thread has been created...
{
SceKernelCpumask mask;
mask = (1 << pTDD->mStartupProcessor) & 0xFF;
int nResult = scePthreadSetaffinity(pTDD->mSysThreadId, mask);
EAT_ASSERT(nResult == SCE_OK); EA_UNUSED(nResult);
}
// Else the thread hasn't started yet, or has already exited. Let the thread set its own
// affinity when it starts.
}
} // namespace
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
const size_t kMaxThreadDynamicDataCount = 128;
struct EAThreadGlobalVars
{
EA_PREFIX_ALIGN(8)
char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)] EA_POSTFIX_ALIGN(8);
AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount];
Mutex gThreadDynamicMutex;
};
EATHREAD_GLOBALVARS_CREATE_INSTANCE;
EAThreadDynamicData* AllocateThreadDynamicData()
{
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0))
return (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
}
// This is a safety fallback mechanism. In practice it won't be used in almost all situations.
if(gpAllocator)
return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData));
else
return (EAThreadDynamicData*)new char[sizeof(EAThreadDynamicData)]; // We assume the returned alignment is sufficient.
}
void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData)
{
if((pEAThreadDynamicData >= (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount)))
{
pEAThreadDynamicData->~EAThreadDynamicData();
EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0);
}
else
{
// Assume the data was allocated via the fallback mechanism.
pEAThreadDynamicData->~EAThreadDynamicData();
if(gpAllocator)
gpAllocator->Free(pEAThreadDynamicData);
else
delete[] (char*)pEAThreadDynamicData;
}
}
// This is a public function.
EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId)
{
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if(pTDD->mThreadId == threadId)
return pTDD;
}
return NULL; // This is no practical way we can find the data unless thread-specific storage was involved.
}
EAThreadDynamicData* FindThreadDynamicData(SysThreadId sysThreadId)
{
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)(void*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if(pTDD->mSysThreadId == sysThreadId)
return pTDD;
}
return NULL; // This is no practical way we can find the data unless thread-specific storage was involved.
}
}
}
EAThreadDynamicData::EAThreadDynamicData()
: mThreadId(EA::Thread::kThreadIdInvalid),
mSysThreadId(0),
mThreadPid(0),
mnStatus(EA::Thread::Thread::kStatusNone),
mnReturnValue(0),
//mpStartContext[],
mpBeginThreadUserWrapper(NULL),
mnRefCount(0),
//mName[],
mStartupProcessor(EA::Thread::kProcessorDefault),
mRunMutex(),
mStartedSemaphore(),
mnThreadAffinityMask(EA::Thread::kThreadAffinityMaskAny)
{
memset(mpStartContext, 0, sizeof(mpStartContext));
memset(mName, 0, sizeof(mName));
}
EAThreadDynamicData::~EAThreadDynamicData()
{
if(mThreadId != EA::Thread::kThreadIdInvalid)
scePthreadDetach(mSysThreadId);
mThreadId = EA::Thread::kThreadIdInvalid;
mThreadPid = 0;
mSysThreadId = 0;
}
void EAThreadDynamicData::AddRef()
{
mnRefCount.Increment(); // Note that mnRefCount is an AtomicInt32.
}
void EAThreadDynamicData::Release()
{
if(mnRefCount.Decrement() == 0) // Note that mnRefCount is an AtomicInt32.
EA::Thread::FreeThreadDynamicData(this);
}
EA::Thread::ThreadParameters::ThreadParameters()
: mpStack(NULL),
mnStackSize(0),
mnPriority(kThreadPriorityDefault),
mnProcessor(kProcessorDefault),
mpName(""),
mnAffinityMask(kThreadAffinityMaskAny),
mbDisablePriorityBoost(false)
{
// Empty
}
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL;
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL;
EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny;
EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff);
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper()
{
return sGlobalRunnableFunctionUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper)
{
if(sGlobalRunnableFunctionUserWrapper)
EAT_FAIL_MSG("Thread::SetGlobalRunnableFunctionUserWrapper already set."); // Can only be set once for the application.
else
sGlobalRunnableFunctionUserWrapper = pUserWrapper;
}
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper()
{
return sGlobalRunnableClassUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper)
{
if(sGlobalRunnableClassUserWrapper)
EAT_FAIL_MSG("EAThread::SetGlobalRunnableClassUserWrapper already set."); // Can only be set once for the application.
else
sGlobalRunnableClassUserWrapper = pUserWrapper;
}
EA::Thread::Thread::Thread()
{
mThreadData.mpData = NULL;
}
EA::Thread::Thread::Thread(const Thread& t)
: mThreadData(t.mThreadData)
{
if(mThreadData.mpData)
mThreadData.mpData->AddRef();
}
EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& t)
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if(t.mThreadData.mpData)
t.mThreadData.mpData->AddRef();
if(mThreadData.mpData)
mThreadData.mpData->Release();
mThreadData = t.mThreadData;
return *this;
}
EA::Thread::Thread::~Thread()
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if(mThreadData.mpData)
mThreadData.mpData->Release();
}
static void* RunnableFunctionInternal(void* pContext)
{
// The parent thread is sharing memory with us and we need to
// make sure our view of it is synchronized with the parent.
EAReadWriteBarrier();
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext;
EA::Thread::RunnableFunction pFunction = (EA::Thread::RunnableFunction)pTDD->mpStartContext[0];
void* pCallContext = pTDD->mpStartContext[1];
pTDD->mThreadPid = 0;
// Lock the runtime mutex which is used to allow other threads to wait on this thread with a timeout.
pTDD->mRunMutex.Lock(); // Important that this be before the semaphore post.
pTDD->mStartedSemaphore.Post(); // Announce that the thread has started.
pTDD->mnStatus = EA::Thread::Thread::kStatusRunning;
pTDD->mpStackBase = EA::Thread::GetStackBase();
#if !EATHREAD_OTHER_THREAD_NAMING_SUPPORTED
// Under Unix we need to set the thread name from the thread that is being named and not from an outside thread.
if(pTDD->mName[0])
SetCurrentThreadName(pTDD->mName);
#endif
#ifdef EA_PLATFORM_ANDROID
AttachJavaThread();
#endif
if(pTDD->mpBeginThreadUserWrapper)
{
// If user wrapper is specified, call user wrapper and pass the pFunction and pContext.
EA::Thread::RunnableFunctionUserWrapper pWrapperFunction = (EA::Thread::RunnableFunctionUserWrapper)pTDD->mpBeginThreadUserWrapper;
pTDD->mnReturnValue = pWrapperFunction(pFunction, pCallContext);
}
else
pTDD->mnReturnValue = pFunction(pCallContext);
#ifdef EA_PLATFORM_ANDROID
DetachJavaThread();
#endif
void* pReturnValue = (void*)pTDD->mnReturnValue;
pTDD->mnStatus = EA::Thread::Thread::kStatusEnded;
pTDD->mRunMutex.Unlock();
pTDD->Release();
return pReturnValue;
}
static void* RunnableObjectInternal(void* pContext)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext;
EA::Thread::IRunnable* pRunnable = (EA::Thread::IRunnable*)pTDD->mpStartContext[0];
void* pCallContext = pTDD->mpStartContext[1];
pTDD->mThreadPid = 0;
pTDD->mRunMutex.Lock(); // Important that this be before the semaphore post.
pTDD->mStartedSemaphore.Post();
pTDD->mnStatus = EA::Thread::Thread::kStatusRunning;
#if !EATHREAD_OTHER_THREAD_NAMING_SUPPORTED
// Under Unix we need to set the thread name from the thread that is being named and not from an outside thread.
if(pTDD->mName[0])
SetCurrentThreadName(pTDD->mName);
#endif
#ifdef EA_PLATFORM_ANDROID
AttachJavaThread();
#endif
if(pTDD->mpBeginThreadUserWrapper)
{
// If user wrapper is specified, call user wrapper and pass the pFunction and pContext.
EA::Thread::RunnableClassUserWrapper pWrapperClass = (EA::Thread::RunnableClassUserWrapper)pTDD->mpBeginThreadUserWrapper;
pTDD->mnReturnValue = pWrapperClass(pRunnable, pCallContext);
}
else
pTDD->mnReturnValue = pRunnable->Run(pCallContext);
#ifdef EA_PLATFORM_ANDROID
DetachJavaThread();
#endif
void* const pReturnValue = (void*)pTDD->mnReturnValue;
pTDD->mnStatus = EA::Thread::Thread::kStatusEnded;
pTDD->mRunMutex.Unlock();
pTDD->Release();
return pReturnValue;
}
void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask)
{
if(mThreadData.mpData && mThreadData.mpData->mThreadId)
{
EA::Thread::SetThreadAffinityMask(mThreadData.mpData->mThreadId, nAffinityMask);
}
}
EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask()
{
if(mThreadData.mpData->mThreadId)
{
return mThreadData.mpData->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
/// BeginThreadInternal
/// Extraction of both RunnableFunction and RunnableObject EA::Thread::Begin in order to have thread initialization
/// in one place
static EA::Thread::ThreadId BeginThreadInternal(EAThreadData& mThreadData, void* pRunnableOrFunction, void* pContext, const EA::Thread::ThreadParameters* pTP,
void* pUserWrapper, void* (*InternalThreadFunction)(void*))
{
using namespace EA::Thread;
// The parent thread is sharing memory with us and we need to
// make sure our view of it is synchronized with the parent.
EAReadWriteBarrier();
// Check there is an entry for the current thread context in our ThreadDynamicData array.
EA::Thread::ThreadId thisThreadId = EA::Thread::GetThreadId();
if(!FindThreadDynamicData(thisThreadId))
{
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData;
if(pData)
{
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
// Do no AddRef for thread execution because this is not an EAThread managed thread.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mThreadId = thisThreadId;
pData->mSysThreadId = GetSysThreadId();
strncpy(pData->mName, "external", EATHREAD_NAME_SIZE);
pData->mName[EATHREAD_NAME_SIZE - 1] = 0;
pData->mpStackBase = EA::Thread::GetStackBase();
}
}
if(mThreadData.mpData)
mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below.
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap.
EAT_ASSERT(pData);
if(pData)
{
mThreadData.mpData = pData;
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mThreadId = kThreadIdInvalid;
pData->mSysThreadId = kSysThreadIdInvalid;
pData->mThreadPid = 0;
pData->mnStatus = Thread::kStatusNone;
pData->mpStartContext[0] = pRunnableOrFunction;
pData->mpStartContext[1] = pContext;
pData->mpBeginThreadUserWrapper = pUserWrapper;
pData->mStartupProcessor = pTP ? pTP->mnProcessor % EA::Thread::GetProcessorCount() : kProcessorDefault;
pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny;
strncpy(pData->mName, (pTP && pTP->mpName) ? pTP->mpName : "", EATHREAD_NAME_SIZE);
pData->mName[EATHREAD_NAME_SIZE - 1] = 0;
// Pass NULL attribute pointer if there are no special setup steps
ScePthreadAttr* pCreationAttribs = NULL;
int result(0);
ScePthreadAttr creationAttribs;
scePthreadAttrInit(&creationAttribs);
// Sony has stated that we should call scePthreadAttrSetinheritsched, otherwise the
// thread priority set up in pthread_attr_t gets ignored by the newly created thread.
scePthreadAttrSetinheritsched(&creationAttribs, SCE_PTHREAD_EXPLICIT_SCHED);
if(pData->mStartupProcessor == EA::Thread::kProcessorAny)
{
if(pData->mnThreadAffinityMask == kThreadAffinityMaskAny)
// Unless you specifically set the thread affinity to SCE_KERNEL_CPUMASK_USER_ALL,
// Sony apparently assigns your thread to a single CPU.
scePthreadAttrSetaffinity(&creationAttribs, SCE_KERNEL_CPUMASK_USER_ALL);
else
scePthreadAttrSetaffinity(&creationAttribs, pData->mnThreadAffinityMask);
}
else if(pData->mStartupProcessor != kProcessorDefault)
{
SceKernelCpumask mask = (1 << pData->mStartupProcessor) & 0xFF;
scePthreadAttrSetaffinity(&creationAttribs, mask);
}
SetupThreadAttributes(creationAttribs, pTP);
pCreationAttribs = &creationAttribs;
result = scePthreadCreate(&pData->mSysThreadId, pCreationAttribs, InternalThreadFunction, pData, mThreadData.mpData->mName);
if(result == 0) // If success...
{
// NOTE: This cast must match the caset that is done in EA::Thread::GetThreadId.
pData->mThreadId = *reinterpret_cast<EA::Thread::ThreadId*>(pData->mSysThreadId);
ThreadId threadIdTemp = pData->mThreadId; // Temp value because Release below might delete pData.
// If additional attributes were used, free initialization data.
if(pCreationAttribs)
{
result = scePthreadAttrDestroy(pCreationAttribs);
EAT_ASSERT(result == 0);
}
pData->Release(); // Matches AddRef for this function.
return threadIdTemp;
}
// If additional attributes were used, free initialization data
if(pCreationAttribs)
{
result = scePthreadAttrDestroy(pCreationAttribs);
EAT_ASSERT(result == 0);
}
pData->Release(); // Matches AddRef for "cleanup" above.
pData->Release(); // Matches AddRef for this Thread class above.
pData->Release(); // Matches AddRef for thread above.
mThreadData.mpData = NULL; // mThreadData.mpData == pData
}
return (ThreadId)kThreadIdInvalid;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction pFunction, void* pContext,
const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper)
{
ThreadId threadId = BeginThreadInternal(mThreadData, reinterpret_cast<void*>((uintptr_t)pFunction), pContext, pTP,
reinterpret_cast<void*>((uintptr_t)pUserWrapper), RunnableFunctionInternal);
return threadId;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* pRunnable, void* pContext,
const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper)
{
ThreadId threadId = BeginThreadInternal(mThreadData, reinterpret_cast<void*>((uintptr_t)pRunnable), pContext, pTP,
reinterpret_cast<void*>((uintptr_t)pUserWrapper), RunnableObjectInternal);
return threadId;
}
EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue)
{
// In order to support timeoutAbsolute, we don't just call pthread_disabled_join, as that's an infinitely blocking call.
// Instead we wait on a Mutex (with a timeout) which the running thread locked, and will unlock as it is exiting.
// Only after the successful Mutex lock do we call pthread_disabled_join, as we know that it won't block for an indeterminate
// amount of time (barring a thread priority inversion problem). If the user never calls WaitForEnd, then we
// will eventually call pthread_disabled_detach in the EAThreadDynamicData destructor.
// The mThreadData memory is shared between threads and when
// reading it we must be synchronized.
EAReadWriteBarrier();
// A mutex lock around mpData is not needed below because mpData is never allowed to go from non-NULL to NULL.
// However, there is an argument that can be made for placing a memory read barrier before reading it.
if(mThreadData.mpData) // If this is non-zero then we must have created the thread.
{
// We must not call WaitForEnd from the thread we are waiting to end.
// That would result in a deadlock, at least if the timeout was infinite.
EAT_ASSERT(mThreadData.mpData->mThreadId != EA::Thread::GetThreadId());
Status currentStatus = GetStatus();
if(currentStatus == kStatusNone) // If the thread hasn't started yet...
{
// The thread has not been started yet. Wait on the semaphore (which is posted when the thread actually starts executing).
Semaphore::Result result = (Semaphore::Result)mThreadData.mpData->mStartedSemaphore.Wait(timeoutAbsolute);
EAT_ASSERT(result != Semaphore::kResultError);
if(result >= 0) // If the Wait succeeded, as opposed to timing out...
{
// We know for sure that the thread status is running now.
currentStatus = kStatusRunning;
mThreadData.mpData->mStartedSemaphore.Post(); // Re-post the semaphore so that any other callers of WaitForEnd don't block on the Wait above.
}
} // fall through.
if(currentStatus == kStatusRunning) // If the thread has started but not yet exited...
{
// Lock on the mutex (which is available when the thread is exiting)
Mutex::Result result = (Mutex::Result)mThreadData.mpData->mRunMutex.Lock(timeoutAbsolute);
EAT_ASSERT(result != Mutex::kResultError);
if(result > 0) // If the Lock succeeded, as opposed to timing out... then the thread has exited or is in the process of exiting.
{
// Do a pthread_disabled join. This is a blocking call, but we know that it will end very soon,
// as the mutex unlock the thread did is done right before the thread returns to the OS.
// The return value of pthread_disabled_join has information that isn't currently useful to us.
scePthreadJoin(mThreadData.mpData->mSysThreadId, NULL);
mThreadData.mpData->mThreadId = kThreadIdInvalid;
// We know for sure that the thread status is ended now.
currentStatus = kStatusEnded;
mThreadData.mpData->mRunMutex.Unlock();
}
// Else the Lock timed out, which means that the thread didn't exit before we ran out of time.
// In this case we need to return to the user that the status is kStatusRunning.
}
else
{
// Else currentStatus == kStatusEnded.
scePthreadJoin(mThreadData.mpData->mSysThreadId, NULL);
mThreadData.mpData->mThreadId = kThreadIdInvalid;
}
if(currentStatus == kStatusEnded)
{
// Call GetStatus again to get the thread return value.
currentStatus = GetStatus(pThreadReturnValue);
}
return currentStatus;
}
else
{
// Else the user hasn't started the thread yet, so we wait until the user starts it.
// Ideally, what we really want to do here is wait for some kind of signal.
// Instead for the time being we do a polling loop.
while((!mThreadData.mpData || (mThreadData.mpData->mThreadId == kThreadIdInvalid)) && (GetThreadTime() < timeoutAbsolute))
{
ThreadSleep(1);
EAReadWriteBarrier();
EACompilerMemoryBarrier();
}
if(mThreadData.mpData)
return WaitForEnd(timeoutAbsolute);
}
return kStatusNone;
}
EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* pThreadReturnValue) const
{
if(mThreadData.mpData)
{
EAReadBarrier();
Status status = (Status)mThreadData.mpData->mnStatus;
if(pThreadReturnValue && (status == kStatusEnded))
*pThreadReturnValue = mThreadData.mpData->mnReturnValue;
return status;
}
return kStatusNone;
}
EA::Thread::ThreadId EA::Thread::Thread::GetId() const
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
return mThreadData.mpData->mThreadId;
return kThreadIdInvalid;
}
int EA::Thread::Thread::GetPriority() const
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
{
int policy;
sched_param param;
int result = scePthreadGetschedparam(mThreadData.mpData->mSysThreadId, &policy, &param);
if(result == 0)
return ConvertFromNativePriority(param, policy);
return kThreadPriorityDefault;
}
return kThreadPriorityUnknown;
}
bool EA::Thread::Thread::SetPriority(int nPriority)
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
EAT_ASSERT(nPriority != kThreadPriorityUnknown);
if(mThreadData.mpData)
{
int policy;
sched_param param;
int result = scePthreadGetschedparam(mThreadData.mpData->mSysThreadId, &policy, &param);
if(result == 0) // If success...
{
ConvertToNativePriority(nPriority, param, policy);
result = scePthreadSetschedparam(mThreadData.mpData->mSysThreadId, policy, &param);
}
return (result == 0);
}
return false;
}
// To consider: Make it so we return a value.
void EA::Thread::Thread::SetProcessor(int nProcessor)
{
if(mThreadData.mpData)
{
mThreadData.mpData->mStartupProcessor = nProcessor; // Assign this in case the thread hasn't started yet and thus we are leaving it a message to set it when it has started.
SetPlatformThreadAffinity(mThreadData.mpData);
}
}
void EA::Thread::Thread::Wake()
{
// Todo: implement this. The solution is to use a signal to wake the sleeping thread via an EINTR.
// Possibly use the SIGCONT signal. Have to look into this to tell what the best approach is.
}
const char* EA::Thread::Thread::GetName() const
{
return mThreadData.mpData ? mThreadData.mpData->mName : "";
}
void EA::Thread::Thread::SetName(const char* pName)
{
if(mThreadData.mpData && pName)
SetThreadName(mThreadData.mpData->mThreadId, pName);
}
@@ -0,0 +1,687 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread.h>
#include <eathread/eathread_atomic.h>
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/eathread_storage.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <unwind.h>
#if defined(EA_PLATFORM_BSD)
#include <sys/signal.h>
#include <machine/signal.h>
#elif defined(EA_PLATFORM_LINUX)
#include <signal.h>
#endif
namespace EA
{
namespace Thread
{
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
// Nothing needed.
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
// Nothing needed.
}
EATHREADLIB_API void GetInstructionPointer(void*& p)
{
// Currently all platforms that have <unwind.h> have __builtin_return_address().
p = __builtin_return_address(0);
}
// This is a callback function which libunwind calls, once per callstack entry.
/*
Linux for ARM:
enum _Unwind_Reason_Code {
_URC_OK = 0, // operation completed successfully
_URC_FOREIGN_EXCEPTION_CAUGHT = 1,
_URC_END_OF_STACK = 5,
_URC_HANDLER_FOUND = 6,
_URC_INSTALL_CONTEXT = 7,
_URC_CONTINUE_UNWIND = 8,
_URC_FAILURE = 9 // unspecified failure of some kind
};
#define _URC_NO_REASON _URC_OK
BSD (and I think also Linux for x86/x64):
enum _Unwind_Reason_Code {
_URC_NO_REASON = 0,
_URC_FOREIGN_EXCEPTION_CAUGHT = 1,
_URC_FATAL_PHASE2_ERROR = 2,
_URC_FATAL_PHASE1_ERROR = 3,
_URC_NORMAL_STOP = 4,
_URC_END_OF_STACK = 5,
_URC_HANDLER_FOUND = 6,
_URC_INSTALL_CONTEXT = 7,
_URC_CONTINUE_UNWIND = 8
};
*/
struct UnwindCallbackContext
{
void** mpReturnAddressArray;
size_t mReturnAddressArrayCapacity;
size_t mReturnAddressArrayIndex;
};
static _Unwind_Reason_Code UnwindCallback(_Unwind_Context* pUnwindContext, void* pUnwindCallbackContextVoid)
{
UnwindCallbackContext* pUnwindCallbackContext = (UnwindCallbackContext*)pUnwindCallbackContextVoid;
if(pUnwindCallbackContext->mReturnAddressArrayIndex < pUnwindCallbackContext->mReturnAddressArrayCapacity)
{
uintptr_t ip = _Unwind_GetIP(pUnwindContext);
pUnwindCallbackContext->mpReturnAddressArray[pUnwindCallbackContext->mReturnAddressArrayIndex++] = (void*)ip;
return _URC_NO_REASON;
}
#if defined(EA_PLATFORM_LINUX)
return _URC_NO_REASON; // Is there a way to tell the caller that we want to stop?
#else
return _URC_NORMAL_STOP;
#endif
}
/*
The following commented-out code is for reading the callstack of a thread other than the current one.
The code below is originally for BSD Unix, and probably needs to be tweaked to support Linux.
namespace Local
{
enum EAThreadBacktraceState
{
// Positive thread lwp ids are here implicitly.
EATHREAD_BACKTRACE_STATE_NONE = -1,
EATHREAD_BACKTRACE_STATE_DUMPING = -2,
EATHREAD_BACKTRACE_STATE_DONE = -3,
EATHREAD_BACKTRACE_STATE_CANCEL = -4
};
struct ThreadBacktraceState
{
EA::Thread::AtomicInt32 mState; // One of enum EAThreadBacktraceState or (initially) the thread id of the thread we are targeting.
void** mCallstack; // Output param
size_t mCallstackCapacity; // Input param, refers to array capacity of mCallstack.
size_t mCallstackCount; // Output param
pthread_t mPthread; // Output param
ThreadBacktraceState() : mState(EATHREAD_BACKTRACE_STATE_NONE), mCallstackCapacity(0), mCallstackCount(0), mPthread(NULL){}
};
static pthread_mutex_t gThreadBacktraceMutex = PTHREAD_MUTEX_INITIALIZER;
static ThreadBacktraceState gThreadBacktraceState; // Protected by gThreadBacktraceMutex.
static void gThreadBacktraceSignalHandler(int sigNum, siginfo_t* pSigInfo, void* pSigContextVoid)
{
int32_t lwpSelf = *(int32_t*)pthread_self();
if(gThreadBacktraceState.mState.SetValueConditional(EATHREAD_BACKTRACE_STATE_DUMPING, lwpSelf))
{
gThreadBacktraceState.mPthread = pthread_self();
if(gThreadBacktraceState.mCallstackCapacity)
{
gThreadBacktraceState.mCallstackCount = GetCallstack(gThreadBacktraceState.mCallstack, gThreadBacktraceState.mCallstackCapacity, (const CallstackContext*)NULL);
// At this point we need to remove the top 6 entries and insert an entry for where the thread's instruction pointer is.
if(gThreadBacktraceState.mCallstackCount >= 6) // This should always be true.
{
gThreadBacktraceState.mCallstackCount -= 5;
memmove(&gThreadBacktraceState.mCallstack[1], &gThreadBacktraceState.mCallstack[6], (gThreadBacktraceState.mCallstackCount - 1) * sizeof(void*));
}
else
gThreadBacktraceState.mCallstackCount = 1;
gThreadBacktraceState.mCallstack[0] = pSigContextVoid ? reinterpret_cast<void*>(reinterpret_cast<sigcontext*>((uintptr_t)pSigContextVoid + 48)->sc_rip) : NULL;
}
else
gThreadBacktraceState.mCallstackCount = 0;
gThreadBacktraceState.mState.SetValue(EATHREAD_BACKTRACE_STATE_DONE);
}
// else this thread received an unexpected SIGURG. This can happen if it was so delayed that
// we timed out waiting for it to happen and moved on.
}
}
/// GetCallstack
///
/// This is a version of GetCallstack which gets the callstack of a thread based on its thread id as opposed to
/// its register state. It works by injecting a signal handler into the given thread and reading the self callstack
/// then exiting from the signal handler. The GetCallstack function sets this up, generates the signal for the
/// other thread, then waits for it to complete. It uses the SIGURG signal for this.
///
/// Primary causes of failure:
/// The target thread has SIGURG explicitly ignored.
/// The target thread somehow is getting too little CPU time to respond to the signal.
///
/// To do: Change this function to take a ThreadInfo as a last parameter instead of pthread_t. And have the
/// ThreadInfo return additional basic thread information. Or maybe even change this function to be a
/// GetThreadInfo function instead of GetCallstack.
///
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, pthread_t& pthread)
{
using namespace Local;
size_t callstackCount = 0;
if(pthread)
{
pthread_t pthreadSelf = pthread_self();
int32_t lwp = *(int32_t*)pthread;
int32_t lwpSelf = *(int32_t*)pthreadSelf;
if(lwp == lwpSelf) // This function can be called only for a thread other than self.
callstackCount = GetCallstack(pReturnAddressArray, nReturnAddressArrayCapacity, (const CallstackContext*)NULL);
else
{
struct sigaction act; memset(&act, 0, sizeof(act));
struct sigaction oact; memset(&oact, 0, sizeof(oact));
act.sa_sigaction = gThreadBacktraceSignalHandler;
act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
pthread_mutex_lock(&gThreadBacktraceMutex);
if(sigaction(SIGURG, &act, &oact) == 0)
{
gThreadBacktraceState.mCallstack = pReturnAddressArray;
gThreadBacktraceState.mCallstackCapacity = nReturnAddressArrayCapacity;
gThreadBacktraceState.mState.SetValue(lwp);
// Signal the specific thread that we want to dump.
int32_t stateTemp = lwp;
if(pthread_kill(pthread, SIGURG) == 0)
{
// Wait for the other thread to start dumping the stack, or time out.
for(int waitMS = 200; waitMS; waitMS--)
{
stateTemp = gThreadBacktraceState.mState.GetValue();
if(stateTemp != lwp)
break;
usleep(1000); // This sleep gives the OS the opportunity to execute the target thread, even if it's of a lower priority than this thread.
}
}
// else apparently failed to send SIGURG to the thread, or the thread was paused in a way that it couldn't receive it.
if(stateTemp == lwp) // If the operation timed out or seemingly never started...
{
if(gThreadBacktraceState.mState.SetValueConditional(EATHREAD_BACKTRACE_STATE_CANCEL, lwp)) // If the backtrace still didn't start, and we were able to stop it by setting the state to cancel...
stateTemp = EATHREAD_BACKTRACE_STATE_CANCEL;
else
stateTemp = gThreadBacktraceState.mState.GetValue(); // It looks like the backtrace thread did in fact get a late start and is now executing
}
// Wait indefinitely for the dump to finish or be canceled.
// We cannot apply a timeout here because the other thread is accessing state that
// is owned by this thread.
for(int waitMS = 100; (stateTemp == EATHREAD_BACKTRACE_STATE_DUMPING) && waitMS; waitMS--) // If the thread is (still) busy writing it out its callstack...
{
usleep(1000);
stateTemp = gThreadBacktraceState.mState.GetValue();
}
if(stateTemp == EATHREAD_BACKTRACE_STATE_DONE)
callstackCount = gThreadBacktraceState.mCallstackCount;
// Else give up on it. It's OK to just fall through.
// Restore the original SIGURG handler.
sigaction(SIGURG, &oact, NULL);
}
pthread_mutex_unlock(&gThreadBacktraceMutex);
}
}
return callstackCount;
}
*/
///////////////////////////////////////////////////////////////////////////////
// GetCallstack
//
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
// libunwind can only read the stack from the current thread.
// However, we can accomplish this for another thread by injecting a signal handler into that thread.
// See the EAThreadBacktrace() function source code above.
if(pContext == NULL) // If reading the current thread's context...
{
UnwindCallbackContext context = { pReturnAddressArray, nReturnAddressArrayCapacity, 0 };
_Unwind_Backtrace(&UnwindCallback, &context);
if (context.mReturnAddressArrayIndex > 0)
{
--context.mReturnAddressArrayIndex; // Remove the first entry, because it refers to this function and by design we don't include this function.
memmove(pReturnAddressArray, pReturnAddressArray + 1, context.mReturnAddressArrayIndex * sizeof(void*));
}
return context.mReturnAddressArrayIndex;
}
// We don't yet have a means to read another thread's callstack via only the CallstackContext.
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId)
{
// True Linux-based ARM platforms (usually tablets and phones) can use pthread_attr_getstack.
#if defined(EA_PLATFORM_ANDROID)
if((threadId == (intptr_t)kThreadIdInvalid) ||
(threadId == (intptr_t)kThreadIdCurrent) ||
(threadId == (intptr_t)EA::Thread::GetThreadId()))
{
// Note: the behavior below is inconsistent between platforms and needs to be made so.
#if defined(__ARMCC_VERSION) // If using the ARM Compiler...
context.mSP = (uint32_t)__current_sp();
context.mLR = (uint32_t)__return_address();
context.mPC = (uint32_t)__current_pc();
context.mStackPointer = context.mSP;
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
#if defined(EA_PROCESSOR_X86_64)
context.mRIP = (uint64_t)__builtin_return_address(0);
context.mRSP = (uint64_t)__builtin_frame_address(1);
context.mRBP = 0;
context.mStackPointer = context.mRSP;
#elif defined(EA_PROCESSOR_X86)
context.mEIP = (uint32_t)__builtin_return_address(0);
context.mESP = (uint32_t)__builtin_frame_address(1);
context.mEBP = 0;
context.mStackPointer = context.mESP;
#elif defined(EA_PROCESSOR_ARM32)
// register uintptr_t current_sp asm ("sp");
context.mSP = (uint32_t)__builtin_frame_address(0);
context.mLR = (uint32_t)__builtin_return_address(0);
void* pInstruction;
EAGetInstructionPointer(pInstruction);
context.mPC = reinterpret_cast<uintptr_t>(pInstruction);
context.mStackPointer = context.mSP;
#elif defined(EA_PROCESSOR_ARM64)
// register uintptr_t current_sp asm ("sp");
context.mSP = (uint64_t)__builtin_frame_address(0);
context.mLR = (uint64_t)__builtin_return_address(0);
void* pInstruction;
EAGetInstructionPointer(pInstruction);
context.mPC = reinterpret_cast<uintptr_t>(pInstruction);
context.mStackPointer = context.mSP;
#endif
#endif
context.mStackBase = (uintptr_t)GetStackBase();
context.mStackLimit = (uintptr_t)GetStackLimit();
return true;
}
else
{
// Else haven't implemented getting the stack info for other threads
memset(&context, 0, sizeof(context));
return false;
}
#else
pthread_t self = pthread_self();
pthread_t pthreadId = (typeof(pthread_t))threadId; // Requires that pthread_t is a pointer or integral type.
if(pthread_equal(pthreadId, self))
{
void* pInstruction;
// This is some crazy GCC code that happens to work:
pInstruction = ({ __label__ label; label: &&label; });
// Note: the behavior below is inconsistent between platforms and needs to be made so.
#if defined(EA_PROCESSOR_X86_64)
context.mRIP = (uint64_t)pInstruction;
context.mRSP = (uint64_t)__builtin_frame_address(1);
context.mRBP = 0;
#elif defined(EA_PROCESSOR_X86)
context.mEIP = (uint32_t)__builtin_return_address(0);
context.mESP = (uint32_t)__builtin_frame_address(1);
context.mEBP = 0;
#endif
return true;
}
else
{
// There is currently no way to do this.
memset(&context, 0, sizeof(context));
return false;
}
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
// Assuming we are using pthreads, sysThreadId == threadId.
return GetCallstackContext(context, sysThreadId);
}
///////////////////////////////////////////////////////////////////////////////
// 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_ARM32)
context.mSP = pContext->mGpr[13];
context.mLR = pContext->mGpr[14];
context.mPC = pContext->mGpr[15];
#elif defined(EA_PROCESSOR_ARM64)
context.mSP = pContext->mGpr[31];
context.mLR = pContext->mGpr[30];
context.mPC = pContext->mPC;
#else
// To do.
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
// Returns the required strlen of pModuleName.
//
EATHREADLIB_API size_t GetModuleFromAddress(const void* address, char* pModuleName, size_t moduleNameCapacity)
{
#if 0 // Disabled until testable: defined(EA_PLATFORM_LINUX)
// The output of reading /proc/self/maps is like the following (there's no leading space on each line).
// We look for entries that have r-x as the first three flags, as they are executable modules.
// The format is (http://linux.die.net/man/5/proc):
// <begin address>-<end address> <flags> <offset> <device major>:<device minor> <inode> <path>
//
// 00400000-0040b000 r-xp 00000000 08:01 655382 /bin/cat
// 0060a000-0060b000 r--p 0000a000 08:01 655382 /bin/cat
// 0060b000-0060c000 rw-p 0000b000 08:01 655382 /bin/cat
// 0060c000-0062d000 rw-p 00000000 00:00 0 [heap]
// 7ffff77b5000-7ffff7a59000 r--p 00000000 08:01 395618 /usr/lib/locale/locale-archive
// 7ffff7a59000-7ffff7bd3000 r-xp 00000000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7bd3000-7ffff7dd2000 ---p 0017a000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7dd2000-7ffff7dd6000 r--p 00179000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7dd6000-7ffff7dd7000 rw-p 0017d000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7dd7000-7ffff7ddc000 rw-p 00000000 00:00 0
// 7ffff7ddc000-7ffff7dfc000 r-xp 00000000 08:01 1062651 /lib/ld-2.12.1.so
// 7ffff7fd9000-7ffff7fdc000 rw-p 00000000 00:00 0
// 7ffff7ff9000-7ffff7ffb000 rw-p 00000000 00:00 0
// 7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
// 7ffff7ffc000-7ffff7ffd000 r--p 00020000 08:01 1062651 /lib/ld-2.12.1.so
// 7ffff7ffd000-7ffff7ffe000 rw-p 00021000 08:01 1062651 /lib/ld-2.12.1.so
// 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
// 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
// ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
FILE* file = fopen("/proc/self/maps", "rt");
if(file)
{
uint64_t address64 = (uint64_t)reinterpret_cast<uintptr_t>(address);
char lineBuffer[1024];
while(fgets(lineBuffer, sizeof(lineBuffer), file) != NULL)
{
size_t lineLength = strlen(lineBuffer);
if((lineLength > 0) && (lineBuffer[lineLength - 1] == '\n'))
lineBuffer[--lineLength] = '\0';
uint64_t start, end, offset, devMajor, devMinor, inode;
char flags[4];
char path[512 + 1];
// 7ffff7ddc000-7ffff7dfc000 r-xp 00000000 08:01 1062651 /lib/ld-2.12.1.so
int fieldCount = EA::StdC::Sscanf(lineBuffer, "%I64x-%I64x %c%c%c%c %I64x %I64d:%I64d %I64x %512s",
&start, &end, &flags[0], &flags[1], &flags[2], &flags[3], &offset,
&devMajor, &devMinor, &inode, path);
if(fieldCount == 11)
{
if((flags[0] == 'r') && (flags[1] == '-') && (flags[2] == 'x')) // If this looks like an executable module...
{
if((address64 >= start) && (address64 < end)) // If this is the module that corresponds to the input address
{
// We can't strcpy path as-is because it might be truncated due to spaces in the file name.
// So we get the location path is in the original lineBuffer and strcpy everything till the end.
char* pPathBegin = EA::StdC::Strstr(lineBuffer, path);
return EA::StdC::Strlcpy(pModuleName, pPathBegin, moduleNameCapacity);
}
}
}
}
fclose(file);
}
#else
EA_UNUSED(address);
// Probably also doable for BSD.
// http://freebsd.1045724.n5.nabble.com/How-to-get-stack-bounds-of-current-process-td4053477.html
#endif
if(moduleNameCapacity > 0)
pModuleName[0] = 0;
return 0;
}
/*
uint64_t GetLibraryAddressLinux(const char* pModuleName)
{
// The output of reading /proc/self/maps is like the following (there's no leading space on each line).
// We look for entries that have r-x as the first three flags, as they are executable modules.
// The format is (http://linux.die.net/man/5/proc):
// <begin address>-<end address> <flags> <offset> <device major>:<device minor> <inode> <path>
//
// 00400000-0040b000 r-xp 00000000 08:01 655382 /bin/cat
// 0060a000-0060b000 r--p 0000a000 08:01 655382 /bin/cat
// 0060b000-0060c000 rw-p 0000b000 08:01 655382 /bin/cat
// 0060c000-0062d000 rw-p 00000000 00:00 0 [heap]
// 7ffff77b5000-7ffff7a59000 r--p 00000000 08:01 395618 /usr/lib/locale/locale-archive
// 7ffff7a59000-7ffff7bd3000 r-xp 00000000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7bd3000-7ffff7dd2000 ---p 0017a000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7dd2000-7ffff7dd6000 r--p 00179000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7dd6000-7ffff7dd7000 rw-p 0017d000 08:01 1062643 /lib/libc-2.12.1.so
// 7ffff7dd7000-7ffff7ddc000 rw-p 00000000 00:00 0
// 7ffff7ddc000-7ffff7dfc000 r-xp 00000000 08:01 1062651 /lib/ld-2.12.1.so
// 7ffff7fd9000-7ffff7fdc000 rw-p 00000000 00:00 0
// 7ffff7ff9000-7ffff7ffb000 rw-p 00000000 00:00 0
// 7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
// 7ffff7ffc000-7ffff7ffd000 r--p 00020000 08:01 1062651 /lib/ld-2.12.1.so
// 7ffff7ffd000-7ffff7ffe000 rw-p 00021000 08:01 1062651 /lib/ld-2.12.1.so
// 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
// 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
// ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
uint64_t baseAddress = 0;
FILE* file = fopen("/proc/self/maps", "rt");
if(file)
{
size_t moduleNameLength = strlen(pModuleName);
char lineBuffer[512];
while(fgets(lineBuffer, sizeof lineBuffer, file) != NULL)
{
size_t lineLength = strlen(lineBuffer);
if((lineLength > 0) && (lineBuffer[lineLength - 1] == '\n'))
lineBuffer[--lineLength] = '\0';
if((lineLength >= moduleNameLength) &&
memcmp(lineBuffer + lineLength - moduleNameLength, pModuleName, moduleNameLength) == 0)
{
uint64_t start, end, offset;
char flags[4];
if(EA::StdC::Sscanf(lineBuffer, "%I64x-%I64x %c%c%c%c %I64x", &start, &end,
&flags[0], &flags[1], &flags[2], &flags[3], &offset) == 7)
{
if((flags[0] == 'r') && (flags[1] == '-') && (flags[2] == 'x')) // If this looks like an executable module...
{
// Note: I don't understand from the Linux documentation what the 'offset' value really means
// and how we are supposed to use it. Example code shows it being subtracted from offset, though
// offset is usually 0.
baseAddress = (start - offset);
break;
}
}
}
}
fclose(file);
}
return baseAddress;
}
*/
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* /*pAddress*/)
{
// This is doable for Linux-based platforms via fopen("/proc/self/maps")
// Probably also doable for BSD.
// http://freebsd.1045724.n5.nabble.com/How-to-get-stack-bounds-of-current-process-td4053477.html
// Not currently implemented for the given platform.
return 0;
}
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()
{
void* pBase;
if(GetPthreadStackInfo(&pBase, NULL))
return pBase;
// 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).
pBase = sStackBase.GetValue();
if(pBase == NULL)
pBase = (void*)(((uintptr_t)&pBase + 4095) & ~4095); // Make a guess, round up to next 4096.
return pBase;
}
///////////////////////////////////////////////////////////////////////////////
// GetStackLimit
//
EATHREADLIB_API void* GetStackLimit()
{
void* pLimit;
if(GetPthreadStackInfo(NULL, &pLimit))
return pLimit;
pLimit = __builtin_frame_address(0);
return (void*)((uintptr_t)pLimit & ~4095); // Round down to nearest page, as the stack grows downward.
}
} // namespace Thread
} // namespace EA
@@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/eathread_storage.h>
#include <string.h>
#if defined(_MSC_VER)
#pragma warning(disable: 4172) // returning address of local variable or temporary
#endif
namespace EA
{
namespace Thread
{
EATHREADLIB_API void InitCallstack()
{
}
EATHREADLIB_API void ShutdownCallstack()
{
}
EATHREADLIB_API size_t GetCallstack(void* /*callstack*/[], size_t /*maxDepth*/, const CallstackContext* /*pContext*/)
{
return 0;
}
EATHREADLIB_API bool GetCallstackContext(CallstackContext& /*context*/, intptr_t /*threadId*/)
{
return false;
}
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& /*context*/, intptr_t /*sysThreadId*/)
{
return false;
}
EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* /*pContext*/)
{
memset(&context, 0, sizeof(context));
}
EATHREADLIB_API size_t GetModuleFromAddress(const void* /*pAddress*/, char* /*pModuleFileName*/, size_t /*moduleNameCapacity*/)
{
return 0;
}
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* /*pAddress*/)
{
return (ModuleHandle)0;
}
#if EA_THREADS_AVAILABLE
static EA::Thread::ThreadLocalStorage sStackBase;
#else
static void* sStackBase;
#endif
///////////////////////////////////////////////////////////////////////////////
// SetStackBase
//
EATHREADLIB_API void SetStackBase(void* pStackBase)
{
if(pStackBase)
{
#if EA_THREADS_AVAILABLE
sStackBase.SetValue(pStackBase);
#else
sStackBase = pStackBase;
#endif
}
else
{
pStackBase = GetStackBase();
SetStackBase(pStackBase);
}
}
///////////////////////////////////////////////////////////////////////////////
// GetStackBase
//
EATHREADLIB_API void* GetStackBase()
{
#if EA_THREADS_AVAILABLE
void* pStackBase = sStackBase.GetValue();
#else
void* pStackBase = sStackBase;
#endif
if(!pStackBase)
pStackBase = (void*)(((uintptr_t)GetStackLimit() + 4095) & ~4095); // Align up to nearest page, as the stack grows downward.
return pStackBase;
}
///////////////////////////////////////////////////////////////////////////////
// GetStackLimit
//
EATHREADLIB_API void* GetStackLimit()
{
void* pStack = NULL;
pStack = &pStack;
return (void*)((uintptr_t)pStack & ~4095); // Round down to nearest page, as the stack grows downward.
}
} // namespace Thread
} // namespace EA
@@ -0,0 +1,536 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/eathread_storage.h>
#if defined(EA_PLATFORM_WIN32) && EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) // The following only works on Win32 and not Win64.
#if defined(_MSC_VER)
#pragma warning(push, 0)
#endif
#include <Windows.h>
#include <DbgHelp.h>
#include <stdio.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#ifdef _MSC_VER
#pragma warning(disable: 4740) // flow in or out of inline asm code suppresses global optimization
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "psapi.lib")
#endif
typedef BOOL (__stdcall *SYMINITIALIZE)(HANDLE, LPSTR, BOOL);
typedef BOOL (__stdcall *SYMCLEANUP)(HANDLE);
typedef BOOL (__stdcall *STACKWALK)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID,PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE,PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE);
typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESS)(HANDLE, DWORD);
typedef DWORD (__stdcall *SYMGETMODULEBASE)(HANDLE, DWORD);
typedef BOOL (__stdcall *SYMGETSYMFROMADDR)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL);
typedef BOOL (__stdcall *SYMGETLINEFROMADDR)(HANDLE, DWORD, PDWORD, PIMAGEHLP_LINE);
namespace // We construct an anonymous namespace because doing so keeps the definitions within it local to this module.
{
struct Win32DbgHelp
{
HMODULE mhDbgHelp;
bool mbSymInitialized;
SYMINITIALIZE mpSymInitialize;
SYMCLEANUP mpSymCleanup;
STACKWALK mpStackWalk;
SYMFUNCTIONTABLEACCESS mpSymFunctionTableAccess;
SYMGETMODULEBASE mpSymGetModuleBase;
SYMGETSYMFROMADDR mpSymGetSymFromAddr;
SYMGETLINEFROMADDR mpSymGetLineFromAddr;
Win32DbgHelp() : mhDbgHelp(0), mbSymInitialized(false), mpSymInitialize(0),
mpSymCleanup(0), mpStackWalk(0), mpSymFunctionTableAccess(0),
mpSymGetModuleBase(0), mpSymGetSymFromAddr(0), mpSymGetLineFromAddr(0)
{
// Empty. The initialization is done externally, due to tricky startup/shutdown ordering issues.
}
~Win32DbgHelp()
{
// Empty. The shutdown is done externally, due to tricky startup/shutdown ordering issues.
}
void Init()
{
if(!mhDbgHelp)
{
mhDbgHelp = ::LoadLibraryA("DbgHelp.dll");
if(mhDbgHelp)
{
mpSymInitialize = (SYMINITIALIZE)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymInitialize");
mpSymCleanup = (SYMCLEANUP)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymCleanup");
mpStackWalk = (STACKWALK)(uintptr_t) ::GetProcAddress(mhDbgHelp, "StackWalk");
mpSymFunctionTableAccess = (SYMFUNCTIONTABLEACCESS)(uintptr_t)::GetProcAddress(mhDbgHelp, "SymFunctionTableAccess");
mpSymGetModuleBase = (SYMGETMODULEBASE)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetModuleBase");
mpSymGetSymFromAddr = (SYMGETSYMFROMADDR)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetSymFromAddr");
mpSymGetLineFromAddr = (SYMGETLINEFROMADDR)(uintptr_t) ::GetProcAddress(mhDbgHelp, "SymGetLineFromAddr");
}
}
}
void Shutdown()
{
if(mhDbgHelp)
{
if(mbSymInitialized && mpSymCleanup)
mpSymCleanup(::GetCurrentProcess());
::FreeLibrary(mhDbgHelp);
}
}
};
static int sInitCount = 0;
static Win32DbgHelp sWin32DbgHelp;
}
namespace EA
{
namespace Thread
{
/* To consider: Enable usage of this below.
///////////////////////////////////////////////////////////////////////////////
// IsAddressReadable
//
static bool IsAddressReadable(const void* pAddress)
{
bool bPageReadable;
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
{
const DWORD flags = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READONLY | PAGE_READWRITE);
bPageReadable = (mbi.State == MEM_COMMIT) && ((mbi.Protect & flags) != 0);
}
else
bPageReadable = false;
return bPageReadable;
}
*/
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
if(++sInitCount == 1)
sWin32DbgHelp.Init();
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
if(--sInitCount == 0)
sWin32DbgHelp.Shutdown();
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstack
//
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
size_t nEntryIndex(0);
if(!sWin32DbgHelp.mhDbgHelp)
sWin32DbgHelp.Init();
if(sWin32DbgHelp.mpStackWalk)
{
CONTEXT context;
memset(&context, 0, sizeof(context));
context.ContextFlags = CONTEXT_CONTROL;
if(pContext)
{
context.Eip = pContext->mEIP;
context.Esp = pContext->mESP;
context.Ebp = pContext->mEBP;
}
else
{
// RtlCaptureStackBackTrace can only generate stack traces on Win32 when the stack frame contains frame
// pointers. This only a limitation on 32-bit Windows and is controlled by the following compilers switches.
//
// /Oy : removes frame-pointers
// /Oy- : emits frame-pointers
//
// The language is wierd here because Microsoft refers it as enabling/disabling an performance optimization.
// https://docs.microsoft.com/en-us/cpp/build/reference/oy-frame-pointer-omission?view=vs-2017
//
// EATHREAD_WIN32_FRAME_POINTER_OPTIMIZATION_DISABLED is enabled/disabled based on if the user has requested eaconfig to disable
// frame-pointer optimizations (enable frame-pointers). See property: 'eaconfig.disable_framepointer_optimization'.
//
#ifdef EATHREAD_WIN32_FRAME_POINTER_OPTIMIZATION_DISABLED
return RtlCaptureStackBackTrace(1, (ULONG)nReturnAddressArrayCapacity, pReturnAddressArray, NULL);
#else
// With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
// VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
__asm{
mov context.Ebp, EBP
mov context.Esp, ESP
call GetEIP
GetEIP:
pop context.Eip
}
#endif
}
// Initialize the STACKFRAME structure for the first call. This is only
// necessary for Intel CPUs, and isn't mentioned in the documentation.
STACKFRAME sf;
memset(&sf, 0, sizeof(sf));
sf.AddrPC.Offset = context.Eip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrStack.Offset = context.Esp;
sf.AddrStack.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context.Ebp;
sf.AddrFrame.Mode = AddrModeFlat;
const HANDLE hCurrentProcess = ::GetCurrentProcess();
const HANDLE hCurrentThread = ::GetCurrentThread();
// To consider: We have had some other code which can read the stack with better success
// than the DbgHelp stack walk function that we use here. In particular, the DbgHelp
// stack walking function doesn't do well unless x86 stack frames are used.
for(int nStackIndex = 0; nEntryIndex < (nReturnAddressArrayCapacity - 1); ++nStackIndex)
{
if(!sWin32DbgHelp.mpStackWalk(IMAGE_FILE_MACHINE_I386, hCurrentProcess, hCurrentThread,
&sf, &context, NULL, sWin32DbgHelp.mpSymFunctionTableAccess,
sWin32DbgHelp.mpSymGetModuleBase, NULL))
{
break;
}
if(sf.AddrFrame.Offset == 0) // Basic sanity check to make sure the frame is OK. Bail if not.
break;
// If using the current execution context, then we ignore the first
// one because it is the one that is our stack walk function itself.
if(pContext || (nStackIndex > 0))
pReturnAddressArray[nEntryIndex++] = ((void*)(uintptr_t)sf.AddrPC.Offset);
}
}
pReturnAddressArray[nEntryIndex] = 0;
return nEntryIndex;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
EATHREADLIB_API void GetCallstackContext(CallstackContext& context, const Context* pContext)
{
#if defined(EA_PLATFORM_WIN32)
EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::Context, Eip) == offsetof(CONTEXT, Eip));
EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::Context, SegSs) == offsetof(CONTEXT, SegSs));
#endif
context.mEIP = pContext->Eip;
context.mESP = pContext->Esp;
context.mEBP = pContext->Ebp;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
EATHREADLIB_API size_t GetModuleFromAddress(const void* address, char* pModuleName, size_t moduleNameCapacity)
{
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(address, &mbi, sizeof(mbi)))
{
HMODULE hModule = (HMODULE)mbi.AllocationBase;
if(hModule)
return GetModuleFileNameA(hModule, pModuleName, (DWORD)moduleNameCapacity);
}
pModuleName[0] = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* pAddress)
{
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
return (ModuleHandle)mbi.AllocationBase;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetThreadIdFromThreadHandle
//
// This implementation is the same as the one in EAThread.
//
EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId)
{
struct THREAD_BASIC_INFORMATION_WIN32
{
BOOL ExitStatus;
PVOID TebBaseAddress;
DWORD UniqueProcessId;
DWORD UniqueThreadId;
DWORD AffinityMask;
DWORD Priority;
DWORD BasePriority;
};
static HMODULE hKernel32 = NULL;
if(!hKernel32)
hKernel32 = LoadLibraryA("kernel32.dll");
if(hKernel32)
{
typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE);
static GetThreadIdFunc pGetThreadIdFunc = NULL;
if(!pGetThreadIdFunc)
pGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId");
if(pGetThreadIdFunc)
return pGetThreadIdFunc((HANDLE)threadId);
}
static HMODULE hNTDLL = NULL;
if(!hNTDLL)
hNTDLL = LoadLibraryA("ntdll.dll");
if(hNTDLL)
{
typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG);
static NtQueryInformationThreadFunc pNtQueryInformationThread = NULL;
if(!pNtQueryInformationThread)
pNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
if(pNtQueryInformationThread)
{
THREAD_BASIC_INFORMATION_WIN32 tbi;
if(pNtQueryInformationThread((HANDLE)threadId, 0, &tbi, sizeof(tbi), NULL) == 0)
return tbi.UniqueThreadId;
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
// The threadId is the same thing as the Windows' HANDLE GetCurrentThread() function
// and not the same thing as Windows' GetCurrentThreadId function. See the
// GetCallstackContextSysThreadId for the latter.
//
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId)
{
if((threadId == (intptr_t)kThreadIdInvalid) || (threadId == (intptr_t)kThreadIdCurrent))
threadId = (intptr_t)::GetCurrentThread(); // GetCurrentThread returns a thread 'pseudohandle' and not a real thread handle.
const DWORD sysThreadId = EA::Thread::GetThreadIdFromThreadHandle(threadId);
const DWORD sysThreadIdCurrent = ::GetCurrentThreadId();
CONTEXT win32CONTEXT;
NT_TIB* pTib;
if(sysThreadIdCurrent == sysThreadId)
{
// With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
// VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
__asm{
mov win32CONTEXT.Ebp, EBP
mov win32CONTEXT.Esp, ESP
call GetEIP
GetEIP:
pop win32CONTEXT.Eip
}
// Offset 0x18 from the FS segment register gives a pointer to
// the thread information block for the current thread
__asm {
mov eax, fs:[18h]
mov pTib, eax
}
}
else
{
// In this case we are working with a separate thread, so we suspend it
// and read information about it and then resume it.
::SuspendThread((HANDLE)threadId);
win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS;
::GetThreadContext((HANDLE)threadId, &win32CONTEXT);
// TODO: This has not been tested!
pTib = *((NT_TIB**)(win32CONTEXT.SegFs * 16 + 18));
::ResumeThread((HANDLE)threadId);
}
context.mEBP = (uint32_t)win32CONTEXT.Ebp;
context.mESP = (uint32_t)win32CONTEXT.Esp;
context.mEIP = (uint32_t)win32CONTEXT.Eip;
context.mStackBase = (uintptr_t)pTib->StackBase;
context.mStackLimit = (uintptr_t)pTib->StackLimit;
context.mStackPointer = (uintptr_t)win32CONTEXT.Esp;
return true;
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
// A sysThreadId is a Microsoft DWORD thread id, which can be obtained from
// the currently running thread via GetCurrentThreadId. It can be obtained from
// a Microsoft thread HANDLE via EA::Thread::GetThreadIdFromThreadHandle();
// A DWORD thread id can be converted to a thread HANDLE via the Microsoft OpenThread
// system function.
//
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
bool bReturnValue = true;
const DWORD sysThreadIdCurrent = ::GetCurrentThreadId();
CONTEXT win32CONTEXT;
if(sysThreadIdCurrent == (DWORD)sysThreadId)
{
// With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
// VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
__asm{
mov win32CONTEXT.Ebp, EBP
mov win32CONTEXT.Esp, ESP
call GetEIP
GetEIP:
pop win32CONTEXT.Eip
}
}
else
{
// In this case we are working with a separate thread, so we suspend it
// and read information about it and then resume it.
HANDLE threadId = ::OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, TRUE, (DWORD)sysThreadId);
if(threadId)
{
::SuspendThread(threadId);
win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
::GetThreadContext(threadId, &win32CONTEXT);
::ResumeThread(threadId);
::CloseHandle(threadId);
}
else
{
memset(&win32CONTEXT, 0, sizeof(win32CONTEXT));
bReturnValue = false;
}
}
context.mEBP = (uint32_t)win32CONTEXT.Ebp;
context.mESP = (uint32_t)win32CONTEXT.Esp;
context.mEIP = (uint32_t)win32CONTEXT.Eip;
//context.mStackBase = (uintptr_t)pTib->StackBase; // To do. (Whoever added mStackBase to CallstackContext forgot to add this code)
//context.mStackLimit = (uintptr_t)pTib->StackLimit;
//context.mStackPointer = (uintptr_t)win32CONTEXT.Esp;
return bReturnValue;
}
///////////////////////////////////////////////////////////////////////////////
// SetStackBase
//
EATHREADLIB_API void SetStackBase(void* /*pStackBase*/)
{
// Nothing to do, as GetStackBase always works on its own.
}
///////////////////////////////////////////////////////////////////////////////
// GetStackBase
//
EATHREADLIB_API void* GetStackBase()
{
CallstackContext context;
GetCallstackContext(context, 0);
return (void*)context.mStackBase;
}
///////////////////////////////////////////////////////////////////////////////
// GetStackLimit
//
EATHREADLIB_API void* GetStackLimit()
{
CallstackContext context;
GetCallstackContext(context, 0);
return (void*)context.mStackLimit;
// Alternative which returns a slightly different answer:
// We return our stack pointer, which is a good approximation of the stack limit of the caller.
// void* pStack = NULL;
// __asm { mov pStack, ESP};
// return pStack;
}
} // namespace Thread
} // namespace EA
#else // Stub out function for WinRT / Windows Phone 8
namespace EA
{
namespace Thread
{
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
EA_UNUSED(pContext);
EA_UNUSED(pReturnAddressArray);
EA_UNUSED(nReturnAddressArrayCapacity);
return 0;
}
} // namespace Thread
} // namespace EA
#endif // defined(EA_PLATFORM_WIN32)
@@ -0,0 +1,622 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <stdio.h>
#include <string.h>
#include <eathread/eathread_storage.h>
#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0500)
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif
#ifdef _MSC_VER
#pragma warning(push, 0)
#include <Windows.h>
#include <math.h> // VS2008 has an acknowledged bug that requires math.h (and possibly also string.h) to be #included before intrin.h.
#include <intrin.h>
#pragma intrinsic(_ReturnAddress)
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
#include <winternl.h>
#else
// Temporary while waiting for formal support:
extern "C" NTSYSAPI PEXCEPTION_ROUTINE NTAPI RtlVirtualUnwind(DWORD, DWORD64, DWORD64, PRUNTIME_FUNCTION, PCONTEXT, PVOID*, PDWORD64, PKNONVOLATILE_CONTEXT_POINTERS);
extern "C" WINBASEAPI DWORD WINAPI GetModuleFileNameA(HMODULE, LPSTR, DWORD);
#endif
#pragma warning(pop)
#else
#include <Windows.h>
#include <winternl.h>
#endif
// Disable optimization of this code under VC++ for x64.
// This is due to some as-yet undetermined crash that happens
// when compiler optimizations are enabled for this code.
// This function is not performance-sensitive and so disabling
// optimizations shouldn't matter.
#if defined(_MSC_VER) && (defined(_M_AMD64) || defined(_WIN64))
#pragma optimize("", off)
#endif
///////////////////////////////////////////////////////////////////////////////
// Stuff that is supposed to be in windows.h and/or winternl.h but isn't
// consistently present in all versions.
//
#ifndef UNW_FLAG_NHANDLER
#define UNW_FLAG_NHANDLER 0
#endif
#ifndef UNWIND_HISTORY_TABLE_SIZE
extern "C"
{
#define UNWIND_HISTORY_TABLE_SIZE 12
#define UNWIND_HISTORY_TABLE_NONE 0
#define UNWIND_HISTORY_TABLE_GLOBAL 1
#define UNWIND_HISTORY_TABLE_LOCAL 2
typedef struct _UNWIND_HISTORY_TABLE_ENTRY
{
ULONG64 ImageBase;
PRUNTIME_FUNCTION FunctionEntry;
} UNWIND_HISTORY_TABLE_ENTRY, *PUNWIND_HISTORY_TABLE_ENTRY;
typedef struct _UNWIND_HISTORY_TABLE
{
ULONG Count;
UCHAR Search;
ULONG64 LowAddress;
ULONG64 HighAddress;
UNWIND_HISTORY_TABLE_ENTRY Entry[UNWIND_HISTORY_TABLE_SIZE];
} UNWIND_HISTORY_TABLE, *PUNWIND_HISTORY_TABLE;
PVOID WINAPI RtlLookupFunctionEntry(ULONG64 ControlPC, PULONG64 ImageBase, PUNWIND_HISTORY_TABLE HistoryTable);
#if !defined(_MSC_VER) || (_MSC_VER < 1500) // if earlier than VS2008...
typedef struct _KNONVOLATILE_CONTEXT_POINTERS
{
PULONGLONG dummy;
} KNONVOLATILE_CONTEXT_POINTERS, *PKNONVOLATILE_CONTEXT_POINTERS;
typedef struct _FRAME_POINTERS
{
ULONGLONG MemoryStackFp;
ULONGLONG BackingStoreFp;
} FRAME_POINTERS, *PFRAME_POINTERS;
ULONGLONG WINAPI RtlVirtualUnwind(ULONG HandlerType, ULONGLONG ImageBase, ULONGLONG ControlPC,
PRUNTIME_FUNCTION FunctionEntry, PCONTEXT ContextRecord, PBOOLEAN InFunction,
PFRAME_POINTERS EstablisherFrame, PKNONVOLATILE_CONTEXT_POINTERS ContextPointers);
#endif
}
#endif
extern "C" WINBASEAPI DWORD WINAPI GetThreadId(_In_ HANDLE hThread);
///////////////////////////////////////////////////////////////////////////////
namespace EA
{
namespace Thread
{
/* To consider: Enable usage of this below.
///////////////////////////////////////////////////////////////////////////////
// IsAddressReadable
//
static bool IsAddressReadable(const void* pAddress)
{
bool bPageReadable;
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
{
const DWORD flags = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READONLY | PAGE_READWRITE);
bPageReadable = (mbi.State == MEM_COMMIT) && ((mbi.Protect & flags) != 0);
}
else
bPageReadable = false;
return bPageReadable;
}
*/
#if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
// GetRSP
//
// Returns the RSP of the caller.
//
// We could also solve this with the following asm function.
// .CODE
// GetRSP PROC
// mov rax, rsp
// add rax, 8
// ret
// GetRSP ENDP
// END
//
static EA_NO_INLINE void* GetRSP()
{
#if defined(_MSC_VER)
uintptr_t ara = (uintptr_t)_AddressOfReturnAddress();
#else
uintptr_t ara = (uintptr_t)__builtin_frame_address();
#endif
return (void*)(ara + 8);
}
#endif
///////////////////////////////////////////////////////////////////////////////
// GetInstructionPointer
//
EATHREADLIB_API EA_NO_INLINE void GetInstructionPointer(void*& pInstruction)
{
#if defined(_MSC_VER)
pInstruction = _ReturnAddress();
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
pInstruction = __builtin_return_address(0);
#else
void* pReturnAddressArray[2] = { 0, 0 };
GetCallstack(pReturnAddressArray, 2, NULL);
pInstruction = pReturnAddressArray[1]; // This is the address of the caller.
#endif
}
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
// Nothing needed.
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
// Nothing needed.
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstack
//
// With the x64 (a.k.a. x86-64) platform, the CPU supports call stack tracing
// natively, by design. This is as opposed to the x86 platform, in which call
// stack tracing (a.k.a. unwinding) is a crap-shoot. The Win64 OS provides
// two functions in particular that take care of the primary work of stack
// tracing: RtlLookupFunctionEntry and RtlVirtualUnwind/RtlUnwindEx.
//
// On x64 each non-leaf function must have an info struct (unwind metadata) in
// static memory associated with it. That info struct describes the prologue and
// epilogue of the function in such a way as tell identify where its return address
// is stored and how to restore non-volatile registers of the caller so that
// an unwind can happen during an exception and C++ object destructors can
// be called, etc. In order to implement a stack unwinding function for
// Microsoft x64, you can go the old x86 route of requiring the compiler to
// emit stack frames and reading the stack frame values. But that would work
// only where the frames were enabled (maybe just debug builds) and wouldn't
// work with third party code that didn't use the frames. But the Microsoft
// x64 ABI -requires- that all non-leaf functions have the info struct
// described above. And Microsoft provides the Rtl functions mentioned above
// to read the info struct (RtlLookupFunctionEntry) and use it to unwind a
// frame (RtlVirtualUnwind/RtlUnwindEx), whether you are in an exception or not.
//
// RtlVirtualUnwind implements a virtual (pretend) unwind of a stack and is
// useful for reading a call stack and its unwind info without necessarily
// executing an unwind (like in an exception handler). RtlVirtualUnwind provides
// the infrastructure upon which higher level exception and unwind handling
// support is implemented. It doesn't exist on x86, as x86 exception unwinding
// is entirely done by generated C++ code and isn't in the ABI. The Virtual in
// RtlVirtualUnwind has nothing to do with virtual memory, virtual functions,
// or virtual disks.
//
// RtlUnwindEx (replaces RtlUnwind) implements an actual unwind and thus is
// mostly useful only in the implementation of an exception handler and not
// for doing an ad-hoc stack trace.
//
// You can't use RtlLookupFunctionEntry on the IP (instruction pointer) of a
// leaf function, as the compiler isn't guaranteed to generate this info for
// such functions. But if a leaf function calls RtlLookupFunctionEntry on its
// own IP then it's no longer a leaf function and by virtue of calling RtlLookupFunctionEntry
// the info will necessarily be generated by the compiler. If you want to read
// the info associated with an IP of another function which may be a leaf
// function, it's best to read the return address of that associated with that
// function's callstack context, which is that that function's rsp register's
// value as a uintptr_t* dereferenced (i.e. rsp holds the address of the
// return address).
//
// UNWIND_HISTORY_TABLE "is used as a cache to speed up repeated exception handling lookups,
// and is typically optional as far as usage with RtlUnwindEx goes though certainly
// recommended from a performance perspective." This may be useful to us, though we'd need
// to make it a thread-safe static variable or similar and not a local variable.
// History table declaration and preparation for use, which needs to be done per-thread:
// UNWIND_HISTORY_TABLE unwindHistoryTable;
// RtlZeroMemory(&unwindHistoryTable, sizeof(UNWIND_HISTORY_TABLE));
// unwindHistoryTable.Unwind = TRUE;
// To do: Implement usage of the history table for faster callstack tracing.
//
// Reading for anybody wanting to understand this:
// http://www.nynaeve.net/?p=105
// http://www.nynaeve.net/?p=106
// http://blogs.msdn.com/b/freik/archive/2005/03/17/398200.aspx
// http://www.codemachine.com/article_x64deepdive.html
// http://blogs.msdn.com/b/ntdebugging/archive/2010/05/12/x64-manual-stack-reconstruction-and-stack-walking.aspx
// http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/
//
EATHREADLIB_API size_t GetCallstack(void* pReturnAddressArray[], size_t nReturnAddressArrayCapacity, const CallstackContext* pContext)
{
CONTEXT context;
PRUNTIME_FUNCTION pRuntimeFunction;
ULONG64 nImageBase = 0;
ULONG64 nPrevImageBase = 0;
size_t nFrameIndex = 0;
if(pContext)
{
RtlZeroMemory(&context, sizeof(context));
context.Rip = pContext->mRIP;
context.Rsp = pContext->mRSP;
context.Rbp = pContext->mRBP;
context.ContextFlags = CONTEXT_CONTROL; // CONTEXT_CONTROL actually specifies SegSs, Rsp, SegCs, Rip, and EFlags. But for callstack tracing and unwinding, all that matters is Rip and Rsp.
// In the case where we are calling 0, we might be able to unwind one frame and see if we are now in a valid stack frame for
// callstack generation. If not abort, otherwise we continue one frame past where the exception (calling 0) was performed
if (context.Rip == 0 && context.Rsp != 0)
{
context.Rip = (ULONG64)(*(PULONG64)context.Rsp); // To consider: Use IsAddressReadable(pFrame) before dereferencing this pointer.
context.Rsp += 8; // reset the stack pointer (+8 since we know there has been no prologue run requiring a larger number since RIP == 0)
}
if(context.Rip && (nFrameIndex < nReturnAddressArrayCapacity))
pReturnAddressArray[nFrameIndex++] = (void*)(uintptr_t)context.Rip;
}
else // Else we are reading the current thread's callstack.
{
// To consider: Don't call the RtlCaptureContext function for EA_WINAPI_PARTITION_DESKTOP and
// instead use the simpler version below it which writes Rip/Rsp/Rbp. RtlCaptureContext is much
// slower. We need to verify that the 'quality' and extent of returned callstacks is good for
// the simpler version before using it exclusively.
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
// Apparently there is no need to memset the context struct.
context.ContextFlags = CONTEXT_ALL; // Actually we should need only CONTEXT_INTEGER, so let's test that next chance we get.
RtlCaptureContext(&context);
#elif defined(EA_PLATFORM_XBOXONE) // This probably isn't limited to just this platform, but until we can test any other platforms we'll leave it at just this.
return RtlCaptureStackBackTrace(1, (ULONG)nReturnAddressArrayCapacity, pReturnAddressArray, NULL);
#else
void* ip = NULL;
EAGetInstructionPointer(ip);
context.Rip = (uintptr_t)ip;
context.Rsp = (uintptr_t)GetRSP();
context.Rbp = 0; // RBP isn't actually needed for stack unwinding on x64, and don't typically need to use it in generated code, as the instruction set provides an efficient way to read/write via rsp offsets. Also, when frame pointers are omitted in the compiler settings then ebp won't be used.
context.ContextFlags = CONTEXT_CONTROL;
#endif
}
// The following loop intentionally skips the first call stack frame because
// that frame corresponds this function (GetCallstack).
while(context.Rip && (nFrameIndex < nReturnAddressArrayCapacity))
{
// Try to look up unwind metadata for the current function.
nPrevImageBase = nImageBase;
__try
{
pRuntimeFunction = (PRUNTIME_FUNCTION)RtlLookupFunctionEntry(context.Rip, &nImageBase, NULL /*&unwindHistoryTable*/);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// Something went wrong in RtlLookupFunctionEntry, and it is unknown
// if it is recoverable; so just get out.
return nFrameIndex;
}
if(pRuntimeFunction)
{
// RtlVirtualUnwind is not declared in the SDK headers for non-desktop apps,
// but for 64 bit targets it's always present and appears to be needed by the
// existing RtlUnwindEx function. If in the end we can't use RtlVirtualUnwind
// and Microsoft doesn't provide an alternative, we can implement RtlVirtualUnwind
// ourselves manually (not trivial, but has the best results) or we can use
// the old style stack frame following, which works only when stack frames are
// enabled in the build, which usually isn't so for optimized builds and for
// third party code.
__try // Under at least the XBox One platform, RtlVirtualUnwind can crash here. It may possibly be due to the context being incomplete.
{
VOID* handlerData = NULL;
ULONG64 establisherFramePointers[2] = { 0, 0 };
RtlVirtualUnwind(UNW_FLAG_NHANDLER, nImageBase, context.Rip, pRuntimeFunction, &context, &handlerData, establisherFramePointers, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
context.Rip = NULL;
context.ContextFlags = 0;
}
}
else
{
// If we don't have a RUNTIME_FUNCTION, then we've encountered an error of some sort (mostly likely only for cases of corruption) or leaf function (which doesn't make sense, given that we are moving up in the call sequence). Adjust the stack appropriately.
context.Rip = (ULONG64)(*(PULONG64)context.Rsp); // To consider: Use IsAddressReadable(pFrame) before dereferencing this pointer.
context.Rsp += 8;
}
if(context.Rip)
{
if(nFrameIndex < nReturnAddressArrayCapacity)
pReturnAddressArray[nFrameIndex++] = (void*)(uintptr_t)context.Rip;
}
}
return nFrameIndex;
}
///////////////////////////////////////////////////////////////////////////////
// GetThreadIdFromThreadHandle
//
// This implementation is the same as the one in EAThread.
// Converts a thread HANDLE (threadId) to a thread id DWORD (sysThreadId).
// Recall that Windows has two independent thread identifier types.
//
EATHREADLIB_API uint32_t GetThreadIdFromThreadHandle(intptr_t threadId)
{
// Win64 has this function natively, unlike earlier versions of 32 bit Windows.
return (uint32_t)::GetThreadId((HANDLE)threadId);
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
// The threadId is the same thing as the Windows' HANDLE GetCurrentThread() function
// and not the same thing as Windows' GetCurrentThreadId function. See the
// GetCallstackContextSysThreadId for the latter.
//
#if EA_USE_CPP11_CONCURRENCY
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, EA::Thread::ThreadId threadId)
{
// Retrieve the Windows thread identifier from the std::thread::id structure.
// This is unavoidable because GetCallstackContextSysThreadId compares the value of 'sysThreadId'
// against data from the Windows API function 'GetCurrentThreadId' which returns a Windows thread identifier.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683183(v=vs.85).aspx
static_assert(sizeof(_Thrd_t) == sizeof(threadId), "We expect the 'std::thread::id' to have a single member of type '_Thrd_t'.");
_Thrd_t wThrd;
memcpy(&wThrd, &threadId, sizeof(wThrd)); // we use memcpy to avoid strict aliasing issues caused by casting to access internal members.
return GetCallstackContextSysThreadId(context, _Thr_val(wThrd));
}
#else
EATHREADLIB_API bool GetCallstackContext(CallstackContext& context, intptr_t threadId)
{
if((threadId == (intptr_t)kThreadIdInvalid) || (threadId == (intptr_t)kThreadIdCurrent))
threadId = (intptr_t)::GetCurrentThread(); // GetCurrentThread returns a thread 'pseudohandle' and not a real thread handle.
const DWORD sysThreadId = EA::Thread::GetThreadIdFromThreadHandle(threadId);
return GetCallstackContextSysThreadId(context, sysThreadId);
}
#endif
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
// A sysThreadId is a Microsoft DWORD thread id, which can be obtained from
// the currently running thread via GetCurrentThreadId. It can be obtained from
// a Microsoft thread HANDLE via EA::Thread::GetThreadIdFromThreadHandle();
// A DWORD thread id can be converted to a thread HANDLE via the Microsoft OpenThread
// system function.
//
EA_DISABLE_VC_WARNING(4701) // potentially uninitialized local variable 'win64CONTEXT' used
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::ContextX86_64, Rip) == offsetof(CONTEXT, Rip));
EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::ContextX86_64, VectorRegister) == offsetof(CONTEXT, VectorRegister));
EAT_COMPILETIME_ASSERT(offsetof(EA::Thread::ContextX86_64, LastExceptionFromRip) == offsetof(CONTEXT, LastExceptionFromRip));
const DWORD sysThreadIdCurrent = GetCurrentThreadId();
CONTEXT win64CONTEXT;
if(sysThreadIdCurrent == (DWORD)sysThreadId) // If getting the context of the current thread...
{
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
RtlCaptureContext(&win64CONTEXT); // This function has no return value.
#else
void* ip = NULL;
EAGetInstructionPointer(ip);
win64CONTEXT.Rip = (uintptr_t)ip;
win64CONTEXT.Rsp = (uintptr_t)GetRSP();
win64CONTEXT.Rbp = 0; // RBP isn't actually needed for stack unwinding on x64, and don't typically need to use it in generated code, as the instruction set provides an efficient way to read/write via rsp offsets. Also, when frame pointers are omitted in the compiler settings then ebp won't be used.
win64CONTEXT.ContextFlags = CONTEXT_CONTROL; // CONTEXT_CONTROL actually specifies SegSs, Rsp, SegCs, Rip, and EFlags. But for callstack tracing and unwinding, all that matters is Rip and Rsp.
#endif
}
else
{
#if !defined(EA_PLATFORM_WINDOWS) || EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
// In this case we are working with a separate thread, so we suspend it
// and read information about it and then resume it. We cannot use this
// technique to get the context of the current thread unless we do it by
// spawing a new thread which suspends this thread and calls GetThreadContext.
HANDLE threadId = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, TRUE, (DWORD)sysThreadId);
BOOL result = false;
EAT_ASSERT(threadId != 0); // If this fails then maybe there's a process security restriction we are running into.
if(threadId)
{
DWORD suspendResult = SuspendThread(threadId);
if(suspendResult != (DWORD)-1)
{
win64CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
result = GetThreadContext(threadId, &win64CONTEXT);
suspendResult = ResumeThread(threadId);
EAT_ASSERT(suspendResult != (DWORD)-1); EA_UNUSED(suspendResult);
}
CloseHandle(threadId);
}
if(!result)
{
win64CONTEXT.Rip = 0;
win64CONTEXT.Rsp = 0;
win64CONTEXT.Rbp = 0;
win64CONTEXT.ContextFlags = 0;
}
#endif
}
context.mRIP = win64CONTEXT.Rip;
context.mRSP = win64CONTEXT.Rsp;
context.mRBP = win64CONTEXT.Rbp;
return (context.mRIP != 0);
}
EA_RESTORE_VC_WARNING()
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContext
//
void GetCallstackContext(CallstackContext& context, const Context* pContext)
{
context.mRIP = pContext->Rip;
context.mRSP = pContext->Rsp;
context.mRBP = pContext->Rbp;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
size_t GetModuleFromAddress(const void* address, char* pModuleName, size_t moduleNameCapacity)
{
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(address, &mbi, sizeof(mbi)))
{
HMODULE hModule = (HMODULE)mbi.AllocationBase;
if(hModule)
{
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) // GetModuleFileName is desktop API-only.
// As of the early Windows 8 SDKs, GetModuleFileName is not exposed to non-desktop
// apps, though it's apparently nevertheless present in the libraries.
return GetModuleFileNameA(hModule, pModuleName, (DWORD)moduleNameCapacity);
#else
// If it turns out in the end that we really can't do this, then for non-shipping builds
// we can likely implement a manual version of this via information found through the
// TEB structure for the process.
return GetModuleFileNameA(hModule, pModuleName, (DWORD)moduleNameCapacity);
#endif
}
}
pModuleName[0] = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
// The input pAddress must be an address of code and not data or stack space.
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* pAddress)
{
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
{
// In Microsoft platforms, the module handle is really just a pointer
// to the code for the module. It corresponds directly to the information
// in the map file, though the actual address may have been changed
// from the value in the map file on loading into memory.
return (ModuleHandle)mbi.AllocationBase;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// SetStackBase
//
EATHREADLIB_API void SetStackBase(void* /*pStackBase*/)
{
// Nothing to do, as GetStackBase always works on its own.
}
///////////////////////////////////////////////////////////////////////////////
// GetStackBase
//
EATHREADLIB_API void* GetStackBase()
{
NT_TIB64* pTIB = (NT_TIB64*)NtCurrentTeb(); // NtCurrentTeb is defined in <WinNT.h> as an inline call to __readgsqword
return (void*)pTIB->StackBase;
}
///////////////////////////////////////////////////////////////////////////////
// GetStackLimit
//
EATHREADLIB_API void* GetStackLimit()
{
NT_TIB64* pTIB = (NT_TIB64*)NtCurrentTeb(); // NtCurrentTeb is defined in <WinNT.h> as an inline call to __readgsqword
return (void*)pTIB->StackLimit;
// The following is an alternative implementation that returns the extent
// of the current stack usage as opposed to the stack limit as seen by the OS.
// This value will be a higher address than Tib.StackLimit (recall that the
// stack grows downward). It's debatable which of these two approaches is
// better, as one returns the thread's -usable- stack space while the
// other returns how much the thread is -currently- using. The determination
// of the usable stack space is complicated by the fact that Microsoft
// platforms auto-extend the stack if the process pushes beyond the current limit.
// In the end the Tib.StackLimit solution is actually the most portable across
// Microsoft OSs and compilers for those OSs (Microsoft or not).
// Alternative implementation:
// We return our stack pointer, which is a good approximation of the stack limit of the caller.
// void* rsp = GetRSP();
// return rsp;
}
} // namespace Thread
} // namespace EA
#if defined(_MSC_VER) && (defined(_M_AMD64) || defined(_WIN64))
#pragma optimize("", on) // See comments above regarding this optimization change.
#endif
@@ -0,0 +1,215 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "EABase/eabase.h"
#include "eathread/eathread_mutex.h"
#include "eathread/eathread.h"
#if defined(EA_PLATFORM_MICROSOFT)
EA_DISABLE_ALL_VC_WARNINGS()
#include <Windows.h>
EA_RESTORE_ALL_VC_WARNINGS()
#endif
#ifdef CreateMutex
#undef CreateMutex // Windows #defines CreateMutex to CreateMutexA or CreateMutexW.
#endif
#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
#if defined(EA_PLATFORM_WINDOWS)
extern "C" WINBASEAPI BOOL WINAPI TryEnterCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);
#endif
EAMutexData::EAMutexData()
: mnLockCount(0), mbIntraProcess(true)
{
#if EAT_ASSERT_ENABLED
mThreadId = EA::Thread::kThreadIdInvalid;
mSysThreadId = EA::Thread::kSysThreadIdInvalid;
#endif
::memset(&mData, 0, sizeof(mData));
}
EA::Thread::MutexParameters::MutexParameters(bool bIntraProcess, const char* pName)
: mbIntraProcess(bIntraProcess)
{
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
}
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);
// Consider doing something to verify the mutex object has been initialized.
#if defined(EA_PLATFORM_WINDOWS)
if(mMutexData.mbIntraProcess)
DeleteCriticalSection((CRITICAL_SECTION*)mMutexData.mData);
else
CloseHandle(*(HANDLE*)mMutexData.mData);
#else
DeleteCriticalSection((CRITICAL_SECTION*)mMutexData.mData);
#endif
}
bool EA::Thread::Mutex::Init(const MutexParameters* pMutexParameters)
{
// Make sure that internal structure is big enough to hold critical section data.
// If this assert fires, please adjust MUTEX_PLATFORM_DATA_SIZE in eathread_mutex.h accordingly.
EAT_COMPILETIME_ASSERT(sizeof(CRITICAL_SECTION) <= (MUTEX_PLATFORM_DATA_SIZE / sizeof(uint64_t) * sizeof(uint64_t)));
EAT_COMPILETIME_ASSERT(sizeof(HANDLE) <= MUTEX_PLATFORM_DATA_SIZE);
if(pMutexParameters)
{
mMutexData.mnLockCount = 0;
#if defined(EA_PLATFORM_WINDOWS)
mMutexData.mbIntraProcess = pMutexParameters->mbIntraProcess;
if(mMutexData.mbIntraProcess)
{
// We use InitializeCriticalSectionAndSpinCount, as that has resulted in improved performance in practice on multiprocessors systems.
int rv = InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION*)mMutexData.mData, 256);
EAT_ASSERT(rv != 0);
EA_UNUSED(rv);
return true;
}
else
{
EAT_COMPILETIME_ASSERT(sizeof(pMutexParameters->mName) <= MAX_PATH);
*(HANDLE*)mMutexData.mData = ::CreateMutexA(NULL, false, pMutexParameters->mName[0] ? pMutexParameters->mName : NULL);
EAT_ASSERT(*(HANDLE*)mMutexData.mData != 0);
return *(HANDLE*)mMutexData.mData != 0;
}
#else
// We use InitializeCriticalSectionAndSpinCount, as that has resulted in improved performance in practice on multiprocessors systems.
InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION*)mMutexData.mData, 256);
return true;
#endif
}
return false;
}
#pragma warning(push)
#pragma warning(disable: 4706) // disable warning about assignment within a conditional expression
int EA::Thread::Mutex::Lock(const ThreadTime& timeoutAbsolute)
{
EAT_ASSERT(mMutexData.mnLockCount < 100000);
#if defined(EA_PLATFORM_WINDOWS) // Non-Windows is always assumed to be intra-process.
if(mMutexData.mbIntraProcess)
{
#endif
if(timeoutAbsolute == kTimeoutNone)
EnterCriticalSection((CRITICAL_SECTION*)mMutexData.mData);
else
{
// To consider: Have a pathway for kTimeoutImmediate which doesn't check the current time.
while(!TryEnterCriticalSection((CRITICAL_SECTION*)mMutexData.mData))
{
if(GetThreadTime() >= timeoutAbsolute)
return kResultTimeout;
Sleep(1);
}
}
#if defined(EA_PLATFORM_WINDOWS)
}
else
{
EAT_ASSERT(*(HANDLE*)mMutexData.mData != 0);
const DWORD dw = ::WaitForSingleObject(*(HANDLE*)mMutexData.mData, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(dw == WAIT_TIMEOUT)
return kResultTimeout;
if(dw != WAIT_OBJECT_0)
{
EAT_ASSERT(false);
return kResultError;
}
}
#endif
EAT_ASSERT((mMutexData.mSysThreadId = EA::Thread::GetSysThreadId()) != kSysThreadIdInvalid);
EAT_ASSERT(mMutexData.mnLockCount >= 0);
return ++mMutexData.mnLockCount; // This is safe to do because we have the lock.
}
#pragma warning(pop)
int EA::Thread::Mutex::Unlock()
{
EAT_ASSERT(mMutexData.mSysThreadId == EA::Thread::GetSysThreadId());
EAT_ASSERT(mMutexData.mnLockCount > 0);
const int nReturnValue(--mMutexData.mnLockCount); // This is safe to do because we have the lock.
#if defined(EA_PLATFORM_WINDOWS)
if(mMutexData.mbIntraProcess)
LeaveCriticalSection((CRITICAL_SECTION*)mMutexData.mData);
else
{
EAT_ASSERT(*(HANDLE*)mMutexData.mData != 0);
ReleaseMutex(*(HANDLE*)mMutexData.mData);
}
#else
LeaveCriticalSection((CRITICAL_SECTION*)mMutexData.mData);
#endif
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.mSysThreadId == EA::Thread::GetSysThreadId());
#else
return (mMutexData.mnLockCount > 0); // This is the best we can do, though it is of limited use, since it doesn't tell you if you are the thread with the lock.
#endif
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,911 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EABase/eabase.h>
#include "eathread/eathread.h"
#include "eathread/eathread_thread.h"
#include "eathread/eathread_storage.h"
#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
#include <process.h>
EA_DISABLE_ALL_VC_WARNINGS()
#include <Windows.h>
#include <stdlib.h> // for mbstowcs
#include <setjmp.h>
EA_RESTORE_ALL_VC_WARNINGS()
#include "eathread/eathread_futex.h"
extern "C" WINBASEAPI DWORD WINAPI SetThreadIdealProcessor(_In_ HANDLE hThread, _In_ DWORD dwIdealProcessor);
#if defined(EA_PLATFORM_WIN64)
extern "C" WINBASEAPI DWORD WINAPI GetThreadId(_In_ HANDLE hThread);
extern "C" WINBASEAPI ULONGLONG GetTickCount64(VOID); // Will not run on pre-Vista OS so 64 bit XP not supported
#endif
// We set this module to initialize early. We want to do this because it
// allows statically initialized objects to call these functions safely.
EA_DISABLE_VC_WARNING(4074) // warning C4074: initializers put in compiler reserved initialization area
#pragma init_seg(compiler)
EA_RESTORE_VC_WARNING()
#ifndef EATHREAD_INIT_SEG_DEFINED
#define EATHREAD_INIT_SEG_DEFINED
#endif
namespace EA
{
namespace Thread
{
// Note by Paul Pedriana:
// There is a bit of code here which implements "dynamic thread array maintenance".
// The reason for this is that we are trying to present to the user a consistently
// behaving GetThreadId function. The Windows threading API has a number of design
// characteristics that make it less than ideal for applications. One of these
// designs is that an application cannot ask the system what its thread id is and
// get a consistent answer; in fact you always get a different answer.
// To consider: Use the VC++ undocumented __tlregdtor function to detect thread exits.
// __tlregdtor is a VC++ CRT function which detects the exiting of any threads created
// with the CRT beginthread family of functions. It cannot detect the exit of any threads
// that are begun via direct OS thread creation functions, nor can it detect the exit of
// threads that are exited by direct OS thread exit functions. This is may not be a major
// problem, because C/C++ programs should virtually always be calling the CRT thread begin
// and end functions so that the CRT can be maintained properly for the thread.
//
// typedef void (*_PVFV)();
// void __tlregdtor(_PVFV func);
// void ThreadExit(){ Do something. May need to be careful about what APIs are called. }
// Assertion variables.
EA::Thread::AssertionFailureFunction gpAssertionFailureFunction = NULL;
void* gpAssertionFailureContext = NULL;
// Dynamic thread array maintenance.
// If the user calls GetThreadId from a thread that was created by some third
// party, then we don't have a thread handle for it. The only current way to get
// such a thread handle is to call OpenThread(GetCurrentThreadId()) or
// DuplicateHandle(GetCurrentThread()). In either case the return value is a
// handle which must be disposed of via CloseHandle. Additionally, since the
// thread was created by a thrid party, it's entirely possible that the thread
// will be exited without us ever finding about it. But we still need to call
// CloseHandle on the handle. So we maintain an array of handles and check their
// status periodically and upon process exit.
const size_t kMaxThreadDynamicArrayCount = 128;
struct DynamicThreadArray
{
static HANDLE mhDynamicThreadArray[kMaxThreadDynamicArrayCount];
static CRITICAL_SECTION mCriticalSection;
static bool mbDynamicThreadArrayInitialized;
static void Initialize();
static void CheckDynamicThreadArray(bool bCloseAll);
static void AddDynamicThreadHandle(HANDLE hThread, bool bAdd);
};
HANDLE DynamicThreadArray::mhDynamicThreadArray[kMaxThreadDynamicArrayCount];
CRITICAL_SECTION DynamicThreadArray::mCriticalSection;
bool DynamicThreadArray::mbDynamicThreadArrayInitialized;
// DynamicThreadArray ctor/dtor were removed to because memory tracking systems that are required to run
// pre-main and post-main. In order to support memory tracking of allocations that occur post-main we
// intentially "leak" a operating system critical section and leave it to be cleaned up by the operating
// system at process shutdown.
//
// DynamicThreadArray::DynamicThreadArray()
// {
// Initialize();
// }
// DynamicThreadArray::~DynamicThreadArray()
// {
// CheckDynamicThreadArray(true);
// DeleteCriticalSection(&mCriticalSection);
// }
void DynamicThreadArray::Initialize()
{
static EA::Thread::Futex m;
const bool done = mbDynamicThreadArrayInitialized;
// ensure that if we've seen previous writes to mbDynamicThreadArrayInitialized, we also
// see the writes to mCriticalSection, to avoid the case where another thread sees the flag
// before it sees the initialization
EAReadBarrier();
if(!done)
{
EA::Thread::AutoFutex _(m);
if (!mbDynamicThreadArrayInitialized)
{
memset(mhDynamicThreadArray, 0, sizeof(mhDynamicThreadArray));
InitializeCriticalSection(&mCriticalSection);
// ensure writes to mCriticalSection and mhDynamicThreadArray are visible before writes
// to mbDynamicThreadArrayInitialized, to avoid the case where another thread sees the
// flag before it sees the initialization
EAWriteBarrier();
mbDynamicThreadArrayInitialized = true;
}
}
}
// This function looks at the existing set of thread ids and see if any of them
// were quit. If so then this function removes their entry from our array of
// thread handles, and most importantly, calls CloseHandle on the thread handle.
void DynamicThreadArray::CheckDynamicThreadArray(bool bCloseAll)
{
Initialize();
EnterCriticalSection(&mCriticalSection);
for(size_t i(0); i < sizeof(mhDynamicThreadArray)/sizeof(mhDynamicThreadArray[0]); i++)
{
if(mhDynamicThreadArray[i])
{
DWORD dwExitCode(0);
// Note that GetExitCodeThread is a hazard if the user of a thread exits
// with a return value that is equal to the value of STILL_ACTIVE (i.e. 259).
// We can document that users shouldn't do this, or we can change the code
// here to use WaitForSingleObject(hThread, 0) and assume the thread is
// still active if the return value is WAIT_TIMEOUT.
if(bCloseAll || !GetExitCodeThread(mhDynamicThreadArray[i], &dwExitCode) || (dwExitCode != STILL_ACTIVE)) // If the thread id is invalid or it has exited...
{
CloseHandle(mhDynamicThreadArray[i]); // This matches the DuplicateHandle call we made below.
mhDynamicThreadArray[i] = 0;
}
}
}
LeaveCriticalSection(&mCriticalSection);
}
void DynamicThreadArray::AddDynamicThreadHandle(HANDLE hThread, bool bAdd)
{
Initialize();
if(hThread)
{
EnterCriticalSection(&mCriticalSection);
if(bAdd)
{
for(size_t i(0); i < sizeof(mhDynamicThreadArray)/sizeof(mhDynamicThreadArray[0]); i++)
{
if(mhDynamicThreadArray[i] == kThreadIdInvalid)
{
mhDynamicThreadArray[i] = hThread;
hThread = kThreadIdInvalid; // This tells us that we succeeded, and we'll use this result below.
break;
}
}
EAT_ASSERT(hThread == kThreadIdInvalid); // Assert that there was enough room (that the above loop found a spot).
if(hThread != kThreadIdInvalid) // If not, then we need to free the handle.
CloseHandle(hThread); // This matches the DuplicateHandle call we made below.
}
else
{
for(size_t i(0); i < sizeof(mhDynamicThreadArray)/sizeof(mhDynamicThreadArray[0]); i++)
{
if(mhDynamicThreadArray[i] == hThread)
{
CloseHandle(hThread); // This matches the DuplicateHandle call we made below.
mhDynamicThreadArray[i] = kThreadIdInvalid;
break;
}
}
// By design, we don't consider a non-found handle an error. It may simply be the
// case that the given handle was not a dynamnic thread handle. Due to the way
// Windows works, there's just no way for us to tell.
}
LeaveCriticalSection(&mCriticalSection);
}
}
// Thread handle local storage.
// We have this code here in order to cache the thread handles for
// threads, so that the user gets a consistent return value from the
// GetThreadId function for each unique thread.
static DWORD dwThreadHandleTLS = TLS_OUT_OF_INDEXES; // We intentionally make this an independent variable so that it is initialized unilaterally on segment load.
struct TLSAlloc
{
TLSAlloc()
{
if(dwThreadHandleTLS == TLS_OUT_OF_INDEXES) // It turns out that the user might have set this to a
dwThreadHandleTLS = TlsAlloc(); // value before this constructor has run. So we check.
}
#if EATHREAD_TLSALLOC_DTOR_ENABLED
// Since this class is used only as a static variable, this destructor would
// only get called during module destruction: app quit or DLL unload.
// In the case of DLL unload, we may have a problem if the DLL was unloaded
// before threads created by it were destroyed. Whether the problem is significant
// depends on the application. In most cases it won't be significant.
//
// We want to call TlsFree because not doing so results in a memory leak and eventual
// exhaustion of TLS ids by the system.
~TLSAlloc()
{
if(dwThreadHandleTLS != TLS_OUT_OF_INDEXES)
{
// We don't read the hThread stored at dwThreadHandleTLS and call CloseHandle
// on it, as the DynamicThreadArray destructor will deal with closing any
// thread handles this module knows about.
TlsFree(dwThreadHandleTLS);
dwThreadHandleTLS = TLS_OUT_OF_INDEXES;
}
}
#endif
};
static TLSAlloc sTLSAlloc;
void SetCurrentThreadHandle(HANDLE hThread, bool bDynamic)
{
// EAT_ASSERT(hThread != kThreadIdInvalid); We can't do this, as we can be intentionally called with an hThread of kThreadIdInvalid.
if(dwThreadHandleTLS == TLS_OUT_OF_INDEXES) // This should generally always evaluate to true because we init dwThreadHandleTLS on startup.
dwThreadHandleTLS = TlsAlloc();
EAT_ASSERT(dwThreadHandleTLS != TLS_OUT_OF_INDEXES);
if(dwThreadHandleTLS != TLS_OUT_OF_INDEXES)
{
DynamicThreadArray::CheckDynamicThreadArray(false);
if(bDynamic)
{
if(hThread != kThreadIdInvalid) // If adding the hThread...
DynamicThreadArray::AddDynamicThreadHandle(hThread, true);
else // Else removing the existing current thread handle...
{
HANDLE hThreadOld = TlsGetValue(dwThreadHandleTLS);
if(hThreadOld != kThreadIdInvalid) // This should always evaluate to true in practice.
DynamicThreadArray::AddDynamicThreadHandle(hThreadOld, false); // Will Close the dynamic thread handle if it is one.
}
}
TlsSetValue(dwThreadHandleTLS, hThread);
}
}
} // namespace Thread
} // namespace EA
EATHREADLIB_API EA::Thread::ThreadId EA::Thread::GetThreadId()
{
// We have some non-trivial code here because Windows doesn't provide a means for a
// thread to read its own thread id (thread handle) in a consistent way.
// If we have allocated thread-local storage for this module...
if(dwThreadHandleTLS != TLS_OUT_OF_INDEXES)
{
void* const pValue = TlsGetValue(dwThreadHandleTLS);
if(pValue) // If the current thread's ThreadId has been previously saved...
return pValue; // Under Win32, type ThreadId should be the same as HANDLE which should be the same as void*.
// Else fall through and get the current thread handle and cache it so that next time the above code will succeed.
}
// In this case the thread was not created by EAThread. So we give
// the thread a new Id, based on GetCurrentThread and DuplicateHandle.
// GetCurrentThread returns a "pseudo handle" which isn't actually the
// thread handle but is a hard-coded constant which means "current thread".
// If you want to get a real thread handle then you need to call DuplicateHandle
// on the pseudo handle. Every time you call DuplicateHandle you get a different
// result, yet we want this GetThreadId function to return a consistent value
// to the user, as that's what a rational user would expect. So after calling
// DuplicateHandle we save the value for the next time the user calls this
// function. We save the value in thread-local storage, so each unique thread
// sees a unique view of GetThreadId.
HANDLE hThread, hThreadPseudo = GetCurrentThread();
BOOL bResult = DuplicateHandle(GetCurrentProcess(), hThreadPseudo, GetCurrentProcess(), &hThread, 0, true, DUPLICATE_SAME_ACCESS);
EAT_ASSERT(bResult && (hThread != kThreadIdInvalid));
if(bResult)
EA::Thread::SetCurrentThreadHandle(hThread, true); // Need to eventually call CloseHandle on hThread, so we store it.
return hThread;
}
EATHREADLIB_API EA::Thread::ThreadId EA::Thread::GetThreadId(EA::Thread::SysThreadId id)
{
EAThreadDynamicData* const pTDD = EA::Thread::FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mhThread;
}
return EA::Thread::kThreadIdInvalid;
}
EATHREADLIB_API EA::Thread::SysThreadId EA::Thread::GetSysThreadId(ThreadId id)
{
#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64)
// Win64 has this function natively.
return ::GetThreadId(id);
// Fast implementation of this, which has been verified:
// uintptr_t pTIB = __readgsqword(0x30);
// uint32_t threadId = *((uint32_t*)(((uint8_t*)pTIB) + 0x48));
// return (EA::Thread::SysThreadId)threadId;
#elif defined(EA_PLATFORM_WIN32)
// What we do here is first try to use the GetThreadId function, which is
// available on some later versions of WinXP and later OSs. If that doesn't
// work then we are using an earlier OS and we use the NtQueryInformationThread
// kernel function to read thread info.
typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE);
typedef BOOL (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG);
// We implement our own manual version of static variables here. We do this because
// the static variable mechanism the compiler provides wouldn't provide thread
// safety for us.
static volatile bool sInitialized = false;
static GetThreadIdFunc spGetThreadIdFunc = NULL;
static NtQueryInformationThreadFunc spNtQueryInformationThread = NULL;
if(!sInitialized)
{
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if(hKernel32)
spGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId");
if(!spGetThreadIdFunc)
{
HMODULE hNTDLL = GetModuleHandleA("ntdll.dll");
if(hNTDLL)
spNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
}
sInitialized = true;
}
if(spGetThreadIdFunc)
return (SysThreadId)spGetThreadIdFunc(id);
if(spNtQueryInformationThread)
{
struct THREAD_BASIC_INFORMATION_WIN32
{
BOOL ExitStatus;
PVOID TebBaseAddress;
DWORD UniqueProcessId;
DWORD UniqueThreadId;
DWORD AffinityMask;
DWORD Priority;
DWORD BasePriority;
};
THREAD_BASIC_INFORMATION_WIN32 tbi;
if(spNtQueryInformationThread(id, 0, &tbi, sizeof(tbi), NULL) == 0)
return (SysThreadId)tbi.UniqueThreadId;
}
return kSysThreadIdInvalid;
#endif
}
EATHREADLIB_API EA::Thread::SysThreadId EA::Thread::GetSysThreadId()
{
return ::GetCurrentThreadId();
}
EATHREADLIB_API int EA::Thread::GetThreadPriority()
{
const int nPriority = ::GetThreadPriority(GetCurrentThread());
return kThreadPriorityDefault + (nPriority - THREAD_PRIORITY_NORMAL);
}
EATHREADLIB_API bool EA::Thread::SetThreadPriority(int nPriority)
{
EAT_ASSERT(nPriority != kThreadPriorityUnknown);
int nNewPriority = THREAD_PRIORITY_NORMAL + (nPriority - kThreadPriorityDefault);
bool result = ::SetThreadPriority(GetCurrentThread(), nNewPriority) != 0;
// Windows process running in NORMAL_PRIORITY_CLASS is picky about the priority passed in.
// So we need to set the priority to the next priority supported
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE)
HANDLE thread = GetCurrentThread();
while(!result)
{
if (nNewPriority >= THREAD_PRIORITY_TIME_CRITICAL)
return ::SetThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL) != 0;
if (nNewPriority <= THREAD_PRIORITY_IDLE)
return ::SetThreadPriority(thread, THREAD_PRIORITY_IDLE) != 0;
result = ::SetThreadPriority(thread, nNewPriority) != 0;
nNewPriority++;
}
#endif
return result;
}
EATHREADLIB_API void EA::Thread::SetThreadProcessor(int nProcessor)
{
#if defined(EA_PLATFORM_XBOXONE)
DWORD mask = 0xFF; //Default to all
if (nProcessor >= 0)
mask = (DWORD)(1 << nProcessor);
SetThreadAffinityMask(GetCurrentThread(), mask);
#else
static const int nProcessorCount = GetProcessorCount();
if(nProcessor < 0)
nProcessor = MAXIMUM_PROCESSORS; // This cases the SetThreadIdealProcessor to reset to 'no ideal processor'.
else
{
if(nProcessor >= nProcessorCount)
nProcessor %= nProcessorCount;
}
// SetThreadIdealProcessor differs from SetThreadAffinityMask in that SetThreadIdealProcessor is not
// a strict assignment, and it allows the OS to move the thread if the ideal processor is busy.
// SetThreadAffinityMask is a more rigid assignment, but it can result in slower performance and
// possibly hangs due to processor contention between threads. For Windows we use SetIdealThreadProcessor
// in the name of safety and likely better overall performance.
SetThreadIdealProcessor(GetCurrentThread(), (DWORD)nProcessor);
#endif
}
void* EA::Thread::GetThreadStackBase()
{
#if defined(EA_PLATFORM_WIN32) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_MSVC)
// Offset 0x18 from the FS segment register gives a pointer to
// the thread information block for the current thread
// VC++ also offers the __readfsdword() intrinsic, which would be better to use here.
NT_TIB* pTib;
__asm {
mov eax, fs:[18h]
mov pTib, eax
}
return (void*)pTib->StackBase;
#elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64) && defined(EA_COMPILER_MSVC)
// VC++ also offers the __readgsdword() intrinsic, which is an alternative which could
// retrieve the current thread TEB if the following proves unreliable.
PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
return (void*)pTib->StackBase;
#elif defined(EA_PLATFORM_WIN32) && defined(EA_PROCESSOR_X86) && defined(EA_COMPILER_GCC)
NT_TIB* pTib;
asm ( "movl %%fs:0x18, %0\n"
: "=r" (pTib)
);
return (void*)pTib->StackBase;
#endif
}
#if defined(EA_PLATFORM_WIN32) && defined(EA_PROCESSOR_X86) && defined(_MSC_VER) && (_MSC_VER >= 1400)
// People report on the Internet that this function can get you what CPU the current thread
// is running on. But that's false, as this function has been seen to return values greater than
// the number of physical or real CPUs present. For example, this function returns 6 for my
// Single CPU that's dual-hyperthreaded.
static int GetCurrentProcessorNumberCPUID()
{
_asm { mov eax, 1 }
_asm { cpuid }
_asm { shr ebx, 24 }
_asm { mov eax, ebx }
}
int GetCurrentProcessorNumberXP()
{
int cpuNumber = GetCurrentProcessorNumberCPUID();
int cpuCount = EA::Thread::GetProcessorCount();
return (cpuNumber % cpuCount); // I don't know if this is the right thing to do, but it's better than returning an impossible number and Windows XP is a fading OS as it is.
}
#endif
EATHREADLIB_API int EA::Thread::GetThreadProcessor()
{
#if defined(EA_PLATFORM_WIN32)
// 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 = GetModuleHandleA("KERNEL32.DLL");
if(hKernel32)
pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)())(uintptr_t)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_WIN64)
static EA_THREAD_LOCAL bool bInitialized = false;
static EA_THREAD_LOCAL DWORD (WINAPI *pfnGetCurrentProcessorNumber)() = NULL;
if(!bInitialized)
{
HMODULE hKernel32 = GetModuleHandleA("KERNEL32.DLL"); // Yes, we want to use Kernel32.dll. There is no Kernel64.dll on Win64.
if(hKernel32)
pfnGetCurrentProcessorNumber = (DWORD (WINAPI*)())(uintptr_t)GetProcAddress(hKernel32, "GetCurrentProcessorNumber");
bInitialized = true;
}
if(pfnGetCurrentProcessorNumber)
return (int)(unsigned)pfnGetCurrentProcessorNumber();
return 0;
#else
return (int)(unsigned)GetCurrentProcessorNumber();
#endif
}
EATHREADLIB_API void EA::Thread::SetThreadAffinityMask(const EA::Thread::ThreadId& id, ThreadAffinityMask nAffinityMask)
{
// Update the affinity mask in the thread dynamic data cache.
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
pTDD->mnThreadAffinityMask = nAffinityMask;
}
#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
#if defined(EA_PLATFORM_XBOXONE)
DWORD_PTR nProcessorCountMask = 0x7F; // default to all 7 available cores.
#else
DWORD_PTR nProcessorCountMask = (DWORD_PTR)1 << GetProcessorCount();
#endif
// Call the Windows library function.
DWORD_PTR nProcessAffinityMask, nSystemAffinityMask;
if(EA_LIKELY(GetProcessAffinityMask(GetCurrentProcess(), &nProcessAffinityMask, &nSystemAffinityMask)))
nProcessorCountMask = nProcessAffinityMask;
nAffinityMask &= nProcessorCountMask;
auto opResult = ::SetThreadAffinityMask(id, static_cast<DWORD_PTR>(nAffinityMask));
EA_UNUSED(opResult);
EAT_ASSERT_FORMATTED(opResult != 0, "The Windows platform SetThreadAffinityMask failed. GetLastError %x", GetLastError());
#endif
}
EATHREADLIB_API EA::Thread::ThreadAffinityMask EA::Thread::GetThreadAffinityMask(const EA::Thread::ThreadId& id)
{
// Update the affinity mask in the thread dynamic data cache.
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
if(pTDD)
{
return pTDD->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
// Internal SetThreadName API's so we don't repeat the implementations
namespace EA {
namespace Thread {
namespace Internal {
bool PixSetThreadName(EA::Thread::ThreadId threadId, const char* pName)
{
EA_UNUSED(threadId); EA_UNUSED(pName);
bool result = true;
#if (defined(EA_PLATFORM_XBOXONE) && EA_CAPILANO_DBG_ENABLED == 1)
wchar_t wName[EATHREAD_NAME_SIZE];
mbstowcs(wName, pName, EATHREAD_NAME_SIZE);
result = (::SetThreadName(threadId, wName) == TRUE); // requires toolhelpx.lib
EAT_ASSERT(result);
#endif
return result;
}
bool WinSetThreadName(EA::Thread::ThreadId threadId, const char* pName)
{
bool result = true;
typedef HRESULT(WINAPI *SetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription);
// Check if Windows Operating System has the 'SetThreadDescription" API.
auto pSetThreadDescription = (SetThreadDescription)GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetThreadDescription");
if (pSetThreadDescription)
{
wchar_t wName[EATHREAD_NAME_SIZE];
mbstowcs(wName, pName, EATHREAD_NAME_SIZE);
result = SUCCEEDED(pSetThreadDescription(threadId, wName));
EAT_ASSERT(result);
}
return result;
}
void WinSetThreadNameByException(EA::Thread::SysThreadId threadId, const char* pName)
{
struct ThreadNameInfo
{
DWORD dwType;
LPCSTR lpName;
DWORD dwThreadId;
DWORD dwFlags;
};
// This setjmp/longjmp weirdness is here to work around an apparent bug in the VS2013 debugger,
// whereby EBX will be trashed on return from RaiseException, causing bad things to happen in code
// which runs later. This only seems to happen when a debugger is attached and there's some managed
// code in the process.
jmp_buf jmpbuf;
__pragma(warning(push))
__pragma(warning(disable : 4611))
if (!setjmp(jmpbuf))
{
ThreadNameInfo threadNameInfo = {0x1000, pName, threadId, 0};
__try { RaiseException(0x406D1388, 0, sizeof(threadNameInfo) / sizeof(ULONG_PTR), (CONST ULONG_PTR*)(uintptr_t)&threadNameInfo); }
__except (GetExceptionCode() == 0x406D1388 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { }
longjmp(jmpbuf, 1);
}
__pragma(warning(pop))
}
void SetThreadName(EAThreadDynamicData* pTDD, const char* pName)
{
strncpy(pTDD->mName, pName, EATHREAD_NAME_SIZE);
pTDD->mName[EATHREAD_NAME_SIZE - 1] = 0;
#if defined(EA_PLATFORM_WINDOWS) && defined(_MSC_VER) || (defined(EA_PLATFORM_XBOXONE))
if(pTDD->mName[0] && (pTDD->mhThread != EA::Thread::kThreadIdInvalid))
{
#if EATHREAD_NAMING == EATHREAD_NAMING_DISABLED
bool namingEnabled = false;
#elif EATHREAD_NAMING == EATHREAD_NAMING_ENABLED
bool namingEnabled = true;
#else
bool namingEnabled = IsDebuggerPresent();
#endif
if(namingEnabled)
{
PixSetThreadName(pTDD->mhThread, pTDD->mName);
WinSetThreadName(pTDD->mhThread, pTDD->mName);
WinSetThreadNameByException(pTDD->mnThreadId, pTDD->mName);
}
}
#endif
}
} // namespace Internal
} // namespace Thread
} // namespace EA
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)
{
Internal::SetThreadName(pTDD, pName);
}
}
EATHREADLIB_API const char* EA::Thread::GetThreadName(const EA::Thread::ThreadId& id)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(id);
return pTDD ? pTDD->mName : "";
}
EATHREADLIB_API int EA::Thread::GetProcessorCount()
{
#if defined(EA_PLATFORM_XBOXONE)
// Capilano has 7-ish physical CPUs available to titles. We can access 50 - 90% of the 7th Core.
// Check platform documentation for details.
DWORD_PTR ProcessAffinityMask;
DWORD_PTR SystemAffinityMask;
unsigned long nCoreCount = 6;
if(EA_LIKELY(GetProcessAffinityMask(GetCurrentProcess(), &ProcessAffinityMask, &SystemAffinityMask)))
{
_BitScanForward(&nCoreCount, (unsigned long)~ProcessAffinityMask);
}
return (int) nCoreCount;
#elif 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;
#else
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));
GetNativeSystemInfo(&systemInfo);
nProcessorCount = (int)systemInfo.dwNumberOfProcessors;
}
return nProcessorCount;
#endif
}
EATHREADLIB_API void EA::Thread::ThreadSleep(const ThreadTime& timeRelative)
{
// Sleep(0) sleeps the current thread if any other thread of equal priority is ready to run.
// Sleep(n) sleeps the current thread for up to n milliseconds if there is any other thread of any priority ready to run.
// SwitchToThread() sleeps the current thread for one time slice if there is any other thread of any priority ready to run.
if(timeRelative == 0)
SwitchToThread(); // It's debateable whether we should do a SwitchToThread or a Sleep(0) here.
else // The only difference is that the former allows threads of lower priority to execute.
SleepEx((unsigned)timeRelative, TRUE);
}
namespace EA {
namespace Thread {
extern EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId);
extern EAThreadDynamicData* FindThreadDynamicData(SysThreadId sysThreadId);
}
}
void EA::Thread::ThreadEnd(intptr_t threadReturnValue)
{
EAThreadDynamicData* const pTDD = FindThreadDynamicData(GetThreadId());
if(pTDD)
{
pTDD->mnStatus = Thread::kStatusEnded;
pTDD->mnReturnValue = threadReturnValue;
pTDD->Release();
}
EA::Thread::SetCurrentThreadHandle(kThreadIdInvalid, true); // We use 'true' here just to be safe, as we don't know who is calling this function.
#if defined(EA_PLATFORM_XBOXONE)
// _endthreadex is not supported on Capilano because it's not compatible with C++/CX and /ZW. Use of ExitThread could result in memory leaks
// as ExitThread does not clean up memory allocated by the C runtime library.
// https://forums.xboxlive.com/AnswerPage.aspx?qid=47c1607c-bb18-4bc4-a79a-a40c59444ff3&tgt=1
ExitThread(static_cast<DWORD>(threadReturnValue));
#elif defined(EA_PLATFORM_MICROSOFT) && defined(EA_PLATFORM_CONSOLE) && !defined(EA_PLATFORM_XBOXONE)
EAT_FAIL_MSG("EA::Thread::ThreadEnd: Not supported by this platform.");
#else
_endthreadex((unsigned int)threadReturnValue);
#endif
}
EATHREADLIB_API EA::Thread::ThreadTime EA::Thread::GetThreadTime()
{
// We choose to use GetTickCount because it low overhead and
// still yields values that have a precision in the same range
// as the Win32 thread time slice time. In particular:
// rdtsc takes ~5 cycles and has a nanosecond resolution. But it is unreliable
// GetTickCount() takes ~80 cycles and has ~15ms resolution.
// timeGetTime() takes ~350 cpu cycles and has 1ms resolution.
// QueryPerformanceCounter() takes ~3000 cpu cycles on most machines and has ~1us resolution.
// We add EATHREAD_MIN_ABSOLUTE_TIME to this absolute time to ensure this absolute time is never less than our min
// (This fix was required because GetTickCount64 starts at 0x0 for titles on capilano)
#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PROCESSOR_X86_64)
return (ThreadTime)(GetTickCount64() + EATHREAD_MIN_ABSOLUTE_TIME);
#else // Note that this value matches the value used by some runtime assertion code within EA::Thread. It would be best to define this as a shared constant between modules.
return (ThreadTime)(GetTickCount() + EATHREAD_MIN_ABSOLUTE_TIME);
#endif
}
EATHREADLIB_API void EA::Thread::SetAssertionFailureFunction(EA::Thread::AssertionFailureFunction pAssertionFailureFunction, void* pContext)
{
gpAssertionFailureFunction = pAssertionFailureFunction;
gpAssertionFailureContext = pContext;
}
EATHREADLIB_API void EA::Thread::AssertionFailure(const char* pExpression)
{
if(gpAssertionFailureFunction)
gpAssertionFailureFunction(pExpression, gpAssertionFailureContext);
else
{
#if EAT_ASSERT_ENABLED
OutputDebugStringA("EA::Thread::AssertionFailure: ");
OutputDebugStringA(pExpression);
OutputDebugStringA("\n");
#ifdef _MSC_VER
__debugbreak();
#endif
#endif
}
}
uint32_t EA::Thread::RelativeTimeoutFromAbsoluteTimeout(ThreadTime timeoutAbsolute)
{
EAT_ASSERT((timeoutAbsolute == kTimeoutImmediate) || (timeoutAbsolute > EATHREAD_MIN_ABSOLUTE_TIME)); // Assert that the user didn't make the mistake of treating time as relative instead of absolute.
DWORD timeoutRelative = 0;
if (timeoutAbsolute == kTimeoutNone)
{
timeoutRelative = INFINITE;
}
else if (timeoutAbsolute == kTimeoutImmediate)
{
timeoutRelative = 0;
}
else
{
ThreadTime timeCurrent(GetThreadTime());
timeoutRelative = (timeoutAbsolute > timeCurrent) ? static_cast<DWORD>(timeoutAbsolute - timeCurrent) : 0;
}
EAT_ASSERT((timeoutRelative == INFINITE) || (timeoutRelative < 100000000)); // Assert that the timeout is a sane value and didn't wrap around.
return timeoutRelative;
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,304 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "EABase/eabase.h"
#include "eathread/eathread_semaphore.h"
#include "eathread/eathread_sync.h"
#include <limits.h>
#if defined(EA_PLATFORM_MICROSOFT)
EA_DISABLE_ALL_VC_WARNINGS()
#include <Windows.h>
EA_RESTORE_ALL_VC_WARNINGS()
#if defined(EA_PLATFORM_WIN64)
#if !defined _Ret_maybenull_
#define _Ret_maybenull_
#endif
#if !defined _Reserved_
#define _Reserved_
#endif
extern "C" WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateSemaphoreExW(_In_opt_ LPSECURITY_ATTRIBUTES, _In_ LONG, _In_ LONG, _In_opt_ LPCWSTR, _Reserved_ DWORD, _In_ DWORD);
#endif
#endif
#ifdef CreateSemaphore
#undef CreateSemaphore
#endif
#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
// Helper function to abstract away differences between APIs for different versions of Windows
static DWORD EASemaphoreWaitForSingleObject(HANDLE handle, DWORD milliseconds)
{
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
return WaitForSingleObject(handle, milliseconds);
#else
return WaitForSingleObjectEx(handle, milliseconds, TRUE);
#endif
}
EASemaphoreData::EASemaphoreData()
: mhSemaphore(0), mnCount(0), mnCancelCount(0), mnMaxCount(INT_MAX), mbIntraProcess(true)
{
EAWriteBarrier();
static_assert(sizeof(int32_t) == sizeof(LONG), "We use int32_t and LONG interchangably. Windows (including Win64) uses 32 bit longs.");
}
void EASemaphoreData::UpdateCancelCount(int32_t cancelCount)
{
// This is used by the fast semaphore path. This function actually isn't called very often -- only under uncommon circumstances.
// This is based on an algorithm discussed on usenet in 2004.
// We safely increment count by min(cancelCount, -count) if count < 0.
int32_t oldCount, newCount, cmpCount;
if(cancelCount > 0)
{
oldCount = mnCount;
while(oldCount < 0)
{
// Increment old count by the number of cancels
if((newCount = oldCount + cancelCount) > 0)
newCount = 0; // ...but not greater then zero.
cmpCount = oldCount;
oldCount = InterlockedCompareExchange((LONG*)&mnCount, newCount, cmpCount);
if(oldCount == cmpCount)
{
cancelCount -= (newCount - oldCount);
break;
}
}
if(cancelCount > 0)
InterlockedExchangeAdd((LONG*)&mnCancelCount, cancelCount);
}
}
EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName)
: mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess)
{
if(pName)
{
strncpy(mName, pName, sizeof(mName)-1);
mName[sizeof(mName)-1] = 0;
}
else
mName[0] = 0;
}
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(mSemaphoreData.mhSemaphore)
CloseHandle(mSemaphoreData.mhSemaphore);
}
bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
{
if(pSemaphoreParameters && !mSemaphoreData.mhSemaphore)
{
mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
if(mSemaphoreData.mnCount < 0)
mSemaphoreData.mnCount = 0;
mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess;
// If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process.
#if EATHREAD_FAST_MS_SEMAPHORE_ENABLED
const bool bIntraProcess = mSemaphoreData.mbIntraProcess;
#else
const bool bIntraProcess = false;
#endif
if(bIntraProcess)
{
// Note that we do things rather differently for intra-process, as we are
// implementing a fast semaphore. This semaphore will be at least 10 times
// faster than the OS semaphore for all Microsoft platforms for the case of
// successful immediate acquire of a semaphore. Semaphore posts (or releases
// will also be much faster than the OS version.
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
mSemaphoreData.mhSemaphore = CreateSemaphoreA(NULL, 0, INT_MAX/2, NULL); // Intentionally ignore mnCount and mnMaxCount here.
#else
mSemaphoreData.mhSemaphore = CreateSemaphoreExW(NULL, 0, INT_MAX/2, NULL, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE); // Intentionally ignore mnCount and mnMaxCount here.
#endif
}
else // Else we create a conventional Win32-style semaphore.
{
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
mSemaphoreData.mhSemaphore = CreateSemaphoreA(NULL, (LONG)mSemaphoreData.mnCount, (LONG)mSemaphoreData.mnMaxCount, pSemaphoreParameters->mName[0] ? pSemaphoreParameters->mName : NULL);
#else
wchar_t wName[EAArrayCount(pSemaphoreParameters->mName)]; // We do an ASCII conversion.
for(size_t c = 0; c < EAArrayCount(wName); c++)
wName[c] = (wchar_t)(uint8_t)pSemaphoreParameters->mName[c];
mSemaphoreData.mhSemaphore = CreateSemaphoreExW(NULL, (LONG)mSemaphoreData.mnCount, (LONG)mSemaphoreData.mnMaxCount, wName[0] ? wName : NULL, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
#endif
}
EAWriteBarrier();
EAT_ASSERT(mSemaphoreData.mhSemaphore != 0);
return (mSemaphoreData.mhSemaphore != 0);
}
return false;
}
int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
{
EAT_ASSERT(mSemaphoreData.mhSemaphore != 0);
// If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process.
#if EATHREAD_FAST_MS_SEMAPHORE_ENABLED
const bool bIntraProcess = mSemaphoreData.mbIntraProcess;
#else
const bool bIntraProcess = false;
#endif
if(bIntraProcess) // If this is true, we are using the fast semaphore pathway.
{
if(InterlockedDecrement((LONG*)&mSemaphoreData.mnCount) < 0) // InterlockedDecrement returns the new value. If the mnCount was > 0 before this decrement, then this Wait function will return very quickly.
{
const DWORD dw = EASemaphoreWaitForSingleObject(mSemaphoreData.mhSemaphore, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(dw != WAIT_OBJECT_0) // If there was a timeout...
{
mSemaphoreData.UpdateCancelCount(1); // or InterlockedIncrement(&mSemaphoreData.mnCancelCount); // The latter has a bug whereby mnCancelCount can increment indefinitely.
EAT_ASSERT(dw == WAIT_TIMEOUT); // Otherwise it was probably a timeout.
if(dw == WAIT_TIMEOUT)
return kResultTimeout;
return kResultError; // WAIT_FAILED
}
}
// It is by design that a semaphore post does a full memory barrier.
// We don't need such a barrier for this pathway to work, but rather
// it is expected by the user that such a barrier is executed. Investigation
// into the choice of a full vs. just read or write barrier has concluded
// (based on the Posix standard) that a full read-write barrier is expected.
EAReadWriteBarrier();
const int count = (int)mSemaphoreData.mnCount; // Make temporary to avoid race condition in ternary operator below.
return (count > 0 ? count : 0);
}
else
{
const DWORD dw = EASemaphoreWaitForSingleObject(mSemaphoreData.mhSemaphore, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(dw == WAIT_OBJECT_0)
return (int)InterlockedDecrement((LONG*)&mSemaphoreData.mnCount);
else if(dw == WAIT_TIMEOUT)
return kResultTimeout;
return kResultError;
}
}
int EA::Thread::Semaphore::Post(int count)
{
EAT_ASSERT((mSemaphoreData.mhSemaphore != 0) && (count >= 0));
if(count > 0)
{
// If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process.
#if EATHREAD_FAST_MS_SEMAPHORE_ENABLED
const bool bIntraProcess = mSemaphoreData.mbIntraProcess;
#else
const bool bIntraProcess = false;
#endif
if(bIntraProcess) // If this is true, we are using the fast semaphore pathway.
{
// It is by design that a semaphore post does a full memory barrier.
// We don't need such a barrier for this pathway to work, but rather
// it is expected by the user that such a barrier is executed. Investigation
// into the choice of a full vs. just read or write barrier has concluded
// (based on the Posix standard) that a full read-write barrier is expected.
EAReadWriteBarrier();
if((mSemaphoreData.mnCancelCount > 0) && (mSemaphoreData.mnCount < 0)) // Much of the time this will evaluate to false due to the first condition.
mSemaphoreData.UpdateCancelCount(InterlockedExchange((LONG*)&mSemaphoreData.mnCancelCount, 0)); // It's possible that mnCancelCount may have decremented down to zero between the previous line of code and this line of code.
const int currentCount = mSemaphoreData.mnCount;
if((mSemaphoreData.mnMaxCount - count) < currentCount) // If count would cause an overflow...
return kResultError; // We do what most OS implementations of max-count do. count = (mSemaphoreData.mnMaxCount - currentCount);
const int32_t nWaiterCount = -InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, count); // InterlockedExchangeAdd returns the initial value of mnCount. If it's below zero, then it's a count of waiters.
const int nNewCount = count - nWaiterCount;
if(nWaiterCount > 0) // If there were waiters blocking...
{
const int32_t nReleaseCount = (count < nWaiterCount) ? count : nWaiterCount; // Call ReleaseSemaphore for as many waiters as possible.
ReleaseSemaphore(mSemaphoreData.mhSemaphore, nReleaseCount, NULL); // Note that by the time this executes, nReleaseCount might be > than the actual number of waiting threads, due to timeouts.
}
return (nNewCount > 0 ? nNewCount : 0);
}
else
{
const int32_t nPreviousCount = InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, count);
const int nNewCount = nPreviousCount + count;
const BOOL result = ReleaseSemaphore(mSemaphoreData.mhSemaphore, count, NULL);
if(!result)
{
InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, -count);
EAT_ASSERT(result);
return kResultError;
}
return nNewCount;
}
}
return (int)mSemaphoreData.mnCount; // We don't worry if this value is changing. There is nothing that you can rely upon about this value anyway.
}
int EA::Thread::Semaphore::GetCount() const
{
// Under our fast pathway, mnCount can go below zero.
// Under the fast pathway, we need to add mnCancelCount to mnCount because mnCount is negative by the number of waiters and mnCancelCount is the number of waiters that have abandoned waiting but the value hasn't been rolled back into mnCount yet.
EAReadBarrier();
const int count = (int)mSemaphoreData.mnCount + (int)mSemaphoreData.mnCancelCount; // Make temporary to avoid race condition in ternary operator below.
return (count > 0 ? count : 0);
}
#endif // EA_PLATFORM_XXX
@@ -0,0 +1,833 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "EABase/eabase.h"
#include "eathread/eathread.h"
#include "eathread/eathread_callstack.h"
#include "eathread/eathread_mutex.h"
#include "eathread/eathread_sync.h"
#include "eathread/eathread_thread.h"
#include "eathread/internal/eathread_global.h"
#if EA_COMPILER_VERSION >= 1900 // VS2015+
// required for windows.h that has mismatch that is included in this file
EA_DISABLE_VC_WARNING(5031 5032)// #pragma warning(pop): likely mismatch, popping warning state pushed in different file / detected #pragma warning(push) with no corresponding
#endif
// Warning 6312 and 6322 are spurious, as we are not execution a case that could possibly loop.
// 6312: Possible infinite loop: use of the constant EXCEPTION_CONTINUE_EXECUTION in the exception-filter expression of a try-except. Execution restarts in the protected block
// 6322: Empty _except block
EA_DISABLE_VC_WARNING(6312 6322)
#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
#include <new>
#include <process.h>
EA_DISABLE_ALL_VC_WARNINGS()
#include <Windows.h>
EA_RESTORE_ALL_VC_WARNINGS()
#if defined(_MSC_VER)
struct ThreadNameInfo{
DWORD dwType;
LPCSTR lpName;
DWORD dwThreadId;
DWORD dwFlags;
};
extern "C" WINBASEAPI DWORD WINAPI SetThreadIdealProcessor(_In_ HANDLE hThread, _In_ DWORD dwIdealProcessor);
extern "C" WINBASEAPI BOOL WINAPI IsDebuggerPresent();
#endif
#ifdef _MSC_VER
#ifndef EATHREAD_INIT_SEG_DEFINED
#define EATHREAD_INIT_SEG_DEFINED
#endif
// We are changing the initalization ordering here because in bulkbuild tool builds the initialization
// order of globals changes and causes a crash when we attempt to lock the Mutex guarding access
// of the EAThreadDynamicData objects. The code attempts to lock a mutex that has been destructed
// and causes a crash within the WindowsSDK. This ensures that global mutex object is not destructed
// until all user code has destructed.
//
#ifndef EATHREAD_INIT_SEG_DEFINED
#define EATHREAD_INIT_SEG_DEFINED
#pragma warning(disable: 4075) // warning C4075: initializers put in unrecognized initialization area
#pragma warning(disable: 4073) //warning C4073: initializers put in library initialization area
#pragma init_seg(lib)
#endif
#endif
namespace EA {
namespace Thread {
extern void SetCurrentThreadHandle(HANDLE hThread, bool bDynamic);
namespace Internal { extern void SetThreadName(EAThreadDynamicData* pTDD, const char* pName); };
}
}
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
static AtomicInt32 nLastProcessor = 0;
const size_t kMaxThreadDynamicDataCount = 128;
struct EAThreadGlobalVars
{
EA_PREFIX_ALIGN(8)
char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)] EA_POSTFIX_ALIGN(8);
AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount];
Mutex gThreadDynamicMutex;
EAThreadGlobalVars() {}
EAThreadGlobalVars(const EAThreadGlobalVars&) {}
EAThreadGlobalVars& operator=(const EAThreadGlobalVars&) {}
};
EATHREAD_GLOBALVARS_CREATE_INSTANCE;
EAThreadDynamicData* AllocateThreadDynamicData()
{
AutoMutex am(EATHREAD_GLOBALVARS.gThreadDynamicMutex);
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0))
return (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
}
// This is a safety fallback mechanism. In practice it won't be used in almost all situations.
if(gpAllocator)
return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData));
else
return new EAThreadDynamicData; // This is a small problem, as this doesn't just allocate it but also constructs it.
}
void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData)
{
AutoMutex am(EATHREAD_GLOBALVARS.gThreadDynamicMutex);
if((pEAThreadDynamicData >= (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount)))
{
pEAThreadDynamicData->~EAThreadDynamicData();
EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0);
}
else
{
// Assume the data was allocated via the fallback mechanism.
if(gpAllocator)
{
pEAThreadDynamicData->~EAThreadDynamicData();
gpAllocator->Free(pEAThreadDynamicData);
}
else
delete pEAThreadDynamicData;
}
}
EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId)
{
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if(pTDD->mhThread == threadId)
return pTDD;
}
return NULL; // This is no practical way we can find the data unless thread-specific storage was involved.
}
EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId)
{
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if (pTDD->mnThreadId == sysThreadId)
return pTDD;
}
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
}
bool IsDebuggerPresent()
{
#if defined(EA_PLATFORM_MICROSOFT)
return ::IsDebuggerPresent() != 0;
#else
return false;
#endif
}
}
}
EAThreadDynamicData::EAThreadDynamicData()
: mhThread(EA::Thread::kThreadIdInvalid),
mnThreadId(0), // Note that this is a Windows "thread id", wheras for us thread ids are what Windows calls a thread handle.
mnStatus(EA::Thread::Thread::kStatusNone),
mnReturnValue(0),
mpBeginThreadUserWrapper(NULL),
mnRefCount(0)
{
// Empty
}
EAThreadDynamicData::~EAThreadDynamicData()
{
if(mhThread)
::CloseHandle(mhThread);
mhThread = EA::Thread::kThreadIdInvalid;
mnThreadId = 0;
}
void EAThreadDynamicData::AddRef()
{
mnRefCount.Increment();
}
void EAThreadDynamicData::Release()
{
if(mnRefCount.Decrement() == 0)
EA::Thread::FreeThreadDynamicData(this);
}
EA::Thread::ThreadParameters::ThreadParameters()
: mpStack(NULL)
, mnStackSize(0)
, mnPriority(kThreadPriorityDefault)
, mnProcessor(kProcessorDefault)
, mnAffinityMask(kThreadAffinityMaskAny)
, mpName("")
, mbDisablePriorityBoost(false)
{
}
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL;
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL;
EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny;
EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff);
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper()
{
return sGlobalRunnableFunctionUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper)
{
if(sGlobalRunnableFunctionUserWrapper != NULL)
{
// Can only be set once in entire game.
EAT_ASSERT(false);
}
else
{
sGlobalRunnableFunctionUserWrapper = pUserWrapper;
}
}
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper()
{
return sGlobalRunnableClassUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper)
{
if(sGlobalRunnableClassUserWrapper != NULL)
{
// Can only be set once.
EAT_ASSERT(false);
}
else
sGlobalRunnableClassUserWrapper = pUserWrapper;
}
// Helper that selects a target processor based on the provided ThreadParameters structure and the various
// pieces of shared state that EAThread maintains to implement a 'round-robin' style processor selection.
int SelectProcessor(const EA::Thread::ThreadParameters* pTP, EA::Thread::AtomicInt32& sDefaultProcessor, EA::Thread::AtomicUint64& sDefaultProcessorMask)
{
int nProcessor;
if (pTP && (pTP->mnProcessor >= 0))
{
nProcessor = pTP->mnProcessor;
// This is a small attempt to try to spread out threads between processors. We don't
// care much if another thread happens to be created here and races with this.
if (nProcessor == EA::Thread::nLastProcessor)
++EA::Thread::nLastProcessor;
}
else
{
#if defined(EA_PLATFORM_MICROSOFT)
if (!pTP || pTP->mnProcessor == EA::Thread::kProcessorAny)
{
// If the processor is not specified, then allow the scheduler to
// run the thread on any available processor
nProcessor = EA::Thread::kProcessorDefault;
}
else
#endif
if (sDefaultProcessor >= 0) // If the user has identified a specific processor...
nProcessor = sDefaultProcessor;
else if(sDefaultProcessor == EA::Thread::kProcessorDefault) // If the user explicitly asked for the default processor
nProcessor = sDefaultProcessor;
else
{
// NOTE(rparolin): The reason we have this round-robin code is that the
// originally we used it on Xenon OS which required us to pick a CPU to run on.
// After the Xenon was deprecated this code remained and is now a functional
// requirement. We should probably deprecate and remove in the future but
// currently teams are dependent on it.
const uint64_t processorMask = sDefaultProcessorMask.GetValue();
do
{
nProcessor = EA::Thread::nLastProcessor.Increment();
if (nProcessor == MAXIMUM_PROCESSORS)
{
EA::Thread::nLastProcessor.SetValueConditional(0, MAXIMUM_PROCESSORS);
nProcessor = 0;
}
} while ((((uint64_t)1 << nProcessor) & processorMask) == 0);
}
}
return nProcessor;
}
EA::Thread::Thread::Thread()
{
mThreadData.mpData = NULL;
}
EA::Thread::Thread::Thread(const Thread& t)
: mThreadData(t.mThreadData)
{
if(mThreadData.mpData)
mThreadData.mpData->AddRef();
}
EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& t)
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if(t.mThreadData.mpData)
t.mThreadData.mpData->AddRef();
if(mThreadData.mpData)
mThreadData.mpData->Release();
mThreadData = t.mThreadData;
return *this;
}
EA::Thread::Thread::~Thread()
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if(mThreadData.mpData)
mThreadData.mpData->Release();
}
#if defined(EA_PLATFORM_XBOXONE)
static DWORD WINAPI RunnableFunctionInternal(void* pContext)
#else
static unsigned int __stdcall RunnableFunctionInternal(void* pContext)
#endif
{
// The parent thread is sharing memory with us and we need to
// make sure our view of it is synchronized with the parent.
EAReadWriteBarrier();
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext;
EA::Thread::RunnableFunction pFunction = (EA::Thread::RunnableFunction)pTDD->mpStartContext[0];
void* pCallContext = pTDD->mpStartContext[1];
EA::Thread::SetCurrentThreadHandle(pTDD->mpStartContext[2], false);
pTDD->mpStackBase = EA::Thread::GetStackBase();
pTDD->mnStatus = EA::Thread::Thread::kStatusRunning;
EA::Thread::SetThreadName(pTDD->mhThread, pTDD->mName);
if(pTDD->mpBeginThreadUserWrapper != NULL)
{
EA::Thread::RunnableFunctionUserWrapper pWrapperFunction = (EA::Thread::RunnableFunctionUserWrapper)pTDD->mpBeginThreadUserWrapper;
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
pTDD->mnReturnValue = pWrapperFunction(pFunction, pCallContext);
}
else
{
pTDD->mnReturnValue = pFunction(pCallContext);
}
const unsigned int nReturnValue = (unsigned int)pTDD->mnReturnValue;
EA::Thread::SetCurrentThreadHandle(0, false);
pTDD->mnStatus = EA::Thread::Thread::kStatusEnded;
pTDD->Release();
return nReturnValue;
}
void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask)
{
if(mThreadData.mpData->mhThread)
{
EA::Thread::SetThreadAffinityMask(mThreadData.mpData->mhThread, nAffinityMask);
}
}
EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask()
{
if(mThreadData.mpData->mhThread)
{
return mThreadData.mpData->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper)
{
// Check there is an entry for the current thread context in our ThreadDynamicData array.
ThreadId thisThreadId = GetThreadId();
if(!FindThreadDynamicData(thisThreadId))
{
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData;
if(pData)
{
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
// Do no AddRef for thread execution because this is not an EAThread managed thread.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mhThread = thisThreadId;
pData->mnThreadId = GetCurrentThreadId();
strncpy(pData->mName, "external", EATHREAD_NAME_SIZE);
pData->mName[EATHREAD_NAME_SIZE - 1] = 0;
pData->mpStackBase = EA::Thread::GetStackBase();
}
}
if(mThreadData.mpData)
mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below.
// Win32-like platforms don't support user-supplied stacks. A user-supplied stack pointer
// here would be a waste of user memory, and so we assert that mpStack == NULL.
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap.
mThreadData.mpData = pData;
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mhThread = kThreadIdInvalid;
pData->mpStartContext[0] = pFunction;
pData->mpStartContext[1] = pContext;
pData->mpBeginThreadUserWrapper = pUserWrapper;
pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny;
const unsigned nStackSize = pTP ? (unsigned)pTP->mnStackSize : 0;
#if defined(EA_PLATFORM_XBOXONE)
// Capilano no longer supports _beginthreadex. Using CreateThread instead may cause issues when using the MS CRT
// according to MSDN (memory leaks or possibly crashes) as it does not initialize the CRT. This a reasonable
// workaround while we wait for clarification from MS on what the recommended threading APIs are for Capilano.
HANDLE hThread = CreateThread(NULL, nStackSize, RunnableFunctionInternal, pData, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&pData->mnThreadId));
#else
HANDLE hThread = (HANDLE)_beginthreadex(NULL, nStackSize, RunnableFunctionInternal, pData, CREATE_SUSPENDED, &pData->mnThreadId);
#endif
if(hThread)
{
pData->mhThread = hThread;
if(pTP)
SetName(pTP->mpName);
pData->mpStartContext[2] = hThread;
if(pTP && (pTP->mnPriority != kThreadPriorityDefault))
SetPriority(pTP->mnPriority);
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE)
if (pTP)
{
auto result = SetThreadPriorityBoost(pData->mhThread, pTP->mbDisablePriorityBoost);
EAT_ASSERT(result != 0);
EA_UNUSED(result);
}
#endif
#if defined(EA_PLATFORM_MICROSOFT)
int nProcessor = SelectProcessor(pTP, sDefaultProcessor, sDefaultProcessorMask);
if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny)
SetAffinityMask(pTP->mnAffinityMask);
else
SetProcessor(nProcessor);
#endif
ResumeThread(hThread);
pData->Release(); // Matches AddRef for this function.
return hThread;
}
pData->Release(); // Matches AddRef for this function.
pData->Release(); // Matches AddRef for this Thread class above.
pData->Release(); // Matches AddRef for the thread above.
mThreadData.mpData = NULL; // mThreadData.mpData == pData
return (ThreadId)kThreadIdInvalid;
}
#if defined(EA_PLATFORM_XBOXONE)
static DWORD WINAPI RunnableObjectInternal(void* pContext)
#else
static unsigned int __stdcall RunnableObjectInternal(void* pContext)
#endif
{
// The parent thread is sharing memory with us and we need to
// make sure our view of it is synchronized with the parent.
EAReadWriteBarrier();
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext;
EA::Thread::IRunnable* pRunnable = (EA::Thread::IRunnable*)pTDD->mpStartContext[0];
void* pCallContext = pTDD->mpStartContext[1];
EA::Thread::SetCurrentThreadHandle(pTDD->mpStartContext[2], false);
pTDD->mnStatus = EA::Thread::Thread::kStatusRunning;
EA::Thread::SetThreadName(pTDD->mhThread, pTDD->mName);
if(pTDD->mpBeginThreadUserWrapper)
{
EA::Thread::RunnableClassUserWrapper pWrapperClass = (EA::Thread::RunnableClassUserWrapper)pTDD->mpBeginThreadUserWrapper;
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
pTDD->mnReturnValue = pWrapperClass(pRunnable, pCallContext);
}
else
pTDD->mnReturnValue = pRunnable->Run(pCallContext);
const unsigned int nReturnValue = (unsigned int)pTDD->mnReturnValue;
EA::Thread::SetCurrentThreadHandle(0, false);
pTDD->mnStatus = EA::Thread::Thread::kStatusEnded;
pTDD->Release();
return nReturnValue;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper)
{
if(mThreadData.mpData)
mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below.
// Win32-like platforms don't support user-supplied stacks. A user-supplied stack pointer
// here would be a waste of user memory, and so we assert that mpStack == NULL.
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap.
mThreadData.mpData = pData;
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mhThread = kThreadIdInvalid;
pData->mpStartContext[0] = pRunnable;
pData->mpStartContext[1] = pContext;
pData->mpBeginThreadUserWrapper = pUserWrapper;
pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny;
const unsigned nStackSize = pTP ? (unsigned)pTP->mnStackSize : 0;
#if defined(EA_PLATFORM_XBOXONE)
// Capilano no longer supports _beginthreadex. Using CreateThread instead may cause issues when using the MS CRT
// according to MSDN (memory leaks or possibly crashes) as it does not initialize the CRT. This a reasonable
// workaround while we wait for clarification from MS on what the recommended threading APIs are for Capilano.
HANDLE hThread = CreateThread(NULL, nStackSize, RunnableObjectInternal, pData, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&pData->mnThreadId));
#else
HANDLE hThread = (HANDLE)_beginthreadex(NULL, nStackSize, RunnableObjectInternal, pData, CREATE_SUSPENDED, &pData->mnThreadId);
#endif
if(hThread)
{
pData->mhThread = hThread;
if(pTP)
SetName(pTP->mpName);
pData->mpStartContext[2] = hThread;
if(pTP && (pTP->mnPriority != kThreadPriorityDefault))
SetPriority(pTP->mnPriority);
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE)
if (pTP)
{
auto result = SetThreadPriorityBoost(pData->mhThread, pTP->mbDisablePriorityBoost);
EAT_ASSERT(result != 0);
EA_UNUSED(result);
}
#endif
#if defined(EA_PLATFORM_MICROSOFT)
int nProcessor = SelectProcessor(pTP, sDefaultProcessor, sDefaultProcessorMask);
if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny)
SetAffinityMask(pTP->mnAffinityMask);
else
SetProcessor(nProcessor);
#endif
ResumeThread(hThread); // This will unsuspend the thread.
pData->Release(); // Matches AddRef for this function.
return hThread;
}
pData->Release(); // Matches AddRef for this function.
pData->Release(); // Matches AddRef for this Thread class above.
pData->Release(); // Matches AddRef for the thread above.
mThreadData.mpData = NULL;
return (ThreadId)kThreadIdInvalid;
}
EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue)
{
// The mThreadData memory is shared between threads and when
// reading it we must be synchronized.
EAReadWriteBarrier();
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
// Todo: Consider that there may be a subtle race condition here if
// the user immediately calls WaitForEnd right after calling Begin.
if(mThreadData.mpData)
{
if(mThreadData.mpData->mhThread) // If it was started...
{
// We must not call WaitForEnd from the thread we are waiting to end. That would result in a deadlock.
EAT_ASSERT(mThreadData.mpData->mhThread != EA::Thread::GetThreadId());
// dwResult normally should be 'WAIT_OBJECT_0', but can also be WAIT_ABANDONED or WAIT_FAILED.
const DWORD dwResult = ::WaitForSingleObject(mThreadData.mpData->mhThread, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(dwResult == WAIT_TIMEOUT)
return kStatusRunning;
// Close the handle now so as to minimize handle proliferation.
::CloseHandle(mThreadData.mpData->mhThread);
mThreadData.mpData->mhThread = 0;
mThreadData.mpData->mnStatus = kStatusEnded;
}
if(pThreadReturnValue)
{
EAReadWriteBarrier();
*pThreadReturnValue = mThreadData.mpData->mnReturnValue;
}
return kStatusEnded; // A thread was created, so it must have ended.
}
else
{
// Else the user hasn't started the thread yet, so we wait until the user starts it.
// Ideally, what we really want to do here is wait for some kind of signal.
// Instead for the time being we do a polling loop.
while((!mThreadData.mpData || !mThreadData.mpData->mhThread) && (GetThreadTime() < timeoutAbsolute))
{
ThreadSleep(1);
EAReadWriteBarrier();
EACompilerMemoryBarrier();
}
if(mThreadData.mpData)
return WaitForEnd(timeoutAbsolute);
}
return kStatusNone; // No thread has been started.
}
EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* pThreadReturnValue) const
{
// The mThreadData memory is shared between threads and when
// reading it we must be synchronized.
EAReadWriteBarrier();
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
{
if(mThreadData.mpData->mhThread) // If the thread has been started...
{
DWORD dwExitStatus;
// Note that GetExitCodeThread is a hazard if the user of a thread exits
// with a return value that is equal to the value of STILL_ACTIVE (i.e. 259).
// We can document that users shouldn't do this, or we can change the code
// here to use WaitForSingleObject(hThread, 0) and assume the thread is
// still active if the return value is WAIT_TIMEOUT.
if(::GetExitCodeThread(mThreadData.mpData->mhThread, &dwExitStatus))
{
if(dwExitStatus == STILL_ACTIVE)
return kStatusRunning; // Nothing has changed.
::CloseHandle(mThreadData.mpData->mhThread); // Do this now so as to minimize handle proliferation.
mThreadData.mpData->mhThread = 0;
} // else fall through.
} // else fall through.
if(pThreadReturnValue)
*pThreadReturnValue = mThreadData.mpData->mnReturnValue;
mThreadData.mpData->mnStatus = kStatusEnded;
return kStatusEnded;
}
return kStatusNone;
}
EA::Thread::ThreadId EA::Thread::Thread::GetId() const
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
return (ThreadId)mThreadData.mpData->mhThread;
return kThreadIdInvalid;
}
int EA::Thread::Thread::GetPriority() const
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
{
const int nPriority = ::GetThreadPriority(mThreadData.mpData->mhThread);
return kThreadPriorityDefault + (nPriority - THREAD_PRIORITY_NORMAL);
}
return kThreadPriorityUnknown;
}
bool EA::Thread::Thread::SetPriority(int nPriority)
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
// For more information on how Windows handle thread priority based on process priority, see
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/scheduling_priorities.asp
EAT_ASSERT(nPriority != kThreadPriorityUnknown);
if(mThreadData.mpData)
{
int nNewPriority = THREAD_PRIORITY_NORMAL + (nPriority - kThreadPriorityDefault);
bool result = ::SetThreadPriority(mThreadData.mpData->mhThread, nNewPriority) != 0;
// Windows process running in NORMAL_PRIORITY_CLASS is picky about the priority passed in.
// So we need to set the priority to the next priority supported
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE)
while(!result)
{
if(nNewPriority >= THREAD_PRIORITY_TIME_CRITICAL)
return ::SetThreadPriority(mThreadData.mpData->mhThread, THREAD_PRIORITY_TIME_CRITICAL) != 0;
if(nNewPriority <= THREAD_PRIORITY_IDLE)
return ::SetThreadPriority(mThreadData.mpData->mhThread, THREAD_PRIORITY_IDLE) != 0;
result = ::SetThreadPriority(mThreadData.mpData->mhThread, nNewPriority) != 0;
nNewPriority++;
}
#endif
return result;
}
return false;
}
void EA::Thread::Thread::SetProcessor(int nProcessor)
{
if(mThreadData.mpData)
{
#if defined(EA_PLATFORM_XBOXONE)
static int nProcessorCount = GetProcessorCount();
if(nProcessor >= nProcessorCount)
nProcessor %= nProcessorCount;
ThreadAffinityMask mask = 0x7F; // default to all 7 available cores.
if (nProcessor >= 0)
mask = ((ThreadAffinityMask)1) << nProcessor;
SetThreadAffinityMask(mThreadData.mpData->mhThread, mask);
#else
static int nProcessorCount = GetProcessorCount();
if(nProcessor < 0)
nProcessor = MAXIMUM_PROCESSORS; // This causes the SetThreadIdealProcessor to reset to 'no ideal processor'.
else
{
if(nProcessor >= nProcessorCount)
nProcessor %= nProcessorCount;
}
// SetThreadIdealProcessor differs from SetThreadAffinityMask in that SetThreadIdealProcessor is not
// a strict assignment, and it allows the OS to move the thread if the ideal processor is busy.
// SetThreadAffinityMask is a more rigid assignment, but it can result in slower performance and
// possibly hangs due to processor contention between threads. For Windows we use SetIdealThreadProcessor
// in the name of safety and likely better overall performance.
SetThreadIdealProcessor(mThreadData.mpData->mhThread, (DWORD)nProcessor);
#endif
}
}
typedef VOID (APIENTRY *PAPCFUNC)(_In_ ULONG_PTR dwParam);
extern "C" WINBASEAPI DWORD WINAPI QueueUserAPC(_In_ PAPCFUNC pfnAPC, _In_ HANDLE hThread, _In_ ULONG_PTR dwData);
void EA::Thread::Thread::Wake()
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
struct ThreadWake{ static void WINAPI Empty(ULONG_PTR){} };
if(mThreadData.mpData && mThreadData.mpData->mhThread)
::QueueUserAPC((PAPCFUNC)ThreadWake::Empty, mThreadData.mpData->mhThread, 0);
}
const char* EA::Thread::Thread::GetName() const
{
if(mThreadData.mpData)
return mThreadData.mpData->mName;
return "";
}
void EA::Thread::Thread::SetName(const char* pName)
{
if (mThreadData.mpData && pName)
EA::Thread::Internal::SetThreadName(mThreadData.mpData, pName);
}
#endif // EA_PLATFORM_MICROSOFT
EA_RESTORE_VC_WARNING()
#if EA_COMPILER_VERSION >= 1900 // VS2015+
EA_RESTORE_VC_WARNING()// #pragma warning(pop): likely mismatch, popping warning state pushed in different file / detected #pragma warning(push) with no corresponding
#endif
@@ -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
+32
View File
@@ -0,0 +1,32 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "eathread/version.h"
namespace EA
{
namespace Thread
{
const Version gVersion =
{
EATHREAD_VERSION_MAJOR,
EATHREAD_VERSION_MINOR,
EATHREAD_VERSION_PATCH
};
const Version* GetVersion()
{
return &gVersion;
}
bool CheckVersion(int majorVersion, int minorVersion, int patchVersion)
{
return (majorVersion == EATHREAD_VERSION_MAJOR) &&
(minorVersion == EATHREAD_VERSION_MINOR) &&
(patchVersion == EATHREAD_VERSION_PATCH);
}
}
}
@@ -0,0 +1,627 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// This file implements a manual callstack reader for the x86 platform.
// It is an alternative to EACallstack_Win32.cpp, which implements
// callstack reading by standard Microsoft debug functions. This file
// on the other hand avoids the use of Microsoft calls and instead
// directly walks the callstack via reading the registers and x86
// instructions. This file can also be used to read x86 callstacks on
// non-Microsoft x86 platforms such as Linux.
///////////////////////////////////////////////////////////////////////////////
#include <eathread/eathread_callstack.h>
#include <eathread/eathread_callstack_context.h>
#include <eathread/eathread_storage.h>
#include <string.h>
#if EATHREAD_GLIBC_BACKTRACE_AVAILABLE
#include <signal.h>
#include <execinfo.h>
#endif
#if defined(EA_COMPILER_MSVC) && defined(EA_PROCESSOR_X86_64)
#pragma warning(push, 0)
#include <math.h> // VS2008 has an acknowledged bug that requires math.h (and possibly also string.h) to be #included before intrin.h.
#include <intrin.h>
#pragma warning(pop)
#endif
#if defined(EA_COMPILER_CLANG)
#include <unwind.h>
#endif
namespace EA
{
namespace Thread
{
///////////////////////////////////////////////////////////////////////////////
// InitCallstack
//
EATHREADLIB_API void InitCallstack()
{
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownCallstack
//
EATHREADLIB_API void ShutdownCallstack()
{
}
#if defined(_MSC_VER)
///////////////////////////////////////////////////////////////////////////////
// ReturnAddressToCallingAddress
//
// Convert the return address to the calling address.
//
void* ReturnAddressToCallingAddress(unsigned char* return_address)
{
// While negative array indices can be considered non-idiomatic it
// was felt that they are semantically appropriate as this code bases
// its comparisons from the return address and that it would be cleaner
// than using *(return_address - index).
// Three op-codes are used for the call instruction, 9A, E8, and FF.
// For a reference on the IA32 instruction format, see:
// http://www.cs.princeton.edu/courses/archive/spr06/cos217/reading/ia32vol2.pdf
// 9A cp - CALL ptr16:32 (7-byte)
if(return_address[-7] == 0x9A)
{
return (return_address - 7);
}
// E8 cd - CALL rel32 (5-byte)
else if(return_address[-5] == 0xE8)
{
return (return_address - 5);
}
else
{
// The third opcode to specify "call" instructions is FF.
// Unfortunately this instruction also needs the succeeding ModR/M
// byte to fully determine instruction length. The SIB value is
// another byte used for extending the range of addressing modes
// supported by the ModR/M byte. The values of this ModR/M byte
// used in conjunction with the call instruction are as follows:
//
// 7-byte call:
// FF [ModR/M] [SIB] [4-byte displacement]
// * ModR/M is either 0x94 or 0x9C
//
// 6-byte call:
// FF [ModR/M] [4-byte displacement]
// * ModR/M can be:
// * 0x90 - 0x9F EXCLUDING 0x94 or 0x9C
// * 0x15 or 0x1D
//
// 4-byte call:
// FF [ModR/M] [SIB] [1-byte displacement]
// * ModR/M is either 0x54 or 0x5C
//
// 3-byte call:
// FF [ModR/M] [1-byte displacement]
// * ModR/M can be:
// * 0x50 - 0x5F EXCLUDING 0x54 or 0x5C
// FF [ModR/M] [SIB]
// * ModR/M is either 0x14 or 0x1C
//
// 2-byte call:
// FF [ModR/M]
// * ModR/M can be:
// * 0xD0 - 0xDF
// * 0x10 - 0x1F EXCEPT 0x14, 0x15, 0x1C, or 0x1D
// The mask of F8 is used because we want to mask out the bottom
// three bits (which are most often used for register selection).
const unsigned char rm_mask = 0xF8;
// 7-byte format:
if(return_address[-7] == 0xFF &&
(return_address[-6] == 0x94 || return_address[-6] == 0x9C))
{
return (return_address - 7);
}
// 6-byte format:
// FF [ModR/M] [4-byte displacement]
else if(return_address[-6] == 0xFF &&
((return_address[-5] & rm_mask) == 0x90 || (return_address[-5] & rm_mask) == 0x98) &&
(return_address[-5] != 0x94 && return_address[-5] != 0x9C))
{
return (return_address - 6);
}
// Alternate 6-byte format:
else if(return_address[-6] == 0xFF &&
(return_address[-5] == 0x15 || return_address[-5] == 0x1D))
{
return (return_address - 6);
}
// 4-byte format:
// FF [ModR/M] [SIB] [1-byte displacement]
else if(return_address[-4] == 0xFF &&
(return_address[-3] == 0x54 || return_address[-3] == 0x5C))
{
return (return_address - 4);
}
// 3-byte format:
// FF [ModR/M] [1-byte displacement]
else if(return_address[-3] == 0xFF &&
((return_address[-2] & rm_mask) == 0x50 || (return_address[-2] & rm_mask) == 0x58) &&
(return_address[-2] != 0x54 && return_address[-2] != 0x5C))
{
return (return_address - 3);
}
// Alternate 3-byte format:
// FF [ModR/M] [SIB]
else if(return_address[-3] == 0xFF &&
(return_address[-2] == 0x14 || return_address[-2] == 0x1C))
{
return (return_address - 3);
}
// 2-byte calling format:
// FF [ModR/M]
else if(return_address[-2] == 0xFF &&
((return_address[-1] & rm_mask) == 0xD0 || (return_address[-1] & rm_mask) == 0xD8))
{
return (return_address - 2);
}
// Alternate 2-byte calling format:
// FF [ModR/M]
else if(return_address[-2] == 0xFF &&
((return_address[-1] & rm_mask) == 0x10 || (return_address[-1] & rm_mask) == 0x18) &&
(return_address[-1] != 0x14 && return_address[-1] != 0x15 &&
return_address[-1] != 0x1C && return_address[-1] != 0x1D))
{
return (return_address - 2);
}
else
{
EA_FAIL_MSG("EACallstack: Unable to determine calling address!");
}
}
EA_FAIL_MSG("EACallstack: Unable to determine calling address!");
return NULL;
}
///////////////////////////////////////////////////////////////////////////////
// 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)
{
size_t index = 0;
// The pContext option is not currently supported.
unsigned char* local_ebp;
if(pContext)
local_ebp = (unsigned char*)pContext->mEBP;
else
{
__asm mov local_ebp, ebp
// Remove the top return address because that's the one for this function
// which we most likely don't want in our pReturnAddressArray.
local_ebp = *(reinterpret_cast<unsigned char**>(local_ebp));
}
for(index = 0; index < nReturnAddressArrayCapacity; ++index)
{
unsigned char* return_address_ptr = *(reinterpret_cast<unsigned char**>(local_ebp + 4));
if(return_address_ptr == 0)
{
pReturnAddressArray[index] = 0;
break;
}
else
{
pReturnAddressArray[index] = ReturnAddressToCallingAddress(return_address_ptr);
local_ebp = *(reinterpret_cast<unsigned char**>(local_ebp));
}
}
return index;
}
#elif defined(__GNUC__) || defined(EA_COMPILER_CLANG)
EATHREADLIB_API void GetInstructionPointer(void *&p)
{
p = __builtin_return_address(0);
}
#if !EATHREAD_GLIBC_BACKTRACE_AVAILABLE && defined(EA_COMPILER_CLANG)
struct CallstackState
{
void** current;
void** end;
};
static _Unwind_Reason_Code UnwindCallback(struct _Unwind_Context* context, void* arg)
{
CallstackState* state = static_cast<CallstackState*>(arg);
uintptr_t pc = _Unwind_GetIP(context);
if (pc)
{
if (state->current == state->end)
{
return _URC_END_OF_STACK;
}
else
{
*state->current++ = reinterpret_cast<void*>(pc);
}
}
return _URC_NO_REASON;
}
#endif
///////////////////////////////////////////////////////////////////////////////
// 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)
{
#if EATHREAD_GLIBC_BACKTRACE_AVAILABLE
size_t count = 0;
// The pContext option is not currently supported.
if(pContext == NULL)
{
count = (size_t)backtrace(pReturnAddressArray, 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(EA_COMPILER_CLANG)
size_t count = 0;
// The pContext option is not currently supported.
if (pContext == NULL)
{
CallstackState state = { pReturnAddressArray, pReturnAddressArray + nReturnAddressArrayCapacity };
_Unwind_Backtrace(UnwindCallback, &state);
count = state.current - pReturnAddressArray;
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;
#else
size_t index = 0;
// The pContext option is not currently supported.
if(pContext == NULL)
{
void** sp;
void** new_sp;
#if defined(__i386__)
// Stack frame format:
// sp[0] pointer to previous frame
// sp[1] caller address
// sp[2] first argument
// ...
sp = (void**)&pReturnAddressArray - 2;
#elif defined(__x86_64__)
// Arguments are passed in registers on x86-64, so we can't just offset from &pReturnAddressArray.
sp = (void**) __builtin_frame_address(0);
#endif
for(int count = 0; sp && (index < nReturnAddressArrayCapacity); sp = new_sp, ++count)
{
if(count > 0) // We skip the current frame.
pReturnAddressArray[index++] = *(sp + 1);
new_sp = (void**)*sp;
if((new_sp < sp) || (new_sp > (sp + 100000)))
break;
}
}
return index;
#endif
}
#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;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleFromAddress
//
EATHREADLIB_API size_t GetModuleFromAddress(const void* address, char* pModuleName, size_t moduleNameCapacity)
{
#ifdef EA_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(address, &mbi, sizeof(mbi)))
{
HMODULE hModule = (HMODULE)mbi.AllocationBase;
if(hModule)
return GetModuleFileNameA(hModule, pModuleName, (DWORD)moduleNameCapacity);
}
#else
// Not currently implemented for the given platform.
EA_UNUSED(address);
EA_UNUSED(moduleNameCapacity);
#endif
pModuleName[0] = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetModuleHandleFromAddress
//
EATHREADLIB_API ModuleHandle GetModuleHandleFromAddress(const void* pAddress)
{
#ifdef EA_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(pAddress, &mbi, sizeof(mbi)))
return (ModuleHandle)mbi.AllocationBase;
#else
// Not currently implemented for the given platform.
EA_UNUSED(pAddress);
#endif
return 0;
}
#ifdef EA_PLATFORM_WINDOWS
///////////////////////////////////////////////////////////////////////////////
// GetThreadIdFromThreadHandleWin32
//
static DWORD GetThreadIdFromThreadHandleWin32(HANDLE hThread)
{
struct THREAD_BASIC_INFORMATION_WIN32
{
BOOL ExitStatus;
PVOID TebBaseAddress;
DWORD UniqueProcessId;
DWORD UniqueThreadId;
DWORD AffinityMask;
DWORD Priority;
DWORD BasePriority;
};
static HMODULE hKernel32 = NULL;
if(!hKernel32)
hKernel32 = LoadLibraryA("kernel32.dll");
if(hKernel32)
{
typedef DWORD (WINAPI *GetThreadIdFunc)(HANDLE);
static GetThreadIdFunc pGetThreadIdFunc = NULL;
if(!pGetThreadIdFunc)
pGetThreadIdFunc = (GetThreadIdFunc)(uintptr_t)GetProcAddress(hKernel32, "GetThreadId");
if(pGetThreadIdFunc)
return pGetThreadIdFunc(hThread);
}
static HMODULE hNTDLL = NULL;
if(!hNTDLL)
hNTDLL = LoadLibraryA("ntdll.dll");
if(hNTDLL)
{
typedef LONG (WINAPI *NtQueryInformationThreadFunc)(HANDLE, int, PVOID, ULONG, PULONG);
static NtQueryInformationThreadFunc pNtQueryInformationThread = NULL;
if(!pNtQueryInformationThread)
pNtQueryInformationThread = (NtQueryInformationThreadFunc)(uintptr_t)GetProcAddress(hNTDLL, "NtQueryInformationThread");
if(pNtQueryInformationThread)
{
THREAD_BASIC_INFORMATION_WIN32 tbi;
if(pNtQueryInformationThread(hThread, 0, &tbi, sizeof(tbi), NULL) == 0)
return tbi.UniqueThreadId;
}
}
return 0;
}
#endif // #ifdef EA_PLATFORM_WINDOWS
///////////////////////////////////////////////////////////////////////////////
// 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)
{
#ifdef EA_PLATFORM_WINDOWS
if((threadId == kThreadIdInvalid) || (threadId == kThreadIdCurrent))
threadId = (intptr_t)GetCurrentThread(); // GetCurrentThread returns a thread 'pseudohandle' and not a real thread handle.
const DWORD dwThreadIdCurrent = GetCurrentThreadId();
const DWORD dwThreadIdArg = GetThreadIdFromThreadHandleWin32((HANDLE)threadId);
CONTEXT win32CONTEXT;
if(dwThreadIdCurrent == dwThreadIdArg)
{
// With VC++, EIP is not accessible directly, but we can use an assembly trick to get it.
// VC++ and Intel C++ compile this fine, but Metrowerks 7 has a bug and fails.
__asm{
mov win32CONTEXT.Ebp, EBP
mov win32CONTEXT.Esp, ESP
call GetEIP
GetEIP:
pop win32CONTEXT.Eip
}
}
else
{
// In this case we are working with a separate thread, so we suspend it
// and read information about it and then resume it. Windows requires that
// GetThreadContext be called on a thread that is stopped. There is a small
// problem when running under the VC++ debugger: It reportedly calls
// ResumeThread on suspended threads itself and so the sequence below can
// be affected when stepped through with a debugger.
SuspendThread((HANDLE)threadId);
win32CONTEXT.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
GetThreadContext((HANDLE)threadId, &win32CONTEXT);
ResumeThread((HANDLE)threadId);
}
context.mEBP = (uint32_t)win32CONTEXT.Ebp;
context.mESP = (uint32_t)win32CONTEXT.Esp;
context.mEIP = (uint32_t)win32CONTEXT.Eip;
return true;
#else
// Not currently implemented for the given platform.
EA_UNUSED(context);
EA_UNUSED(threadId);
memset(&context, 0, sizeof(context));
return false;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// GetCallstackContextSysThreadId
//
EATHREADLIB_API bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId)
{
#ifdef EA_PLATFORM_WINDOWS
// To do: Implement this if/when necessary.
EA_UNUSED(context);
EA_UNUSED(sysThreadId);
return false;
#else
return GetCallstackContext(context, sysThreadId);
#endif
}
// 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 Linux in general doesn't need it.
EA::Thread::ThreadLocalStorage sStackBase;
///////////////////////////////////////////////////////////////////////////////
// SetStackBase
//
EATHREADLIB_API void SetStackBase(void* pStackBase)
{
if(pStackBase)
sStackBase.SetValue(pStackBase);
else
{
#if defined(EA_COMPILER_MSVC) && defined(EA_PROCESSOR_X86)
__asm mov pStackBase, ESP
#elif defined(EA_COMPILER_MSVC) && defined(EA_PROCESSOR_X86_64)
pStackBase = _AddressOfReturnAddress(); // Need to #include <intrin.h> for this.
#elif (defined(EA_COMPILER_GNUC) && defined(EA_PROCESSOR_X86)) || (defined(EA_COMPILER_CLANG) && defined(EA_PROCESSOR_X86))
asm volatile("mov %%esp, %0" : "=g"(pStackBase));
#elif (defined(EA_COMPILER_GNUC) && defined(EA_PROCESSOR_X86_64)) || (defined(EA_COMPILER_CLANG) && defined(EA_PROCESSOR_X86_64))
asm volatile("mov %%rsp, %0" : "=g"(pStackBase));
#else
#error Unsupported compiler/processor combination.
#endif
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
// We call strcat in order to create a stack frame here and to avoid a
// compiler warning about returning a local variable in the case that we
// simply returned &stackLocation itself.
char stackLocation = 0;
char* pStack = strcat(&stackLocation, "");
return (void*)((uintptr_t)pStack & ~4095); // Round down to nearest page, as the stack grows downward.
}
} // namespace Callstack
} // namespace EA