//========= 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)
# if defined(ANDROID)
# include <fcntl.h>
# include <unistd.h>
# else
# include <sys/fcntl.h>
# include <sys/unistd.h>
# endif
# 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
int64 val ; memcpy ( & val , & newValue , sizeof ( val ) ) ;
ThreadInterlockedExchange64 ( ( int64 * ) & m_lockInfo , val ) ;
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(ANDROID)
sched_yield ( ) ;
# 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 ( ) ;
}
//-----------------------------------------------------------------------------