|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "pch_tier0.h"
#include "tier1/strtools.h"
#include "tier0/dynfunction.h"
#if defined( _WIN32 ) && !defined( _X360 )
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifdef _WIN32
#include <process.h>
#ifdef IS_WINDOWS_PC
#include <Mmsystem.h>
#pragma comment(lib, "winmm.lib")
#endif // IS_WINDOWS_PC
#elif defined(POSIX)
#if !defined(OSX)
#include <sys/fcntl.h>
#include <sys/unistd.h>
#define sem_unlink( arg )
#define OS_TO_PTHREAD(x) (x)
#else
#define pthread_yield pthread_yield_np
#include <mach/thread_act.h>
#include <mach/mach.h>
#define OS_TO_PTHREAD(x) pthread_from_mach_thread_np( x )
#endif // !OSX
#ifdef LINUX
#include <dlfcn.h> // RTLD_NEXT
#endif
typedef int (*PTHREAD_START_ROUTINE)( void *lpThreadParameter ); typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE; #include <sched.h>
#include <exception>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <sys/time.h>
#define GetLastError() errno
typedef void *LPVOID; #endif
#include "tier0/valve_minmax_off.h"
#include <memory>
#include "tier0/valve_minmax_on.h"
#include "tier0/threadtools.h"
#include "tier0/vcrmode.h"
#ifdef _X360
#include "xbox/xbox_win32stubs.h"
#endif
#include "tier0/vprof_telemetry.h"
// Must be last header...
#include "tier0/memdbgon.h"
#define THREADS_DEBUG 1
// Need to ensure initialized before other clients call in for main thread ID
#ifdef _WIN32
#pragma warning(disable:4073)
#pragma init_seg(lib)
#endif
#ifdef _WIN32
ASSERT_INVARIANT(TT_SIZEOF_CRITICALSECTION == sizeof(CRITICAL_SECTION)); ASSERT_INVARIANT(TT_INFINITE == INFINITE); #endif
//-----------------------------------------------------------------------------
// Simple thread functions.
// Because _beginthreadex uses stdcall, we need to convert to cdecl
//-----------------------------------------------------------------------------
struct ThreadProcInfo_t { ThreadProcInfo_t( ThreadFunc_t pfnThread, void *pParam ) : pfnThread( pfnThread), pParam( pParam ) { } ThreadFunc_t pfnThread; void * pParam; };
//---------------------------------------------------------
#ifdef _WIN32
static unsigned __stdcall ThreadProcConvert( void *pParam ) #elif defined(POSIX)
static void *ThreadProcConvert( void *pParam ) #else
#error
#endif
{ ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); delete ((ThreadProcInfo_t *)pParam); #ifdef _WIN32
return (*info.pfnThread)(info.pParam); #elif defined(POSIX)
return (void *)(*info.pfnThread)(info.pParam); #else
#error
#endif
}
//---------------------------------------------------------
ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, ThreadId_t *pID, unsigned stackSize ) { #ifdef _WIN32
ThreadId_t idIgnored; if ( !pID ) pID = &idIgnored; HANDLE h = VCRHook_CreateThread(NULL, stackSize, (LPTHREAD_START_ROUTINE)ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ), CREATE_SUSPENDED, pID); if ( h != INVALID_HANDLE_VALUE ) { Plat_ApplyHardwareDataBreakpointsToNewThread( *pID ); ResumeThread( h ); } return (ThreadHandle_t)h; #elif defined(POSIX)
pthread_t tid;
// If we need to create threads that are detached right out of the gate, we would need to do something like this:
// pthread_attr_t attr;
// int rc = pthread_attr_init(&attr);
// rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// ... pthread_create( &tid, &attr, ... ) ...
// rc = pthread_attr_destroy(&attr);
// ... pthread_join will now fail
int ret = pthread_create( &tid, NULL, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ) ); if ( ret ) { // There are only PTHREAD_THREADS_MAX number of threads, and we're probably leaking handles if ret == EAGAIN here?
Error( "CreateSimpleThread: pthread_create failed. Someone not calling pthread_detach() or pthread_join. Ret:%d\n", ret ); } if ( pID ) *pID = (ThreadId_t)tid; Plat_ApplyHardwareDataBreakpointsToNewThread( (long unsigned int)tid ); return (ThreadHandle_t)tid; #endif
}
ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, unsigned stackSize ) { return CreateSimpleThread( pfnThread, pParam, NULL, stackSize ); }
PLATFORM_INTERFACE void ThreadDetach( ThreadHandle_t hThread ) { #if defined( POSIX )
// The resources of this thread will be freed immediately when it terminates,
// instead of waiting for another thread to perform PTHREAD_JOIN.
pthread_t tid = ( pthread_t )hThread;
pthread_detach( tid ); #endif
}
bool ReleaseThreadHandle( ThreadHandle_t hThread ) { #ifdef _WIN32
return ( CloseHandle( hThread ) != 0 ); #else
return true; #endif
}
//-----------------------------------------------------------------------------
//
// Wrappers for other simple threading operations
//
//-----------------------------------------------------------------------------
void ThreadSleep(unsigned nMilliseconds) { #ifdef _WIN32
#ifdef IS_WINDOWS_PC
static bool bInitialized = false; if ( !bInitialized ) { bInitialized = true; // Set the timer resolution to 1 ms (default is 10.0, 15.6, 2.5, 1.0 or
// some other value depending on hardware and software) so that we can
// use Sleep( 1 ) to avoid wasting CPU time without missing our frame
// rate.
timeBeginPeriod( 1 ); } #endif // IS_WINDOWS_PC
Sleep( nMilliseconds ); #elif defined(POSIX)
usleep( nMilliseconds * 1000 ); #endif
}
//-----------------------------------------------------------------------------
#ifndef ThreadGetCurrentId
uint ThreadGetCurrentId() { #ifdef _WIN32
return GetCurrentThreadId(); #elif defined(POSIX)
return (uint)pthread_self(); #endif
} #endif
//-----------------------------------------------------------------------------
ThreadHandle_t ThreadGetCurrentHandle() { #ifdef _WIN32
return (ThreadHandle_t)GetCurrentThread(); #elif defined(POSIX)
return (ThreadHandle_t)pthread_self(); #endif
}
// On PS3, this will return true for zombie threads
bool ThreadIsThreadIdRunning( ThreadId_t uThreadId ) { #ifdef _WIN32
bool bRunning = true; HANDLE hThread = ::OpenThread( THREAD_QUERY_INFORMATION , false, uThreadId ); if ( hThread ) { DWORD dwExitCode; if( !::GetExitCodeThread( hThread, &dwExitCode ) || dwExitCode != STILL_ACTIVE ) bRunning = false;
CloseHandle( hThread ); } else { bRunning = false; } return bRunning; #elif defined( _PS3 )
// will return CELL_OK for zombie threads
int priority; return (sys_ppu_thread_get_priority( uThreadId, &priority ) == CELL_OK );
#elif defined(POSIX)
pthread_t thread = OS_TO_PTHREAD(uThreadId); if ( thread ) { int iResult = pthread_kill( thread, 0 ); if ( iResult == 0 ) return true; } else { // We really ought not to be passing NULL in to here
AssertMsg( false, "ThreadIsThreadIdRunning received a null thread ID" ); }
return false; #endif
}
//-----------------------------------------------------------------------------
int ThreadGetPriority( ThreadHandle_t hThread ) { if ( !hThread ) { hThread = ThreadGetCurrentHandle(); }
#ifdef _WIN32
return ::GetThreadPriority( (HANDLE)hThread ); #else
struct sched_param thread_param; int policy; pthread_getschedparam( (pthread_t)hThread, &policy, &thread_param ); return thread_param.sched_priority; #endif
}
//-----------------------------------------------------------------------------
bool ThreadSetPriority( ThreadHandle_t hThread, int priority ) { if ( !hThread ) { hThread = ThreadGetCurrentHandle(); }
#ifdef _WIN32
return ( SetThreadPriority(hThread, priority) != 0 ); #elif defined(POSIX)
struct sched_param thread_param; thread_param.sched_priority = priority; pthread_setschedparam( (pthread_t)hThread, SCHED_OTHER, &thread_param ); return true; #endif
}
//-----------------------------------------------------------------------------
void ThreadSetAffinity( ThreadHandle_t hThread, int nAffinityMask ) { if ( !hThread ) { hThread = ThreadGetCurrentHandle(); }
#ifdef _WIN32
SetThreadAffinityMask( hThread, nAffinityMask ); #elif defined(POSIX)
// cpu_set_t cpuSet;
// CPU_ZERO( cpuSet );
// for( int i = 0 ; i < 32; i++ )
// if ( nAffinityMask & ( 1 << i ) )
// CPU_SET( cpuSet, i );
// sched_setaffinity( hThread, sizeof( cpuSet ), &cpuSet );
#endif
}
//-----------------------------------------------------------------------------
uint InitMainThread() { #ifndef LINUX
// Skip doing the setname on Linux for the main thread. Here is why...
// From Pierre-Loup e-mail about why pthread_setname_np() on the main thread
// in Linux will cause some tools to display "MainThrd" as the executable name:
//
// You have two things in procfs, comm and cmdline. Each of the threads have
// a different `comm`, which is the value you set through pthread_setname_np
// or prctl(PR_SET_NAME). Top can either display cmdline or comm; it
// switched to display comm by default; htop still displays cmdline by
// default. Top -c will output cmdline rather than comm.
//
// If you press 'H' while top is running it will display each thread as a
// separate process, so you will have different entries for MainThrd,
// MatQueue0, etc with their own CPU usage. But when that mode isn't enabled
// it just displays the 'comm' name from the first thread.
ThreadSetDebugName( "MainThrd" ); #endif
#ifdef _WIN32
return ThreadGetCurrentId(); #elif defined(POSIX)
return (uint)pthread_self(); #endif
}
uint g_ThreadMainThreadID = InitMainThread();
bool ThreadInMainThread() { return ( ThreadGetCurrentId() == g_ThreadMainThreadID ); }
//-----------------------------------------------------------------------------
void DeclareCurrentThreadIsMainThread() { g_ThreadMainThreadID = ThreadGetCurrentId(); }
bool ThreadJoin( ThreadHandle_t hThread, unsigned timeout ) { // You should really never be calling this with a NULL thread handle. If you
// are then that probably implies a race condition or threading misunderstanding.
Assert( hThread ); if ( !hThread ) { return false; }
#ifdef _WIN32
DWORD dwWait = VCRHook_WaitForSingleObject((HANDLE)hThread, timeout); if ( dwWait == WAIT_TIMEOUT) return false; if ( dwWait != WAIT_OBJECT_0 && ( dwWait != WAIT_FAILED && GetLastError() != 0 ) ) { Assert( 0 ); return false; } #elif defined(POSIX)
if ( pthread_join( (pthread_t)hThread, NULL ) != 0 ) return false; #endif
return true; }
#ifdef RAD_TELEMETRY_ENABLED
void TelemetryThreadSetDebugName( ThreadId_t id, const char *pszName ); #endif
//-----------------------------------------------------------------------------
void ThreadSetDebugName( ThreadId_t id, const char *pszName ) { if( !pszName ) return;
#ifdef RAD_TELEMETRY_ENABLED
TelemetryThreadSetDebugName( id, pszName ); #endif
#ifdef _WIN32
if ( Plat_IsInDebugSession() ) { #define MS_VC_EXCEPTION 0x406d1388
typedef struct tagTHREADNAME_INFO { DWORD dwType; // must be 0x1000
LPCSTR szName; // pointer to name (in same addr space)
DWORD dwThreadID; // thread ID (-1 caller thread)
DWORD dwFlags; // reserved for future use, most be zero
} THREADNAME_INFO;
THREADNAME_INFO info; info.dwType = 0x1000; info.szName = pszName; info.dwThreadID = id; info.dwFlags = 0;
__try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); } __except (EXCEPTION_CONTINUE_EXECUTION) { } } #elif defined( _LINUX )
// As of glibc v2.12, we can use pthread_setname_np.
typedef int (pthread_setname_np_func)(pthread_t, const char *); static pthread_setname_np_func *s_pthread_setname_np_func = (pthread_setname_np_func *)dlsym(RTLD_DEFAULT, "pthread_setname_np");
if ( s_pthread_setname_np_func ) { if ( id == (uint32)-1 ) id = pthread_self();
/*
pthread_setname_np() in phthread_setname.c has the following code: #define TASK_COMM_LEN 16
size_t name_len = strlen (name); if (name_len >= TASK_COMM_LEN) return ERANGE; So we need to truncate the threadname to 16 or the call will just fail. */ char szThreadName[ 16 ]; strncpy( szThreadName, pszName, ARRAYSIZE( szThreadName ) ); szThreadName[ ARRAYSIZE( szThreadName ) - 1 ] = 0; (*s_pthread_setname_np_func)( id, szThreadName ); } #endif
}
//-----------------------------------------------------------------------------
#ifdef _WIN32
ASSERT_INVARIANT( TW_FAILED == WAIT_FAILED ); ASSERT_INVARIANT( TW_TIMEOUT == WAIT_TIMEOUT ); ASSERT_INVARIANT( WAIT_OBJECT_0 == 0 );
int ThreadWaitForObjects( int nEvents, const HANDLE *pHandles, bool bWaitAll, unsigned timeout ) { return VCRHook_WaitForMultipleObjects( nEvents, pHandles, bWaitAll, timeout ); } #endif
//-----------------------------------------------------------------------------
// Used to thread LoadLibrary on the 360
//-----------------------------------------------------------------------------
static ThreadedLoadLibraryFunc_t s_ThreadedLoadLibraryFunc = 0; PLATFORM_INTERFACE void SetThreadedLoadLibraryFunc( ThreadedLoadLibraryFunc_t func ) { s_ThreadedLoadLibraryFunc = func; }
PLATFORM_INTERFACE ThreadedLoadLibraryFunc_t GetThreadedLoadLibraryFunc() { return s_ThreadedLoadLibraryFunc; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CThreadSyncObject::CThreadSyncObject() #ifdef _WIN32
: m_hSyncObject( NULL ), m_bCreatedHandle(false) #elif defined(POSIX)
: m_bInitalized( false ) #endif
{ }
//---------------------------------------------------------
CThreadSyncObject::~CThreadSyncObject() { #ifdef _WIN32
if ( m_hSyncObject && m_bCreatedHandle ) { if ( !CloseHandle(m_hSyncObject) ) { Assert( 0 ); } } #elif defined(POSIX)
if ( m_bInitalized ) { pthread_cond_destroy( &m_Condition ); pthread_mutex_destroy( &m_Mutex ); m_bInitalized = false; } #endif
}
//---------------------------------------------------------
bool CThreadSyncObject::operator!() const { #ifdef _WIN32
return !m_hSyncObject; #elif defined(POSIX)
return !m_bInitalized; #endif
}
//---------------------------------------------------------
void CThreadSyncObject::AssertUseable() { #ifdef THREADS_DEBUG
#ifdef _WIN32
AssertMsg( m_hSyncObject, "Thread synchronization object is unuseable" ); #elif defined(POSIX)
AssertMsg( m_bInitalized, "Thread synchronization object is unuseable" ); #endif
#endif
}
//---------------------------------------------------------
bool CThreadSyncObject::Wait( uint32 dwTimeout ) { #ifdef THREADS_DEBUG
AssertUseable(); #endif
#ifdef _WIN32
return ( VCRHook_WaitForSingleObject( m_hSyncObject, dwTimeout ) == WAIT_OBJECT_0 ); #elif defined(POSIX)
pthread_mutex_lock( &m_Mutex ); bool bRet = false; if ( m_cSet > 0 ) { bRet = true; m_bWakeForEvent = false; } else { volatile int ret = 0;
while ( !m_bWakeForEvent && ret != ETIMEDOUT ) { struct timeval tv; gettimeofday( &tv, NULL ); volatile struct timespec tm; uint64 actualTimeout = dwTimeout; if ( dwTimeout == TT_INFINITE && m_bManualReset ) actualTimeout = 10; // just wait 10 msec at most for manual reset events and loop instead
volatile uint64 nNanoSec = (uint64)tv.tv_usec*1000 + (uint64)actualTimeout*1000000; tm.tv_sec = tv.tv_sec + nNanoSec /1000000000; tm.tv_nsec = nNanoSec % 1000000000;
do { ret = pthread_cond_timedwait( &m_Condition, &m_Mutex, (const timespec *)&tm ); } while( ret == EINTR );
bRet = ( ret == 0 ); if ( m_bManualReset ) { if ( m_cSet ) break; if ( dwTimeout == TT_INFINITE && ret == ETIMEDOUT ) ret = 0; // force the loop to spin back around
} } if ( bRet ) m_bWakeForEvent = false; } if ( !m_bManualReset && bRet ) m_cSet = 0; pthread_mutex_unlock( &m_Mutex ); return bRet; #endif
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CThreadEvent::CThreadEvent( bool bManualReset ) { #ifdef _WIN32
m_hSyncObject = CreateEvent( NULL, bManualReset, FALSE, NULL ); m_bCreatedHandle = true; AssertMsg1(m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); #elif defined( POSIX )
pthread_mutexattr_t Attr; pthread_mutexattr_init( &Attr ); pthread_mutex_init( &m_Mutex, &Attr ); pthread_mutexattr_destroy( &Attr ); pthread_cond_init( &m_Condition, NULL ); m_bInitalized = true; m_cSet = 0; m_bWakeForEvent = false; m_bManualReset = bManualReset; #else
#error "Implement me"
#endif
}
#ifdef _WIN32
CThreadEvent::CThreadEvent( HANDLE hHandle ) { m_hSyncObject = hHandle; m_bCreatedHandle = false; AssertMsg(m_hSyncObject, "Null event passed into constructor" ); } #endif
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
//---------------------------------------------------------
bool CThreadEvent::Set() { AssertUseable(); #ifdef _WIN32
return ( SetEvent( m_hSyncObject ) != 0 ); #elif defined(POSIX)
pthread_mutex_lock( &m_Mutex ); m_cSet = 1; m_bWakeForEvent = true; int ret = pthread_cond_signal( &m_Condition ); pthread_mutex_unlock( &m_Mutex ); return ret == 0; #endif
}
//---------------------------------------------------------
bool CThreadEvent::Reset() { #ifdef THREADS_DEBUG
AssertUseable(); #endif
#ifdef _WIN32
return ( ResetEvent( m_hSyncObject ) != 0 ); #elif defined(POSIX)
pthread_mutex_lock( &m_Mutex ); m_cSet = 0; m_bWakeForEvent = false; pthread_mutex_unlock( &m_Mutex ); return true; #endif
}
//---------------------------------------------------------
bool CThreadEvent::Check() { #ifdef THREADS_DEBUG
AssertUseable(); #endif
return Wait( 0 ); }
bool CThreadEvent::Wait( uint32 dwTimeout ) { return CThreadSyncObject::Wait( dwTimeout ); }
#ifdef _WIN32
//-----------------------------------------------------------------------------
//
// CThreadSemaphore
//
// To get Posix implementation, try http://www-128.ibm.com/developerworks/eserver/library/es-win32linux-sem.html
//
//-----------------------------------------------------------------------------
CThreadSemaphore::CThreadSemaphore( long initialValue, long maxValue ) { if ( maxValue ) { AssertMsg( maxValue > 0, "Invalid max value for semaphore" ); AssertMsg( initialValue >= 0 && initialValue <= maxValue, "Invalid initial value for semaphore" );
m_hSyncObject = CreateSemaphore( NULL, initialValue, maxValue, NULL );
AssertMsg1(m_hSyncObject, "Failed to create semaphore (error 0x%x)", GetLastError()); } else { m_hSyncObject = NULL; } }
//---------------------------------------------------------
bool CThreadSemaphore::Release( long releaseCount, long *pPreviousCount ) { #ifdef THRDTOOL_DEBUG
AssertUseable(); #endif
return ( ReleaseSemaphore( m_hSyncObject, releaseCount, pPreviousCount ) != 0 ); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CThreadFullMutex::CThreadFullMutex( bool bEstablishInitialOwnership, const char *pszName ) { m_hSyncObject = CreateMutex( NULL, bEstablishInitialOwnership, pszName );
AssertMsg1( m_hSyncObject, "Failed to create mutex (error 0x%x)", GetLastError() ); }
//---------------------------------------------------------
bool CThreadFullMutex::Release() { #ifdef THRDTOOL_DEBUG
AssertUseable(); #endif
return ( ReleaseMutex( m_hSyncObject ) != 0 ); }
#endif
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CThreadLocalBase::CThreadLocalBase() { #ifdef _WIN32
m_index = TlsAlloc(); AssertMsg( m_index != 0xFFFFFFFF, "Bad thread local" ); if ( m_index == 0xFFFFFFFF ) Error( "Out of thread local storage!\n" ); #elif defined(POSIX)
if ( pthread_key_create( &m_index, NULL ) != 0 ) Error( "Out of thread local storage!\n" ); #endif
}
//---------------------------------------------------------
CThreadLocalBase::~CThreadLocalBase() { #ifdef _WIN32
if ( m_index != 0xFFFFFFFF ) TlsFree( m_index ); m_index = 0xFFFFFFFF; #elif defined(POSIX)
pthread_key_delete( m_index ); #endif
}
//---------------------------------------------------------
void * CThreadLocalBase::Get() const { #ifdef _WIN32
if ( m_index != 0xFFFFFFFF ) return TlsGetValue( m_index ); AssertMsg( 0, "Bad thread local" ); return NULL; #elif defined(POSIX)
void *value = pthread_getspecific( m_index ); return value; #endif
}
//---------------------------------------------------------
void CThreadLocalBase::Set( void *value ) { #ifdef _WIN32
if (m_index != 0xFFFFFFFF) TlsSetValue(m_index, value); else AssertMsg( 0, "Bad thread local" ); #elif defined(POSIX)
if ( pthread_setspecific( m_index, value ) != 0 ) AssertMsg( 0, "Bad thread local" ); #endif
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifdef _WIN32
#ifdef _X360
#define TO_INTERLOCK_PARAM(p) ((long *)p)
#define TO_INTERLOCK_PTR_PARAM(p) ((void **)p)
#else
#define TO_INTERLOCK_PARAM(p) (p)
#define TO_INTERLOCK_PTR_PARAM(p) (p)
#endif
#ifndef USE_INTRINSIC_INTERLOCKED
long ThreadInterlockedIncrement( long volatile *pDest ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedIncrement( TO_INTERLOCK_PARAM(pDest) ); }
long ThreadInterlockedDecrement( long volatile *pDest ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedDecrement( TO_INTERLOCK_PARAM(pDest) ); }
long ThreadInterlockedExchange( long volatile *pDest, long value ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedExchange( TO_INTERLOCK_PARAM(pDest), value ); }
long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedExchangeAdd( TO_INTERLOCK_PARAM(pDest), value ); }
long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ); }
bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) { Assert( (size_t)pDest % 4 == 0 );
#if !(defined(_WIN64) || defined (_X360))
__asm { mov eax,comperand mov ecx,pDest mov edx,value lock cmpxchg [ecx],edx mov eax,0 setz al } #else
return ( InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ) == comperand ); #endif
}
#endif
#if !defined( USE_INTRINSIC_INTERLOCKED ) || defined( _WIN64 )
void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedExchangePointer( TO_INTERLOCK_PARAM(pDest), value ); }
void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ); }
bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) { Assert( (size_t)pDest % 4 == 0 ); #if !(defined(_WIN64) || defined (_X360))
__asm { mov eax,comperand mov ecx,pDest mov edx,value lock cmpxchg [ecx],edx mov eax,0 setz al } #else
return ( InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ) == comperand ); #endif
} #endif
int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 );
#if defined(_WIN64) || defined (_X360)
return InterlockedCompareExchange64( pDest, value, comperand ); #else
__asm { lea esi,comperand; lea edi,value;
mov eax,[esi]; mov edx,4[esi]; mov ebx,[edi]; mov ecx,4[edi]; mov esi,pDest; lock CMPXCHG8B [esi]; } #endif
}
bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 );
#if defined(PLATFORM_WINDOWS_PC32 )
__asm { lea esi,comperand; lea edi,value;
mov eax,[esi]; mov edx,4[esi]; mov ebx,[edi]; mov ecx,4[edi]; mov esi,pDest; lock CMPXCHG8B [esi]; mov eax,0; setz al; } #else
return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); #endif
}
#if defined( PLATFORM_64BITS )
#if _MSC_VER < 1500
// This intrinsic isn't supported on VS2005.
extern "C" unsigned char _InterlockedCompareExchange128( int64 volatile * Destination, int64 ExchangeHigh, int64 ExchangeLow, int64 * ComparandResult ); #endif
bool ThreadInterlockedAssignIf128( volatile int128 *pDest, const int128 &value, const int128 &comperand ) { Assert( ( (size_t)pDest % 16 ) == 0 );
volatile int64 *pDest64 = ( volatile int64 * )pDest; int64 *pValue64 = ( int64 * )&value; int64 *pComperand64 = ( int64 * )&comperand;
// Description:
// The CMPXCHG16B instruction compares the 128-bit value in the RDX:RAX and RCX:RBX registers
// with a 128-bit memory location. If the values are equal, the zero flag (ZF) is set,
// and the RCX:RBX value is copied to the memory location.
// Otherwise, the ZF flag is cleared, and the memory value is copied to RDX:RAX.
// _InterlockedCompareExchange128: http://msdn.microsoft.com/en-us/library/bb514094.aspx
return _InterlockedCompareExchange128( pDest64, pValue64[1], pValue64[0], pComperand64 ) == 1; }
#endif // PLATFORM_64BITS
int64 ThreadInterlockedIncrement64( int64 volatile *pDest ) { Assert( (size_t)pDest % 8 == 0 );
int64 Old;
do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, Old + 1, Old) != Old);
return Old + 1; }
int64 ThreadInterlockedDecrement64( int64 volatile *pDest ) { Assert( (size_t)pDest % 8 == 0 ); int64 Old;
do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, Old - 1, Old) != Old);
return Old - 1; }
int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) { Assert( (size_t)pDest % 8 == 0 ); int64 Old;
do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old);
return Old; }
int64 ThreadInterlockedExchangeAdd64( int64 volatile *pDest, int64 value ) { Assert( (size_t)pDest % 8 == 0 ); int64 Old;
do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, Old + value, Old) != Old);
return Old; }
#elif defined(GNUC)
#ifdef OSX
#include <libkern/OSAtomic.h>
#endif
long ThreadInterlockedIncrement( long volatile *pDest ) { return __sync_fetch_and_add( pDest, 1 ) + 1; }
long ThreadInterlockedDecrement( long volatile *pDest ) { return __sync_fetch_and_sub( pDest, 1 ) - 1; }
long ThreadInterlockedExchange( long volatile *pDest, long value ) { return __sync_lock_test_and_set( pDest, value ); }
long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) { return __sync_fetch_and_add( pDest, value ); }
long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) { return __sync_val_compare_and_swap( pDest, comperand, value ); }
bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) { return __sync_bool_compare_and_swap( pDest, comperand, value ); }
void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) { return __sync_lock_test_and_set( pDest, value ); }
void *ThreadInterlockedCompareExchangePointer( void *volatile *pDest, void *value, void *comperand ) { return __sync_val_compare_and_swap( pDest, comperand, value ); }
bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) { return __sync_bool_compare_and_swap( pDest, comperand, value ); }
int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) { #if defined(OSX)
int64 retVal = *pDest; if ( OSAtomicCompareAndSwap64( comperand, value, pDest ) ) retVal = *pDest; return retVal; #else
return __sync_val_compare_and_swap( pDest, comperand, value ); #endif
}
bool ThreadInterlockedAssignIf64( int64 volatile * pDest, int64 value, int64 comperand ) { return __sync_bool_compare_and_swap( pDest, comperand, value ); }
int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) { Assert( (size_t)pDest % 8 == 0 ); int64 Old; do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); return Old; }
#else
// This will perform horribly,
#error "Falling back to mutexed interlocked operations, you really don't have intrinsics you can use?"ß
CThreadMutex g_InterlockedMutex;
long ThreadInterlockedIncrement( long volatile *pDest ) { AUTO_LOCK( g_InterlockedMutex ); return ++(*pDest); }
long ThreadInterlockedDecrement( long volatile *pDest ) { AUTO_LOCK( g_InterlockedMutex ); return --(*pDest); }
long ThreadInterlockedExchange( long volatile *pDest, long value ) { AUTO_LOCK( g_InterlockedMutex ); long retVal = *pDest; *pDest = value; return retVal; }
void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) { AUTO_LOCK( g_InterlockedMutex ); void *retVal = *pDest; *pDest = value; return retVal; }
long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) { AUTO_LOCK( g_InterlockedMutex ); long retVal = *pDest; *pDest += value; return retVal; }
long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) { AUTO_LOCK( g_InterlockedMutex ); long retVal = *pDest; if ( *pDest == comperand ) *pDest = value; return retVal; }
void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) { AUTO_LOCK( g_InterlockedMutex ); void *retVal = *pDest; if ( *pDest == comperand ) *pDest = value; return retVal; }
int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 ); AUTO_LOCK( g_InterlockedMutex ); int64 retVal = *pDest; if ( *pDest == comperand ) *pDest = value; return retVal; }
int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) { Assert( (size_t)pDest % 8 == 0 ); int64 Old;
do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old);
return Old; }
bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 ); return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); }
bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) { Assert( (size_t)pDest % 4 == 0 ); return ( ThreadInterlockedCompareExchange( pDest, value, comperand ) == comperand ); }
#endif
//-----------------------------------------------------------------------------
#if defined(_WIN32) && defined(THREAD_PROFILER)
void ThreadNotifySyncNoop(void *p) {}
#define MAP_THREAD_PROFILER_CALL( from, to ) \
void from(void *p) \ { \ static CDynamicFunction<void (*)(void *)> dynFunc( "libittnotify.dll", #to, ThreadNotifySyncNoop ); \ (*dynFunc)(p); \ }
MAP_THREAD_PROFILER_CALL( ThreadNotifySyncPrepare, __itt_notify_sync_prepare ); MAP_THREAD_PROFILER_CALL( ThreadNotifySyncCancel, __itt_notify_sync_cancel ); MAP_THREAD_PROFILER_CALL( ThreadNotifySyncAcquired, __itt_notify_sync_acquired ); MAP_THREAD_PROFILER_CALL( ThreadNotifySyncReleasing, __itt_notify_sync_releasing );
#endif
//-----------------------------------------------------------------------------
//
// CThreadMutex
//
//-----------------------------------------------------------------------------
#ifndef POSIX
CThreadMutex::CThreadMutex() { #ifdef THREAD_MUTEX_TRACING_ENABLED
memset( &m_CriticalSection, 0, sizeof(m_CriticalSection) ); #endif
InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *)&m_CriticalSection, 4000); #ifdef THREAD_MUTEX_TRACING_SUPPORTED
// These need to be initialized unconditionally in case mixing release & debug object modules
// Lock and unlock may be emitted as COMDATs, in which case may get spurious output
m_currentOwnerID = m_lockCount = 0; m_bTrace = false; #endif
}
CThreadMutex::~CThreadMutex() { DeleteCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); } #endif // !POSIX
#if defined( _WIN32 ) && !defined( _X360 )
typedef BOOL (WINAPI*TryEnterCriticalSectionFunc_t)(LPCRITICAL_SECTION); static CDynamicFunction<TryEnterCriticalSectionFunc_t> DynTryEnterCriticalSection( "Kernel32.dll", "TryEnterCriticalSection" ); #elif defined( _X360 )
#define DynTryEnterCriticalSection TryEnterCriticalSection
#endif
bool CThreadMutex::TryLock() {
#if defined( _WIN32 )
#ifdef THREAD_MUTEX_TRACING_ENABLED
uint thisThreadID = ThreadGetCurrentId(); if ( m_bTrace && m_currentOwnerID && ( m_currentOwnerID != thisThreadID ) ) Msg( "Thread %u about to try-wait for lock %p owned by %u\n", ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, m_currentOwnerID ); #endif
if ( DynTryEnterCriticalSection != NULL ) { if ( (*DynTryEnterCriticalSection )( (CRITICAL_SECTION *)&m_CriticalSection ) != FALSE ) { #ifdef THREAD_MUTEX_TRACING_ENABLED
if (m_lockCount == 0) { // we now own it for the first time. Set owner information
m_currentOwnerID = thisThreadID; if ( m_bTrace ) Msg( "Thread %u now owns lock 0x%p\n", m_currentOwnerID, (CRITICAL_SECTION *)&m_CriticalSection ); } m_lockCount++; #endif
return true; } return false; } Lock(); return true; #elif defined( POSIX )
return pthread_mutex_trylock( &m_Mutex ) == 0; #else
#error "Implement me!"
return true; #endif
}
//-----------------------------------------------------------------------------
//
// CThreadFastMutex
//
//-----------------------------------------------------------------------------
#define THREAD_SPIN (8*1024)
void CThreadFastMutex::Lock( const uint32 threadId, unsigned nSpinSleepTime ) volatile { int i; if ( nSpinSleepTime != TT_INFINITE ) { for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLock( threadId ) ) { return; } ThreadPause(); }
for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLock( threadId ) ) { return; } ThreadPause(); if ( i % 1024 == 0 ) { ThreadSleep( 0 ); } }
#ifdef _WIN32
if ( !nSpinSleepTime && GetThreadPriority( GetCurrentThread() ) > THREAD_PRIORITY_NORMAL ) { nSpinSleepTime = 1; } else #endif
if ( nSpinSleepTime ) { for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLock( threadId ) ) { return; }
ThreadPause(); ThreadSleep( 0 ); }
}
for ( ;; ) // coded as for instead of while to make easy to breakpoint success
{ if ( TryLock( threadId ) ) { return; }
ThreadPause(); ThreadSleep( nSpinSleepTime ); } } else { for ( ;; ) // coded as for instead of while to make easy to breakpoint success
{ if ( TryLock( threadId ) ) { return; }
ThreadPause(); } } }
//-----------------------------------------------------------------------------
//
// CThreadRWLock
//
//-----------------------------------------------------------------------------
void CThreadRWLock::WaitForRead() { m_nPendingReaders++;
do { m_mutex.Unlock(); m_CanRead.Wait(); m_mutex.Lock(); } while (m_nWriters);
m_nPendingReaders--; }
void CThreadRWLock::LockForWrite() { m_mutex.Lock(); bool bWait = ( m_nWriters != 0 || m_nActiveReaders != 0 ); m_nWriters++; m_CanRead.Reset(); m_mutex.Unlock();
if ( bWait ) { m_CanWrite.Wait(); } }
void CThreadRWLock::UnlockWrite() { m_mutex.Lock(); m_nWriters--; if ( m_nWriters == 0) { if ( m_nPendingReaders ) { m_CanRead.Set(); } } else { m_CanWrite.Set(); } m_mutex.Unlock(); }
//-----------------------------------------------------------------------------
//
// CThreadSpinRWLock
//
//-----------------------------------------------------------------------------
void CThreadSpinRWLock::SpinLockForWrite( const uint32 threadId ) { int i;
for ( i = 1000; i != 0; --i ) { if ( TryLockForWrite( threadId ) ) { return; } ThreadPause(); }
for ( i = 20000; i != 0; --i ) { if ( TryLockForWrite( threadId ) ) { return; }
ThreadPause(); ThreadSleep( 0 ); }
for ( ;; ) // coded as for instead of while to make easy to breakpoint success
{ if ( TryLockForWrite( threadId ) ) { return; }
ThreadPause(); ThreadSleep( 1 ); } }
void CThreadSpinRWLock::LockForRead() { int i;
// In order to grab a read lock, the number of readers must not change and no thread can own the write lock
LockInfo_t oldValue; LockInfo_t newValue;
oldValue.m_nReaders = m_lockInfo.m_nReaders; oldValue.m_writerId = 0; newValue.m_nReaders = oldValue.m_nReaders + 1; newValue.m_writerId = 0;
if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) return; ThreadPause(); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders + 1;
for ( i = 1000; i != 0; --i ) { if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) return; ThreadPause(); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders + 1; }
for ( i = 20000; i != 0; --i ) { if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) return; ThreadPause(); ThreadSleep( 0 ); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders + 1; }
for ( ;; ) // coded as for instead of while to make easy to breakpoint success
{ if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) return; ThreadPause(); ThreadSleep( 1 ); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders + 1; } }
void CThreadSpinRWLock::UnlockRead() { int i;
Assert( m_lockInfo.m_nReaders > 0 && m_lockInfo.m_writerId == 0 ); LockInfo_t oldValue; LockInfo_t newValue;
oldValue.m_nReaders = m_lockInfo.m_nReaders; oldValue.m_writerId = 0; newValue.m_nReaders = oldValue.m_nReaders - 1; newValue.m_writerId = 0;
if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1;
for ( i = 500; i != 0; --i ) { if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; }
for ( i = 20000; i != 0; --i ) { if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); ThreadSleep( 0 ); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; }
for ( ;; ) // coded as for instead of while to make easy to breakpoint success
{ if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); ThreadSleep( 1 ); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; } }
void CThreadSpinRWLock::UnlockWrite() { Assert( m_lockInfo.m_writerId == ThreadGetCurrentId() && m_lockInfo.m_nReaders == 0 ); static const LockInfo_t newValue = { 0, 0 }; #if defined(_X360)
// X360TBD: Serious Perf implications, not yet. __sync();
#endif
ThreadInterlockedExchange64( (int64 *)&m_lockInfo, *((int64 *)&newValue) ); m_nWriters--; }
//-----------------------------------------------------------------------------
//
// CThread
//
//-----------------------------------------------------------------------------
CThreadLocalPtr<CThread> g_pCurThread;
//---------------------------------------------------------
CThread::CThread() : #ifdef _WIN32
m_hThread( NULL ), #endif
m_threadId( 0 ), m_result( 0 ), m_flags( 0 ) { m_szName[0] = 0; }
//---------------------------------------------------------
CThread::~CThread() { #ifdef _WIN32
if (m_hThread) #elif defined(POSIX)
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!!
} }
#ifdef _WIN32
// Now that the worker thread has exited (which we know because we presumably waited
// on the thread handle for it to exit) we can finally close the thread handle. We
// cannot do this any earlier, and certainly not in CThread::ThreadProc().
CloseHandle( m_hThread ); #endif
} }
//---------------------------------------------------------
const char *CThread::GetName() { AUTO_LOCK( m_Lock ); if ( !m_szName[0] ) { #ifdef _WIN32
_snprintf( m_szName, sizeof(m_szName) - 1, "Thread(%p/%p)", this, m_hThread ); #elif defined(POSIX)
_snprintf( m_szName, sizeof(m_szName) - 1, "Thread(0x%x/0x%x)", (uint)this, (uint)m_threadId ); #endif
m_szName[sizeof(m_szName) - 1] = 0; } return m_szName; }
//---------------------------------------------------------
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; }
//---------------------------------------------------------
bool CThread::Start( unsigned nBytesStack ) { 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 };
#ifdef _WIN32
HANDLE hThread; m_hThread = hThread = (HANDLE)VCRHook_CreateThread( NULL, nBytesStack, (LPTHREAD_START_ROUTINE)GetThreadProc(), new ThreadInit_t(init), CREATE_SUSPENDED, &m_threadId ); if ( !hThread ) { AssertMsg1( 0, "Failed to create thread (error 0x%x)", GetLastError() ); return false; } Plat_ApplyHardwareDataBreakpointsToNewThread( m_threadId ); ResumeThread( hThread );
#elif defined(POSIX)
pthread_attr_t attr; pthread_attr_init( &attr ); // From http://www.kernel.org/doc/man-pages/online/pages/man3/pthread_attr_setstacksize.3.html
// A thread's stack size is fixed at the time of thread creation. Only the main thread can dynamically grow its stack.
pthread_attr_setstacksize( &attr, MAX( nBytesStack, 1024u*1024 ) ); if ( pthread_create( &m_threadId, &attr, (void *(*)(void *))GetThreadProc(), new ThreadInit_t( init ) ) != 0 ) { AssertMsg1( 0, "Failed to create thread (error 0x%x)", GetLastError() ); return false; } Plat_ApplyHardwareDataBreakpointsToNewThread( (long unsigned int)m_threadId ); bInitSuccess = true; #endif
#if !defined( OSX )
ThreadSetDebugName( m_threadId, m_szName ); #endif
if ( !WaitForCreateComplete( &createComplete ) ) { Msg( "Thread failed to initialize\n" ); #ifdef _WIN32
CloseHandle( m_hThread ); m_hThread = NULL; m_threadId = 0; #elif defined(POSIX)
m_threadId = 0; #endif
return false; }
if ( !bInitSuccess ) { Msg( "Thread failed to initialize\n" ); #ifdef _WIN32
CloseHandle( m_hThread ); m_hThread = NULL; m_threadId = 0; #elif defined(POSIX)
m_threadId = 0; #endif
return false; }
#ifdef _WIN32
if ( !m_hThread ) { Msg( "Thread exited immediately\n" ); } #endif
#ifdef _WIN32
return !!m_hThread; #elif defined(POSIX)
return !!m_threadId; #endif
}
//---------------------------------------------------------
//
// Return true if the thread exists. false otherwise
//
bool CThread::IsAlive() { #ifdef _WIN32
DWORD dwExitCode;
return ( m_hThread && GetExitCodeThread( m_hThread, &dwExitCode ) && dwExitCode == STILL_ACTIVE ); #elif defined(POSIX)
return m_threadId; #endif
}
//---------------------------------------------------------
bool CThread::Join(unsigned timeout) { #ifdef _WIN32
if ( m_hThread ) #elif defined(POSIX)
if ( m_threadId ) #endif
{ AssertMsg(GetCurrentCThread() != this, _T("Thread cannot be joined with self"));
#ifdef _WIN32
return ThreadJoin( (ThreadHandle_t)m_hThread ); #elif defined(POSIX)
return ThreadJoin( (ThreadHandle_t)m_threadId ); #endif
} return true; }
//---------------------------------------------------------
#ifdef _WIN32
HANDLE CThread::GetThreadHandle() { return m_hThread; }
#endif
#if defined( _WIN32 ) || defined( LINUX )
//---------------------------------------------------------
uint CThread::GetThreadId() { return m_threadId; }
#endif
//---------------------------------------------------------
int CThread::GetResult() { return m_result; }
//---------------------------------------------------------
//
// Forcibly, abnormally, but relatively cleanly stop the thread
//
void CThread::Stop(int exitCode) { if ( !IsAlive() ) return;
if ( GetCurrentCThread() == this ) { m_result = exitCode; if ( !( m_flags & SUPPORT_STOP_PROTOCOL ) ) { OnExit(); g_pCurThread = (int)NULL;
#ifdef _WIN32
CloseHandle( m_hThread ); m_hThread = NULL; #endif
Cleanup(); } throw exitCode; } else AssertMsg( 0, "Only thread can stop self: Use a higher-level protocol"); }
//---------------------------------------------------------
int CThread::GetPriority() const { #ifdef _WIN32
return GetThreadPriority(m_hThread); #elif defined(POSIX)
struct sched_param thread_param; int policy; pthread_getschedparam( m_threadId, &policy, &thread_param ); return thread_param.sched_priority; #endif
}
//---------------------------------------------------------
bool CThread::SetPriority(int priority) { #ifdef _WIN32
return ThreadSetPriority( (ThreadHandle_t)m_hThread, priority ); #else
return ThreadSetPriority( (ThreadHandle_t)m_threadId, priority ); #endif
}
//---------------------------------------------------------
void CThread::SuspendCooperative() { if ( ThreadGetCurrentId() == (ThreadId_t)m_threadId ) { m_SuspendEventSignal.Set(); m_nSuspendCount = 1; m_SuspendEvent.Wait(); m_nSuspendCount = 0; } else { Assert( !"Suspend not called from worker thread, this would be a bug" ); } }
//---------------------------------------------------------
void CThread::ResumeCooperative() { Assert( m_nSuspendCount == 1 ); m_SuspendEvent.Set(); }
void CThread::BWaitForThreadSuspendCooperative() { m_SuspendEventSignal.Wait(); }
#ifndef LINUX
//---------------------------------------------------------
unsigned int CThread::Suspend() { #ifdef _WIN32
return ( SuspendThread(m_hThread) != 0 ); #elif defined(OSX)
int susCount = m_nSuspendCount++; while ( thread_suspend( pthread_mach_thread_np(m_threadId) ) != KERN_SUCCESS ) { }; return ( susCount) != 0; #else
#error
#endif
}
//---------------------------------------------------------
unsigned int CThread::Resume() { #ifdef _WIN32
return ( ResumeThread(m_hThread) != 0 ); #elif defined(OSX)
int susCount = m_nSuspendCount++; while ( thread_resume( pthread_mach_thread_np(m_threadId) ) != KERN_SUCCESS ) { }; return ( susCount - 1) != 0; #else
#error
#endif
} #endif
//---------------------------------------------------------
bool CThread::Terminate(int exitCode) { #ifndef _X360
#ifdef _WIN32
// I hope you know what you're doing!
if (!TerminateThread(m_hThread, exitCode)) return false; CloseHandle( m_hThread ); m_hThread = NULL; Cleanup(); #elif defined(POSIX)
pthread_kill( m_threadId, SIGKILL ); Cleanup(); #endif
return true; #else
AssertMsg( 0, "Cannot terminate a thread on the Xbox!" ); return false; #endif
}
//---------------------------------------------------------
//
// Get the Thread object that represents the current thread, if any.
// Can return NULL if the current thread was not created using
// CThread
//
CThread *CThread::GetCurrentCThread() { return g_pCurThread; }
//---------------------------------------------------------
//
// Offer a context switch. Under Win32, equivalent to Sleep(0)
//
void CThread::Yield() { #ifdef _WIN32
::Sleep(0); #elif defined(POSIX)
pthread_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.
//
void CThread::Sleep(unsigned duration) { #ifdef _WIN32
::Sleep(duration); #elif defined(POSIX)
usleep( duration * 1000 ); #endif
}
//---------------------------------------------------------
bool CThread::Init() { return true; }
//---------------------------------------------------------
void CThread::OnExit() { }
//---------------------------------------------------------
void CThread::Cleanup() { m_threadId = 0; }
//---------------------------------------------------------
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; }
//---------------------------------------------------------
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
}
//---------------------------------------------------------
CThread::ThreadProc_t CThread::GetThreadProc() { return ThreadProc; }
//---------------------------------------------------------
unsigned __stdcall CThread::ThreadProc(LPVOID pv) { std::auto_ptr<ThreadInit_t> pInit((ThreadInit_t *)pv); #ifdef _X360
// Make sure all threads are consistent w.r.t floating-point math
SetupFPUControlWord(); #endif
CThread *pThread = pInit->pThread; g_pCurThread = pThread; g_pCurThread->m_pStackBase = AlignValue( &pThread, 4096 ); pInit->pThread->m_result = -1; bool bInitSuccess = true; if ( pInit->pfInitSuccess ) *(pInit->pfInitSuccess) = false; try { bInitSuccess = pInit->pThread->Init(); } catch (...) { pInit->pInitCompleteEvent->Set(); throw; } if ( pInit->pfInitSuccess ) *(pInit->pfInitSuccess) = bInitSuccess; pInit->pInitCompleteEvent->Set(); if (!bInitSuccess) return 0; if ( pInit->pThread->m_flags & SUPPORT_STOP_PROTOCOL ) { try { pInit->pThread->m_result = pInit->pThread->Run(); } catch (...) { } } else { pInit->pThread->m_result = pInit->pThread->Run(); } pInit->pThread->OnExit(); g_pCurThread = (int)NULL; pInit->pThread->Cleanup(); return pInit->pThread->m_result; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CWorkerThread::CWorkerThread() : m_EventSend(true), // must be manual-reset for PeekCall()
m_EventComplete(true), // must be manual-reset to handle multiple wait with thread properly
m_Param(0), m_pParamFunctor(NULL), m_ReturnVal(0) { }
//---------------------------------------------------------
int CWorkerThread::CallWorker(unsigned dw, unsigned timeout, bool fBoostWorkerPriorityToMaster, CFunctor *pParamFunctor) { return Call(dw, timeout, fBoostWorkerPriorityToMaster, NULL, pParamFunctor); }
//---------------------------------------------------------
int CWorkerThread::CallMaster(unsigned dw, unsigned timeout) { return Call(dw, timeout, false); }
//---------------------------------------------------------
CThreadEvent &CWorkerThread::GetCallHandle() { return m_EventSend; }
//---------------------------------------------------------
unsigned CWorkerThread::GetCallParam( CFunctor **ppParamFunctor ) const { if( ppParamFunctor ) *ppParamFunctor = m_pParamFunctor; return m_Param; }
//---------------------------------------------------------
int CWorkerThread::BoostPriority() { int iInitialPriority = GetPriority(); const int iNewPriority = ThreadGetPriority( (ThreadHandle_t)GetThreadID() ); if (iNewPriority > iInitialPriority) ThreadSetPriority( (ThreadHandle_t)GetThreadID(), iNewPriority); return iInitialPriority; }
//---------------------------------------------------------
static uint32 __stdcall DefaultWaitFunc( int nEvents, CThreadEvent * const *pEvents, int bWaitAll, uint32 timeout ) { return ThreadWaitForEvents( nEvents, pEvents, bWaitAll!=0, timeout ); // return VCRHook_WaitForMultipleObjects( nHandles, (const void **)pHandles, bWaitAll, timeout );
}
int CWorkerThread::Call(unsigned dwParam, unsigned timeout, bool fBoostPriority, WaitFunc_t pfnWait, CFunctor *pParamFunctor) { AssertMsg(!m_EventSend.Check(), "Cannot perform call if there's an existing call pending" );
AUTO_LOCK( m_Lock );
if (!IsAlive()) return WTCR_FAIL;
int iInitialPriority = 0; if (fBoostPriority) { iInitialPriority = BoostPriority(); }
// set the parameter, signal the worker thread, wait for the completion to be signaled
m_Param = dwParam; m_pParamFunctor = pParamFunctor;
m_EventComplete.Reset(); m_EventSend.Set();
WaitForReply( timeout, pfnWait );
// MWD: Investigate why setting thread priorities is killing the 360
#ifndef _X360
if (fBoostPriority) SetPriority(iInitialPriority); #endif
return m_ReturnVal; }
//---------------------------------------------------------
//
// Wait for a request from the client
//
//---------------------------------------------------------
int CWorkerThread::WaitForReply( unsigned timeout ) { return WaitForReply( timeout, NULL ); }
int CWorkerThread::WaitForReply( unsigned timeout, WaitFunc_t pfnWait ) { if (!pfnWait) { pfnWait = DefaultWaitFunc; }
#ifdef WIN32
CThreadEvent threadEvent( GetThreadHandle() ); #endif
CThreadEvent *waits[] = { #ifdef WIN32
&threadEvent, #endif
&m_EventComplete };
unsigned result; bool bInDebugger = Plat_IsInDebugSession();
do { #ifdef WIN32
// Make sure the thread handle hasn't been closed
if ( !GetThreadHandle() ) { result = WAIT_OBJECT_0 + 1; break; } #endif
result = (*pfnWait)((sizeof(waits) / sizeof(waits[0])), waits, false, (timeout != TT_INFINITE) ? timeout : 30000);
AssertMsg(timeout != TT_INFINITE || result != WAIT_TIMEOUT, "Possible hung thread, call to thread timed out");
} while ( bInDebugger && ( timeout == TT_INFINITE && result == WAIT_TIMEOUT ) );
if ( result != WAIT_OBJECT_0 + 1 ) { if (result == WAIT_TIMEOUT) m_ReturnVal = WTCR_TIMEOUT; else if (result == WAIT_OBJECT_0) { DevMsg( 2, "Thread failed to respond, probably exited\n"); m_EventSend.Reset(); m_ReturnVal = WTCR_TIMEOUT; } else { m_EventSend.Reset(); m_ReturnVal = WTCR_THREAD_GONE; } }
return m_ReturnVal; }
//---------------------------------------------------------
//
// Wait for a request from the client
//
//---------------------------------------------------------
bool CWorkerThread::WaitForCall(unsigned * pResult) { return WaitForCall(TT_INFINITE, pResult); }
//---------------------------------------------------------
bool CWorkerThread::WaitForCall(unsigned dwTimeout, unsigned * pResult) { bool returnVal = m_EventSend.Wait(dwTimeout); if (pResult) *pResult = m_Param; return returnVal; }
//---------------------------------------------------------
//
// is there a request?
//
bool CWorkerThread::PeekCall(unsigned * pParam, CFunctor **ppParamFunctor) { if (!m_EventSend.Check()) { return false; } else { if (pParam) { *pParam = m_Param; } if( ppParamFunctor ) { *ppParamFunctor = m_pParamFunctor; } return true; } }
//---------------------------------------------------------
//
// Reply to the request
//
void CWorkerThread::Reply(unsigned dw) { m_Param = 0; m_ReturnVal = dw;
// The request is now complete so PeekCall() should fail from
// now on
//
// This event should be reset BEFORE we signal the client
m_EventSend.Reset();
// Tell the client we're finished
m_EventComplete.Set(); }
//-----------------------------------------------------------------------------
|