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 | ** Header: COMThreadPool.cpp |
9 | ** |
10 | ** Purpose: Native methods on System.ThreadPool |
11 | ** and its inner classes |
12 | ** |
13 | ** |
14 | ===========================================================*/ |
15 | |
16 | /********************************************************************************************************************/ |
17 | #include "common.h" |
18 | #include "comdelegate.h" |
19 | #include "comthreadpool.h" |
20 | #include "threadpoolrequest.h" |
21 | #include "win32threadpool.h" |
22 | #include "class.h" |
23 | #include "object.h" |
24 | #include "field.h" |
25 | #include "excep.h" |
26 | #include "eeconfig.h" |
27 | #include "corhost.h" |
28 | #include "nativeoverlapped.h" |
29 | #include "comsynchronizable.h" |
30 | #include "callhelpers.h" |
31 | #include "appdomain.inl" |
32 | /*****************************************************************************************************/ |
33 | #ifdef _DEBUG |
34 | void LogCall(MethodDesc* pMD, LPCUTF8 api) |
35 | { |
36 | LIMITED_METHOD_CONTRACT; |
37 | |
38 | LPCUTF8 cls = pMD->GetMethodTable()->GetDebugClassName(); |
39 | LPCUTF8 name = pMD->GetName(); |
40 | |
41 | LOG((LF_THREADPOOL,LL_INFO1000,"%s: " , api)); |
42 | LOG((LF_THREADPOOL, LL_INFO1000, |
43 | " calling %s.%s\n" , cls, name)); |
44 | } |
45 | #else |
46 | #define LogCall(pMd,api) |
47 | #endif |
48 | |
49 | VOID |
50 | AcquireDelegateInfo(DelegateInfo *pDelInfo) |
51 | { |
52 | LIMITED_METHOD_CONTRACT; |
53 | } |
54 | |
55 | VOID |
56 | ReleaseDelegateInfo(DelegateInfo *pDelInfo) |
57 | { |
58 | CONTRACTL |
59 | { |
60 | NOTHROW; |
61 | GC_TRIGGERS; |
62 | MODE_ANY; |
63 | } CONTRACTL_END; |
64 | |
65 | // The release methods of holders can be called with preemptive GC enabled. Ensure we're in cooperative mode |
66 | // before calling pDelInfo->Release(), since that requires coop mode. |
67 | GCX_COOP(); |
68 | |
69 | pDelInfo->Release(); |
70 | ThreadpoolMgr::RecycleMemory( pDelInfo, ThreadpoolMgr::MEMTYPE_DelegateInfo ); |
71 | } |
72 | |
73 | //typedef Holder<DelegateInfo *, AcquireDelegateInfo, ReleaseDelegateInfo> DelegateInfoHolder; |
74 | |
75 | typedef Wrapper<DelegateInfo *, AcquireDelegateInfo, ReleaseDelegateInfo> DelegateInfoHolder; |
76 | |
77 | /*****************************************************************************************************/ |
78 | // Caller has to GC protect Objectrefs being passed in |
79 | DelegateInfo *DelegateInfo::MakeDelegateInfo(AppDomain *pAppDomain, |
80 | OBJECTREF *state, |
81 | OBJECTREF *waitEvent, |
82 | OBJECTREF *registeredWaitHandle) |
83 | { |
84 | CONTRACTL |
85 | { |
86 | THROWS; |
87 | GC_TRIGGERS; |
88 | if (state != NULL || waitEvent != NULL || registeredWaitHandle != NULL) |
89 | { |
90 | MODE_COOPERATIVE; |
91 | } |
92 | else |
93 | { |
94 | MODE_ANY; |
95 | } |
96 | PRECONDITION(state == NULL || IsProtectedByGCFrame(state)); |
97 | PRECONDITION(waitEvent == NULL || IsProtectedByGCFrame(waitEvent)); |
98 | PRECONDITION(registeredWaitHandle == NULL || IsProtectedByGCFrame(registeredWaitHandle)); |
99 | PRECONDITION(CheckPointer(pAppDomain)); |
100 | INJECT_FAULT(COMPlusThrowOM()); |
101 | } |
102 | CONTRACTL_END; |
103 | |
104 | DelegateInfoHolder delegateInfo = (DelegateInfo*) ThreadpoolMgr::GetRecycledMemory(ThreadpoolMgr::MEMTYPE_DelegateInfo); |
105 | |
106 | delegateInfo->m_appDomainId = pAppDomain->GetId(); |
107 | |
108 | if (state != NULL) |
109 | delegateInfo->m_stateHandle = pAppDomain->CreateHandle(*state); |
110 | else |
111 | delegateInfo->m_stateHandle = NULL; |
112 | |
113 | if (waitEvent != NULL) |
114 | delegateInfo->m_eventHandle = pAppDomain->CreateHandle(*waitEvent); |
115 | else |
116 | delegateInfo->m_eventHandle = NULL; |
117 | |
118 | if (registeredWaitHandle != NULL) |
119 | delegateInfo->m_registeredWaitHandle = pAppDomain->CreateHandle(*registeredWaitHandle); |
120 | else |
121 | delegateInfo->m_registeredWaitHandle = NULL; |
122 | |
123 | delegateInfo.SuppressRelease(); |
124 | |
125 | return delegateInfo; |
126 | } |
127 | |
128 | /*****************************************************************************************************/ |
129 | FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMaxThreads,DWORD workerThreads, DWORD completionPortThreads) |
130 | { |
131 | FCALL_CONTRACT; |
132 | |
133 | BOOL bRet = FALSE; |
134 | HELPER_METHOD_FRAME_BEGIN_RET_0(); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
135 | |
136 | bRet = ThreadpoolMgr::SetMaxThreads(workerThreads,completionPortThreads); |
137 | HELPER_METHOD_FRAME_END(); |
138 | FC_RETURN_BOOL(bRet); |
139 | } |
140 | FCIMPLEND |
141 | |
142 | /*****************************************************************************************************/ |
143 | FCIMPL2(VOID, ThreadPoolNative::CorGetMaxThreads,DWORD* workerThreads, DWORD* completionPortThreads) |
144 | { |
145 | FCALL_CONTRACT; |
146 | |
147 | ThreadpoolMgr::GetMaxThreads(workerThreads,completionPortThreads); |
148 | return; |
149 | } |
150 | FCIMPLEND |
151 | |
152 | /*****************************************************************************************************/ |
153 | FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMinThreads,DWORD workerThreads, DWORD completionPortThreads) |
154 | { |
155 | FCALL_CONTRACT; |
156 | |
157 | BOOL bRet = FALSE; |
158 | HELPER_METHOD_FRAME_BEGIN_RET_0(); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
159 | |
160 | bRet = ThreadpoolMgr::SetMinThreads(workerThreads,completionPortThreads); |
161 | HELPER_METHOD_FRAME_END(); |
162 | FC_RETURN_BOOL(bRet); |
163 | } |
164 | FCIMPLEND |
165 | |
166 | /*****************************************************************************************************/ |
167 | FCIMPL2(VOID, ThreadPoolNative::CorGetMinThreads,DWORD* workerThreads, DWORD* completionPortThreads) |
168 | { |
169 | FCALL_CONTRACT; |
170 | |
171 | ThreadpoolMgr::GetMinThreads(workerThreads,completionPortThreads); |
172 | return; |
173 | } |
174 | FCIMPLEND |
175 | |
176 | /*****************************************************************************************************/ |
177 | FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWORD* completionPortThreads) |
178 | { |
179 | FCALL_CONTRACT; |
180 | |
181 | ThreadpoolMgr::GetAvailableThreads(workerThreads,completionPortThreads); |
182 | return; |
183 | } |
184 | FCIMPLEND |
185 | |
186 | /*****************************************************************************************************/ |
187 | |
188 | FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) |
189 | { |
190 | FCALL_CONTRACT; |
191 | |
192 | ThreadpoolMgr::NotifyWorkItemCompleted(); |
193 | |
194 | if (ThreadpoolMgr::ShouldAdjustMaxWorkersActive()) |
195 | { |
196 | DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock); |
197 | if (tal.Acquired()) |
198 | { |
199 | HELPER_METHOD_FRAME_BEGIN_0(); |
200 | ThreadpoolMgr::AdjustMaxWorkersActive(); |
201 | HELPER_METHOD_FRAME_END(); |
202 | } |
203 | else |
204 | { |
205 | // the lock is held by someone else, so they will take care of this for us. |
206 | } |
207 | } |
208 | } |
209 | FCIMPLEND |
210 | |
211 | FCIMPL1(VOID, ThreadPoolNative::ReportThreadStatus, CLR_BOOL isWorking) |
212 | { |
213 | FCALL_CONTRACT; |
214 | ThreadpoolMgr::ReportThreadStatus(isWorking); |
215 | } |
216 | FCIMPLEND |
217 | |
218 | FCIMPL0(FC_BOOL_RET, ThreadPoolNative::NotifyRequestComplete) |
219 | { |
220 | FCALL_CONTRACT; |
221 | |
222 | ThreadpoolMgr::NotifyWorkItemCompleted(); |
223 | |
224 | // |
225 | // Now we need to possibly do one or both of: reset the thread's state, and/or perform a |
226 | // "worker thread adjustment" (i.e., invoke Hill Climbing). We try to avoid these at all costs, |
227 | // because they require an expensive helper method frame. So we first try a minimal thread reset, |
228 | // then check if it covered everything that was needed, and we ask ThreadpoolMgr whether |
229 | // we need a thread adjustment, before setting up the frame. |
230 | // |
231 | Thread *pThread = GetThread(); |
232 | _ASSERTE (pThread); |
233 | |
234 | INT32 priority = pThread->ResetManagedThreadObjectInCoopMode(ThreadNative::PRIORITY_NORMAL); |
235 | |
236 | bool needReset = |
237 | priority != ThreadNative::PRIORITY_NORMAL || |
238 | pThread->HasThreadStateNC(Thread::TSNC_SOWorkNeeded) || |
239 | !pThread->IsBackground() || |
240 | pThread->HasCriticalRegion() || |
241 | pThread->HasThreadAffinity(); |
242 | |
243 | bool shouldAdjustWorkers = ThreadpoolMgr::ShouldAdjustMaxWorkersActive(); |
244 | |
245 | // |
246 | // If it's time for a thread adjustment, try to get the lock. This is just a "try," it won't block, |
247 | // so it's ok to do this in cooperative mode. If we can't get the lock, then some other thread is |
248 | // already doing the thread adjustment, so we needn't bother. |
249 | // |
250 | DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock, shouldAdjustWorkers); |
251 | if (!tal.Acquired()) |
252 | shouldAdjustWorkers = false; |
253 | |
254 | if (needReset || shouldAdjustWorkers) |
255 | { |
256 | HELPER_METHOD_FRAME_BEGIN_RET_0(); |
257 | |
258 | if (shouldAdjustWorkers) |
259 | { |
260 | ThreadpoolMgr::AdjustMaxWorkersActive(); |
261 | tal.Release(); |
262 | } |
263 | |
264 | if (needReset) |
265 | pThread->InternalReset(TRUE, TRUE, FALSE); |
266 | |
267 | HELPER_METHOD_FRAME_END(); |
268 | } |
269 | |
270 | // |
271 | // Finally, ask ThreadpoolMgr whether it's ok to keep running work on this thread. Maybe Hill Climbing |
272 | // wants this thread back. |
273 | // |
274 | BOOL result = ThreadpoolMgr::ShouldWorkerKeepRunning() ? TRUE : FALSE; |
275 | FC_RETURN_BOOL(result); |
276 | } |
277 | FCIMPLEND |
278 | |
279 | |
280 | /*****************************************************************************************************/ |
281 | |
282 | void QCALLTYPE ThreadPoolNative::InitializeVMTp(CLR_BOOL* pEnableWorkerTracking) |
283 | { |
284 | QCALL_CONTRACT; |
285 | |
286 | BEGIN_QCALL; |
287 | ThreadpoolMgr::EnsureInitialized(); |
288 | *pEnableWorkerTracking = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking) ? TRUE : FALSE; |
289 | END_QCALL; |
290 | } |
291 | |
292 | /*****************************************************************************************************/ |
293 | |
294 | struct RegisterWaitForSingleObjectCallback_Args |
295 | { |
296 | DelegateInfo *delegateInfo; |
297 | BOOLEAN TimerOrWaitFired; |
298 | }; |
299 | |
300 | static VOID |
301 | RegisterWaitForSingleObjectCallback_Worker(LPVOID ptr) |
302 | { |
303 | CONTRACTL |
304 | { |
305 | GC_TRIGGERS; |
306 | THROWS; |
307 | MODE_COOPERATIVE; |
308 | } |
309 | CONTRACTL_END; |
310 | |
311 | OBJECTREF orState = NULL; |
312 | |
313 | GCPROTECT_BEGIN( orState ); |
314 | |
315 | RegisterWaitForSingleObjectCallback_Args *args = (RegisterWaitForSingleObjectCallback_Args *) ptr; |
316 | orState = ObjectFromHandle(((DelegateInfo*) args->delegateInfo)->m_stateHandle); |
317 | |
318 | #ifdef _DEBUG |
319 | MethodDesc *pMeth = MscorlibBinder::GetMethod(METHOD__TPWAITORTIMER_HELPER__PERFORM_WAITORTIMER_CALLBACK); |
320 | LogCall(pMeth,"RWSOCallback" ); |
321 | #endif |
322 | |
323 | // Caution: the args are not protected, we have to garantee there's no GC from here till |
324 | // the managed call happens. |
325 | PREPARE_NONVIRTUAL_CALLSITE(METHOD__TPWAITORTIMER_HELPER__PERFORM_WAITORTIMER_CALLBACK); |
326 | DECLARE_ARGHOLDER_ARRAY(arg, 2); |
327 | arg[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(orState); |
328 | arg[ARGNUM_1] = DWORD_TO_ARGHOLDER(args->TimerOrWaitFired); |
329 | |
330 | // Call the method... |
331 | CALL_MANAGED_METHOD_NORET(arg); |
332 | |
333 | GCPROTECT_END(); |
334 | } |
335 | |
336 | VOID NTAPI RegisterWaitForSingleObjectCallback(PVOID delegateInfo, BOOLEAN TimerOrWaitFired) |
337 | { |
338 | Thread* pThread = GetThread(); |
339 | if (pThread == NULL) |
340 | { |
341 | ClrFlsSetThreadType(ThreadType_Threadpool_Worker); |
342 | pThread = SetupThreadNoThrow(); |
343 | if (pThread == NULL) { |
344 | return; |
345 | } |
346 | } |
347 | |
348 | CONTRACTL |
349 | { |
350 | MODE_PREEMPTIVE; // Worker thread will be in preempt mode. We switch to coop below. |
351 | THROWS; |
352 | GC_TRIGGERS; |
353 | |
354 | PRECONDITION(CheckPointer(delegateInfo)); |
355 | } |
356 | CONTRACTL_END; |
357 | |
358 | // This thread should not have any locks held at entry point. |
359 | _ASSERTE(pThread->m_dwLockCount == 0); |
360 | |
361 | GCX_COOP(); |
362 | |
363 | RegisterWaitForSingleObjectCallback_Args args = { ((DelegateInfo*) delegateInfo), TimerOrWaitFired }; |
364 | |
365 | ManagedThreadBase::ThreadPool(((DelegateInfo*) delegateInfo)->m_appDomainId, RegisterWaitForSingleObjectCallback_Worker, &args); |
366 | |
367 | // We should have released all locks. |
368 | _ASSERTE(g_fEEShutDown || pThread->m_dwLockCount == 0 || pThread->m_fRudeAborted); |
369 | return; |
370 | } |
371 | |
372 | FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, |
373 | Object* waitObjectUNSAFE, |
374 | Object* stateUNSAFE, |
375 | UINT32 timeout, |
376 | CLR_BOOL executeOnlyOnce, |
377 | Object* registeredWaitObjectUNSAFE) |
378 | { |
379 | FCALL_CONTRACT; |
380 | |
381 | HANDLE handle = 0; |
382 | struct _gc |
383 | { |
384 | WAITHANDLEREF waitObject; |
385 | OBJECTREF state; |
386 | OBJECTREF registeredWaitObject; |
387 | } gc; |
388 | gc.waitObject = (WAITHANDLEREF) ObjectToOBJECTREF(waitObjectUNSAFE); |
389 | gc.state = (OBJECTREF) stateUNSAFE; |
390 | gc.registeredWaitObject = (OBJECTREF) registeredWaitObjectUNSAFE; |
391 | HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
392 | |
393 | if(gc.waitObject == NULL) |
394 | COMPlusThrow(kArgumentNullException); |
395 | |
396 | _ASSERTE(gc.registeredWaitObject != NULL); |
397 | |
398 | ULONG flag = executeOnlyOnce ? WAIT_SINGLE_EXECUTION | WAIT_FREE_CONTEXT : WAIT_FREE_CONTEXT; |
399 | |
400 | HANDLE hWaitHandle = gc.waitObject->GetWaitHandle(); |
401 | _ASSERTE(hWaitHandle); |
402 | |
403 | Thread* pCurThread = GetThread(); |
404 | _ASSERTE( pCurThread); |
405 | |
406 | AppDomain* appDomain = pCurThread->GetDomain(); |
407 | _ASSERTE(appDomain); |
408 | |
409 | DelegateInfoHolder delegateInfo = DelegateInfo::MakeDelegateInfo(appDomain, |
410 | &gc.state, |
411 | (OBJECTREF *)&gc.waitObject, |
412 | &gc.registeredWaitObject); |
413 | |
414 | if (!(ThreadpoolMgr::RegisterWaitForSingleObject(&handle, |
415 | hWaitHandle, |
416 | RegisterWaitForSingleObjectCallback, |
417 | (PVOID) delegateInfo, |
418 | (ULONG) timeout, |
419 | flag))) |
420 | |
421 | { |
422 | _ASSERTE(GetLastError() != ERROR_CALL_NOT_IMPLEMENTED); |
423 | |
424 | COMPlusThrowWin32(); |
425 | } |
426 | |
427 | delegateInfo.SuppressRelease(); |
428 | HELPER_METHOD_FRAME_END(); |
429 | return (LPVOID) handle; |
430 | } |
431 | FCIMPLEND |
432 | |
433 | |
434 | VOID QueueUserWorkItemManagedCallback(PVOID pArg) |
435 | { |
436 | CONTRACTL |
437 | { |
438 | GC_TRIGGERS; |
439 | THROWS; |
440 | MODE_COOPERATIVE; |
441 | } CONTRACTL_END; |
442 | |
443 | _ASSERTE(NULL != pArg); |
444 | |
445 | // This thread should not have any locks held at entry point. |
446 | _ASSERTE(GetThread()->m_dwLockCount == 0); |
447 | |
448 | bool* wasNotRecalled = (bool*)pArg; |
449 | |
450 | MethodDescCallSite dispatch(METHOD__TP_WAIT_CALLBACK__PERFORM_WAIT_CALLBACK); |
451 | *wasNotRecalled = dispatch.Call_RetBool(NULL); |
452 | } |
453 | |
454 | |
455 | BOOL QCALLTYPE ThreadPoolNative::RequestWorkerThread() |
456 | { |
457 | QCALL_CONTRACT; |
458 | |
459 | BOOL res = FALSE; |
460 | |
461 | BEGIN_QCALL; |
462 | |
463 | ThreadpoolMgr::SetAppDomainRequestsActive(); |
464 | |
465 | res = ThreadpoolMgr::QueueUserWorkItem(NULL, |
466 | NULL, |
467 | 0, |
468 | FALSE); |
469 | if (!res) |
470 | { |
471 | if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) |
472 | COMPlusThrow(kNotSupportedException); |
473 | else |
474 | COMPlusThrowWin32(); |
475 | } |
476 | |
477 | END_QCALL; |
478 | return res; |
479 | } |
480 | |
481 | |
482 | /********************************************************************************************************************/ |
483 | |
484 | FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorUnregisterWait, LPVOID WaitHandle, Object* objectToNotify) |
485 | { |
486 | FCALL_CONTRACT; |
487 | |
488 | BOOL retVal = false; |
489 | SAFEHANDLEREF refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(objectToNotify); |
490 | HELPER_METHOD_FRAME_BEGIN_RET_1(refSH); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
491 | |
492 | HANDLE hWait = (HANDLE) WaitHandle; |
493 | HANDLE hObjectToNotify = NULL; |
494 | |
495 | ThreadpoolMgr::WaitInfo *pWaitInfo = (ThreadpoolMgr::WaitInfo *)hWait; |
496 | _ASSERTE(pWaitInfo != NULL); |
497 | |
498 | ThreadpoolMgr::WaitInfoHolder wiHolder(NULL); |
499 | |
500 | if (refSH != NULL) |
501 | { |
502 | // Create a GCHandle in the WaitInfo, so that it can hold on to the safe handle |
503 | pWaitInfo->ExternalEventSafeHandle = GetAppDomain()->CreateHandle(NULL); |
504 | pWaitInfo->handleOwningAD = GetAppDomain()->GetId(); |
505 | |
506 | // Holder will now release objecthandle in face of exceptions |
507 | wiHolder.Assign(pWaitInfo); |
508 | |
509 | // Store SafeHandle in object handle. Holder will now release both safehandle and objecthandle |
510 | // in case of exceptions |
511 | StoreObjectInHandle(pWaitInfo->ExternalEventSafeHandle, refSH); |
512 | |
513 | // Acquire safe handle to examine its handle, then release. |
514 | SafeHandleHolder shHolder(&refSH); |
515 | |
516 | if (refSH->GetHandle() == INVALID_HANDLE_VALUE) |
517 | { |
518 | hObjectToNotify = INVALID_HANDLE_VALUE; |
519 | // We do not need the ObjectHandle, refcount on the safehandle etc |
520 | wiHolder.Release(); |
521 | _ASSERTE(pWaitInfo->ExternalEventSafeHandle == NULL); |
522 | } |
523 | } |
524 | |
525 | _ASSERTE(hObjectToNotify == NULL || hObjectToNotify == INVALID_HANDLE_VALUE); |
526 | |
527 | // When hObjectToNotify is NULL ExternalEventSafeHandle contains event to notify (if it is non NULL). |
528 | // When hObjectToNotify is INVALID_HANDLE_VALUE, UnregisterWaitEx blocks until dispose is complete. |
529 | retVal = ThreadpoolMgr::UnregisterWaitEx(hWait, hObjectToNotify); |
530 | |
531 | if (retVal) |
532 | wiHolder.SuppressRelease(); |
533 | |
534 | HELPER_METHOD_FRAME_END(); |
535 | FC_RETURN_BOOL(retVal); |
536 | } |
537 | FCIMPLEND |
538 | |
539 | /********************************************************************************************************************/ |
540 | FCIMPL1(void, ThreadPoolNative::CorWaitHandleCleanupNative, LPVOID WaitHandle) |
541 | { |
542 | FCALL_CONTRACT; |
543 | |
544 | HELPER_METHOD_FRAME_BEGIN_0(); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
545 | |
546 | HANDLE hWait = (HANDLE)WaitHandle; |
547 | ThreadpoolMgr::WaitHandleCleanup(hWait); |
548 | |
549 | HELPER_METHOD_FRAME_END(); |
550 | } |
551 | FCIMPLEND |
552 | |
553 | /********************************************************************************************************************/ |
554 | |
555 | /********************************************************************************************************************/ |
556 | |
557 | struct BindIoCompletion_Args |
558 | { |
559 | DWORD ErrorCode; |
560 | DWORD numBytesTransferred; |
561 | LPOVERLAPPED lpOverlapped; |
562 | }; |
563 | |
564 | void SetAsyncResultProperties( |
565 | OVERLAPPEDDATAREF overlapped, |
566 | DWORD dwErrorCode, |
567 | DWORD dwNumBytes |
568 | ) |
569 | { |
570 | STATIC_CONTRACT_THROWS; |
571 | STATIC_CONTRACT_GC_NOTRIGGER; |
572 | STATIC_CONTRACT_MODE_ANY; |
573 | STATIC_CONTRACT_SO_TOLERANT; |
574 | |
575 | } |
576 | |
577 | VOID BindIoCompletionCallBack_Worker(LPVOID args) |
578 | { |
579 | STATIC_CONTRACT_THROWS; |
580 | STATIC_CONTRACT_GC_TRIGGERS; |
581 | STATIC_CONTRACT_MODE_ANY; |
582 | STATIC_CONTRACT_SO_INTOLERANT; |
583 | |
584 | DWORD ErrorCode = ((BindIoCompletion_Args *)args)->ErrorCode; |
585 | DWORD numBytesTransferred = ((BindIoCompletion_Args *)args)->numBytesTransferred; |
586 | LPOVERLAPPED lpOverlapped = ((BindIoCompletion_Args *)args)->lpOverlapped; |
587 | |
588 | OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); |
589 | |
590 | GCPROTECT_BEGIN(overlapped); |
591 | // we set processed to TRUE, now it's our responsibility to guarantee proper cleanup |
592 | |
593 | #ifdef _DEBUG |
594 | MethodDesc *pMeth = MscorlibBinder::GetMethod(METHOD__IOCB_HELPER__PERFORM_IOCOMPLETION_CALLBACK); |
595 | LogCall(pMeth,"IOCallback" ); |
596 | #endif |
597 | |
598 | if (overlapped->m_callback != NULL) |
599 | { |
600 | // Caution: the args are not protected, we have to garantee there's no GC from here till |
601 | PREPARE_NONVIRTUAL_CALLSITE(METHOD__IOCB_HELPER__PERFORM_IOCOMPLETION_CALLBACK); |
602 | DECLARE_ARGHOLDER_ARRAY(arg, 3); |
603 | arg[ARGNUM_0] = DWORD_TO_ARGHOLDER(ErrorCode); |
604 | arg[ARGNUM_1] = DWORD_TO_ARGHOLDER(numBytesTransferred); |
605 | arg[ARGNUM_2] = PTR_TO_ARGHOLDER(lpOverlapped); |
606 | |
607 | // Call the method... |
608 | CALL_MANAGED_METHOD_NORET(arg); |
609 | } |
610 | else |
611 | { |
612 | // no user delegate to callback |
613 | _ASSERTE((overlapped->m_callback == NULL) || !"This is benign, but should be optimized" ); |
614 | |
615 | |
616 | SetAsyncResultProperties(overlapped, ErrorCode, numBytesTransferred); |
617 | } |
618 | GCPROTECT_END(); |
619 | } |
620 | |
621 | void __stdcall BindIoCompletionCallbackStubEx(DWORD ErrorCode, |
622 | DWORD numBytesTransferred, |
623 | LPOVERLAPPED lpOverlapped, |
624 | BOOL setStack) |
625 | { |
626 | Thread* pThread = GetThread(); |
627 | if (pThread == NULL) |
628 | { |
629 | // TODO: how do we notify user of OOM here? |
630 | ClrFlsSetThreadType(ThreadType_Threadpool_Worker); |
631 | pThread = SetupThreadNoThrow(); |
632 | if (pThread == NULL) { |
633 | return; |
634 | } |
635 | } |
636 | |
637 | CONTRACTL |
638 | { |
639 | THROWS; |
640 | MODE_ANY; |
641 | GC_TRIGGERS; |
642 | SO_INTOLERANT; |
643 | } |
644 | CONTRACTL_END; |
645 | |
646 | // This thread should not have any locks held at entry point. |
647 | _ASSERTE(pThread->m_dwLockCount == 0); |
648 | |
649 | LOG((LF_INTEROP, LL_INFO10000, "In IO_CallBackStub thread 0x%x retCode 0x%x, overlap 0x%x\n" , pThread, ErrorCode, lpOverlapped)); |
650 | |
651 | GCX_COOP(); |
652 | |
653 | BindIoCompletion_Args args = {ErrorCode, numBytesTransferred, lpOverlapped}; |
654 | ManagedThreadBase::ThreadPool((ADID)DefaultADID, BindIoCompletionCallBack_Worker, &args); |
655 | |
656 | LOG((LF_INTEROP, LL_INFO10000, "Leaving IO_CallBackStub thread 0x%x retCode 0x%x, overlap 0x%x\n" , pThread, ErrorCode, lpOverlapped)); |
657 | // We should have released all locks. |
658 | _ASSERTE(g_fEEShutDown || pThread->m_dwLockCount == 0 || pThread->m_fRudeAborted); |
659 | return; |
660 | } |
661 | |
662 | void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, |
663 | DWORD numBytesTransferred, |
664 | LPOVERLAPPED lpOverlapped) |
665 | { |
666 | WRAPPER_NO_CONTRACT; |
667 | BindIoCompletionCallbackStubEx(ErrorCode, numBytesTransferred, lpOverlapped, TRUE); |
668 | |
669 | #ifndef FEATURE_PAL |
670 | extern Volatile<ULONG> g_fCompletionPortDrainNeeded; |
671 | |
672 | Thread *pThread = GetThread(); |
673 | if (g_fCompletionPortDrainNeeded && pThread) |
674 | { |
675 | // We have started draining completion port. |
676 | // The next job picked up by this thread is going to be after our special marker. |
677 | if (!pThread->IsCompletionPortDrained()) |
678 | { |
679 | pThread->MarkCompletionPortDrained(); |
680 | } |
681 | } |
682 | #endif // !FEATURE_PAL |
683 | } |
684 | |
685 | FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorBindIoCompletionCallback, HANDLE fileHandle) |
686 | { |
687 | FCALL_CONTRACT; |
688 | |
689 | BOOL retVal = FALSE; |
690 | |
691 | HELPER_METHOD_FRAME_BEGIN_RET_0(); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
692 | |
693 | HANDLE hFile = (HANDLE) fileHandle; |
694 | DWORD errCode = 0; |
695 | |
696 | retVal = ThreadpoolMgr::BindIoCompletionCallback(hFile, |
697 | BindIoCompletionCallbackStub, |
698 | 0, // reserved, must be 0 |
699 | OUT errCode); |
700 | if (!retVal) |
701 | { |
702 | if (errCode == ERROR_CALL_NOT_IMPLEMENTED) |
703 | COMPlusThrow(kPlatformNotSupportedException); |
704 | else |
705 | { |
706 | SetLastError(errCode); |
707 | COMPlusThrowWin32(); |
708 | } |
709 | } |
710 | |
711 | HELPER_METHOD_FRAME_END(); |
712 | FC_RETURN_BOOL(retVal); |
713 | } |
714 | FCIMPLEND |
715 | |
716 | FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped) |
717 | { |
718 | FCALL_CONTRACT; |
719 | |
720 | OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); |
721 | |
722 | BOOL res = FALSE; |
723 | |
724 | HELPER_METHOD_FRAME_BEGIN_RET_1(overlapped); // Eventually calls BEGIN_SO_INTOLERANT_CODE_NOTHROW |
725 | |
726 | // OS doesn't signal handle, so do it here |
727 | lpOverlapped->Internal = 0; |
728 | |
729 | if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context, ThreadPoolIOEnqueue)) |
730 | FireEtwThreadPoolIOEnqueue(lpOverlapped, OBJECTREFToObject(overlapped), false, GetClrInstanceId()); |
731 | |
732 | res = ThreadpoolMgr::PostQueuedCompletionStatus(lpOverlapped, |
733 | BindIoCompletionCallbackStub); |
734 | |
735 | if (!res) |
736 | { |
737 | if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) |
738 | COMPlusThrow(kPlatformNotSupportedException); |
739 | else |
740 | COMPlusThrowWin32(); |
741 | } |
742 | |
743 | HELPER_METHOD_FRAME_END(); |
744 | FC_RETURN_BOOL(res); |
745 | } |
746 | FCIMPLEND |
747 | |
748 | |
749 | /********************************************************************************************************************/ |
750 | |
751 | |
752 | /******************************************************************************************/ |
753 | /* */ |
754 | /* Timer Functions */ |
755 | /* */ |
756 | /******************************************************************************************/ |
757 | |
758 | void AppDomainTimerCallback_Worker(LPVOID ptr) |
759 | { |
760 | CONTRACTL |
761 | { |
762 | GC_TRIGGERS; |
763 | THROWS; |
764 | MODE_COOPERATIVE; |
765 | } |
766 | CONTRACTL_END; |
767 | |
768 | #ifdef _DEBUG |
769 | MethodDesc *pMeth = MscorlibBinder::GetMethod(METHOD__TIMER_QUEUE__APPDOMAIN_TIMER_CALLBACK); |
770 | LogCall(pMeth,"AppDomainTimerCallback" ); |
771 | #endif |
772 | |
773 | ThreadpoolMgr::TimerInfoContext* pTimerInfoContext = (ThreadpoolMgr::TimerInfoContext*)ptr; |
774 | ARG_SLOT args[] = { PtrToArgSlot(pTimerInfoContext->TimerId) }; |
775 | MethodDescCallSite(METHOD__TIMER_QUEUE__APPDOMAIN_TIMER_CALLBACK).Call(args); |
776 | } |
777 | |
778 | VOID WINAPI AppDomainTimerCallback(PVOID callbackState, BOOLEAN timerOrWaitFired) |
779 | { |
780 | Thread* pThread = GetThread(); |
781 | if (pThread == NULL) |
782 | { |
783 | // TODO: how do we notify user of OOM here? |
784 | ClrFlsSetThreadType(ThreadType_Threadpool_Worker); |
785 | pThread = SetupThreadNoThrow(); |
786 | if (pThread == NULL) { |
787 | return; |
788 | } |
789 | } |
790 | |
791 | CONTRACTL |
792 | { |
793 | THROWS; |
794 | MODE_ANY; |
795 | GC_TRIGGERS; |
796 | SO_INTOLERANT; |
797 | } |
798 | CONTRACTL_END; |
799 | |
800 | // This thread should not have any locks held at entry point. |
801 | _ASSERTE(pThread->m_dwLockCount == 0); |
802 | |
803 | GCX_COOP(); |
804 | |
805 | ThreadpoolMgr::TimerInfoContext* pTimerInfoContext = (ThreadpoolMgr::TimerInfoContext*)callbackState; |
806 | ManagedThreadBase::ThreadPool(pTimerInfoContext->AppDomainId, AppDomainTimerCallback_Worker, pTimerInfoContext); |
807 | |
808 | // We should have released all locks. |
809 | _ASSERTE(g_fEEShutDown || pThread->m_dwLockCount == 0 || pThread->m_fRudeAborted); |
810 | } |
811 | |
812 | HANDLE QCALLTYPE AppDomainTimerNative::CreateAppDomainTimer(INT32 dueTime, INT32 timerId) |
813 | { |
814 | QCALL_CONTRACT; |
815 | |
816 | HANDLE hTimer = NULL; |
817 | BEGIN_QCALL; |
818 | |
819 | _ASSERTE(dueTime >= 0); |
820 | _ASSERTE(timerId >= 0); |
821 | |
822 | AppDomain* pAppDomain = GetThread()->GetDomain(); |
823 | ADID adid = pAppDomain->GetId(); |
824 | |
825 | ThreadpoolMgr::TimerInfoContext* timerContext = new ThreadpoolMgr::TimerInfoContext(); |
826 | timerContext->AppDomainId = adid; |
827 | timerContext->TimerId = timerId; |
828 | NewHolder<ThreadpoolMgr::TimerInfoContext> timerContextHolder(timerContext); |
829 | |
830 | BOOL res = ThreadpoolMgr::CreateTimerQueueTimer( |
831 | &hTimer, |
832 | (WAITORTIMERCALLBACK)AppDomainTimerCallback, |
833 | (PVOID)timerContext, |
834 | (ULONG)dueTime, |
835 | (ULONG)-1 /* this timer doesn't repeat */, |
836 | 0 /* no flags */); |
837 | |
838 | if (!res) |
839 | { |
840 | if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) |
841 | COMPlusThrow(kNotSupportedException); |
842 | else |
843 | COMPlusThrowWin32(); |
844 | } |
845 | else |
846 | { |
847 | timerContextHolder.SuppressRelease(); |
848 | } |
849 | |
850 | END_QCALL; |
851 | return hTimer; |
852 | } |
853 | |
854 | BOOL QCALLTYPE AppDomainTimerNative::DeleteAppDomainTimer(HANDLE hTimer) |
855 | { |
856 | QCALL_CONTRACT; |
857 | |
858 | BOOL res = FALSE; |
859 | BEGIN_QCALL; |
860 | |
861 | _ASSERTE(hTimer != NULL && hTimer != INVALID_HANDLE_VALUE); |
862 | res = ThreadpoolMgr::DeleteTimerQueueTimer(hTimer, NULL); |
863 | |
864 | if (!res) |
865 | { |
866 | DWORD errorCode = ::GetLastError(); |
867 | if (errorCode != ERROR_IO_PENDING) |
868 | COMPlusThrowWin32(HRESULT_FROM_WIN32(errorCode)); |
869 | } |
870 | |
871 | END_QCALL; |
872 | return res; |
873 | } |
874 | |
875 | |
876 | BOOL QCALLTYPE AppDomainTimerNative::ChangeAppDomainTimer(HANDLE hTimer, INT32 dueTime) |
877 | { |
878 | QCALL_CONTRACT; |
879 | |
880 | BOOL res = FALSE; |
881 | BEGIN_QCALL; |
882 | |
883 | _ASSERTE(hTimer != NULL && hTimer != INVALID_HANDLE_VALUE); |
884 | _ASSERTE(dueTime >= 0); |
885 | |
886 | res = ThreadpoolMgr::ChangeTimerQueueTimer( |
887 | hTimer, |
888 | (ULONG)dueTime, |
889 | (ULONG)-1 /* this timer doesn't repeat */); |
890 | |
891 | if (!res) |
892 | COMPlusThrowWin32(); |
893 | |
894 | END_QCALL; |
895 | return res; |
896 | } |
897 | |