1 // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
2 // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
3 // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
4 // PARTICULAR PURPOSE.
5 //
6 // Copyright (c) Microsoft Corporation. All rights reserved.
7 
8 #include "../include/ThreadEmulation.h"
9 
10 #include <assert.h>
11 #include <vector>
12 #include <set>
13 #include <map>
14 #include <mutex>
15 
16 using namespace std;
17 using namespace Platform;
18 using namespace Windows::Foundation;
19 using namespace Windows::System::Threading;
20 
21 //namespace ThreadEmulation
22 //{
23     // Stored data for CREATE_SUSPENDED and ResumeThread.
24     struct PendingThreadInfo
25     {
26         LPTHREAD_START_ROUTINE lpStartAddress;
27         LPVOID lpParameter;
28         HANDLE completionEvent;
29         int nPriority;
30     };
31 
32     static map<HANDLE, PendingThreadInfo> pendingThreads;
33     static mutex pendingThreadsLock;
34 
35 
36     // Thread local storage.
37     typedef vector<void*> ThreadLocalData;
38 
39     static __declspec(thread) ThreadLocalData* currentThreadData = nullptr;
40     static set<ThreadLocalData*> allThreadData;
41     static DWORD nextTlsIndex = 0;
42     static vector<DWORD> freeTlsIndices;
43     static mutex tlsAllocationLock;
44 
45 
46     // Converts a Win32 thread priority to WinRT format.
GetWorkItemPriority(int nPriority)47     static WorkItemPriority GetWorkItemPriority(int nPriority)
48     {
49         if (nPriority < 0)
50             return WorkItemPriority::Low;
51         else if (nPriority > 0)
52             return WorkItemPriority::High;
53         else
54             return WorkItemPriority::Normal;
55     }
56 
57 
58     // Helper shared between CreateThread and ResumeThread.
StartThread(LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,HANDLE completionEvent,int nPriority)59     static void StartThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, HANDLE completionEvent, int nPriority)
60     {
61         auto workItemHandler = ref new WorkItemHandler([=](IAsyncAction^)
62         {
63             // Run the user callback.
64             try
65             {
66                 lpStartAddress(lpParameter);
67             }
68             catch (...) { }
69 
70             // Clean up any TLS allocations made by this thread.
71             TlsShutdown();
72 
73             // Signal that the thread has completed.
74             SetEvent(completionEvent);
75             CloseHandle(completionEvent);
76 
77         }, CallbackContext::Any);
78 
79         ThreadPool::RunAsync(workItemHandler, GetWorkItemPriority(nPriority), WorkItemOptions::TimeSliced);
80     }
81 
82 
CreateThreadRT(LPSECURITY_ATTRIBUTES unusedThreadAttributes,SIZE_T unusedStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD unusedThreadId)83     _Use_decl_annotations_ HANDLE WINAPI CreateThreadRT(LPSECURITY_ATTRIBUTES unusedThreadAttributes, SIZE_T unusedStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD unusedThreadId)
84     {
85         // Validate parameters.
86         assert(unusedThreadAttributes == nullptr);
87         assert(unusedStackSize == 0);
88         assert((dwCreationFlags & ~CREATE_SUSPENDED) == 0);
89         assert(unusedThreadId == nullptr);
90 
91         // Create a handle that will be signalled when the thread has completed.
92         HANDLE threadHandle = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
93 
94         if (!threadHandle)
95             return nullptr;
96 
97         // Make a copy of the handle for internal use. This is necessary because
98         // the caller is responsible for closing the handle returned by CreateThread,
99         // and they may do that before or after the thread has finished running.
100         HANDLE completionEvent;
101 
102         if (!DuplicateHandle(GetCurrentProcess(), threadHandle, GetCurrentProcess(), &completionEvent, 0, false, DUPLICATE_SAME_ACCESS))
103         {
104             CloseHandle(threadHandle);
105             return nullptr;
106         }
107 
108         try
109         {
110             if (dwCreationFlags & CREATE_SUSPENDED)
111             {
112                 // Store info about a suspended thread.
113                 PendingThreadInfo info;
114 
115                 info.lpStartAddress = lpStartAddress;
116                 info.lpParameter = lpParameter;
117                 info.completionEvent = completionEvent;
118                 info.nPriority = 0;
119 
120                 lock_guard<mutex> lock(pendingThreadsLock);
121 
122                 pendingThreads[threadHandle] = info;
123             }
124             else
125             {
126                 // Start the thread immediately.
127                 StartThread(lpStartAddress, lpParameter, completionEvent, 0);
128             }
129 
130             return threadHandle;
131         }
132         catch (...)
133         {
134             // Clean up if thread creation fails.
135             CloseHandle(threadHandle);
136             CloseHandle(completionEvent);
137 
138             return nullptr;
139         }
140     }
141 
142 
ResumeThreadRT(HANDLE hThread)143     _Use_decl_annotations_ DWORD WINAPI ResumeThreadRT(HANDLE hThread)
144     {
145         lock_guard<mutex> lock(pendingThreadsLock);
146 
147         // Look up the requested thread.
148         auto threadInfo = pendingThreads.find(hThread);
149 
150         if (threadInfo == pendingThreads.end())
151         {
152             // Can only resume threads while they are in CREATE_SUSPENDED state.
153             assert(false);
154             return (DWORD)-1;
155         }
156 
157         // Start the thread.
158         try
159         {
160             PendingThreadInfo& info = threadInfo->second;
161 
162             StartThread(info.lpStartAddress, info.lpParameter, info.completionEvent, info.nPriority);
163         }
164         catch (...)
165         {
166             return (DWORD)-1;
167         }
168 
169         // Remove this thread from the pending list.
170         pendingThreads.erase(threadInfo);
171 
172         return 0;
173     }
174 
175 
SetThreadPriorityRT(HANDLE hThread,int nPriority)176     _Use_decl_annotations_ BOOL WINAPI SetThreadPriorityRT(HANDLE hThread, int nPriority)
177     {
178         lock_guard<mutex> lock(pendingThreadsLock);
179 
180         // Look up the requested thread.
181         auto threadInfo = pendingThreads.find(hThread);
182 
183         if (threadInfo == pendingThreads.end())
184         {
185             // Can only set priority on threads while they are in CREATE_SUSPENDED state.
186             assert(false);
187             return false;
188         }
189 
190         // Store the new priority.
191         threadInfo->second.nPriority = nPriority;
192 
193         return true;
194     }
195 
196 
SleepRT(DWORD dwMilliseconds)197     _Use_decl_annotations_ VOID WINAPI SleepRT(DWORD dwMilliseconds)
198     {
199         static HANDLE singletonEvent = nullptr;
200 
201         HANDLE sleepEvent = singletonEvent;
202 
203         // Demand create the event.
204         if (!sleepEvent)
205         {
206             sleepEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
207 
208             if (!sleepEvent)
209                 return;
210 
211             HANDLE previousEvent = InterlockedCompareExchangePointerRelease(&singletonEvent, sleepEvent, nullptr);
212 
213             if (previousEvent)
214             {
215                 // Back out if multiple threads try to demand create at the same time.
216                 CloseHandle(sleepEvent);
217                 sleepEvent = previousEvent;
218             }
219         }
220 
221         // Emulate sleep by waiting with timeout on an event that is never signalled.
222         WaitForSingleObjectEx(sleepEvent, dwMilliseconds, false);
223     }
224 
225 
TlsAllocRT()226     DWORD WINAPI TlsAllocRT()
227     {
228         lock_guard<mutex> lock(tlsAllocationLock);
229 
230         // Can we reuse a previously freed TLS slot?
231         if (!freeTlsIndices.empty())
232         {
233             DWORD result = freeTlsIndices.back();
234             freeTlsIndices.pop_back();
235             return result;
236         }
237 
238         // Allocate a new TLS slot.
239         return nextTlsIndex++;
240     }
241 
242 
TlsFreeRT(DWORD dwTlsIndex)243     _Use_decl_annotations_ BOOL WINAPI TlsFreeRT(DWORD dwTlsIndex)
244     {
245         lock_guard<mutex> lock(tlsAllocationLock);
246 
247         assert(dwTlsIndex < nextTlsIndex);
248         assert(find(freeTlsIndices.begin(), freeTlsIndices.end(), dwTlsIndex) == freeTlsIndices.end());
249 
250         // Store this slot for reuse by TlsAlloc.
251         try
252         {
253             freeTlsIndices.push_back(dwTlsIndex);
254         }
255         catch (...)
256         {
257             return false;
258         }
259 
260         // Zero the value for all threads that might be using this now freed slot.
261         for each (auto threadData in allThreadData)
262         {
263             if (threadData->size() > dwTlsIndex)
264             {
265                 threadData->at(dwTlsIndex) = nullptr;
266             }
267         }
268 
269         return true;
270     }
271 
272 
TlsGetValueRT(DWORD dwTlsIndex)273     _Use_decl_annotations_ LPVOID WINAPI TlsGetValueRT(DWORD dwTlsIndex)
274     {
275         ThreadLocalData* threadData = currentThreadData;
276 
277         if (threadData && threadData->size() > dwTlsIndex)
278         {
279             // Return the value of an allocated TLS slot.
280             return threadData->at(dwTlsIndex);
281         }
282         else
283         {
284             // Default value for unallocated slots.
285             return nullptr;
286         }
287     }
288 
289 
TlsSetValueRT(DWORD dwTlsIndex,LPVOID lpTlsValue)290     _Use_decl_annotations_ BOOL WINAPI TlsSetValueRT(DWORD dwTlsIndex, LPVOID lpTlsValue)
291     {
292         ThreadLocalData* threadData = currentThreadData;
293 
294         if (!threadData)
295         {
296             // First time allocation of TLS data for this thread.
297             try
298             {
299                 threadData = new ThreadLocalData(dwTlsIndex + 1, nullptr);
300 
301                 lock_guard<mutex> lock(tlsAllocationLock);
302 
303                 allThreadData.insert(threadData);
304 
305                 currentThreadData = threadData;
306             }
307             catch (...)
308             {
309                 if (threadData)
310                     delete threadData;
311 
312                 return false;
313             }
314         }
315         else if (threadData->size() <= dwTlsIndex)
316         {
317             // This thread already has a TLS data block, but it must be expanded to fit the specified slot.
318             try
319             {
320                 lock_guard<mutex> lock(tlsAllocationLock);
321 
322                 threadData->resize(dwTlsIndex + 1, nullptr);
323             }
324             catch (...)
325             {
326                 return false;
327             }
328         }
329 
330         // Store the new value for this slot.
331         threadData->at(dwTlsIndex) = lpTlsValue;
332 
333         return true;
334     }
335 
336 
337     // Called at thread exit to clean up TLS allocations.
TlsShutdown()338     void WINAPI TlsShutdown()
339     {
340         ThreadLocalData* threadData = currentThreadData;
341 
342         if (threadData)
343         {
344             {
345                 lock_guard<mutex> lock(tlsAllocationLock);
346 
347                 allThreadData.erase(threadData);
348             }
349 
350             currentThreadData = nullptr;
351 
352             delete threadData;
353         }
354     }
355 //}
356