#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 _WIN32 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 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 _WIN32 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(pv); pInit->pThread->m_result = pInit->pThread->Run(); } #ifdef _WIN32 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(pv); // #else // std::auto_ptr 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