r5sdk/r5dev/thirdparty/ea/EAThread/source/pc/eathread_thread_pc.cpp
Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

832 lines
30 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include "EABase/eabase.h"
#include "eathread/eathread.h"
#include "eathread/eathread_callstack.h"
#include "eathread/eathread_mutex.h"
#include "eathread/eathread_sync.h"
#include "eathread/eathread_thread.h"
#include "eathread/internal/eathread_global.h"
#if defined(EA_COMPILER_MSVC) && EA_COMPILER_VERSION >= 1900 // VS2015+
// required for windows.h that has mismatch that is included in this file
EA_DISABLE_VC_WARNING(5031 5032)// #pragma warning(pop): likely mismatch, popping warning state pushed in different file / detected #pragma warning(push) with no corresponding
#endif
// Warning 6312 and 6322 are spurious, as we are not execution a case that could possibly loop.
// 6312: Possible infinite loop: use of the constant EXCEPTION_CONTINUE_EXECUTION in the exception-filter expression of a try-except. Execution restarts in the protected block
// 6322: Empty _except block
EA_DISABLE_VC_WARNING(6312 6322)
#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
#include <new>
#include <process.h>
EA_DISABLE_ALL_VC_WARNINGS()
#include <Windows.h>
EA_RESTORE_ALL_VC_WARNINGS()
#if defined(EA_COMPILER_MSVC)
struct ThreadNameInfo{
DWORD dwType;
LPCSTR lpName;
DWORD dwThreadId;
DWORD dwFlags;
};
extern "C" WINBASEAPI DWORD WINAPI SetThreadIdealProcessor(_In_ HANDLE hThread, _In_ DWORD dwIdealProcessor);
extern "C" WINBASEAPI BOOL WINAPI IsDebuggerPresent();
#endif
#ifdef EA_COMPILER_MSVC
#ifndef EATHREAD_INIT_SEG_DEFINED
#define EATHREAD_INIT_SEG_DEFINED
#endif
// We are changing the initalization ordering here because in bulkbuild tool builds the initialization
// order of globals changes and causes a crash when we attempt to lock the Mutex guarding access
// of the EAThreadDynamicData objects. The code attempts to lock a mutex that has been destructed
// and causes a crash within the WindowsSDK. This ensures that global mutex object is not destructed
// until all user code has destructed.
//
#ifndef EATHREAD_INIT_SEG_DEFINED
#define EATHREAD_INIT_SEG_DEFINED
// warning C4075: initializers put in unrecognized initialization area
//warning C4073: initializers put in library initialization area
EA_DISABLE_VC_WARNING(4075 4073)
#pragma init_seg(lib)
#endif
#endif
namespace EA {
namespace Thread {
extern void SetCurrentThreadHandle(HANDLE hThread, bool bDynamic);
namespace Internal { extern void SetThreadName(EAThreadDynamicData* pTDD, const char* pName); };
}
}
namespace EA
{
namespace Thread
{
extern Allocator* gpAllocator;
static AtomicInt32 nLastProcessor = 0;
const size_t kMaxThreadDynamicDataCount = 128;
struct EAThreadGlobalVars
{
EA_PREFIX_ALIGN(8)
char gThreadDynamicData[kMaxThreadDynamicDataCount][sizeof(EAThreadDynamicData)] EA_POSTFIX_ALIGN(8);
AtomicInt32 gThreadDynamicDataAllocated[kMaxThreadDynamicDataCount];
Mutex gThreadDynamicMutex;
EAThreadGlobalVars() {}
EAThreadGlobalVars(const EAThreadGlobalVars&) {}
EAThreadGlobalVars& operator=(const EAThreadGlobalVars&) {}
};
EATHREAD_GLOBALVARS_CREATE_INSTANCE;
EAThreadDynamicData* AllocateThreadDynamicData()
{
AutoMutex am(EATHREAD_GLOBALVARS.gThreadDynamicMutex);
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
if(EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[i].SetValueConditional(1, 0))
return (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
}
// This is a safety fallback mechanism. In practice it won't be used in almost all situations.
if(gpAllocator)
return (EAThreadDynamicData*)gpAllocator->Alloc(sizeof(EAThreadDynamicData));
else
return new EAThreadDynamicData; // This is a small problem, as this doesn't just allocate it but also constructs it.
}
void FreeThreadDynamicData(EAThreadDynamicData* pEAThreadDynamicData)
{
AutoMutex am(EATHREAD_GLOBALVARS.gThreadDynamicMutex);
if((pEAThreadDynamicData >= (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData) && (pEAThreadDynamicData < ((EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData + kMaxThreadDynamicDataCount)))
{
pEAThreadDynamicData->~EAThreadDynamicData();
EATHREAD_GLOBALVARS.gThreadDynamicDataAllocated[pEAThreadDynamicData - (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData].SetValue(0);
}
else
{
// Assume the data was allocated via the fallback mechanism.
if(gpAllocator)
{
pEAThreadDynamicData->~EAThreadDynamicData();
gpAllocator->Free(pEAThreadDynamicData);
}
else
delete pEAThreadDynamicData;
}
}
EAThreadDynamicData* FindThreadDynamicData(ThreadId threadId)
{
for(size_t i(0); i < kMaxThreadDynamicDataCount; i++)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if(pTDD->mhThread == threadId)
return pTDD;
}
return NULL; // This is no practical way we can find the data unless thread-specific storage was involved.
}
EAThreadDynamicData* FindThreadDynamicData(EA::Thread::SysThreadId sysThreadId)
{
for (size_t i(0); i < kMaxThreadDynamicDataCount; ++i)
{
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)EATHREAD_GLOBALVARS.gThreadDynamicData[i];
if (pTDD->mnThreadId == sysThreadId)
return pTDD;
}
return nullptr; // This is no practical way we can find the data unless thread-specific storage was involved.
}
bool IsDebuggerPresent()
{
#if defined(EA_PLATFORM_MICROSOFT)
return ::IsDebuggerPresent() != 0;
#else
return false;
#endif
}
}
}
EAThreadDynamicData::EAThreadDynamicData()
: mhThread(EA::Thread::kThreadIdInvalid),
mnThreadId(0), // Note that this is a Windows "thread id", wheras for us thread ids are what Windows calls a thread handle.
mnStatus(EA::Thread::Thread::kStatusNone),
mnReturnValue(0),
mpBeginThreadUserWrapper(NULL),
mnRefCount(0)
{
// Empty
}
EAThreadDynamicData::~EAThreadDynamicData()
{
if(mhThread)
::CloseHandle(mhThread);
mhThread = EA::Thread::kThreadIdInvalid;
mnThreadId = 0;
}
void EAThreadDynamicData::AddRef()
{
mnRefCount.Increment();
}
void EAThreadDynamicData::Release()
{
if(mnRefCount.Decrement() == 0)
EA::Thread::FreeThreadDynamicData(this);
}
EA::Thread::ThreadParameters::ThreadParameters()
: mpStack(NULL)
, mnStackSize(0)
, mnPriority(kThreadPriorityDefault)
, mnProcessor(kProcessorDefault)
, mnAffinityMask(kThreadAffinityMaskAny)
, mpName("")
, mbDisablePriorityBoost(false)
{
}
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::sGlobalRunnableFunctionUserWrapper = NULL;
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::sGlobalRunnableClassUserWrapper = NULL;
EA::Thread::AtomicInt32 EA::Thread::Thread::sDefaultProcessor = kProcessorAny;
EA::Thread::AtomicUint64 EA::Thread::Thread::sDefaultProcessorMask = UINT64_C(0xffffffffffffffff);
EA::Thread::RunnableFunctionUserWrapper EA::Thread::Thread::GetGlobalRunnableFunctionUserWrapper()
{
return sGlobalRunnableFunctionUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(EA::Thread::RunnableFunctionUserWrapper pUserWrapper)
{
if(sGlobalRunnableFunctionUserWrapper != NULL)
{
// Can only be set once in entire game.
EAT_ASSERT(false);
}
else
{
sGlobalRunnableFunctionUserWrapper = pUserWrapper;
}
}
EA::Thread::RunnableClassUserWrapper EA::Thread::Thread::GetGlobalRunnableClassUserWrapper()
{
return sGlobalRunnableClassUserWrapper;
}
void EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(EA::Thread::RunnableClassUserWrapper pUserWrapper)
{
if(sGlobalRunnableClassUserWrapper != NULL)
{
// Can only be set once.
EAT_ASSERT(false);
}
else
sGlobalRunnableClassUserWrapper = pUserWrapper;
}
// Helper that selects a target processor based on the provided ThreadParameters structure and the various
// pieces of shared state that EAThread maintains to implement a 'round-robin' style processor selection.
int SelectProcessor(const EA::Thread::ThreadParameters* pTP, EA::Thread::AtomicInt32& sDefaultProcessor, EA::Thread::AtomicUint64& sDefaultProcessorMask)
{
int nProcessor;
if (pTP && (pTP->mnProcessor >= 0))
{
nProcessor = pTP->mnProcessor;
// This is a small attempt to try to spread out threads between processors. We don't
// care much if another thread happens to be created here and races with this.
if (nProcessor == EA::Thread::nLastProcessor)
++EA::Thread::nLastProcessor;
}
else
{
#if defined(EA_PLATFORM_MICROSOFT)
if (!pTP || pTP->mnProcessor == EA::Thread::kProcessorAny)
{
// If the processor is not specified, then allow the scheduler to
// run the thread on any available processor
nProcessor = EA::Thread::kProcessorDefault;
}
else
#endif
if (sDefaultProcessor >= 0) // If the user has identified a specific processor...
nProcessor = sDefaultProcessor;
else if(sDefaultProcessor == EA::Thread::kProcessorDefault) // If the user explicitly asked for the default processor
nProcessor = sDefaultProcessor;
else
{
// NOTE(rparolin): The reason we have this round-robin code is that the
// originally we used it on Xenon OS which required us to pick a CPU to run on.
// After the Xenon was deprecated this code remained and is now a functional
// requirement. We should probably deprecate and remove in the future but
// currently teams are dependent on it.
const uint64_t processorMask = sDefaultProcessorMask.GetValue();
do
{
nProcessor = EA::Thread::nLastProcessor.Increment();
if (nProcessor == MAXIMUM_PROCESSORS)
{
EA::Thread::nLastProcessor.SetValueConditional(0, MAXIMUM_PROCESSORS);
nProcessor = 0;
}
} while ((((uint64_t)1 << nProcessor) & processorMask) == 0);
}
}
return nProcessor;
}
EA::Thread::Thread::Thread()
{
mThreadData.mpData = NULL;
}
EA::Thread::Thread::Thread(const Thread& t)
: mThreadData(t.mThreadData)
{
if(mThreadData.mpData)
mThreadData.mpData->AddRef();
}
EA::Thread::Thread& EA::Thread::Thread::operator=(const Thread& t)
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if(t.mThreadData.mpData)
t.mThreadData.mpData->AddRef();
if(mThreadData.mpData)
mThreadData.mpData->Release();
mThreadData = t.mThreadData;
return *this;
}
EA::Thread::Thread::~Thread()
{
// We don't synchronize access to mpData; we assume that the user
// synchronizes it or this Thread instances is used from a single thread.
if(mThreadData.mpData)
mThreadData.mpData->Release();
}
#if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
static DWORD WINAPI RunnableFunctionInternal(void* pContext)
#else
static unsigned int __stdcall RunnableFunctionInternal(void* pContext)
#endif
{
// The parent thread is sharing memory with us and we need to
// make sure our view of it is synchronized with the parent.
EAReadWriteBarrier();
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext;
EA::Thread::RunnableFunction pFunction = (EA::Thread::RunnableFunction)pTDD->mpStartContext[0];
void* pCallContext = pTDD->mpStartContext[1];
EA::Thread::SetCurrentThreadHandle(pTDD->mpStartContext[2], false);
pTDD->mpStackBase = EA::Thread::GetStackBase();
pTDD->mnStatus = EA::Thread::Thread::kStatusRunning;
EA::Thread::SetThreadName(pTDD->mhThread, pTDD->mName);
if(pTDD->mpBeginThreadUserWrapper != NULL)
{
EA::Thread::RunnableFunctionUserWrapper pWrapperFunction = (EA::Thread::RunnableFunctionUserWrapper)pTDD->mpBeginThreadUserWrapper;
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
pTDD->mnReturnValue = pWrapperFunction(pFunction, pCallContext);
}
else
{
pTDD->mnReturnValue = pFunction(pCallContext);
}
const unsigned int nReturnValue = (unsigned int)pTDD->mnReturnValue;
EA::Thread::SetCurrentThreadHandle(0, false);
pTDD->mnStatus = EA::Thread::Thread::kStatusEnded;
pTDD->Release();
return nReturnValue;
}
void EA::Thread::Thread::SetAffinityMask(EA::Thread::ThreadAffinityMask nAffinityMask)
{
if(mThreadData.mpData->mhThread)
{
EA::Thread::SetThreadAffinityMask(mThreadData.mpData->mhThread, nAffinityMask);
}
}
EA::Thread::ThreadAffinityMask EA::Thread::Thread::GetAffinityMask()
{
if(mThreadData.mpData->mhThread)
{
return mThreadData.mpData->mnThreadAffinityMask;
}
return kThreadAffinityMaskAny;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(RunnableFunction pFunction, void* pContext, const ThreadParameters* pTP, RunnableFunctionUserWrapper pUserWrapper)
{
// Check there is an entry for the current thread context in our ThreadDynamicData array.
ThreadId thisThreadId = GetThreadId();
if(!FindThreadDynamicData(thisThreadId))
{
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData;
if(pData)
{
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
// Do no AddRef for thread execution because this is not an EAThread managed thread.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mhThread = thisThreadId;
pData->mnThreadId = GetCurrentThreadId();
strncpy(pData->mName, "external", EATHREAD_NAME_SIZE);
pData->mName[EATHREAD_NAME_SIZE - 1] = 0;
pData->mpStackBase = EA::Thread::GetStackBase();
}
}
if(mThreadData.mpData)
mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below.
// Win32-like platforms don't support user-supplied stacks. A user-supplied stack pointer
// here would be a waste of user memory, and so we assert that mpStack == NULL.
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap.
mThreadData.mpData = pData;
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mhThread = kThreadIdInvalid;
pData->mpStartContext[0] = pFunction;
pData->mpStartContext[1] = pContext;
pData->mpBeginThreadUserWrapper = pUserWrapper;
pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny;
const unsigned nStackSize = pTP ? (unsigned)pTP->mnStackSize : 0;
#if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
// Capilano no longer supports _beginthreadex. Using CreateThread instead may cause issues when using the MS CRT
// according to MSDN (memory leaks or possibly crashes) as it does not initialize the CRT. This a reasonable
// workaround while we wait for clarification from MS on what the recommended threading APIs are for Capilano.
HANDLE hThread = CreateThread(NULL, nStackSize, RunnableFunctionInternal, pData, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&pData->mnThreadId));
#else
HANDLE hThread = (HANDLE)_beginthreadex(NULL, nStackSize, RunnableFunctionInternal, pData, CREATE_SUSPENDED, &pData->mnThreadId);
#endif
if(hThread)
{
pData->mhThread = hThread;
if(pTP)
SetName(pTP->mpName);
pData->mpStartContext[2] = hThread;
if(pTP && (pTP->mnPriority != kThreadPriorityDefault))
SetPriority(pTP->mnPriority);
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
if (pTP)
{
auto result = SetThreadPriorityBoost(pData->mhThread, pTP->mbDisablePriorityBoost);
EAT_ASSERT(result != 0);
EA_UNUSED(result);
}
#endif
#if defined(EA_PLATFORM_MICROSOFT)
int nProcessor = SelectProcessor(pTP, sDefaultProcessor, sDefaultProcessorMask);
if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny)
SetAffinityMask(pTP->mnAffinityMask);
else
SetProcessor(nProcessor);
#endif
ResumeThread(hThread);
pData->Release(); // Matches AddRef for this function.
return hThread;
}
pData->Release(); // Matches AddRef for this function.
pData->Release(); // Matches AddRef for this Thread class above.
pData->Release(); // Matches AddRef for the thread above.
mThreadData.mpData = NULL; // mThreadData.mpData == pData
return (ThreadId)kThreadIdInvalid;
}
#if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
static DWORD WINAPI RunnableObjectInternal(void* pContext)
#else
static unsigned int __stdcall RunnableObjectInternal(void* pContext)
#endif
{
// The parent thread is sharing memory with us and we need to
// make sure our view of it is synchronized with the parent.
EAReadWriteBarrier();
EAThreadDynamicData* const pTDD = (EAThreadDynamicData*)pContext;
EA::Thread::IRunnable* pRunnable = (EA::Thread::IRunnable*)pTDD->mpStartContext[0];
void* pCallContext = pTDD->mpStartContext[1];
EA::Thread::SetCurrentThreadHandle(pTDD->mpStartContext[2], false);
pTDD->mnStatus = EA::Thread::Thread::kStatusRunning;
EA::Thread::SetThreadName(pTDD->mhThread, pTDD->mName);
if(pTDD->mpBeginThreadUserWrapper)
{
EA::Thread::RunnableClassUserWrapper pWrapperClass = (EA::Thread::RunnableClassUserWrapper)pTDD->mpBeginThreadUserWrapper;
// if user wrapper is specified, call user wrapper and pass down the pFunction and pContext
pTDD->mnReturnValue = pWrapperClass(pRunnable, pCallContext);
}
else
pTDD->mnReturnValue = pRunnable->Run(pCallContext);
const unsigned int nReturnValue = (unsigned int)pTDD->mnReturnValue;
EA::Thread::SetCurrentThreadHandle(0, false);
pTDD->mnStatus = EA::Thread::Thread::kStatusEnded;
pTDD->Release();
return nReturnValue;
}
EA::Thread::ThreadId EA::Thread::Thread::Begin(IRunnable* pRunnable, void* pContext, const ThreadParameters* pTP, RunnableClassUserWrapper pUserWrapper)
{
if(mThreadData.mpData)
mThreadData.mpData->Release(); // Matches the "AddRef for ourselves" below.
// Win32-like platforms don't support user-supplied stacks. A user-supplied stack pointer
// here would be a waste of user memory, and so we assert that mpStack == NULL.
EAT_ASSERT(!pTP || (pTP->mpStack == NULL));
// We use the pData temporary throughout this function because it's possible that mThreadData.mpData could be
// modified as we are executing, in particular in the case that mThreadData.mpData is destroyed and changed
// during execution.
EAThreadDynamicData* pData = new(AllocateThreadDynamicData()) EAThreadDynamicData; // Note that we use a special new here which doesn't use the heap.
mThreadData.mpData = pData;
pData->AddRef(); // AddRef for ourselves, to be released upon this Thread class being deleted or upon Begin being called again for a new thread.
pData->AddRef(); // AddRef for the thread, to be released upon the thread exiting.
pData->AddRef(); // AddRef for this function, to be released upon this function's exit.
pData->mhThread = kThreadIdInvalid;
pData->mpStartContext[0] = pRunnable;
pData->mpStartContext[1] = pContext;
pData->mpBeginThreadUserWrapper = pUserWrapper;
pData->mnThreadAffinityMask = pTP ? pTP->mnAffinityMask : kThreadAffinityMaskAny;
const unsigned nStackSize = pTP ? (unsigned)pTP->mnStackSize : 0;
#if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
// Capilano no longer supports _beginthreadex. Using CreateThread instead may cause issues when using the MS CRT
// according to MSDN (memory leaks or possibly crashes) as it does not initialize the CRT. This a reasonable
// workaround while we wait for clarification from MS on what the recommended threading APIs are for Capilano.
HANDLE hThread = CreateThread(NULL, nStackSize, RunnableObjectInternal, pData, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&pData->mnThreadId));
#else
HANDLE hThread = (HANDLE)_beginthreadex(NULL, nStackSize, RunnableObjectInternal, pData, CREATE_SUSPENDED, &pData->mnThreadId);
#endif
if(hThread)
{
pData->mhThread = hThread;
if(pTP)
SetName(pTP->mpName);
pData->mpStartContext[2] = hThread;
if(pTP && (pTP->mnPriority != kThreadPriorityDefault))
SetPriority(pTP->mnPriority);
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
if (pTP)
{
auto result = SetThreadPriorityBoost(pData->mhThread, pTP->mbDisablePriorityBoost);
EAT_ASSERT(result != 0);
EA_UNUSED(result);
}
#endif
#if defined(EA_PLATFORM_MICROSOFT)
int nProcessor = SelectProcessor(pTP, sDefaultProcessor, sDefaultProcessorMask);
if(pTP && pTP->mnProcessor == EA::Thread::kProcessorAny)
SetAffinityMask(pTP->mnAffinityMask);
else
SetProcessor(nProcessor);
#endif
ResumeThread(hThread); // This will unsuspend the thread.
pData->Release(); // Matches AddRef for this function.
return hThread;
}
pData->Release(); // Matches AddRef for this function.
pData->Release(); // Matches AddRef for this Thread class above.
pData->Release(); // Matches AddRef for the thread above.
mThreadData.mpData = NULL;
return (ThreadId)kThreadIdInvalid;
}
EA::Thread::Thread::Status EA::Thread::Thread::WaitForEnd(const ThreadTime& timeoutAbsolute, intptr_t* pThreadReturnValue)
{
// The mThreadData memory is shared between threads and when
// reading it we must be synchronized.
EAReadWriteBarrier();
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
// Todo: Consider that there may be a subtle race condition here if
// the user immediately calls WaitForEnd right after calling Begin.
if(mThreadData.mpData)
{
if(mThreadData.mpData->mhThread) // If it was started...
{
// We must not call WaitForEnd from the thread we are waiting to end. That would result in a deadlock.
EAT_ASSERT(mThreadData.mpData->mhThread != EA::Thread::GetThreadId());
// dwResult normally should be 'WAIT_OBJECT_0', but can also be WAIT_ABANDONED or WAIT_FAILED.
const DWORD dwResult = ::WaitForSingleObject(mThreadData.mpData->mhThread, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
if(dwResult == WAIT_TIMEOUT)
return kStatusRunning;
// Close the handle now so as to minimize handle proliferation.
::CloseHandle(mThreadData.mpData->mhThread);
mThreadData.mpData->mhThread = 0;
mThreadData.mpData->mnStatus = kStatusEnded;
}
if(pThreadReturnValue)
{
EAReadWriteBarrier();
*pThreadReturnValue = mThreadData.mpData->mnReturnValue;
}
return kStatusEnded; // A thread was created, so it must have ended.
}
else
{
// Else the user hasn't started the thread yet, so we wait until the user starts it.
// Ideally, what we really want to do here is wait for some kind of signal.
// Instead for the time being we do a polling loop.
while((!mThreadData.mpData || !mThreadData.mpData->mhThread) && (GetThreadTime() < timeoutAbsolute))
{
ThreadSleep(1);
EAReadWriteBarrier();
EACompilerMemoryBarrier();
}
if(mThreadData.mpData)
return WaitForEnd(timeoutAbsolute);
}
return kStatusNone; // No thread has been started.
}
EA::Thread::Thread::Status EA::Thread::Thread::GetStatus(intptr_t* pThreadReturnValue) const
{
// The mThreadData memory is shared between threads and when
// reading it we must be synchronized.
EAReadWriteBarrier();
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
{
if(mThreadData.mpData->mhThread) // If the thread has been started...
{
DWORD dwExitStatus;
// Note that GetExitCodeThread is a hazard if the user of a thread exits
// with a return value that is equal to the value of STILL_ACTIVE (i.e. 259).
// We can document that users shouldn't do this, or we can change the code
// here to use WaitForSingleObject(hThread, 0) and assume the thread is
// still active if the return value is WAIT_TIMEOUT.
if(::GetExitCodeThread(mThreadData.mpData->mhThread, &dwExitStatus))
{
if(dwExitStatus == STILL_ACTIVE)
return kStatusRunning; // Nothing has changed.
::CloseHandle(mThreadData.mpData->mhThread); // Do this now so as to minimize handle proliferation.
mThreadData.mpData->mhThread = 0;
} // else fall through.
} // else fall through.
if(pThreadReturnValue)
*pThreadReturnValue = mThreadData.mpData->mnReturnValue;
mThreadData.mpData->mnStatus = kStatusEnded;
return kStatusEnded;
}
return kStatusNone;
}
EA::Thread::ThreadId EA::Thread::Thread::GetId() const
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
return (ThreadId)mThreadData.mpData->mhThread;
return kThreadIdInvalid;
}
int EA::Thread::Thread::GetPriority() const
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
if(mThreadData.mpData)
{
const int nPriority = ::GetThreadPriority(mThreadData.mpData->mhThread);
return kThreadPriorityDefault + (nPriority - THREAD_PRIORITY_NORMAL);
}
return kThreadPriorityUnknown;
}
bool EA::Thread::Thread::SetPriority(int nPriority)
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
// For more information on how Windows handle thread priority based on process priority, see
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/scheduling_priorities.asp
EAT_ASSERT(nPriority != kThreadPriorityUnknown);
if(mThreadData.mpData)
{
int nNewPriority = THREAD_PRIORITY_NORMAL + (nPriority - kThreadPriorityDefault);
bool result = ::SetThreadPriority(mThreadData.mpData->mhThread, nNewPriority) != 0;
// Windows process running in NORMAL_PRIORITY_CLASS is picky about the priority passed in.
// So we need to set the priority to the next priority supported
#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
while(!result)
{
if(nNewPriority >= THREAD_PRIORITY_TIME_CRITICAL)
return ::SetThreadPriority(mThreadData.mpData->mhThread, THREAD_PRIORITY_TIME_CRITICAL) != 0;
if(nNewPriority <= THREAD_PRIORITY_IDLE)
return ::SetThreadPriority(mThreadData.mpData->mhThread, THREAD_PRIORITY_IDLE) != 0;
result = ::SetThreadPriority(mThreadData.mpData->mhThread, nNewPriority) != 0;
nNewPriority++;
}
#endif
return result;
}
return false;
}
void EA::Thread::Thread::SetProcessor(int nProcessor)
{
if(mThreadData.mpData)
{
#if defined(EA_PLATFORM_XBOXONE) || defined(EA_PLATFORM_XBSX)
static int nProcessorCount = GetProcessorCount();
if(nProcessor >= nProcessorCount)
nProcessor %= nProcessorCount;
ThreadAffinityMask mask = 0x7F; // default to all 7 available cores.
if (nProcessor >= 0)
mask = ((ThreadAffinityMask)1) << nProcessor;
SetThreadAffinityMask(mThreadData.mpData->mhThread, mask);
#else
static int nProcessorCount = GetProcessorCount();
if(nProcessor < 0)
nProcessor = MAXIMUM_PROCESSORS; // This causes the SetThreadIdealProcessor to reset to 'no ideal processor'.
else
{
if(nProcessor >= nProcessorCount)
nProcessor %= nProcessorCount;
}
// SetThreadIdealProcessor differs from SetThreadAffinityMask in that SetThreadIdealProcessor is not
// a strict assignment, and it allows the OS to move the thread if the ideal processor is busy.
// SetThreadAffinityMask is a more rigid assignment, but it can result in slower performance and
// possibly hangs due to processor contention between threads. For Windows we use SetIdealThreadProcessor
// in the name of safety and likely better overall performance.
SetThreadIdealProcessor(mThreadData.mpData->mhThread, (DWORD)nProcessor);
#endif
}
}
typedef VOID (APIENTRY *PAPCFUNC)(_In_ ULONG_PTR dwParam);
extern "C" WINBASEAPI DWORD WINAPI QueueUserAPC(_In_ PAPCFUNC pfnAPC, _In_ HANDLE hThread, _In_ ULONG_PTR dwData);
void EA::Thread::Thread::Wake()
{
// A mutex lock around mpData is not needed below because
// mpData is never allowed to go from non-NULL to NULL.
struct ThreadWake{ static void WINAPI Empty(ULONG_PTR){} };
if(mThreadData.mpData && mThreadData.mpData->mhThread)
::QueueUserAPC((PAPCFUNC)ThreadWake::Empty, mThreadData.mpData->mhThread, 0);
}
const char* EA::Thread::Thread::GetName() const
{
if(mThreadData.mpData)
return mThreadData.mpData->mName;
return "";
}
void EA::Thread::Thread::SetName(const char* pName)
{
if (mThreadData.mpData && pName)
EA::Thread::Internal::SetThreadName(mThreadData.mpData, pName);
}
#endif // EA_PLATFORM_MICROSOFT
EA_RESTORE_VC_WARNING() // 6312 6322
#if defined(EA_COMPILER_MSVC) && EA_COMPILER_VERSION >= 1900 // VS2015+
EA_RESTORE_VC_WARNING() // 5031 5032
#endif