doubango/tinySAK/winrt/ThreadEmulation.cxx
175b478c
 // 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;
         }
     }
 }