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
34void 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
49VOID
50AcquireDelegateInfo(DelegateInfo *pDelInfo)
51{
52 LIMITED_METHOD_CONTRACT;
53}
54
55VOID
56ReleaseDelegateInfo(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
75typedef Wrapper<DelegateInfo *, AcquireDelegateInfo, ReleaseDelegateInfo> DelegateInfoHolder;
76
77/*****************************************************************************************************/
78// Caller has to GC protect Objectrefs being passed in
79DelegateInfo *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/*****************************************************************************************************/
129FCIMPL2(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}
140FCIMPLEND
141
142/*****************************************************************************************************/
143FCIMPL2(VOID, ThreadPoolNative::CorGetMaxThreads,DWORD* workerThreads, DWORD* completionPortThreads)
144{
145 FCALL_CONTRACT;
146
147 ThreadpoolMgr::GetMaxThreads(workerThreads,completionPortThreads);
148 return;
149}
150FCIMPLEND
151
152/*****************************************************************************************************/
153FCIMPL2(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}
164FCIMPLEND
165
166/*****************************************************************************************************/
167FCIMPL2(VOID, ThreadPoolNative::CorGetMinThreads,DWORD* workerThreads, DWORD* completionPortThreads)
168{
169 FCALL_CONTRACT;
170
171 ThreadpoolMgr::GetMinThreads(workerThreads,completionPortThreads);
172 return;
173}
174FCIMPLEND
175
176/*****************************************************************************************************/
177FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWORD* completionPortThreads)
178{
179 FCALL_CONTRACT;
180
181 ThreadpoolMgr::GetAvailableThreads(workerThreads,completionPortThreads);
182 return;
183}
184FCIMPLEND
185
186/*****************************************************************************************************/
187
188FCIMPL0(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}
209FCIMPLEND
210
211FCIMPL1(VOID, ThreadPoolNative::ReportThreadStatus, CLR_BOOL isWorking)
212{
213 FCALL_CONTRACT;
214 ThreadpoolMgr::ReportThreadStatus(isWorking);
215}
216FCIMPLEND
217
218FCIMPL0(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}
277FCIMPLEND
278
279
280/*****************************************************************************************************/
281
282void 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
294struct RegisterWaitForSingleObjectCallback_Args
295{
296 DelegateInfo *delegateInfo;
297 BOOLEAN TimerOrWaitFired;
298};
299
300static VOID
301RegisterWaitForSingleObjectCallback_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
336VOID 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
372FCIMPL5(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}
431FCIMPLEND
432
433
434VOID 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
455BOOL 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
484FCIMPL2(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}
537FCIMPLEND
538
539/********************************************************************************************************************/
540FCIMPL1(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}
551FCIMPLEND
552
553/********************************************************************************************************************/
554
555/********************************************************************************************************************/
556
557struct BindIoCompletion_Args
558{
559 DWORD ErrorCode;
560 DWORD numBytesTransferred;
561 LPOVERLAPPED lpOverlapped;
562};
563
564void 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
577VOID 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
621void __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
662void 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
685FCIMPL1(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}
714FCIMPLEND
715
716FCIMPL1(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}
746FCIMPLEND
747
748
749/********************************************************************************************************************/
750
751
752/******************************************************************************************/
753/* */
754/* Timer Functions */
755/* */
756/******************************************************************************************/
757
758void 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
778VOID 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
812HANDLE 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
854BOOL 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
876BOOL 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