| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 |
- ///////////////////////////////////////////////////////////////////////////////
- // 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
- // 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)
- #define WARNING_INIT_SEG_PUSHED
- #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)
- 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)
- // 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)
- 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)
- 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)
- // 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)
- 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)
- 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)
- 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);
- }
- #ifdef WARNING_INIT_SEG_PUSHED
- #undef WARNING_INIT_SEG_PUSHED
- EA_RESTORE_VC_WARNING() // 4073 4075
- #endif
- #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
- // #pragma warning(pop): likely mismatch, popping warning state pushed in different file / detected #pragma warning(push) with no corresponding
- #endif
|