You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
653 lines
15 KiB
653 lines
15 KiB
#ifndef THREADTOOLS_INL |
|
#define THREADTOOLS_INL |
|
|
|
// This file is included in threadtools.h for PS3 and threadtools.cpp for all other platforms |
|
// |
|
// Do not #include other files here |
|
|
|
#ifndef _PS3 |
|
// this is defined in the .cpp for the PS3 to avoid introducing a dependency for files including the header |
|
CTHREADLOCALPTR(CThread) g_pCurThread; |
|
|
|
#define INLINE_ON_PS3 |
|
#else |
|
// Inlining these functions on PS3 (which are called across PRX boundaries) saves us over 1ms per frame |
|
#define INLINE_ON_PS3 inline |
|
#endif |
|
|
|
INLINE_ON_PS3 CThread::CThread() : |
|
#ifdef _WIN32 |
|
m_hThread( NULL ), |
|
m_threadId( 0 ), |
|
#elif defined( _PS3 ) || defined(_POSIX) |
|
m_threadId( 0 ), |
|
m_threadZombieId( 0 ) , |
|
#endif |
|
m_result( 0 ), |
|
m_flags( 0 ) |
|
{ |
|
m_szName[0] = 0; |
|
m_NotSuspendedEvent.Set(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
INLINE_ON_PS3 CThread::~CThread() |
|
{ |
|
#ifdef MSVC |
|
if (m_hThread) |
|
#elif defined(POSIX) && !defined( _PS3 ) |
|
if ( m_threadId ) |
|
#endif |
|
{ |
|
if ( IsAlive() ) |
|
{ |
|
Msg( "Illegal termination of worker thread! Threads must negotiate an end to the thread before the CThread object is destroyed.\n" ); |
|
#ifdef _WIN32 |
|
|
|
DoNewAssertDialog( __FILE__, __LINE__, "Illegal termination of worker thread! Threads must negotiate an end to the thread before the CThread object is destroyed.\n" ); |
|
#endif |
|
if ( GetCurrentCThread() == this ) |
|
{ |
|
Stop(); // BUGBUG: Alfred - this doesn't make sense, this destructor fires from the hosting thread not the thread itself!! |
|
} |
|
} |
|
} |
|
#if defined(POSIX) || defined( _PS3 ) |
|
if ( m_threadZombieId ) |
|
{ |
|
// just clean up zombie threads immediately (the destructor is fired from the hosting thread) |
|
Join(); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
|
|
INLINE_ON_PS3 const char *CThread::GetName() |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
if ( !m_szName[0] ) |
|
{ |
|
#if defined( _WIN32 ) |
|
_snprintf( m_szName, sizeof(m_szName) - 1, "Thread(%p/%p)", this, m_hThread ); |
|
#elif defined( _PS3 ) |
|
snprintf( m_szName, sizeof(m_szName) - 1, "Thread(%p)", this ); |
|
#elif defined( POSIX ) |
|
_snprintf( m_szName, sizeof(m_szName) - 1, "Thread(%p/0x%p)", this, (void*)m_threadId ); |
|
#endif |
|
m_szName[sizeof(m_szName) - 1] = 0; |
|
} |
|
return m_szName; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
INLINE_ON_PS3 void CThread::SetName(const char *pszName) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
strncpy( m_szName, pszName, sizeof(m_szName) - 1 ); |
|
m_szName[sizeof(m_szName) - 1] = 0; |
|
} |
|
|
|
//----------------------------------------------------- |
|
// Functions for the other threads |
|
//----------------------------------------------------- |
|
|
|
// Start thread running - error if already running |
|
INLINE_ON_PS3 bool CThread::Start( unsigned nBytesStack, ThreadPriorityEnum_t nPriority ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
|
|
if ( IsAlive() ) |
|
{ |
|
AssertMsg( 0, "Tried to create a thread that has already been created!" ); |
|
return false; |
|
} |
|
|
|
bool bInitSuccess = false; |
|
CThreadEvent createComplete; |
|
ThreadInit_t init = { this, &createComplete, &bInitSuccess }; |
|
|
|
#if defined( THREAD_PARENT_STACK_TRACE_ENABLED ) |
|
{ |
|
int iValidEntries = GetCallStack_Fast( init.ParentStackTrace, ARRAYSIZE( init.ParentStackTrace ), 0 ); |
|
for( int i = iValidEntries; i < ARRAYSIZE( init.ParentStackTrace ); ++i ) |
|
{ |
|
init.ParentStackTrace[i] = NULL; |
|
} |
|
} |
|
#endif |
|
|
|
#ifdef PLATFORM_WINDOWS |
|
m_hThread = (HANDLE)CreateThread( NULL, |
|
nBytesStack, |
|
(LPTHREAD_START_ROUTINE)GetThreadProc(), |
|
new ThreadInit_t(init), |
|
nBytesStack ? STACK_SIZE_PARAM_IS_A_RESERVATION : 0, |
|
(LPDWORD)&m_threadId ); |
|
|
|
if( nPriority != TP_PRIORITY_DEFAULT ) |
|
{ |
|
SetThreadPriority( m_hThread, nPriority ); |
|
} |
|
|
|
if ( !m_hThread ) |
|
{ |
|
AssertMsg1( 0, "Failed to create thread (error 0x%x)", GetLastError() ); |
|
return false; |
|
} |
|
#elif PLATFORM_PS3 |
|
// On the PS3, a stack size of 0 doesn't imply a default stack size, so we need to force it to our |
|
// own default size. |
|
if ( nBytesStack == 0 ) |
|
{ |
|
nBytesStack = PS3_SYS_PPU_THREAD_COMMON_STACK_SIZE; |
|
} |
|
|
|
//The thread is about to begin |
|
m_threadEnd.Reset(); |
|
|
|
// sony documentation: |
|
// "If the PPU thread is not joined by sys_ppu_thread_join() after exit, |
|
// it should always be created as non-joinable (not specifying |
|
// SYS_PPU_THREAD_CREATE_JOINABLE). Otherwise, some resources are left |
|
// allocated after termination of the PPU thread as if memory leaks." |
|
const char* threadName=m_szName; |
|
if ( sys_ppu_thread_create( &m_threadId, |
|
(void(*)(uint64_t))GetThreadProc(), |
|
(uint64_t)(new ThreadInit_t( init )), |
|
nPriority, |
|
nBytesStack, |
|
SYS_PPU_THREAD_CREATE_JOINABLE , |
|
threadName ) != CELL_OK ) |
|
{ |
|
AssertMsg1( 0, "Failed to create thread (error 0x%x)", errno ); |
|
return false; |
|
} |
|
|
|
bInitSuccess = true; |
|
#elif PLATFORM_POSIX |
|
pthread_attr_t attr; |
|
pthread_attr_init( &attr ); |
|
pthread_attr_setstacksize( &attr, MAX( nBytesStack, 1024u*1024 ) ); |
|
//lwss - fix memory leak here |
|
m_threadInit = ThreadInit_t( init ); |
|
//if ( pthread_create( &m_threadId, &attr, (void *(*)(void *))GetThreadProc(), new ThreadInit_t( init ) ) != 0 ) |
|
if ( pthread_create( &m_threadId, &attr, (void *(*)(void *))GetThreadProc(), &m_threadInit ) != 0 ) |
|
//lwss end |
|
{ |
|
AssertMsg1( 0, "Failed to create thread (error 0x%x)", GetLastError() ); |
|
return false; |
|
} |
|
bInitSuccess = true; |
|
#endif |
|
|
|
|
|
|
|
if ( !WaitForCreateComplete( &createComplete ) ) |
|
{ |
|
Msg( "Thread failed to initialize\n" ); |
|
#ifdef _WIN32 |
|
CloseHandle( m_hThread ); |
|
m_hThread = NULL; |
|
#elif defined( _PS3 ) |
|
m_threadEnd.Set(); |
|
m_threadId = NULL; |
|
m_threadZombieId = 0; |
|
#endif |
|
|
|
return false; |
|
} |
|
|
|
if ( !bInitSuccess ) |
|
{ |
|
Msg( "Thread failed to initialize\n" ); |
|
#ifdef _WIN32 |
|
CloseHandle( m_hThread ); |
|
m_hThread = NULL; |
|
#elif defined(POSIX) && !defined( _PS3 ) |
|
m_threadId = 0; |
|
m_threadZombieId = 0; |
|
#endif |
|
return false; |
|
} |
|
|
|
#ifdef _WIN32 |
|
if ( !m_hThread ) |
|
{ |
|
Msg( "Thread exited immediately\n" ); |
|
} |
|
#endif |
|
|
|
#ifdef _WIN32 |
|
AddThreadHandleToIDMap( m_hThread, m_threadId ); |
|
return !!m_hThread; |
|
#elif defined(POSIX) |
|
return !!m_threadId; |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
// |
|
// Return true if the thread has been created and hasn't yet exited |
|
// |
|
|
|
INLINE_ON_PS3 bool CThread::IsAlive() |
|
{ |
|
#ifdef PLATFORM_WINDOWS |
|
DWORD dwExitCode; |
|
return ( |
|
m_hThread |
|
&& GetExitCodeThread(m_hThread, &dwExitCode) |
|
&& dwExitCode == STILL_ACTIVE ); |
|
#elif defined(POSIX) |
|
return !!m_threadId; |
|
#endif |
|
} |
|
|
|
// This method causes the current thread to wait until this thread |
|
// is no longer alive. |
|
INLINE_ON_PS3 bool CThread::Join( unsigned timeout ) |
|
{ |
|
#ifdef _WIN32 |
|
if ( m_hThread ) |
|
#elif defined(POSIX) |
|
if ( m_threadId || m_threadZombieId ) |
|
#endif |
|
{ |
|
AssertMsg(GetCurrentCThread() != this, _T("Thread cannot be joined with self")); |
|
|
|
#ifdef _WIN32 |
|
return ThreadJoin( (ThreadHandle_t)m_hThread, timeout ); |
|
#elif defined(POSIX) |
|
bool ret = ThreadJoin( (ThreadHandle_t)(m_threadId ? m_threadId : m_threadZombieId), timeout ); |
|
m_threadZombieId = 0; |
|
return ret; |
|
#endif |
|
} |
|
return true; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
INLINE_ON_PS3 ThreadHandle_t CThread::GetThreadHandle() |
|
{ |
|
#ifdef _WIN32 |
|
return (ThreadHandle_t)m_hThread; |
|
#else |
|
return (ThreadHandle_t)m_threadId; |
|
#endif |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
|
|
INLINE_ON_PS3 int CThread::GetResult() |
|
{ |
|
return m_result; |
|
} |
|
|
|
//----------------------------------------------------- |
|
// Functions for both this, and maybe, and other threads |
|
//----------------------------------------------------- |
|
|
|
// Forcibly, abnormally, but relatively cleanly stop the thread |
|
// |
|
|
|
INLINE_ON_PS3 void CThread::Stop(int exitCode) |
|
{ |
|
if ( !IsAlive() ) |
|
return; |
|
|
|
if ( GetCurrentCThread() == this ) |
|
{ |
|
#if !defined( _PS3 ) |
|
m_result = exitCode; |
|
if ( !( m_flags & SUPPORT_STOP_PROTOCOL ) ) |
|
{ |
|
OnExit(); |
|
g_pCurThread = NULL; |
|
|
|
#ifdef _WIN32 |
|
CloseHandle( m_hThread ); |
|
RemoveThreadHandleToIDMap( m_hThread ); |
|
m_hThread = NULL; |
|
#else |
|
m_threadId = 0; |
|
m_threadZombieId = 0; |
|
#endif |
|
} |
|
else |
|
{ |
|
throw exitCode; |
|
} |
|
#else |
|
AssertMsg( false, "Called CThread::Stop() for a platform that doesn't have it!\n"); |
|
#endif |
|
} |
|
else |
|
AssertMsg( 0, "Only thread can stop self: Use a higher-level protocol"); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
// Get the priority |
|
INLINE_ON_PS3 int CThread::GetPriority() const |
|
{ |
|
#ifdef _WIN32 |
|
return GetThreadPriority(m_hThread); |
|
#elif defined( _PS3 ) |
|
return ThreadGetPriority( (ThreadHandle_t) m_threadId ); |
|
#elif defined(POSIX) |
|
struct sched_param thread_param; |
|
int policy; |
|
pthread_getschedparam( m_threadId, &policy, &thread_param ); |
|
return thread_param.sched_priority; |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
// Set the priority |
|
INLINE_ON_PS3 bool CThread::SetPriority(int priority) |
|
{ |
|
#ifdef WIN32 |
|
return ThreadSetPriority( (ThreadHandle_t)m_hThread, priority ); |
|
#else |
|
return ThreadSetPriority( (ThreadHandle_t)m_threadId, priority ); |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
// Suspend a thread |
|
INLINE_ON_PS3 unsigned CThread::Suspend() |
|
{ |
|
AssertMsg( ThreadGetCurrentId() == (ThreadId_t)m_threadId, "Cannot call CThread::Suspend from outside thread" ); |
|
|
|
if ( ThreadGetCurrentId() != (ThreadId_t)m_threadId ) |
|
{ |
|
DebuggerBreakIfDebugging(); |
|
} |
|
|
|
m_NotSuspendedEvent.Reset(); |
|
m_NotSuspendedEvent.Wait(); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
|
|
INLINE_ON_PS3 unsigned CThread::Resume() |
|
{ |
|
if ( m_NotSuspendedEvent.Check() ) |
|
{ |
|
DevWarning( "Called Resume() on a thread that is not suspended!\n" ); |
|
} |
|
m_NotSuspendedEvent.Set(); |
|
return 0; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
// Force hard-termination of thread. Used for critical failures. |
|
INLINE_ON_PS3 bool CThread::Terminate(int exitCode) |
|
{ |
|
#if defined( _X360 ) |
|
AssertMsg( 0, "Cannot terminate a thread on the Xbox!" ); |
|
return false; |
|
#elif defined( _WIN32 ) |
|
// I hope you know what you're doing! |
|
if (!TerminateThread(m_hThread, exitCode)) |
|
return false; |
|
CloseHandle( m_hThread ); |
|
RemoveThreadHandleToIDMap( m_hThread ); |
|
m_hThread = NULL; |
|
#elif defined( _PS3 ) |
|
m_threadEnd.Set(); |
|
m_threadId = NULL; |
|
#elif defined(POSIX) |
|
pthread_kill( m_threadId, SIGKILL ); |
|
m_threadId = 0; |
|
#endif |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------- |
|
// Global methods |
|
//----------------------------------------------------- |
|
|
|
// Get the Thread object that represents the current thread, if any. |
|
// Can return NULL if the current thread was not created using |
|
// CThread |
|
// |
|
|
|
INLINE_ON_PS3 CThread *CThread::GetCurrentCThread() |
|
{ |
|
#ifdef _PS3 |
|
return GetCurThreadPS3(); |
|
#else |
|
return g_pCurThread; |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
// |
|
// Offer a context switch. Under Win32, equivalent to Sleep(0) |
|
// |
|
|
|
#ifdef Yield |
|
#undef Yield |
|
#endif |
|
INLINE_ON_PS3 void CThread::Yield() |
|
{ |
|
#ifdef _WIN32 |
|
::Sleep(0); |
|
#elif defined( _PS3 ) |
|
// sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. |
|
sys_timer_usleep( 60 ); |
|
#elif defined(POSIX) |
|
sched_yield(); |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
// |
|
// This method causes the current thread to yield and not to be |
|
// scheduled for further execution until a certain amount of real |
|
// time has elapsed, more or less. Duration is in milliseconds |
|
|
|
INLINE_ON_PS3 void CThread::Sleep( unsigned duration ) |
|
{ |
|
#ifdef _WIN32 |
|
::Sleep(duration); |
|
#elif defined (_PS3) |
|
sys_timer_usleep( duration * 1000 ); |
|
#elif defined(POSIX) |
|
usleep( duration * 1000 ); |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
// Optional pre-run call, with ability to fail-create. Note Init() |
|
// is forced synchronous with Start() |
|
INLINE_ON_PS3 bool CThread::Init() |
|
{ |
|
return true; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
|
|
#if defined( _PS3 ) |
|
INLINE_ON_PS3 int CThread::Run() |
|
{ |
|
return -1; |
|
} |
|
#endif // _PS3 |
|
|
|
// Called when the thread exits |
|
INLINE_ON_PS3 void CThread::OnExit() { } |
|
|
|
// Allow for custom start waiting |
|
INLINE_ON_PS3 bool CThread::WaitForCreateComplete( CThreadEvent *pEvent ) |
|
{ |
|
// Force serialized thread creation... |
|
if (!pEvent->Wait(60000)) |
|
{ |
|
AssertMsg( 0, "Probably deadlock or failure waiting for thread to initialize." ); |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
INLINE_ON_PS3 bool CThread::IsThreadRunning() |
|
{ |
|
#ifdef _PS3 |
|
// ThreadIsThreadIdRunning() doesn't work on PS3 if the thread is in a zombie state |
|
return m_eventTheadExit.Check(); |
|
#else |
|
return ThreadIsThreadIdRunning( (ThreadId_t)m_threadId ); |
|
#endif |
|
} |
|
|
|
//--------------------------------------------------------- |
|
INLINE_ON_PS3 CThread::ThreadProc_t CThread::GetThreadProc() |
|
{ |
|
return ThreadProc; |
|
} |
|
|
|
INLINE_ON_PS3 void CThread::ThreadProcRunWithMinidumpHandler( void *pv ) |
|
{ |
|
ThreadInit_t *pInit = reinterpret_cast<ThreadInit_t*>(pv); |
|
pInit->pThread->m_result = pInit->pThread->Run(); |
|
} |
|
|
|
#ifdef PLATFORM_WINDOWS |
|
unsigned long STDCALL CThread::ThreadProc(LPVOID pv) |
|
#else |
|
INLINE_ON_PS3 void* CThread::ThreadProc(LPVOID pv) |
|
#endif |
|
{ |
|
#if defined( POSIX ) || defined( _PS3 ) |
|
ThreadInit_t *pInit = reinterpret_cast<ThreadInit_t*>(pv); |
|
#else |
|
std::auto_ptr<ThreadInit_t> pInit((ThreadInit_t *)pv); |
|
#endif |
|
|
|
#ifdef _X360 |
|
// Make sure all threads are consistent w.r.t floating-point math |
|
SetupFPUControlWord(); |
|
#endif |
|
AllocateThreadID(); |
|
|
|
CThread *pThread = pInit->pThread; |
|
#ifdef _PS3 |
|
SetCurThreadPS3( pThread ); |
|
#else |
|
g_pCurThread = pThread; |
|
#endif |
|
|
|
pThread->m_pStackBase = AlignValue( &pThread, 4096 ); |
|
|
|
pInit->pThread->m_result = -1; |
|
|
|
#if defined( THREAD_PARENT_STACK_TRACE_ENABLED ) |
|
CStackTop_ReferenceParentStack stackTop( pInit->ParentStackTrace, ARRAYSIZE( pInit->ParentStackTrace ) ); |
|
#endif |
|
|
|
bool bInitSuccess = true; |
|
if ( pInit->pfInitSuccess ) |
|
*(pInit->pfInitSuccess) = false; |
|
|
|
#ifdef _PS3 |
|
*(pInit->pfInitSuccess) = pInit->pThread->Init(); |
|
#else |
|
try |
|
{ |
|
bInitSuccess = pInit->pThread->Init(); |
|
} |
|
|
|
catch (...) |
|
{ |
|
pInit->pInitCompleteEvent->Set(); |
|
throw; |
|
} |
|
#endif // _PS3 |
|
|
|
if ( pInit->pfInitSuccess ) |
|
*(pInit->pfInitSuccess) = bInitSuccess; |
|
pInit->pInitCompleteEvent->Set(); |
|
if (!bInitSuccess) |
|
return 0; |
|
|
|
if ( !Plat_IsInDebugSession() && (pInit->pThread->m_flags & SUPPORT_STOP_PROTOCOL) ) |
|
{ |
|
#ifndef _PS3 |
|
try |
|
#endif |
|
{ |
|
pInit->pThread->m_result = pInit->pThread->Run(); |
|
} |
|
|
|
#ifndef _PS3 |
|
catch (...) |
|
{ |
|
} |
|
#endif |
|
} |
|
else |
|
{ |
|
#if defined( _WIN32 ) |
|
CatchAndWriteMiniDumpForVoidPtrFn( ThreadProcRunWithMinidumpHandler, pv, false ); |
|
#else |
|
pInit->pThread->m_result = pInit->pThread->Run(); |
|
#endif |
|
} |
|
|
|
pInit->pThread->OnExit(); |
|
#ifdef _PS3 |
|
SetCurThreadPS3( NULL ); |
|
#else |
|
g_pCurThread = NULL; |
|
#endif |
|
FreeThreadID(); |
|
|
|
AUTO_LOCK( pThread->m_Lock ); |
|
#ifdef _WIN32 |
|
CloseHandle( pThread->m_hThread ); |
|
RemoveThreadHandleToIDMap( pThread->m_hThread ); |
|
pThread->m_hThread = NULL; |
|
#elif defined( _PS3 ) |
|
pThread->m_threadZombieId = pThread->m_threadId; |
|
pThread->m_threadEnd.Set(); |
|
pThread->m_threadId = 0; |
|
#elif defined(POSIX) |
|
pThread->m_threadZombieId = pThread->m_threadId; |
|
pThread->m_threadId = 0; |
|
#else |
|
#error |
|
#endif |
|
|
|
pThread->m_ExitEvent.Set(); |
|
#ifdef _PS3 |
|
{ |
|
pThread->m_Lock.Unlock(); |
|
sys_ppu_thread_exit( pInit->pThread->m_result ); |
|
// reacquire the lock in case thread exit didn't actually exit the thread, so that |
|
// AUTO_LOCK won't double-unlock the lock (to keep it paired) |
|
pThread->m_Lock.Lock(); |
|
} |
|
#endif |
|
|
|
#if defined( POSIX )|| defined( _PS3 ) |
|
return (void*)(uintp)pInit->pThread->m_result; |
|
#else |
|
return pInit->pThread->m_result; |
|
#endif |
|
} |
|
|
|
#endif // THREADTOOLS_INL
|
|
|