source-engine/public/tier0/threadtools.inl

654 lines
15 KiB
Plaintext
Raw Normal View History

#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 )
2022-06-15 18:59:06 +00:00
_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