// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. #include "ThreadEmulation.h" #include <assert.h> #include <vector> #include <set> #include <map> #include <mutex> using namespace std; using namespace Platform; using namespace Windows::Foundation; using namespace Windows::System::Threading; namespace ThreadEmulation { // Stored data for CREATE_SUSPENDED and ResumeThread. struct PendingThreadInfo { LPTHREAD_START_ROUTINE lpStartAddress; LPVOID lpParameter; HANDLE completionEvent; int nPriority; }; static map<HANDLE, PendingThreadInfo> pendingThreads; static mutex pendingThreadsLock; // Thread local storage. typedef vector<void*> ThreadLocalData; static __declspec(thread) ThreadLocalData* currentThreadData = nullptr; static set<ThreadLocalData*> allThreadData; static DWORD nextTlsIndex = 0; static vector<DWORD> freeTlsIndices; static mutex tlsAllocationLock; // Converts a Win32 thread priority to WinRT format. static WorkItemPriority GetWorkItemPriority(int nPriority) { if (nPriority < 0) return WorkItemPriority::Low; else if (nPriority > 0) return WorkItemPriority::High; else return WorkItemPriority::Normal; } // Helper shared between CreateThread and ResumeThread. static void StartThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, HANDLE completionEvent, int nPriority) { auto workItemHandler = ref new WorkItemHandler([=](IAsyncAction^) { // Run the user callback. try { lpStartAddress(lpParameter); } catch (...) { } // Clean up any TLS allocations made by this thread. TlsShutdown(); // Signal that the thread has completed. SetEvent(completionEvent); CloseHandle(completionEvent); }, CallbackContext::Any); ThreadPool::RunAsync(workItemHandler, GetWorkItemPriority(nPriority), WorkItemOptions::TimeSliced); } _Use_decl_annotations_ HANDLE WINAPI CreateThread(LPSECURITY_ATTRIBUTES unusedThreadAttributes, SIZE_T unusedStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD unusedThreadId) { // Validate parameters. assert(unusedThreadAttributes == nullptr); assert(unusedStackSize == 0); assert((dwCreationFlags & ~CREATE_SUSPENDED) == 0); assert(unusedThreadId == nullptr); // Create a handle that will be signalled when the thread has completed. HANDLE threadHandle = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS); if (!threadHandle) return nullptr; // Make a copy of the handle for internal use. This is necessary because // the caller is responsible for closing the handle returned by CreateThread, // and they may do that before or after the thread has finished running. HANDLE completionEvent; if (!DuplicateHandle(GetCurrentProcess(), threadHandle, GetCurrentProcess(), &completionEvent, 0, false, DUPLICATE_SAME_ACCESS)) { CloseHandle(threadHandle); return nullptr; } try { if (dwCreationFlags & CREATE_SUSPENDED) { // Store info about a suspended thread. PendingThreadInfo info; info.lpStartAddress = lpStartAddress; info.lpParameter = lpParameter; info.completionEvent = completionEvent; info.nPriority = 0; lock_guard<mutex> lock(pendingThreadsLock); pendingThreads[threadHandle] = info; } else { // Start the thread immediately. StartThread(lpStartAddress, lpParameter, completionEvent, 0); } return threadHandle; } catch (...) { // Clean up if thread creation fails. CloseHandle(threadHandle); CloseHandle(completionEvent); return nullptr; } } _Use_decl_annotations_ DWORD WINAPI ResumeThread(HANDLE hThread) { lock_guard<mutex> lock(pendingThreadsLock); // Look up the requested thread. auto threadInfo = pendingThreads.find(hThread); if (threadInfo == pendingThreads.end()) { // Can only resume threads while they are in CREATE_SUSPENDED state. assert(false); return (DWORD)-1; } // Start the thread. try { PendingThreadInfo& info = threadInfo->second; StartThread(info.lpStartAddress, info.lpParameter, info.completionEvent, info.nPriority); } catch (...) { return (DWORD)-1; } // Remove this thread from the pending list. pendingThreads.erase(threadInfo); return 0; } _Use_decl_annotations_ BOOL WINAPI SetThreadPriority(HANDLE hThread, int nPriority) { lock_guard<mutex> lock(pendingThreadsLock); // Look up the requested thread. auto threadInfo = pendingThreads.find(hThread); if (threadInfo == pendingThreads.end()) { // Can only set priority on threads while they are in CREATE_SUSPENDED state. return false; } // Store the new priority. threadInfo->second.nPriority = nPriority; return true; } _Use_decl_annotations_ VOID WINAPI Sleep(DWORD dwMilliseconds) { static HANDLE singletonEvent = nullptr; HANDLE sleepEvent = singletonEvent; // Demand create the event. if (!sleepEvent) { sleepEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS); if (!sleepEvent) return; HANDLE previousEvent = InterlockedCompareExchangePointerRelease(&singletonEvent, sleepEvent, nullptr); if (previousEvent) { // Back out if multiple threads try to demand create at the same time. CloseHandle(sleepEvent); sleepEvent = previousEvent; } } // Emulate sleep by waiting with timeout on an event that is never signalled. WaitForSingleObjectEx(sleepEvent, dwMilliseconds, false); } DWORD WINAPI TlsAlloc() { lock_guard<mutex> lock(tlsAllocationLock); // Can we reuse a previously freed TLS slot? if (!freeTlsIndices.empty()) { DWORD result = freeTlsIndices.back(); freeTlsIndices.pop_back(); return result; } // Allocate a new TLS slot. return nextTlsIndex++; } _Use_decl_annotations_ BOOL WINAPI TlsFree(DWORD dwTlsIndex) { lock_guard<mutex> lock(tlsAllocationLock); assert(dwTlsIndex < nextTlsIndex); assert(find(freeTlsIndices.begin(), freeTlsIndices.end(), dwTlsIndex) == freeTlsIndices.end()); // Store this slot for reuse by TlsAlloc. try { freeTlsIndices.push_back(dwTlsIndex); } catch (...) { return false; } // Zero the value for all threads that might be using this now freed slot. for each (auto threadData in allThreadData) { if (threadData->size() > dwTlsIndex) { threadData->at(dwTlsIndex) = nullptr; } } return true; } _Use_decl_annotations_ LPVOID WINAPI TlsGetValue(DWORD dwTlsIndex) { ThreadLocalData* threadData = currentThreadData; if (threadData && threadData->size() > dwTlsIndex) { // Return the value of an allocated TLS slot. return threadData->at(dwTlsIndex); } else { // Default value for unallocated slots. return nullptr; } } _Use_decl_annotations_ BOOL WINAPI TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue) { ThreadLocalData* threadData = currentThreadData; if (!threadData) { // First time allocation of TLS data for this thread. try { threadData = new ThreadLocalData(dwTlsIndex + 1, nullptr); lock_guard<mutex> lock(tlsAllocationLock); allThreadData.insert(threadData); currentThreadData = threadData; } catch (...) { if (threadData) delete threadData; return false; } } else if (threadData->size() <= dwTlsIndex) { // This thread already has a TLS data block, but it must be expanded to fit the specified slot. try { lock_guard<mutex> lock(tlsAllocationLock); threadData->resize(dwTlsIndex + 1, nullptr); } catch (...) { return false; } } // Store the new value for this slot. threadData->at(dwTlsIndex) = lpTlsValue; return true; } // Called at thread exit to clean up TLS allocations. void WINAPI TlsShutdown() { ThreadLocalData* threadData = currentThreadData; if (threadData) { { lock_guard<mutex> lock(tlsAllocationLock); allThreadData.erase(threadData); } currentThreadData = nullptr; delete threadData; } } }