source-engine/tier0/threadtools.cpp
2021-08-20 19:56:20 +03:00

2412 lines
55 KiB
C++

//========= 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
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(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();
}
//-----------------------------------------------------------------------------