(Win32 refresh) Threads_Win32

This file handles OS side interaction as well as the creation of threads within the game -  std::unique_ptr and std::mutex are introduced here for their safety and memory management benefits.

A custom wrapper PortableSignalObjectAndWait was removed in favor of directly calling SignalObjectAndWait.

In SetThreadName, we change from EXCEPTION_CONTINUE_EXECUTION to EXCEPTION_EXECUTE_HANDLER as suggested by the compiler to avoid a potential infinite loop.

ThreadImpl_Win32::Halt has been refactored to unequivocably call SuspendThread instead of TerminateThread, as TerminateThread doesn't allow for proper thread cleanup.

Don't bother with the try-catch in SetThreadName unless this is a debug build (since exceptions are disabled in ITGm, so it won't work as expected).
This commit is contained in:
sukibaby
2025-03-02 09:04:34 -08:00
committed by teejusb
parent 3f348117e2
commit 1da7f6167f
+75 -82
View File
@@ -6,37 +6,46 @@
#include "archutils/Win32/ErrorStrings.h"
#include <cstdint>
#include <mutex>
#include <memory>
const int MAX_THREADS=128;
const int MAX_THREADS = 128;
static std::unique_ptr<MutexImpl_Win32> g_pThreadIdMutex;
static bool g_ThreadIdMutexInitialized = false;
static std::mutex g_ThreadIdMutexInitLock;
static MutexImpl_Win32 *g_pThreadIdMutex = nullptr;
static void InitThreadIdMutex()
{
if( g_pThreadIdMutex != nullptr )
std::lock_guard<std::mutex> lock(g_ThreadIdMutexInitLock);
if (g_ThreadIdMutexInitialized)
return;
g_pThreadIdMutex = new MutexImpl_Win32(nullptr);
std::unique_ptr<MutexImpl_Win32> temp = std::make_unique<MutexImpl_Win32>(nullptr);
g_ThreadIdMutexInitialized = true;
g_pThreadIdMutex = std::move(temp);
}
static uint64_t g_ThreadIds[MAX_THREADS];
static HANDLE g_ThreadHandles[MAX_THREADS];
static std::mutex g_ThreadDataMutex;
HANDLE Win32ThreadIdToHandle( uint64_t iID )
HANDLE Win32ThreadIdToHandle(uint64_t iID)
{
for( int i = 0; i < MAX_THREADS; ++i )
std::lock_guard<std::mutex> lock(g_ThreadDataMutex);
for (int i = 0; i < MAX_THREADS; ++i)
{
if( g_ThreadIds[i] == iID )
if (g_ThreadIds[i] == iID)
return g_ThreadHandles[i];
}
return nullptr;
}
void ThreadImpl_Win32::Halt( bool Kill )
void ThreadImpl_Win32::Halt(bool /*Kill*/)
{
if( Kill )
TerminateThread( ThreadHandle, 0 );
else
SuspendThread( ThreadHandle );
SuspendThread(ThreadHandle);
}
void ThreadImpl_Win32::Resume()
@@ -46,7 +55,7 @@ void ThreadImpl_Win32::Resume()
uint64_t ThreadImpl_Win32::GetThreadId() const
{
return (uint64_t) ThreadId;
return static_cast<uint64_t>(ThreadId);
}
int ThreadImpl_Win32::Wait()
@@ -82,10 +91,16 @@ static void SetThreadName( DWORD dwThreadID, LPCTSTR szThreadName )
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try {
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info);
} __except (EXCEPTION_CONTINUE_EXECUTION) {
#ifdef _DEBUG
__try
{
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR*)&info);
}
__except (EXCEPTION_CONTINUE_EXECUTION) // NOTE(sukibaby): compiler suggests trying EXCEPTION_EXECUTE_HANDLER to avoid possible infinite loop.
{
}
#endif
#elif defined(__GNUC__)
pthread_setname_np(pthread_self(), szThreadName);
#endif
@@ -99,7 +114,8 @@ static DWORD WINAPI StartThread( LPVOID pData )
DWORD ret = static_cast<DWORD>(pThis->m_pFunc(pThis->m_pData));
for( int i = 0; i < MAX_THREADS; ++i )
std::lock_guard<std::mutex> lock(g_ThreadDataMutex);
for (int i = 0; i < MAX_THREADS; ++i)
{
if( g_ThreadIds[i] == RageThread::GetCurrentThreadID() )
{
@@ -133,7 +149,7 @@ static int GetOpenSlot( uint64_t iID )
ThreadImpl *MakeThisThread()
{
ThreadImpl_Win32 *thread = new ThreadImpl_Win32;
std::unique_ptr<ThreadImpl_Win32> thread = std::make_unique<ThreadImpl_Win32>();
SetThreadName( GetCurrentThreadId(), RageThread::GetCurrentThreadName() );
@@ -143,9 +159,6 @@ ThreadImpl *MakeThisThread()
if( !ret )
{
// LOG->Warn( werr_ssprintf( GetLastError(), "DuplicateHandle(%p, %p) failed",
// CurProc, GetCurrentThread() ) );
thread->ThreadHandle = nullptr;
}
@@ -154,18 +167,18 @@ ThreadImpl *MakeThisThread()
int slot = GetOpenSlot( GetCurrentThreadId() );
g_ThreadHandles[slot] = thread->ThreadHandle;
return thread;
return thread.release();
}
ThreadImpl *MakeThread( int (*pFunc)(void *pData), void *pData, uint64_t *piThreadID )
{
ThreadImpl_Win32 *thread = new ThreadImpl_Win32;
std::unique_ptr<ThreadImpl_Win32> thread = std::make_unique<ThreadImpl_Win32>();
thread->m_pFunc = pFunc;
thread->m_pData = pData;
thread->ThreadHandle = CreateThread( nullptr, 0, &StartThread, thread, CREATE_SUSPENDED, &thread->ThreadId );
*piThreadID = (uint64_t) thread->ThreadId;
ASSERT_M( thread->ThreadHandle != nullptr, ssprintf("%s", werr_ssprintf(GetLastError(), "CreateThread").c_str() ) );
thread->ThreadHandle = CreateThread(nullptr, 0, &StartThread, thread.get(), CREATE_SUSPENDED, &thread->ThreadId);
*piThreadID = static_cast<uint64_t>(thread->ThreadId);
ASSERT_M(thread->ThreadHandle != nullptr, ssprintf("%s", werr_ssprintf(GetLastError(), "CreateThread").c_str()));
int slot = GetOpenSlot( thread->ThreadId );
g_ThreadHandles[slot] = thread->ThreadHandle;
@@ -173,7 +186,7 @@ ThreadImpl *MakeThread( int (*pFunc)(void *pData), void *pData, uint64_t *piThre
int iRet = ResumeThread( thread->ThreadHandle );
ASSERT_M( iRet == 1, ssprintf("%s", werr_ssprintf(GetLastError(), "ResumeThread").c_str() ) );
return thread;
return thread.release();
}
@@ -189,6 +202,12 @@ MutexImpl_Win32::~MutexImpl_Win32()
CloseHandle( mutex );
}
/* NOTE(sukibaby): the function name here is a bit misleading.
* It provides additional error handling and assertions which
* are not present in WaitForSingleObject.
* It also consolidates some switch case logic we need to use
* mutliple times within this file.
* For these reasons I've left this function alone. */
static bool SimpleWaitForSingleObject( HANDLE h, DWORD ms )
{
ASSERT( h != nullptr );
@@ -203,35 +222,34 @@ static bool SimpleWaitForSingleObject( HANDLE h, DWORD ms )
return false;
case WAIT_ABANDONED:
// The docs aren't particular about what this does, but it should never happen.
FAIL_M( "WAIT_ABANDONED" );
FAIL_M("WAIT_ABANDONED");
case WAIT_FAILED:
FAIL_M( werr_ssprintf(GetLastError(), "WaitForSingleObject") );
FAIL_M(werr_ssprintf(GetLastError(), "WaitForSingleObject"));
default:
FAIL_M( "unknown" );
FAIL_M("unknown");
}
}
bool MutexImpl_Win32::Lock()
{
DWORD dwWaitResult = WaitForSingleObject(mutex, INFINITE);
switch (dwWaitResult)
{
DWORD dwWaitResult = WaitForSingleObject(mutex, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_OBJECT_0:
return true;
case WAIT_ABANDONED:
return false;
case WAIT_TIMEOUT:
return false;
default:
FAIL_M( "WaitForSingleObject failed in a way that shouldn't have been possible" );
}
case WAIT_ABANDONED:
return false;
default:
FAIL_M("WaitForSingleObject failed in a way that shouldn't have been possible");
}
}
bool MutexImpl_Win32::TryLock()
{
@@ -282,37 +300,6 @@ EventImpl_Win32::~EventImpl_Win32()
CloseHandle( m_WaitersDone );
}
static bool PortableSignalObjectAndWait( HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, bool bFirstParamIsMutex, unsigned iMilliseconds = INFINITE )
{
if( bFirstParamIsMutex )
{
const bool bRet = !!ReleaseMutex( hObjectToSignal );
if( !bRet )
sm_crash( werr_ssprintf( GetLastError(), "ReleaseMutex failed" ) );
}
else
{
SetEvent( hObjectToSignal );
}
DWORD ret = WaitForSingleObject( hObjectToWaitOn, iMilliseconds );
switch( ret )
{
case WAIT_OBJECT_0:
return true;
case WAIT_ABANDONED:
// The docs aren't particular about what this does, but it should never happen.
FAIL_M( "WAIT_ABANDONED" );
case WAIT_TIMEOUT:
return false;
default:
FAIL_M( "unknown" );
}
}
// Event logic from http://www.cs.wustl.edu/~schmidt/win32-cv-1.html.
bool EventImpl_Win32::Wait( RageTimer *pTimeout )
{
@@ -328,28 +315,34 @@ bool EventImpl_Win32::Wait( RageTimer *pTimeout )
}
// Unlock the mutex and wait for a signal.
bool bSuccess = PortableSignalObjectAndWait( m_pParent->mutex, m_WakeupSema, true, iMilliseconds );
bool bSuccess = (SignalObjectAndWait(m_pParent->mutex, m_WakeupSema, iMilliseconds, FALSE) == WAIT_OBJECT_0);
EnterCriticalSection( &m_iNumWaitingLock );
if( !bSuccess )
{
/* Avoid a race condition: someone may have signalled the object
* between PortableSignalObjectAndWait and EnterCriticalSection.
* between SignalObjectAndWait and EnterCriticalSection.
* While we hold m_iNumWaitingLock, poll (with a zero timeout) the
* object one last time. */
if( WaitForSingleObject( m_WakeupSema, 0 ) == WAIT_OBJECT_0 )
{
bSuccess = true;
}
}
--m_iNumWaiting;
bool bLastWaiting = m_iNumWaiting == 0;
bool bLastWaiting = (m_iNumWaiting == 0);
LeaveCriticalSection( &m_iNumWaitingLock );
/* If we're the last waiter to wake up, and we were actually woken by
* another thread (not by timeout), wake up the signaller. */
if( bLastWaiting && bSuccess )
PortableSignalObjectAndWait( m_WaitersDone, m_pParent->mutex, false );
if (bLastWaiting && bSuccess)
{
SignalObjectAndWait(m_WaitersDone, m_pParent->mutex, INFINITE, FALSE);
}
else
WaitForSingleObject( m_pParent->mutex, INFINITE );
{
WaitForSingleObject(m_pParent->mutex, INFINITE);
}
return bSuccess;
}
@@ -393,7 +386,7 @@ void EventImpl_Win32::Broadcast()
EventImpl *MakeEvent( MutexImpl *pMutex )
{
MutexImpl_Win32 *pWin32Mutex = (MutexImpl_Win32 *) pMutex;
MutexImpl_Win32* pWin32Mutex = static_cast<MutexImpl_Win32*>(pMutex);
return new EventImpl_Win32( pWin32Mutex );
}