1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | //========================================================================= |
6 | |
7 | // |
8 | // ThreadPoolRequest.h |
9 | // |
10 | |
11 | // |
12 | // This file contains definitions of classes needed to mainain per-appdomain |
13 | // thread pool work requests. This is needed as unmanaged and managed work |
14 | // requests are allocted, managed and dispatched in drastically different ways. |
15 | // However, the scheduler need be aware of these differences, and it should |
16 | // simply talk to a common interface for managing work request counts. |
17 | // |
18 | //========================================================================= |
19 | |
20 | #ifndef _THREADPOOL_REQUEST_H |
21 | #define _THREADPOOL_REQUEST_H |
22 | |
23 | #include "util.hpp" |
24 | |
25 | #define TP_QUANTUM 2 |
26 | #define UNUSED_THREADPOOL_INDEX (DWORD)-1 |
27 | |
28 | //-------------------------------------------------------------------------- |
29 | //IPerAppDomainTPCount is an interface for implementing per-appdomain thread |
30 | //pool state. It's implementation should include logic to maintain work-counts, |
31 | //notify thread pool class when work arrives or no work is left. Finally |
32 | //there is logic to dipatch work items correctly in the right domain. |
33 | // |
34 | //Notes: |
35 | //This class was designed to support both the managed and unmanaged uses |
36 | //of thread pool. The unmananged part may directly be used through com |
37 | //interfaces. The differences between the actual management of counts and |
38 | //dispatching of work is quite different between the two. This interface |
39 | //hides these differences to the thread scheduler implemented by the thread pool |
40 | //class. |
41 | // |
42 | |
43 | class IPerAppDomainTPCount{ |
44 | public: |
45 | virtual void ResetState() = 0; |
46 | virtual BOOL IsRequestPending() = 0; |
47 | |
48 | //This functions marks the begining of requests queued for the domain. |
49 | //It needs to notify the scheduler of work-arrival among other things. |
50 | virtual void SetAppDomainRequestsActive() = 0; |
51 | |
52 | //This functions marks the end of requests queued for this domain. |
53 | virtual void ClearAppDomainRequestsActive(BOOL bADU = FALSE) = 0; |
54 | |
55 | //Clears the "active" flag if it was set, and returns whether it was set. |
56 | virtual bool TakeActiveRequest() = 0; |
57 | |
58 | //Takes care of dispatching requests in the right domain. |
59 | virtual void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) = 0; |
60 | virtual void SetAppDomainId(ADID id) = 0; |
61 | virtual void SetTPIndexUnused() = 0; |
62 | virtual BOOL IsTPIndexUnused() = 0; |
63 | virtual void SetTPIndex(TPIndex index) = 0; |
64 | virtual void SetAppDomainUnloading() = 0; |
65 | virtual void ClearAppDomainUnloading() = 0; |
66 | }; |
67 | |
68 | typedef DPTR(IPerAppDomainTPCount) PTR_IPerAppDomainTPCount; |
69 | |
70 | static const LONG ADUnloading = -1; |
71 | |
72 | #ifdef _MSC_VER |
73 | // Disable this warning - we intentionally want __declspec(align()) to insert padding for us |
74 | #pragma warning(disable: 4324) // structure was padded due to __declspec(align()) |
75 | #endif |
76 | |
77 | //-------------------------------------------------------------------------- |
78 | //ManagedPerAppDomainTPCount maintains per-appdomain thread pool state. |
79 | //This class maintains the count of per-appdomain work-items queued by |
80 | //ThreadPool.QueueUserWorkItem. It also dispatches threads in the appdomain |
81 | //correctly by setting up the right exception handling frames etc. |
82 | // |
83 | //Note: The counts are not accurate, and neither do they need to be. The |
84 | //actual work queue is in managed (implemented in threadpool.cs). This class |
85 | //just provides heuristics to the thread pool scheduler, along with |
86 | //synchronization to indicate start/end of requests to the scheduler. |
87 | class ManagedPerAppDomainTPCount : public IPerAppDomainTPCount { |
88 | public: |
89 | |
90 | ManagedPerAppDomainTPCount(TPIndex index) {ResetState(); m_index = index;} |
91 | |
92 | inline void ResetState() |
93 | { |
94 | LIMITED_METHOD_CONTRACT; |
95 | VolatileStore(&m_numRequestsPending, (LONG)0); |
96 | m_id.m_dwId = 0; |
97 | } |
98 | |
99 | inline BOOL IsRequestPending() |
100 | { |
101 | LIMITED_METHOD_CONTRACT; |
102 | |
103 | LONG count = VolatileLoad(&m_numRequestsPending); |
104 | return count != ADUnloading && count > 0; |
105 | } |
106 | |
107 | void SetAppDomainRequestsActive(); |
108 | void ClearAppDomainRequestsActive(BOOL bADU); |
109 | bool TakeActiveRequest(); |
110 | |
111 | inline void SetAppDomainId(ADID id) |
112 | { |
113 | LIMITED_METHOD_CONTRACT; |
114 | //This function should be called during appdomain creation when no managed code |
115 | //has started running yet. That implies, no requests should be pending |
116 | //or dispatched to this structure yet. |
117 | |
118 | _ASSERTE(VolatileLoad(&m_numRequestsPending) != ADUnloading); |
119 | _ASSERTE(m_id.m_dwId == 0); |
120 | |
121 | m_id = id; |
122 | } |
123 | |
124 | inline void SetTPIndex(TPIndex index) |
125 | { |
126 | LIMITED_METHOD_CONTRACT; |
127 | //This function should be called during appdomain creation when no managed code |
128 | //has started running yet. That implies, no requests should be pending |
129 | //or dispatched to this structure yet. |
130 | |
131 | _ASSERTE(VolatileLoad(&m_numRequestsPending) != ADUnloading); |
132 | _ASSERTE(m_id.m_dwId == 0); |
133 | _ASSERTE(m_index.m_dwIndex == UNUSED_THREADPOOL_INDEX); |
134 | |
135 | m_index = index; |
136 | } |
137 | |
138 | inline BOOL IsTPIndexUnused() |
139 | { |
140 | LIMITED_METHOD_CONTRACT; |
141 | if (m_index.m_dwIndex == UNUSED_THREADPOOL_INDEX) |
142 | { |
143 | //This function is called during appdomain creation, and no new appdomains can be |
144 | //added removed at this time. So, make sure that the per-appdomain structures that |
145 | //have been cleared(reclaimed) don't have any pending requests to them. |
146 | |
147 | _ASSERTE(VolatileLoad(&m_numRequestsPending) != ADUnloading); |
148 | _ASSERTE(m_id.m_dwId == 0); |
149 | |
150 | return TRUE; |
151 | } |
152 | |
153 | return FALSE; |
154 | } |
155 | |
156 | inline void SetTPIndexUnused() |
157 | { |
158 | WRAPPER_NO_CONTRACT; |
159 | //This function should be called during appdomain unload when all threads have |
160 | //succesfully exited the appdomain. That implies, no requests should be pending |
161 | //or dispatched to this structure. |
162 | |
163 | _ASSERTE(m_id.m_dwId == 0); |
164 | |
165 | m_index.m_dwIndex = UNUSED_THREADPOOL_INDEX; |
166 | } |
167 | |
168 | inline void SetAppDomainUnloading() |
169 | { |
170 | LIMITED_METHOD_CONTRACT; |
171 | VolatileStore(&m_numRequestsPending, ADUnloading); |
172 | } |
173 | |
174 | inline void ClearAppDomainUnloading(); |
175 | |
176 | inline BOOL IsAppDomainUnloading() |
177 | { |
178 | return VolatileLoad(&m_numRequestsPending) == ADUnloading; |
179 | } |
180 | |
181 | void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); |
182 | |
183 | private: |
184 | ADID m_id; |
185 | TPIndex m_index; |
186 | DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) struct { |
187 | BYTE m_padding1[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; |
188 | // Only use with VolatileLoad+VolatileStore+FastInterlockCompareExchange |
189 | LONG m_numRequestsPending; |
190 | BYTE m_padding2[MAX_CACHE_LINE_SIZE]; |
191 | }; |
192 | }; |
193 | |
194 | //-------------------------------------------------------------------------- |
195 | //UnManagedPerAppDomainTPCount maintains the thread pool state/counts for |
196 | //unmanaged work requests. From thread pool point of view we treat unmanaged |
197 | //requests as a special "appdomain". This helps in scheduling policies, and |
198 | //follow same fairness policies as requests in other appdomains. |
199 | class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { |
200 | public: |
201 | |
202 | UnManagedPerAppDomainTPCount() |
203 | { |
204 | LIMITED_METHOD_CONTRACT; |
205 | ResetState(); |
206 | } |
207 | |
208 | inline void InitResources() |
209 | { |
210 | CONTRACTL |
211 | { |
212 | THROWS; |
213 | MODE_ANY; |
214 | GC_NOTRIGGER; |
215 | INJECT_FAULT(COMPlusThrowOM()); |
216 | } |
217 | CONTRACTL_END; |
218 | |
219 | } |
220 | |
221 | inline void CleanupResources() |
222 | { |
223 | } |
224 | |
225 | inline void ResetState() |
226 | { |
227 | LIMITED_METHOD_CONTRACT; |
228 | m_NumRequests = 0; |
229 | VolatileStore(&m_outstandingThreadRequestCount, (LONG)0); |
230 | } |
231 | |
232 | inline BOOL IsRequestPending() |
233 | { |
234 | LIMITED_METHOD_CONTRACT; |
235 | return VolatileLoad(&m_outstandingThreadRequestCount) != (LONG)0 ? TRUE : FALSE; |
236 | } |
237 | |
238 | void SetAppDomainRequestsActive(); |
239 | |
240 | inline void ClearAppDomainRequestsActive(BOOL bADU) |
241 | { |
242 | LIMITED_METHOD_CONTRACT; |
243 | VolatileStore(&m_outstandingThreadRequestCount, (LONG)0); |
244 | } |
245 | |
246 | bool TakeActiveRequest(); |
247 | |
248 | inline void SetAppDomainId(ADID id) |
249 | { |
250 | } |
251 | |
252 | void QueueUnmanagedWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context); |
253 | PVOID DeQueueUnManagedWorkRequest(bool* lastOne); |
254 | |
255 | void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); |
256 | |
257 | inline void SetTPIndexUnused() |
258 | { |
259 | WRAPPER_NO_CONTRACT; |
260 | _ASSERT(FALSE); |
261 | } |
262 | |
263 | inline BOOL IsTPIndexUnused() |
264 | { |
265 | WRAPPER_NO_CONTRACT; |
266 | _ASSERT(FALSE); |
267 | return FALSE; |
268 | } |
269 | |
270 | inline void SetTPIndex(TPIndex index) |
271 | { |
272 | WRAPPER_NO_CONTRACT; |
273 | _ASSERT(FALSE); |
274 | } |
275 | |
276 | inline void SetAppDomainUnloading() |
277 | { |
278 | WRAPPER_NO_CONTRACT; |
279 | _ASSERT(FALSE); |
280 | } |
281 | |
282 | inline void ClearAppDomainUnloading() |
283 | { |
284 | WRAPPER_NO_CONTRACT; |
285 | _ASSERT(FALSE); |
286 | } |
287 | |
288 | private: |
289 | SpinLock m_lock; |
290 | ULONG m_NumRequests; |
291 | DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) struct { |
292 | BYTE m_padding1[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; |
293 | // Only use with VolatileLoad+VolatileStore+FastInterlockCompareExchange |
294 | LONG m_outstandingThreadRequestCount; |
295 | BYTE m_padding2[MAX_CACHE_LINE_SIZE]; |
296 | }; |
297 | }; |
298 | |
299 | #ifdef _MSC_VER |
300 | #pragma warning(default: 4324) // structure was padded due to __declspec(align()) |
301 | #endif |
302 | |
303 | //-------------------------------------------------------------------------- |
304 | //PerAppDomainTPCountList maintains the collection of per-appdomain thread |
305 | //pool states. Per appdomain counts are added to the list during appdomain |
306 | //creation inside the sdomain lock. The counts are reset during appdomain |
307 | //unload after all the threads have |
308 | //This class maintains the count of per-appdomain work-items queued by |
309 | //ThreadPool.QueueUserWorkItem. It also dispatches threads in the appdomain |
310 | //correctly by setting up the right exception handling frames etc. |
311 | // |
312 | //Note: The counts are not accurate, and neither do they need to be. The |
313 | //actual work queue is in managed (implemented in threadpool.cs). This class |
314 | //just provides heuristics to the thread pool scheduler, along with |
315 | //synchronization to indicate start/end of requests to the scheduler. |
316 | class PerAppDomainTPCountList{ |
317 | public: |
318 | static void InitAppDomainIndexList(); |
319 | static void ResetAppDomainIndex(TPIndex index); |
320 | static void ResetAppDomainTPCounts(TPIndex index); |
321 | static bool AreRequestsPendingInAnyAppDomains(); |
322 | static LONG GetAppDomainIndexForThreadpoolDispatch(); |
323 | static void SetAppDomainId(TPIndex index, ADID id); |
324 | static TPIndex AddNewTPIndex(); |
325 | static void SetAppDomainUnloading(TPIndex index) |
326 | { |
327 | WRAPPER_NO_CONTRACT; |
328 | IPerAppDomainTPCount * pAdCount = dac_cast<PTR_IPerAppDomainTPCount> (s_appDomainIndexList.Get(index.m_dwIndex-1)); |
329 | _ASSERTE(pAdCount); |
330 | pAdCount->SetAppDomainUnloading(); |
331 | } |
332 | |
333 | static void ClearAppDomainUnloading(TPIndex index) |
334 | { |
335 | WRAPPER_NO_CONTRACT; |
336 | IPerAppDomainTPCount * pAdCount = dac_cast<PTR_IPerAppDomainTPCount> (s_appDomainIndexList.Get(index.m_dwIndex-1)); |
337 | _ASSERTE(pAdCount); |
338 | pAdCount->ClearAppDomainUnloading(); |
339 | } |
340 | |
341 | typedef Holder<TPIndex, SetAppDomainUnloading, ClearAppDomainUnloading> AppDomainUnloadingHolder; |
342 | |
343 | inline static IPerAppDomainTPCount* GetPerAppdomainCount(TPIndex index) |
344 | { |
345 | return dac_cast<PTR_IPerAppDomainTPCount>(s_appDomainIndexList.Get(index.m_dwIndex-1)); |
346 | } |
347 | |
348 | inline static UnManagedPerAppDomainTPCount* GetUnmanagedTPCount() |
349 | { |
350 | return &s_unmanagedTPCount; |
351 | } |
352 | |
353 | private: |
354 | static DWORD FindFirstFreeTpEntry(); |
355 | |
356 | static BYTE s_padding[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; |
357 | DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG s_ADHint; |
358 | DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static UnManagedPerAppDomainTPCount s_unmanagedTPCount; |
359 | |
360 | //The list of all per-appdomain work-request counts. |
361 | static ArrayListStatic s_appDomainIndexList; |
362 | }; |
363 | |
364 | #endif //_THREADPOOL_REQUEST_H |
365 | |