First
This commit is contained in:
@@ -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(¶meters);
|
||||
}
|
||||
else
|
||||
Init(pBarrierParameters);
|
||||
}
|
||||
|
||||
|
||||
EA::Thread::Barrier::Barrier(int height)
|
||||
{
|
||||
BarrierParameters parameters(height);
|
||||
Init(¶meters);
|
||||
}
|
||||
|
||||
|
||||
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(¶meters);
|
||||
}
|
||||
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, ¶m);
|
||||
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, ¶m);
|
||||
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(¶meters);
|
||||
}
|
||||
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(¶meters);
|
||||
}
|
||||
else
|
||||
{
|
||||
Init(pSemaphoreParameters);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EA::Thread::Semaphore::Semaphore(int initialCount)
|
||||
{
|
||||
SemaphoreParameters parameters(initialCount);
|
||||
Init(¶meters);
|
||||
}
|
||||
|
||||
|
||||
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, ¶m);
|
||||
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, ¶m);
|
||||
|
||||
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, ¶m);
|
||||
|
||||
if(result == 0) // If success...
|
||||
{
|
||||
ConvertToNativePriority(nPriority, param, policy);
|
||||
|
||||
result = scePthreadSetschedparam(mThreadData.mpData->mSysThreadId, policy, ¶m);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user