From d064de8b8ce7c27ec33fb0eb7e49ad3eb567255e Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Wed, 6 Jul 2022 09:21:10 -0700 Subject: [PATCH 01/20] Remove UsePortableThreadPool() and UsePortableThreadPoolForIO() --- src/coreclr/vm/comthreadpool.cpp | 462 +--- src/coreclr/vm/hillclimbing.cpp | 443 ---- src/coreclr/vm/nativeoverlapped.cpp | 68 - src/coreclr/vm/threadpoolrequest.cpp | 604 +---- src/coreclr/vm/threads.cpp | 34 - src/coreclr/vm/win32threadpool.cpp | 3658 +++----------------------- 6 files changed, 321 insertions(+), 4948 deletions(-) delete mode 100644 src/coreclr/vm/hillclimbing.cpp diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 9cf77c6c3b201b..e0f89a8350448d 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -138,14 +138,6 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, _ASSERTE(isBooleanRef != NULL); _ASSERTE(appContextConfigNameRef != NULL); - if (!ThreadpoolMgr::UsePortableThreadPool()) - { - *configValueRef = 0; - *isBooleanRef = false; - *appContextConfigNameRef = NULL; - return -1; - } - auto TryGetConfig = [=](const CLRConfig::ConfigDWORDInfo &configInfo, bool isBoolean, const WCHAR *appContextConfigName) -> bool { @@ -164,8 +156,7 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, switch (configVariableIndex) { case 0: - // Special case for UsePortableThreadPool and similar, which don't go into the AppContext - *configValueRef = ThreadpoolMgr::UsePortableThreadPoolForIO() ? 2 : 1; + *configValueRef = 2; *isBooleanRef = false; *appContextConfigNameRef = NULL; return 1; @@ -205,7 +196,6 @@ FCIMPLEND FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMinIOCompletionThreads, DWORD ioCompletionThreads) { FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); BOOL result = ThreadpoolMgr::CanSetMinIOCompletionThreads(ioCompletionThreads); FC_RETURN_BOOL(result); @@ -216,220 +206,12 @@ FCIMPLEND FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMaxIOCompletionThreads, DWORD ioCompletionThreads) { FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); BOOL result = ThreadpoolMgr::CanSetMaxIOCompletionThreads(ioCompletionThreads); FC_RETURN_BOOL(result); } FCIMPLEND -/*****************************************************************************************************/ -FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMaxThreads,DWORD workerThreads, DWORD completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - BOOL bRet = FALSE; - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - bRet = ThreadpoolMgr::SetMaxThreads(workerThreads,completionPortThreads); - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(bRet); -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(VOID, ThreadPoolNative::CorGetMaxThreads,DWORD* workerThreads, DWORD* completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::GetMaxThreads(workerThreads,completionPortThreads); - return; -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMinThreads,DWORD workerThreads, DWORD completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - BOOL bRet = FALSE; - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - bRet = ThreadpoolMgr::SetMinThreads(workerThreads,completionPortThreads); - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(bRet); -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(VOID, ThreadPoolNative::CorGetMinThreads,DWORD* workerThreads, DWORD* completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::GetMinThreads(workerThreads,completionPortThreads); - return; -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWORD* completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::GetAvailableThreads(workerThreads,completionPortThreads); - return; -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL0(INT32, ThreadPoolNative::GetThreadCount) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - return ThreadpoolMgr::GetThreadCount(); -} -FCIMPLEND - -/*****************************************************************************************************/ -extern "C" INT64 QCALLTYPE ThreadPool_GetCompletedWorkItemCount() -{ - QCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - INT64 result = 0; - - BEGIN_QCALL; - - result = (INT64)Thread::GetTotalThreadPoolCompletionCount(); - - END_QCALL; - return result; -} - -/*****************************************************************************************************/ -FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests(); -} -FCIMPLEND - -/*****************************************************************************************************/ - -FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first - - ThreadpoolMgr::NotifyWorkItemCompleted(); - - if (ThreadpoolMgr::ShouldAdjustMaxWorkersActive()) - { - DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock); - if (tal.Acquired()) - { - HELPER_METHOD_FRAME_BEGIN_0(); - ThreadpoolMgr::AdjustMaxWorkersActive(); - HELPER_METHOD_FRAME_END(); - } - else - { - // the lock is held by someone else, so they will take care of this for us. - } - } -} -FCIMPLEND - -FCIMPL1(VOID, ThreadPoolNative::ReportThreadStatus, CLR_BOOL isWorking) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - ThreadpoolMgr::ReportThreadStatus(isWorking); -} -FCIMPLEND - -FCIMPL0(FC_BOOL_RET, ThreadPoolNative::NotifyRequestComplete) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first - - ThreadpoolMgr::NotifyWorkItemCompleted(); - - // - // Now we need to possibly do one or both of: reset the thread's state, and/or perform a - // "worker thread adjustment" (i.e., invoke Hill Climbing). We try to avoid these at all costs, - // because they require an expensive helper method frame. So we first try a minimal thread reset, - // then check if it covered everything that was needed, and we ask ThreadpoolMgr whether - // we need a thread adjustment, before setting up the frame. - // - Thread *pThread = GetThread(); - - INT32 priority = pThread->ResetManagedThreadObjectInCoopMode(ThreadNative::PRIORITY_NORMAL); - - bool needReset = - priority != ThreadNative::PRIORITY_NORMAL || - !pThread->IsBackground(); - - bool shouldAdjustWorkers = ThreadpoolMgr::ShouldAdjustMaxWorkersActive(); - - // - // If it's time for a thread adjustment, try to get the lock. This is just a "try," it won't block, - // so it's ok to do this in cooperative mode. If we can't get the lock, then some other thread is - // already doing the thread adjustment, so we needn't bother. - // - DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock, shouldAdjustWorkers); - if (!tal.Acquired()) - shouldAdjustWorkers = false; - - if (needReset || shouldAdjustWorkers) - { - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - if (shouldAdjustWorkers) - { - ThreadpoolMgr::AdjustMaxWorkersActive(); - tal.Release(); - } - - if (needReset) - pThread->InternalReset(TRUE, TRUE, FALSE); - - HELPER_METHOD_FRAME_END(); - } - - // - // Finally, ask ThreadpoolMgr whether it's ok to keep running work on this thread. Maybe Hill Climbing - // wants this thread back. - // - BOOL result = ThreadpoolMgr::ShouldWorkerKeepRunning() ? TRUE : FALSE; - FC_RETURN_BOOL(result); -} -FCIMPLEND - - -/*****************************************************************************************************/ - -FCIMPL0(FC_BOOL_RET, ThreadPoolNative::GetEnableWorkerTracking) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - BOOL result = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking) ? TRUE : FALSE; - FC_RETURN_BOOL(result); -} -FCIMPLEND - /*****************************************************************************************************/ struct RegisterWaitForSingleObjectCallback_Args @@ -503,64 +285,6 @@ VOID NTAPI RegisterWaitForSingleObjectCallback(PVOID delegateInfo, BOOLEAN Timer ManagedThreadBase::ThreadPool(RegisterWaitForSingleObjectCallback_Worker, &args); } -FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, - Object* waitObjectUNSAFE, - Object* stateUNSAFE, - UINT32 timeout, - CLR_BOOL executeOnlyOnce, - Object* registeredWaitObjectUNSAFE) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - HANDLE handle = 0; - struct _gc - { - WAITHANDLEREF waitObject; - OBJECTREF state; - OBJECTREF registeredWaitObject; - } gc; - gc.waitObject = (WAITHANDLEREF) ObjectToOBJECTREF(waitObjectUNSAFE); - gc.state = (OBJECTREF) stateUNSAFE; - gc.registeredWaitObject = (OBJECTREF) registeredWaitObjectUNSAFE; - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); - - if(gc.waitObject == NULL) - COMPlusThrow(kArgumentNullException); - - _ASSERTE(gc.registeredWaitObject != NULL); - - ULONG flag = executeOnlyOnce ? WAIT_SINGLE_EXECUTION | WAIT_FREE_CONTEXT : WAIT_FREE_CONTEXT; - - HANDLE hWaitHandle = gc.waitObject->GetWaitHandle(); - _ASSERTE(hWaitHandle); - - Thread* pCurThread = GetThread(); - - DelegateInfoHolder delegateInfo = DelegateInfo::MakeDelegateInfo( - &gc.state, - (OBJECTREF *)&gc.waitObject, - &gc.registeredWaitObject); - - if (!(ThreadpoolMgr::RegisterWaitForSingleObject(&handle, - hWaitHandle, - RegisterWaitForSingleObjectCallback, - (PVOID) delegateInfo, - (ULONG) timeout, - flag))) - - { - _ASSERTE(GetLastError() != ERROR_CALL_NOT_IMPLEMENTED); - - COMPlusThrowWin32(); - } - - delegateInfo.SuppressRelease(); - HELPER_METHOD_FRAME_END(); - return (LPVOID) handle; -} -FCIMPLEND - VOID QueueUserWorkItemManagedCallback(PVOID pArg) { CONTRACTL @@ -578,125 +302,6 @@ VOID QueueUserWorkItemManagedCallback(PVOID pArg) *wasNotRecalled = dispatch.Call_RetBool(NULL); } -extern "C" BOOL QCALLTYPE ThreadPool_RequestWorkerThread() -{ - QCALL_CONTRACT; - - BOOL res = FALSE; - - BEGIN_QCALL; - - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - ThreadpoolMgr::EnsureInitialized(); - ThreadpoolMgr::SetAppDomainRequestsActive(); - - res = ThreadpoolMgr::QueueUserWorkItem(NULL, - NULL, - 0, - FALSE); - if (!res) - { - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - COMPlusThrow(kNotSupportedException); - else - COMPlusThrowWin32(); - } - - END_QCALL; - return res; -} - -extern "C" BOOL QCALLTYPE ThreadPool_PerformGateActivities(INT32 cpuUtilization) -{ - QCALL_CONTRACT; - - bool needGateThread = false; - - BEGIN_QCALL; - - _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool() && !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::PerformGateActivities(cpuUtilization); - needGateThread = ThreadpoolMgr::NeedGateThreadForIOCompletions(); - - END_QCALL; - - return needGateThread; -} - -/********************************************************************************************************************/ - -FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorUnregisterWait, LPVOID WaitHandle, Object* objectToNotify) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - BOOL retVal = false; - SAFEHANDLEREF refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(objectToNotify); - HELPER_METHOD_FRAME_BEGIN_RET_1(refSH); - - HANDLE hWait = (HANDLE) WaitHandle; - HANDLE hObjectToNotify = NULL; - - ThreadpoolMgr::WaitInfo *pWaitInfo = (ThreadpoolMgr::WaitInfo *)hWait; - _ASSERTE(pWaitInfo != NULL); - - ThreadpoolMgr::WaitInfoHolder wiHolder(NULL); - - if (refSH != NULL) - { - // Create a GCHandle in the WaitInfo, so that it can hold on to the safe handle - pWaitInfo->ExternalEventSafeHandle = GetAppDomain()->CreateHandle(NULL); - - // Holder will now release objecthandle in face of exceptions - wiHolder.Assign(pWaitInfo); - - // Store SafeHandle in object handle. Holder will now release both safehandle and objecthandle - // in case of exceptions - StoreObjectInHandle(pWaitInfo->ExternalEventSafeHandle, refSH); - - // Acquire safe handle to examine its handle, then release. - SafeHandleHolder shHolder(&refSH); - - if (refSH->GetHandle() == INVALID_HANDLE_VALUE) - { - hObjectToNotify = INVALID_HANDLE_VALUE; - // We do not need the ObjectHandle, refcount on the safehandle etc - wiHolder.Release(); - _ASSERTE(pWaitInfo->ExternalEventSafeHandle == NULL); - } - } - - _ASSERTE(hObjectToNotify == NULL || hObjectToNotify == INVALID_HANDLE_VALUE); - - // When hObjectToNotify is NULL ExternalEventSafeHandle contains event to notify (if it is non NULL). - // When hObjectToNotify is INVALID_HANDLE_VALUE, UnregisterWaitEx blocks until dispose is complete. - retVal = ThreadpoolMgr::UnregisterWaitEx(hWait, hObjectToNotify); - - if (retVal) - wiHolder.SuppressRelease(); - - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(retVal); -} -FCIMPLEND - -/********************************************************************************************************************/ -FCIMPL1(void, ThreadPoolNative::CorWaitHandleCleanupNative, LPVOID WaitHandle) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); - - HELPER_METHOD_FRAME_BEGIN_0(); - - HANDLE hWait = (HANDLE)WaitHandle; - ThreadpoolMgr::WaitHandleCleanup(hWait); - - HELPER_METHOD_FRAME_END(); -} -FCIMPLEND - /********************************************************************************************************************/ struct BindIoCompletion_Args @@ -788,71 +393,6 @@ void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, BindIoCompletionCallbackStubEx(ErrorCode, numBytesTransferred, lpOverlapped, TRUE); } -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorBindIoCompletionCallback, HANDLE fileHandle) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - BOOL retVal = FALSE; - - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - HANDLE hFile = (HANDLE) fileHandle; - DWORD errCode = 0; - - retVal = ThreadpoolMgr::BindIoCompletionCallback(hFile, - BindIoCompletionCallbackStub, - 0, // reserved, must be 0 - OUT errCode); - if (!retVal) - { - if (errCode == ERROR_CALL_NOT_IMPLEMENTED) - COMPlusThrow(kPlatformNotSupportedException); - else - { - SetLastError(errCode); - COMPlusThrowWin32(); - } - } - - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(retVal); -} -FCIMPLEND - -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - - BOOL res = FALSE; - - HELPER_METHOD_FRAME_BEGIN_RET_1(overlapped); - - // OS doesn't signal handle, so do it here - lpOverlapped->Internal = 0; - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIOEnqueue)) - FireEtwThreadPoolIOEnqueue(lpOverlapped, OBJECTREFToObject(overlapped), false, GetClrInstanceId()); - - res = ThreadpoolMgr::PostQueuedCompletionStatus(lpOverlapped, - BindIoCompletionCallbackStub); - - if (!res) - { - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - COMPlusThrow(kPlatformNotSupportedException); - else - COMPlusThrowWin32(); - } - - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(res); -} -FCIMPLEND - /********************************************************************************************************************/ diff --git a/src/coreclr/vm/hillclimbing.cpp b/src/coreclr/vm/hillclimbing.cpp deleted file mode 100644 index 315ceb2bba8ea0..00000000000000 --- a/src/coreclr/vm/hillclimbing.cpp +++ /dev/null @@ -1,443 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// HillClimbing.cpp -// -// Defines classes for the ThreadPool's HillClimbing concurrency-optimization -// algorithm. -// - -//========================================================================= - -// -// TODO: write an essay about how/why this works. Maybe put it in BotR? -// - -#include "common.h" -#include "hillclimbing.h" -#include "win32threadpool.h" - -// -// Default compilation mode is /fp:precise, which disables fp intrinsics. This causes us to pull in FP stuff (sin,cos,etc.) from -// The CRT, and increases our download size by ~5k. We don't need the extra precision this gets us, so let's switch to -// the intrinsic versions. -// -#ifdef _MSC_VER -#pragma float_control(precise, off) -#endif - - - -const double pi = 3.141592653589793; - -void HillClimbing::Initialize() -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - m_wavePeriod = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_WavePeriod); - m_maxThreadWaveMagnitude = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude); - m_threadMagnitudeMultiplier = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier) / 100.0; - m_samplesToMeasure = m_wavePeriod * (int)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize); - m_targetThroughputRatio = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_Bias) / 100.0; - m_targetSignalToNoiseRatio = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio) / 100.0; - m_maxChangePerSecond = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond); - m_maxChangePerSample = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample); - m_sampleIntervalLow = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow); - m_sampleIntervalHigh = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh); - m_throughputErrorSmoothingFactor = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor) / 100.0; - m_gainExponent = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_GainExponent) / 100.0; - m_maxSampleError = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent) / 100.0; - m_currentControlSetting = 0; - m_totalSamples = 0; - m_lastThreadCount = 0; - m_averageThroughputNoise = 0; - m_elapsedSinceLastChange = 0; - m_completionsSinceLastChange = 0; - m_accumulatedCompletionCount = 0; - m_accumulatedSampleDuration = 0; - - m_samples = new double[m_samplesToMeasure]; - m_threadCounts = new double[m_samplesToMeasure]; - - // seed our random number generator with the CLR instance ID and the process ID, to avoid correlations with other CLR ThreadPool instances. -#ifndef DACCESS_COMPILE - m_randomIntervalGenerator.Init(((int)GetClrInstanceId() << 16) ^ (int)GetCurrentProcessId()); -#endif - m_currentSampleInterval = m_randomIntervalGenerator.Next(m_sampleIntervalLow, m_sampleIntervalHigh+1); -} - -int HillClimbing::Update(int currentThreadCount, double sampleDuration, int numCompletions, int* pNewSampleInterval) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifdef DACCESS_COMPILE - return 1; -#else - - // - // If someone changed the thread count without telling us, update our records accordingly. - // - if (currentThreadCount != m_lastThreadCount) - ForceChange(currentThreadCount, Initializing); - - // - // Update the cumulative stats for this thread count - // - m_elapsedSinceLastChange += sampleDuration; - m_completionsSinceLastChange += numCompletions; - - // - // Add in any data we've already collected about this sample - // - sampleDuration += m_accumulatedSampleDuration; - numCompletions += m_accumulatedCompletionCount; - - // - // We need to make sure we're collecting reasonably accurate data. Since we're just counting the end - // of each work item, we are goinng to be missing some data about what really happened during the - // sample interval. The count produced by each thread includes an initial work item that may have - // started well before the start of the interval, and each thread may have been running some new - // work item for some time before the end of the interval, which did not yet get counted. So - // our count is going to be off by +/- threadCount workitems. - // - // The exception is that the thread that reported to us last time definitely wasn't running any work - // at that time, and the thread that's reporting now definitely isn't running a work item now. So - // we really only need to consider threadCount-1 threads. - // - // Thus the percent error in our count is +/- (threadCount-1)/numCompletions. - // - // We cannot rely on the frequency-domain analysis we'll be doing later to filter out this error, because - // of the way it accumulates over time. If this sample is off by, say, 33% in the negative direction, - // then the next one likely will be too. The one after that will include the sum of the completions - // we missed in the previous samples, and so will be 33% positive. So every three samples we'll have - // two "low" samples and one "high" sample. This will appear as periodic variation right in the frequency - // range we're targeting, which will not be filtered by the frequency-domain translation. - // - if (m_totalSamples > 0 && ((currentThreadCount-1.0) / numCompletions) >= m_maxSampleError) - { - // not accurate enough yet. Let's accumulate the data so far, and tell the ThreadPool - // to collect a little more. - m_accumulatedSampleDuration = sampleDuration; - m_accumulatedCompletionCount = numCompletions; - *pNewSampleInterval = 10; - return currentThreadCount; - } - - // - // We've got enouugh data for our sample; reset our accumulators for next time. - // - m_accumulatedSampleDuration = 0; - m_accumulatedCompletionCount = 0; - - // - // Add the current thread count and throughput sample to our history - // - double throughput = (double)numCompletions / sampleDuration; - FireEtwThreadPoolWorkerThreadAdjustmentSample(throughput, GetClrInstanceId()); - - int sampleIndex = m_totalSamples % m_samplesToMeasure; - m_samples[sampleIndex] = throughput; - m_threadCounts[sampleIndex] = currentThreadCount; - m_totalSamples++; - - // - // Set up defaults for our metrics - // - Complex threadWaveComponent = 0; - Complex throughputWaveComponent = 0; - double throughputErrorEstimate = 0; - Complex ratio = 0; - double confidence = 0; - - HillClimbingStateTransition transition = Warmup; - - // - // How many samples will we use? It must be at least the three wave periods we're looking for, and it must also be a whole - // multiple of the primary wave's period; otherwise the frequency we're looking for will fall between two frequency bands - // in the Fourier analysis, and we won't be able to measure it accurately. - // - int sampleCount = ((int)min(m_totalSamples-1, m_samplesToMeasure) / m_wavePeriod) * m_wavePeriod; - - if (sampleCount > m_wavePeriod) - { - // - // Average the throughput and thread count samples, so we can scale the wave magnitudes later. - // - double sampleSum = 0; - double threadSum = 0; - for (int i = 0; i < sampleCount; i++) - { - sampleSum += m_samples[(m_totalSamples - sampleCount + i) % m_samplesToMeasure]; - threadSum += m_threadCounts[(m_totalSamples - sampleCount + i) % m_samplesToMeasure]; - } - double averageThroughput = sampleSum / sampleCount; - double averageThreadCount = threadSum / sampleCount; - - if (averageThroughput > 0 && averageThreadCount > 0) - { - // - // Calculate the periods of the adjacent frequency bands we'll be using to measure noise levels. - // We want the two adjacent Fourier frequency bands. - // - double adjacentPeriod1 = sampleCount / (((double)sampleCount / (double)m_wavePeriod) + 1); - double adjacentPeriod2 = sampleCount / (((double)sampleCount / (double)m_wavePeriod) - 1); - - // - // Get the three different frequency components of the throughput (scaled by average - // throughput). Our "error" estimate (the amount of noise that might be present in the - // frequency band we're really interested in) is the average of the adjacent bands. - // - throughputWaveComponent = GetWaveComponent(m_samples, sampleCount, m_wavePeriod) / averageThroughput; - throughputErrorEstimate = abs(GetWaveComponent(m_samples, sampleCount, adjacentPeriod1) / averageThroughput); - if (adjacentPeriod2 <= sampleCount) - throughputErrorEstimate = max(throughputErrorEstimate, abs(GetWaveComponent(m_samples, sampleCount, adjacentPeriod2) / averageThroughput)); - - // - // Do the same for the thread counts, so we have something to compare to. We don't measure thread count - // noise, because there is none; these are exact measurements. - // - threadWaveComponent = GetWaveComponent(m_threadCounts, sampleCount, m_wavePeriod) / averageThreadCount; - - // - // Update our moving average of the throughput noise. We'll use this later as feedback to - // determine the new size of the thread wave. - // - if (m_averageThroughputNoise == 0) - m_averageThroughputNoise = throughputErrorEstimate; - else - m_averageThroughputNoise = (m_throughputErrorSmoothingFactor * throughputErrorEstimate) + ((1.0-m_throughputErrorSmoothingFactor) * m_averageThroughputNoise); - - if (abs(threadWaveComponent) > 0) - { - // - // Adjust the throughput wave so it's centered around the target wave, and then calculate the adjusted throughput/thread ratio. - // - ratio = (throughputWaveComponent - (m_targetThroughputRatio * threadWaveComponent)) / threadWaveComponent; - transition = ClimbingMove; - } - else - { - ratio = 0; - transition = Stabilizing; - } - - // - // Calculate how confident we are in the ratio. More noise == less confident. This has - // the effect of slowing down movements that might be affected by random noise. - // - double noiseForConfidence = max(m_averageThroughputNoise, throughputErrorEstimate); - if (noiseForConfidence > 0) - confidence = (abs(threadWaveComponent) / noiseForConfidence) / m_targetSignalToNoiseRatio; - else - confidence = 1.0; //there is no noise! - - } - } - - // - // We use just the real part of the complex ratio we just calculated. If the throughput signal - // is exactly in phase with the thread signal, this will be the same as taking the magnitude of - // the complex move and moving that far up. If they're 180 degrees out of phase, we'll move - // backward (because this indicates that our changes are having the opposite of the intended effect). - // If they're 90 degrees out of phase, we won't move at all, because we can't tell whether we're - // having a negative or positive effect on throughput. - // - double move = min(1.0, max(-1.0, ratio.r)); - - // - // Apply our confidence multiplier. - // - move *= min(1.0, max(0.0, confidence)); - - // - // Now apply non-linear gain, such that values around zero are attenuated, while higher values - // are enhanced. This allows us to move quickly if we're far away from the target, but more slowly - // if we're getting close, giving us rapid ramp-up without wild oscillations around the target. - // - double gain = m_maxChangePerSecond * sampleDuration; - move = pow(fabs(move), m_gainExponent) * (move >= 0.0 ? 1 : -1) * gain; - move = min(move, m_maxChangePerSample); - - // - // If the result was positive, and CPU is > 95%, refuse the move. - // - if (move > 0.0 && ThreadpoolMgr::cpuUtilization > CpuUtilizationHigh) - move = 0.0; - - // - // Apply the move to our control setting - // - m_currentControlSetting += move; - - // - // Calculate the new thread wave magnitude, which is based on the moving average we've been keeping of - // the throughput error. This average starts at zero, so we'll start with a nice safe little wave at first. - // - int newThreadWaveMagnitude = (int)(0.5 + (m_currentControlSetting * m_averageThroughputNoise * m_targetSignalToNoiseRatio * m_threadMagnitudeMultiplier * 2.0)); - newThreadWaveMagnitude = min(newThreadWaveMagnitude, m_maxThreadWaveMagnitude); - newThreadWaveMagnitude = max(newThreadWaveMagnitude, 1); - - // - // Make sure our control setting is within the ThreadPool's limits - // - m_currentControlSetting = min(ThreadpoolMgr::MaxLimitTotalWorkerThreads-newThreadWaveMagnitude, m_currentControlSetting); - m_currentControlSetting = max(ThreadpoolMgr::MinLimitTotalWorkerThreads, m_currentControlSetting); - - // - // Calculate the new thread count (control setting + square wave) - // - int newThreadCount = (int)(m_currentControlSetting + newThreadWaveMagnitude * ((m_totalSamples / (m_wavePeriod/2)) % 2)); - - // - // Make sure the new thread count doesn't exceed the ThreadPool's limits - // - newThreadCount = min(ThreadpoolMgr::MaxLimitTotalWorkerThreads, newThreadCount); - newThreadCount = max(ThreadpoolMgr::MinLimitTotalWorkerThreads, newThreadCount); - - // - // Record these numbers for posterity - // - FireEtwThreadPoolWorkerThreadAdjustmentStats( - sampleDuration, - throughput, - threadWaveComponent.r, - throughputWaveComponent.r, - throughputErrorEstimate, - m_averageThroughputNoise, - ratio.r, - confidence, - m_currentControlSetting, - (unsigned short)newThreadWaveMagnitude, - GetClrInstanceId()); - - // - // If all of this caused an actual change in thread count, log that as well. - // - if (newThreadCount != currentThreadCount) - ChangeThreadCount(newThreadCount, transition); - - // - // Return the new thread count and sample interval. This is randomized to prevent correlations with other periodic - // changes in throughput. Among other things, this prevents us from getting confused by Hill Climbing instances - // running in other processes. - // - // If we're at minThreads, and we seem to be hurting performance by going higher, we can't go any lower to fix this. So - // we'll simply stay at minThreads much longer, and only occasionally try a higher value. - // - if (ratio.r < 0.0 && newThreadCount == ThreadpoolMgr::MinLimitTotalWorkerThreads) - *pNewSampleInterval = (int)(0.5 + m_currentSampleInterval * (10.0 * min(-ratio.r, 1.0))); - else - *pNewSampleInterval = m_currentSampleInterval; - - return newThreadCount; - -#endif //DACCESS_COMPILE -} - - -void HillClimbing::ForceChange(int newThreadCount, HillClimbingStateTransition transition) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - if (newThreadCount != m_lastThreadCount) - { - m_currentControlSetting += (newThreadCount - m_lastThreadCount); - ChangeThreadCount(newThreadCount, transition); - } -} - - -void HillClimbing::ChangeThreadCount(int newThreadCount, HillClimbingStateTransition transition) -{ - LIMITED_METHOD_CONTRACT; - - m_lastThreadCount = newThreadCount; - m_currentSampleInterval = m_randomIntervalGenerator.Next(m_sampleIntervalLow, m_sampleIntervalHigh+1); - double throughput = (m_elapsedSinceLastChange > 0) ? (m_completionsSinceLastChange / m_elapsedSinceLastChange) : 0; - LogTransition(newThreadCount, throughput, transition); - m_elapsedSinceLastChange = 0; - m_completionsSinceLastChange = 0; -} - - -GARY_IMPL(HillClimbingLogEntry, HillClimbingLog, HillClimbingLogCapacity); -GVAL_IMPL(int, HillClimbingLogFirstIndex); -GVAL_IMPL(int, HillClimbingLogSize); - - -void HillClimbing::LogTransition(int threadCount, double throughput, HillClimbingStateTransition transition) -{ - LIMITED_METHOD_CONTRACT; - -#ifndef DACCESS_COMPILE - int index = (HillClimbingLogFirstIndex + HillClimbingLogSize) % HillClimbingLogCapacity; - - if (HillClimbingLogSize == HillClimbingLogCapacity) - { - HillClimbingLogFirstIndex = (HillClimbingLogFirstIndex + 1) % HillClimbingLogCapacity; - HillClimbingLogSize--; //hide this slot while we update it - } - - HillClimbingLogEntry* entry = &HillClimbingLog[index]; - - entry->TickCount = GetTickCount(); - entry->Transition = transition; - entry->NewControlSetting = threadCount; - - entry->LastHistoryCount = (int)(min(m_totalSamples, m_samplesToMeasure) / m_wavePeriod) * m_wavePeriod; - entry->LastHistoryMean = (float) throughput; - - HillClimbingLogSize++; - - FireEtwThreadPoolWorkerThreadAdjustmentAdjustment( - throughput, - threadCount, - transition, - GetClrInstanceId()); - -#endif //DACCESS_COMPILE -} - -Complex HillClimbing::GetWaveComponent(double* samples, int sampleCount, double period) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - _ASSERTE(sampleCount >= period); //can't measure a wave that doesn't fit - _ASSERTE(period >= 2); //can't measure above the Nyquist frequency - - // - // Calculate the sinusoid with the given period. - // We're using the Goertzel algorithm for this. See http://en.wikipedia.org/wiki/Goertzel_algorithm. - // - double w = 2.0 * pi / period; - double cosine = cos(w); - double sine = sin(w); - double coeff = 2.0 * cosine; - double q0 = 0, q1 = 0, q2 = 0; - - for (int i = 0; i < sampleCount; i++) - { - double sample = samples[(m_totalSamples - sampleCount + i) % m_samplesToMeasure]; - - q0 = coeff * q1 - q2 + sample; - q2 = q1; - q1 = q0; - } - - return Complex(q1 - q2 * cosine, q2 * sine) / (double)sampleCount; -} - diff --git a/src/coreclr/vm/nativeoverlapped.cpp b/src/coreclr/vm/nativeoverlapped.cpp index 6c8f56a12acb96..8c4a79be68b19b 100644 --- a/src/coreclr/vm/nativeoverlapped.cpp +++ b/src/coreclr/vm/nativeoverlapped.cpp @@ -20,74 +20,6 @@ #include "comthreadpool.h" #include "marshalnative.h" -// -//The function is called from managed code to quicly check if a packet is available. -//This is a perf-critical function. Even helper method frames are not created. We fall -//back to the VM to do heavy weight operations like creating a new CP thread. -// -FCIMPL3(void, CheckVMForIOPacket, LPOVERLAPPED* lpOverlapped, DWORD* errorCode, DWORD* numBytes) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); - -#ifndef TARGET_UNIX - Thread *pThread = GetThread(); - size_t key=0; - - //Poll and wait if GC is in progress, to avoid blocking GC for too long. - FC_GC_POLL(); - - *lpOverlapped = ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain(pThread, errorCode, numBytes, &key); - if(*lpOverlapped == NULL) - { - return; - } - - OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(*lpOverlapped)); - - if (overlapped->m_callback == NULL) - { - //We're not initialized yet, go back to the Vm, and process the packet there. - ThreadpoolMgr::StoreOverlappedInfoInThread(pThread, *errorCode, *numBytes, key, *lpOverlapped); - - *lpOverlapped = NULL; - return; - } - else - { - if(!pThread->IsRealThreadPoolResetNeeded()) - { - pThread->ResetManagedThreadObjectInCoopMode(ThreadNative::PRIORITY_NORMAL); - pThread->InternalReset(TRUE, FALSE, FALSE); - if(ThreadpoolMgr::ShouldGrowCompletionPortThreadpool(ThreadpoolMgr::CPThreadCounter.DangerousGetDirtyCounts())) - { - //We may have to create a CP thread, go back to the Vm, and process the packet there. - ThreadpoolMgr::StoreOverlappedInfoInThread(pThread, *errorCode, *numBytes, key, *lpOverlapped); - *lpOverlapped = NULL; - } - } - else - { - //A more complete reset is needed (due to change in priority etc), go back to the VM, - //and process the packet there. - - ThreadpoolMgr::StoreOverlappedInfoInThread(pThread, *errorCode, *numBytes, key, *lpOverlapped); - *lpOverlapped = NULL; - } - } - - // if this will be "dispatched" to the managed callback fire the IODequeue event: - if (*lpOverlapped != NULL && ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) - FireEtwThreadPoolIODequeue(*lpOverlapped, OverlappedDataObject::GetOverlapped(*lpOverlapped), GetClrInstanceId()); - -#else // !TARGET_UNIX - *lpOverlapped = NULL; -#endif // !TARGET_UNIX - - return; -} -FCIMPLEND - FCIMPL1(LPOVERLAPPED, AllocateNativeOverlapped, OverlappedDataObject* overlappedUNSAFE) { FCALL_CONTRACT; diff --git a/src/coreclr/vm/threadpoolrequest.cpp b/src/coreclr/vm/threadpoolrequest.cpp index 6e1caf5187650c..e2eb502324f03e 100644 --- a/src/coreclr/vm/threadpoolrequest.cpp +++ b/src/coreclr/vm/threadpoolrequest.cpp @@ -38,11 +38,6 @@ ArrayListStatic PerAppDomainTPCountList::s_appDomainIndexList; void PerAppDomainTPCountList::InitAppDomainIndexList() { LIMITED_METHOD_CONTRACT; - - if (!ThreadpoolMgr::UsePortableThreadPool()) - { - s_appDomainIndexList.Init(); - } } @@ -60,72 +55,7 @@ TPIndex PerAppDomainTPCountList::AddNewTPIndex() { STANDARD_VM_CONTRACT; - if (ThreadpoolMgr::UsePortableThreadPool()) - { - return TPIndex(); - } - - DWORD count = s_appDomainIndexList.GetCount(); - DWORD i = FindFirstFreeTpEntry(); - - if (i == UNUSED_THREADPOOL_INDEX) - i = count; - - TPIndex index(i+1); - if(count > i) - { - - IPerAppDomainTPCount * pAdCount = dac_cast(s_appDomainIndexList.Get(i)); - pAdCount->SetTPIndex(index); - return index; - } - -#ifdef _MSC_VER - // Disable this warning - we intentionally want __declspec(align()) to insert trailing padding for us -#pragma warning(disable:4316) // Object allocated on the heap may not be aligned for this type. -#endif - ManagedPerAppDomainTPCount * pAdCount = new ManagedPerAppDomainTPCount(index); -#ifdef _MSC_VER -#pragma warning(default:4316) // Object allocated on the heap may not be aligned for this type. -#endif - pAdCount->ResetState(); - - IfFailThrow(s_appDomainIndexList.Append(pAdCount)); - - return index; -} - -DWORD PerAppDomainTPCountList::FindFirstFreeTpEntry() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - DWORD DwnumADs = s_appDomainIndexList.GetCount(); - DWORD Dwi; - IPerAppDomainTPCount * pAdCount; - DWORD DwfreeIndex = UNUSED_THREADPOOL_INDEX; - - for (Dwi=0;Dwi < DwnumADs;Dwi++) - { - pAdCount = dac_cast(s_appDomainIndexList.Get(Dwi)); - _ASSERTE(pAdCount); - - if(pAdCount->IsTPIndexUnused()) - { - DwfreeIndex = Dwi; - STRESS_LOG1(LF_THREADPOOL, LL_INFO1000, "FindFirstFreeTpEntry: reusing index %d\n", DwfreeIndex + 1); - break; - } - } - - return DwfreeIndex; + return TPIndex(); } //--------------------------------------------------------------------------- @@ -153,540 +83,10 @@ void PerAppDomainTPCountList::ResetAppDomainIndex(TPIndex index) } CONTRACTL_END; - if (ThreadpoolMgr::UsePortableThreadPool()) - { - _ASSERTE(index.m_dwIndex == TPIndex().m_dwIndex); - return; - } - - IPerAppDomainTPCount * pAdCount = dac_cast(s_appDomainIndexList.Get(index.m_dwIndex-1)); - _ASSERTE(pAdCount); - - STRESS_LOG2(LF_THREADPOOL, LL_INFO1000, "ResetAppDomainIndex: index %d pAdCount %p\n", index.m_dwIndex, pAdCount); - - pAdCount->ResetState(); - pAdCount->SetTPIndexUnused(); -} - -//--------------------------------------------------------------------------- -//AreRequestsPendingInAnyAppDomains checks to see if there any requests pending -//in other appdomains. It also checks for pending unmanaged work requests. -//This function is called at end of thread quantum to see if the thread needs to -//transition into a different appdomain. This function may also be called by -//the scheduler to check for any unscheduled work. -// -bool PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - DWORD DwnumADs = s_appDomainIndexList.GetCount(); - DWORD Dwi; - IPerAppDomainTPCount * pAdCount; - bool fRequestsPending = false; - - for (Dwi=0;Dwi < DwnumADs;Dwi++) - { - - pAdCount = dac_cast(s_appDomainIndexList.Get(Dwi)); - _ASSERTE(pAdCount); - - if(pAdCount->IsRequestPending()) - { - fRequestsPending = true; - break; - } - } - - if(s_unmanagedTPCount.IsRequestPending()) - { - fRequestsPending = true; - } - - return fRequestsPending; -} - - -//--------------------------------------------------------------------------- -//GetAppDomainIndexForThreadpoolDispatch is essentailly the -//"AppDomain Scheduler". This function makes fairness/policy decisions as to -//which appdomain the thread needs to enter to. This function needs to guarantee -//that all appdomain work requests are processed fairly. At this time all -//appdomain requests and the unmanaged work requests are treated with the same -//priority. -// -//Return Value: -//The appdomain ID in which to dispatch the worker thread,nmanaged work items -//need to be processed. -// -LONG PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - LONG hint = s_ADHint; - DWORD count = s_appDomainIndexList.GetCount(); - IPerAppDomainTPCount * pAdCount; - DWORD Dwi; - - - if (hint != -1) - { - pAdCount = dac_cast(s_appDomainIndexList.Get(hint)); - } - else - { - pAdCount = &s_unmanagedTPCount; - } - - //temphint ensures that the check for appdomains proceeds in a pure round robin fashion. - LONG temphint = hint; - - _ASSERTE( pAdCount); - - if (pAdCount->TakeActiveRequest()) - goto HintDone; - - //If there is no work in any appdomains, check the unmanaged queue, - hint = -1; - - for (Dwi=0;Dwi(s_appDomainIndexList.Get(temphint)); - if (pAdCount->TakeActiveRequest()) - { - hint = temphint; - goto HintDone; - } - - temphint++; - - _ASSERTE( temphint <= (LONG)count); - - if(temphint == (LONG)count) - { - temphint = 0; - } - } - - if (hint == -1 && !s_unmanagedTPCount.TakeActiveRequest()) - { - //no work! - return 0; - } - -HintDone: - - if((hint+1) < (LONG)count) - { - s_ADHint = hint+1; - } - else - { - s_ADHint = -1; - } - - if (hint == -1) - { - return hint; - } - else - { - return (hint+1); - } -} - - -void UnManagedPerAppDomainTPCount::SetAppDomainRequestsActive() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifndef DACCESS_COMPILE - LONG count = VolatileLoad(&m_outstandingThreadRequestCount); - while (count < (LONG)ThreadpoolMgr::NumberOfProcessors) - { - LONG prevCount = InterlockedCompareExchange(&m_outstandingThreadRequestCount, count+1, count); - if (prevCount == count) - { - ThreadpoolMgr::MaybeAddWorkingWorker(); - ThreadpoolMgr::EnsureGateThreadRunning(); - break; - } - count = prevCount; - } -#endif -} - -bool FORCEINLINE UnManagedPerAppDomainTPCount::TakeActiveRequest() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - LONG count = VolatileLoad(&m_outstandingThreadRequestCount); - - while (count > 0) - { - LONG prevCount = InterlockedCompareExchange(&m_outstandingThreadRequestCount, count-1, count); - if (prevCount == count) - return true; - count = prevCount; - } - - return false; + _ASSERTE(index.m_dwIndex == TPIndex().m_dwIndex); } FORCEINLINE void ReleaseWorkRequest(WorkRequest *workRequest) { ThreadpoolMgr::RecycleMemory( workRequest, ThreadpoolMgr::MEMTYPE_WorkRequest ); } typedef Wrapper< WorkRequest *, DoNothing, ReleaseWorkRequest > WorkRequestHolder; -void UnManagedPerAppDomainTPCount::QueueUnmanagedWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END;; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifndef DACCESS_COMPILE - WorkRequestHolder pWorkRequest; - - //Note, ideally we would want to use our own queues instead of those in - //the thread pool class. However, the queus in thread pool class have - //caching support, that shares memory with other commonly used structures - //in the VM thread pool implementation. So, we decided to leverage those. - - pWorkRequest = ThreadpoolMgr::MakeWorkRequest(function, context); - - //MakeWorkRequest should throw if unable to allocate memory - _ASSERTE(pWorkRequest != NULL); - PREFIX_ASSUME(pWorkRequest != NULL); - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolEnqueue) && - !ThreadpoolMgr::AreEtwQueueEventsSpeciallyHandled(function)) - FireEtwThreadPoolEnqueue(pWorkRequest, GetClrInstanceId()); - - m_lock.Init(LOCK_TYPE_DEFAULT); - - { - SpinLock::Holder slh(&m_lock); - - ThreadpoolMgr::EnqueueWorkRequest(pWorkRequest); - pWorkRequest.SuppressRelease(); - m_NumRequests++; - } - - SetAppDomainRequestsActive(); -#endif //DACCESS_COMPILE -} - -PVOID UnManagedPerAppDomainTPCount::DeQueueUnManagedWorkRequest(bool* lastOne) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - *lastOne = true; - - WorkRequest * pWorkRequest = ThreadpoolMgr::DequeueWorkRequest(); - - if (pWorkRequest) - { - m_NumRequests--; - - if(m_NumRequests > 0) - *lastOne = false; - } - - return (PVOID) pWorkRequest; -} - -//--------------------------------------------------------------------------- -//DispatchWorkItem manages dispatching of unmanaged work requests. It keeps -//processing unmanaged requests for the "Quanta". Essentially this function is -//a tight loop of dequeueing unmanaged work requests and dispatching them. -// -void UnManagedPerAppDomainTPCount::DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) -{ - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifndef DACCESS_COMPILE - *foundWork = false; - *wasNotRecalled = true; - - bool enableWorkerTracking = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking) ? true : false; - - DWORD startTime; - DWORD endTime; - - startTime = GetTickCount(); - - //For all practical puposes, the unmanaged part of thread pool is treated - //as a special appdomain for thread pool purposes. The same logic as the - //one in managed code for dispatching thread pool requests is repeated here. - //Namely we continue to process requests until eithere there are none, or - //the "Quanta has expired". See threadpool.cs for the managed counterpart. - - WorkRequest * pWorkRequest=NULL; - LPTHREAD_START_ROUTINE wrFunction; - LPVOID wrContext; - - bool firstIteration = true; - bool lastOne = false; - - while (*wasNotRecalled) - { - m_lock.Init(LOCK_TYPE_DEFAULT); - { - SpinLock::Holder slh(&m_lock); - pWorkRequest = (WorkRequest*) DeQueueUnManagedWorkRequest(&lastOne); - } - - if (NULL == pWorkRequest) - break; - - if (firstIteration && !lastOne) - SetAppDomainRequestsActive(); - - firstIteration = false; - *foundWork = true; - - if (GCHeapUtilities::IsGCInProgress(TRUE)) - { - // GC is imminent, so wait until GC is complete before executing next request. - // this reduces in-flight objects allocated right before GC, easing the GC's work - GCHeapUtilities::WaitForGCCompletion(TRUE); - } - - PREFIX_ASSUME(pWorkRequest != NULL); - _ASSERTE(pWorkRequest); - - wrFunction = pWorkRequest->Function; - wrContext = pWorkRequest->Context; - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolDequeue) && - !ThreadpoolMgr::AreEtwQueueEventsSpeciallyHandled(wrFunction)) - FireEtwThreadPoolDequeue(pWorkRequest, GetClrInstanceId()); - - ThreadpoolMgr::FreeWorkRequest(pWorkRequest); - - if (enableWorkerTracking) - { - ThreadpoolMgr::ReportThreadStatus(true); - (wrFunction) (wrContext); - ThreadpoolMgr::ReportThreadStatus(false); - } - else - { - (wrFunction) (wrContext); - } - - ThreadpoolMgr::NotifyWorkItemCompleted(); - if (ThreadpoolMgr::ShouldAdjustMaxWorkersActive()) - { - DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock); - if (tal.Acquired()) - { - ThreadpoolMgr::AdjustMaxWorkersActive(); - } - else - { - // the lock is held by someone else, so they will take care of this for us. - } - } - *wasNotRecalled = ThreadpoolMgr::ShouldWorkerKeepRunning(); - - Thread *pThread = GetThreadNULLOk(); - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } - - endTime = GetTickCount(); - - if ((endTime - startTime) >= TP_QUANTUM) - { - break; - } - } - - // if we're exiting for any reason other than the queue being empty, then we need to make sure another thread - // will visit us later. - if (NULL != pWorkRequest) - { - SetAppDomainRequestsActive(); - } - -#endif //DACCESS_COMPILE -} - - -void ManagedPerAppDomainTPCount::SetAppDomainRequestsActive() -{ - //This function should either be called by managed code or during AD unload, but before - //the TpIndex is set to unused. - // - // Note that there is a separate count in managed code that stays in sync with this one over time. - // The manage count is incremented before this one, and this one is decremented before the managed - // one. - // - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - _ASSERTE(m_index.m_dwIndex != UNUSED_THREADPOOL_INDEX); - -#ifndef DACCESS_COMPILE - LONG count = VolatileLoad(&m_numRequestsPending); - while (true) - { - LONG prev = InterlockedCompareExchange(&m_numRequestsPending, count+1, count); - if (prev == count) - { - ThreadpoolMgr::MaybeAddWorkingWorker(); - ThreadpoolMgr::EnsureGateThreadRunning(); - break; - } - count = prev; - } -#endif -} - -void ManagedPerAppDomainTPCount::ClearAppDomainRequestsActive() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - //This function should either be called by managed code or during AD unload, but before - //the TpIndex is set to unused. - - _ASSERTE(m_index.m_dwIndex != UNUSED_THREADPOOL_INDEX); - - LONG count = VolatileLoad(&m_numRequestsPending); - while (count > 0) - { - LONG prev = InterlockedCompareExchange(&m_numRequestsPending, 0, count); - if (prev == count) - break; - count = prev; - } -} - -bool ManagedPerAppDomainTPCount::TakeActiveRequest() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - LONG count = VolatileLoad(&m_numRequestsPending); - while (count > 0) - { - LONG prev = InterlockedCompareExchange(&m_numRequestsPending, count-1, count); - if (prev == count) - return true; - count = prev; - } - return false; -} - -#ifndef DACCESS_COMPILE - -//--------------------------------------------------------------------------- -//DispatchWorkItem makes sure the right exception handling frames are setup, -//the thread is transitioned into the correct appdomain, and the right managed -//callback is called. -// -void ManagedPerAppDomainTPCount::DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) -{ - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - *foundWork = false; - *wasNotRecalled = true; - - HRESULT hr; - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - if (pThread == NULL) - { - return; - } - } - - { - CONTRACTL - { - MODE_PREEMPTIVE; - THROWS; - GC_TRIGGERS; - } - CONTRACTL_END; - - GCX_COOP(); - - // - // NOTE: there is a potential race between the time we retrieve the app - // domain pointer, and the time which this thread enters the domain. - // - // To solve the race, we rely on the fact that there is a thread sync (via - // GC) between releasing an app domain's handle, and destroying the - // app domain. Thus it is important that we not go into preemptive gc mode - // in that window. - // - - { - // This TPIndex may have been recycled since we chose it for workitem dispatch. - // If so, the new AppDomain will necessarily have zero requests - // pending (because the destruction of the previous AD that used this TPIndex - // will have reset this object). We don't want to call into such an AppDomain. - // TODO: fix this another way! - // if (IsRequestPending()) - { - ManagedThreadBase::ThreadPool(QueueUserWorkItemManagedCallback, wasNotRecalled); - } - - if (pThread->IsAbortRequested()) - { - // thread was aborted, and may not have had a chance to tell us it has work. - ThreadpoolMgr::SetAppDomainRequestsActive(); - ThreadpoolMgr::QueueUserWorkItem(NULL, - NULL, - 0, - FALSE); - } - } - - *foundWork = true; - } -} - -#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index f7cdf70d02d195..aadb6397c4fdb4 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -7908,40 +7908,6 @@ UINT64 Thread::GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCoun return total; } -UINT64 Thread::GetTotalThreadPoolCompletionCount() -{ - CONTRACTL { - NOTHROW; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - bool usePortableThreadPool = ThreadpoolMgr::UsePortableThreadPool(); - - // enumerate all threads, summing their local counts. - ThreadStoreLockHolder tsl; - - UINT64 total = GetIOThreadPoolCompletionCountOverflow(); - if (!usePortableThreadPool) - { - total += GetWorkerThreadPoolCompletionCountOverflow(); - } - - Thread *pThread = NULL; - while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) - { - if (!usePortableThreadPool) - { - total += pThread->m_workerThreadPoolCompletionCount; - } - total += pThread->m_ioThreadPoolCompletionCount; - } - - return total; -} - INT32 Thread::ResetManagedThreadObject(INT32 nPriority) { CONTRACTL { diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp index e8e49f4179802a..bbd4dcda462d27 100644 --- a/src/coreclr/vm/win32threadpool.cpp +++ b/src/coreclr/vm/win32threadpool.cpp @@ -275,69 +275,6 @@ NOINLINE void ThreadpoolMgr::EnsureInitializedSlow() } } -DWORD GetDefaultMaxLimitWorkerThreads(DWORD minLimit) -{ - CONTRACTL - { - MODE_ANY; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - // - // We determine the max limit for worker threads as follows: - // - // 1) It must be at least MinLimitTotalWorkerThreads - // 2) It must be no greater than (half the virtual address space)/(thread stack size) - // 3) It must be <= MaxPossibleWorkerThreads - // - // TODO: what about CP threads? Can they follow a similar plan? How do we allocate - // thread counts between the two kinds of threads? - // - SIZE_T stackReserveSize = 0; - Thread::GetProcessDefaultStackSize(&stackReserveSize, NULL); - - ULONGLONG halfVirtualAddressSpace; - - MEMORYSTATUSEX memStats; - memStats.dwLength = sizeof(memStats); - if (GlobalMemoryStatusEx(&memStats)) - { - halfVirtualAddressSpace = memStats.ullTotalVirtual / 2; - } - else - { - //assume the normal Win32 32-bit virtual address space - halfVirtualAddressSpace = 0x000000007FFE0000ull / 2; - } - - ULONGLONG limit = halfVirtualAddressSpace / stackReserveSize; - limit = max(limit, (ULONGLONG)minLimit); - limit = min(limit, (ULONGLONG)ThreadpoolMgr::ThreadCounter::MaxPossibleCount); - - _ASSERTE(FitsIn(limit)); - return (DWORD)limit; -} - -DWORD GetForceMinWorkerThreadsValue() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - return Configuration::GetKnobDWORDValue(W("System.Threading.ThreadPool.MinThreads"), CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads); -} - -DWORD GetForceMaxWorkerThreadsValue() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - return Configuration::GetKnobDWORDValue(W("System.Threading.ThreadPool.MaxThreads"), CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads); -} - BOOL ThreadpoolMgr::Initialize() { CONTRACTL @@ -357,45 +294,11 @@ BOOL ThreadpoolMgr::Initialize() EX_TRY { - if (!UsePortableThreadPool()) - { - WorkerThreadSpinLimit = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit); - IsHillClimbingDisabled = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_Disable) != 0; - ThreadAdjustmentInterval = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow); - - WaitThreadsCriticalSection.Init(CrstThreadpoolWaitThreads); - } - if (!UsePortableThreadPoolForIO()) - { - WorkerCriticalSection.Init(CrstThreadpoolWorker); - } TimerQueueCriticalSection.Init(CrstThreadpoolTimerQueue); - if (!UsePortableThreadPool()) - { - // initialize WaitThreadsHead - InitializeListHead(&WaitThreadsHead); - } - // initialize TimerQueue InitializeListHead(&TimerQueue); - if (!UsePortableThreadPoolForIO()) - { - RetiredCPWakeupEvent = new CLREvent(); - RetiredCPWakeupEvent->CreateAutoEvent(FALSE); - _ASSERTE(RetiredCPWakeupEvent->IsValid()); - } - - if (!UsePortableThreadPool()) - { - WorkerSemaphore = new CLRLifoSemaphore(); - WorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); - - RetiredWorkerSemaphore = new CLRLifoSemaphore(); - RetiredWorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); - } - #ifndef TARGET_UNIX //ThreadPool_CPUGroup if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) @@ -408,21 +311,7 @@ BOOL ThreadpoolMgr::Initialize() } EX_CATCH { - if (!UsePortableThreadPoolForIO() && RetiredCPWakeupEvent) - { - delete RetiredCPWakeupEvent; - RetiredCPWakeupEvent = NULL; - } - // Note: It is fine to call Destroy on uninitialized critical sections - if (!UsePortableThreadPool()) - { - WaitThreadsCriticalSection.Destroy(); - } - if (!UsePortableThreadPoolForIO()) - { - WorkerCriticalSection.Destroy(); - } TimerQueueCriticalSection.Destroy(); bExceptionCaught = TRUE; @@ -434,55 +323,6 @@ BOOL ThreadpoolMgr::Initialize() goto end; } - if (!UsePortableThreadPool()) - { - // initialize Worker thread settings - DWORD forceMin; - forceMin = GetForceMinWorkerThreadsValue(); - MinLimitTotalWorkerThreads = forceMin > 0 ? (LONG)forceMin : (LONG)NumberOfProcessors; - - DWORD forceMax; - forceMax = GetForceMaxWorkerThreadsValue(); - MaxLimitTotalWorkerThreads = forceMax > 0 ? (LONG)forceMax : (LONG)GetDefaultMaxLimitWorkerThreads(MinLimitTotalWorkerThreads); - - ThreadCounter::Counts counts; - counts.NumActive = 0; - counts.NumWorking = 0; - counts.NumRetired = 0; - counts.MaxWorking = MinLimitTotalWorkerThreads; - WorkerCounter.counts.AsLongLong = counts.AsLongLong; - } - - if (!UsePortableThreadPoolForIO()) - { - // initialize CP thread settings - MinLimitTotalCPThreads = NumberOfProcessors; - - // Use volatile store to guarantee make the value visible to the DAC (the store can be optimized out otherwise) - VolatileStoreWithoutBarrier(&MaxFreeCPThreads, NumberOfProcessors*MaxFreeCPThreadsPerCPU); - - ThreadCounter::Counts counts; - counts.NumActive = 0; - counts.NumWorking = 0; - counts.NumRetired = 0; - counts.MaxWorking = MinLimitTotalCPThreads; - CPThreadCounter.counts.AsLongLong = counts.AsLongLong; - -#ifndef TARGET_UNIX - { - GlobalCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, - NULL, - 0, /*ignored for invalid handle value*/ - NumberOfProcessors); - } -#endif // !TARGET_UNIX - } - - if (!UsePortableThreadPool()) - { - HillClimbingInstance.Initialize(); - } - bRet = TRUE; end: return bRet; @@ -505,11 +345,6 @@ void ThreadpoolMgr::InitPlatformVariables() CONTRACT_VIOLATION(GCViolation|FaultViolation); hNtDll = CLRLoadLibrary(W("ntdll.dll")); _ASSERTE(hNtDll); - if (!UsePortableThreadPool()) - { - hCoreSynch = CLRLoadLibrary(W("api-ms-win-core-synch-l1-1-0.dll")); - _ASSERTE(hCoreSynch); - } } // These APIs must be accessed via dynamic binding since they may be removed in future @@ -517,299 +352,9 @@ void ThreadpoolMgr::InitPlatformVariables() g_pufnNtQueryInformationThread = (NtQueryInformationThreadProc)GetProcAddress(hNtDll,"NtQueryInformationThread"); g_pufnNtQuerySystemInformation = (NtQuerySystemInformationProc)GetProcAddress(hNtDll,"NtQuerySystemInformation"); - if (!UsePortableThreadPool()) - { - // These APIs are only supported on newer Windows versions - g_pufnCreateWaitableTimerEx = (CreateWaitableTimerExProc)GetProcAddress(hCoreSynch, "CreateWaitableTimerExW"); - g_pufnSetWaitableTimerEx = (SetWaitableTimerExProc)GetProcAddress(hCoreSynch, "SetWaitableTimerEx"); - } #endif } -bool ThreadpoolMgr::CanSetMinIOCompletionThreads(DWORD ioCompletionThreads) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(UsePortableThreadPool()); - _ASSERTE(!UsePortableThreadPoolForIO()); - - EnsureInitialized(); - - // The lock used by SetMinThreads() and SetMaxThreads() is not taken here, the caller is expected to synchronize between - // them. The conditions here should be the same as in the corresponding Set function. - return ioCompletionThreads <= (DWORD)MaxLimitTotalCPThreads; -} - -bool ThreadpoolMgr::CanSetMaxIOCompletionThreads(DWORD ioCompletionThreads) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(UsePortableThreadPool()); - _ASSERTE(!UsePortableThreadPoolForIO()); - _ASSERTE(ioCompletionThreads != 0); - - EnsureInitialized(); - - // The lock used by SetMinThreads() and SetMaxThreads() is not taken here, the caller is expected to synchronize between - // them. The conditions here should be the same as in the corresponding Set function. - return ioCompletionThreads >= (DWORD)MinLimitTotalCPThreads; -} - -BOOL ThreadpoolMgr::SetMaxThreadsHelper(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads) -{ - CONTRACTL - { - THROWS; // Crst can throw and toggle GC mode - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - BOOL result = FALSE; - - // doesn't need to be WorkerCS, but using it to avoid race condition between setting min and max, and didn't want to create a new CS. - CrstHolder csh(&WorkerCriticalSection); - - bool usePortableThreadPool = UsePortableThreadPool(); - if (( - usePortableThreadPool || - ( - MaxWorkerThreads >= (DWORD)MinLimitTotalWorkerThreads && - MaxWorkerThreads != 0 - ) - ) && - MaxIOCompletionThreads >= (DWORD)MinLimitTotalCPThreads && - MaxIOCompletionThreads != 0) - { - if (!usePortableThreadPool && GetForceMaxWorkerThreadsValue() == 0) - { - MaxLimitTotalWorkerThreads = min(MaxWorkerThreads, (DWORD)ThreadCounter::MaxPossibleCount); - - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - while (counts.MaxWorking > MaxLimitTotalWorkerThreads) - { - ThreadCounter::Counts newCounts = counts; - newCounts.MaxWorking = MaxLimitTotalWorkerThreads; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - counts = newCounts; - else - counts = oldCounts; - } - } - - MaxLimitTotalCPThreads = min(MaxIOCompletionThreads, (DWORD)ThreadCounter::MaxPossibleCount); - - result = TRUE; - } - - return result; - } - -/************************************************************************/ -BOOL ThreadpoolMgr::SetMaxThreads(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads) -{ - CONTRACTL - { - THROWS; // SetMaxThreadsHelper can throw and toggle GC mode - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - EnsureInitialized(); - - return SetMaxThreadsHelper(MaxWorkerThreads, MaxIOCompletionThreads); -} - -BOOL ThreadpoolMgr::GetMaxThreads(DWORD* MaxWorkerThreads, - DWORD* MaxIOCompletionThreads) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!MaxWorkerThreads || !MaxIOCompletionThreads) - { - SetLastHRError(ERROR_INVALID_DATA); - return FALSE; - } - - EnsureInitialized(); - - *MaxWorkerThreads = UsePortableThreadPool() ? 1 : (DWORD)MaxLimitTotalWorkerThreads; - *MaxIOCompletionThreads = MaxLimitTotalCPThreads; - return TRUE; -} - -BOOL ThreadpoolMgr::SetMinThreads(DWORD MinWorkerThreads, - DWORD MinIOCompletionThreads) -{ - CONTRACTL - { - THROWS; // Crst can throw and toggle GC mode - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - EnsureInitialized(); - - // doesn't need to be WorkerCS, but using it to avoid race condition between setting min and max, and didn't want to create a new CS. - CrstHolder csh(&WorkerCriticalSection); - - BOOL init_result = FALSE; - - bool usePortableThreadPool = UsePortableThreadPool(); - if (( - usePortableThreadPool || - ( - MinWorkerThreads >= 0 && - MinWorkerThreads <= (DWORD) MaxLimitTotalWorkerThreads - ) - ) && - MinIOCompletionThreads >= 0 && - MinIOCompletionThreads <= (DWORD) MaxLimitTotalCPThreads) - { - if (!usePortableThreadPool && GetForceMinWorkerThreadsValue() == 0) - { - MinLimitTotalWorkerThreads = max(1, min(MinWorkerThreads, (DWORD)ThreadCounter::MaxPossibleCount)); - - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - while (counts.MaxWorking < MinLimitTotalWorkerThreads) - { - ThreadCounter::Counts newCounts = counts; - newCounts.MaxWorking = MinLimitTotalWorkerThreads; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - { - counts = newCounts; - - // if we increased the limit, and there are pending workitems, we need - // to dispatch a thread to process the work. - if (newCounts.MaxWorking > oldCounts.MaxWorking && - PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains()) - { - MaybeAddWorkingWorker(); - } - } - else - { - counts = oldCounts; - } - } - } - - MinLimitTotalCPThreads = max(1, min(MinIOCompletionThreads, (DWORD)ThreadCounter::MaxPossibleCount)); - - init_result = TRUE; - } - - return init_result; -} - -BOOL ThreadpoolMgr::GetMinThreads(DWORD* MinWorkerThreads, - DWORD* MinIOCompletionThreads) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!MinWorkerThreads || !MinIOCompletionThreads) - { - SetLastHRError(ERROR_INVALID_DATA); - return FALSE; - } - - EnsureInitialized(); - - *MinWorkerThreads = UsePortableThreadPool() ? 1 : (DWORD)MinLimitTotalWorkerThreads; - *MinIOCompletionThreads = MinLimitTotalCPThreads; - return TRUE; -} - -BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, - DWORD* AvailableIOCompletionThreads) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!AvailableWorkerThreads || !AvailableIOCompletionThreads) - { - SetLastHRError(ERROR_INVALID_DATA); - return FALSE; - } - - EnsureInitialized(); - - if (UsePortableThreadPool()) - { - *AvailableWorkerThreads = 0; - } - else - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - - if (MaxLimitTotalWorkerThreads < counts.NumActive) - *AvailableWorkerThreads = 0; - else - *AvailableWorkerThreads = MaxLimitTotalWorkerThreads - counts.NumWorking; - } - - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - if (MaxLimitTotalCPThreads < counts.NumActive) - *AvailableIOCompletionThreads = counts.NumActive - counts.NumWorking; - else - *AvailableIOCompletionThreads = MaxLimitTotalCPThreads - counts.NumWorking; - return TRUE; -} - -INT32 ThreadpoolMgr::GetThreadCount() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!IsInitialized()) - { - return 0; - } - - INT32 workerThreadCount = UsePortableThreadPool() ? 0 : WorkerCounter.DangerousGetDirtyCounts().NumActive; - return workerThreadCount + CPThreadCounter.DangerousGetDirtyCounts().NumActive; -} - -void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_ANY; - /* Cannot use contract here because of SEH - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END;*/ - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - Function(Context); - - Thread *pThread = GetThreadNULLOk(); - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } -} - // // WorkingThreadCounts tracks the number of worker threads currently doing user work, and the maximum number of such threads // since the last time TakeMaxWorkingThreadCount was called. This information is for diagnostic purposes only, @@ -901,2591 +446,481 @@ int TakeMaxWorkingThreadCount() /************************************************************************/ -BOOL ThreadpoolMgr::QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, - PVOID Context, - DWORD Flags, - BOOL UnmanagedTPRequest) +DangerousNonHostedSpinLock ThreadpoolMgr::ThreadAdjustmentLock; + +void ThreadpoolMgr::WaitIOCompletionCallback( + DWORD dwErrorCode, + DWORD numBytesTransferred, + LPOVERLAPPED lpOverlapped) { CONTRACTL { - THROWS; // EnsureInitialized, EnqueueWorkRequest can throw OOM - GC_TRIGGERS; + THROWS; MODE_ANY; } CONTRACTL_END; - _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPool()); + if (dwErrorCode == ERROR_SUCCESS) + DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); +} - EnsureInitialized(); +extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, + DWORD numBytesTransferred, + LPOVERLAPPED lpOverlapped); - if (Flags == CALL_OR_QUEUE) +// Remove a block from the appropriate recycleList and return. +// If recycleList is empty, fall back to new. +LPVOID ThreadpoolMgr::GetRecycledMemory(enum MemType memType) +{ + LPVOID result = NULL; + CONTRACT(LPVOID) { - // we've been asked to call this directly if the thread pressure is not too high - - int MinimumAvailableCPThreads = (NumberOfProcessors < 3) ? 3 : NumberOfProcessors; + THROWS; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM()); + POSTCONDITION(CheckPointer(result)); + } CONTRACT_END; - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - if ((MaxLimitTotalCPThreads - counts.NumActive) >= MinimumAvailableCPThreads ) - { - QueueUserWorkItemHelp(Function, Context); - return TRUE; - } - - } - - if (UnmanagedTPRequest) - { - UnManagedPerAppDomainTPCount* pADTPCount; - pADTPCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - pADTPCount->QueueUnmanagedWorkRequest(Function, Context); - } - else + if(RecycledLists.IsInitialized()) { - // caller has already registered its TPCount; this call is just to adjust the thread count - } - - return TRUE; -} - - -bool ThreadpoolMgr::ShouldWorkerKeepRunning() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); + RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - // - // Maybe this thread should retire now. Let's see. - // - bool shouldThisThreadKeepRunning = true; + result = list.Remove(); + } - // Dirty read is OK here; the worst that can happen is that we won't retire this time. In the - // case where we might retire, we have to succeed a CompareExchange, which will have the effect - // of validating this read. - ThreadCounter::Counts counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) + if(result == NULL) { - if (counts.NumActive <= counts.MaxWorking) - { - shouldThisThreadKeepRunning = true; - break; - } - - ThreadCounter::Counts newCounts = counts; - newCounts.NumWorking--; - newCounts.NumActive--; - newCounts.NumRetired++; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) + switch (memType) { - shouldThisThreadKeepRunning = false; - break; + case MEMTYPE_DelegateInfo: + result = new DelegateInfo; + break; + case MEMTYPE_AsyncCallback: + result = new AsyncCallback; + break; + case MEMTYPE_WorkRequest: + result = new WorkRequest; + break; + default: + _ASSERTE(!"Unknown Memtype"); + result = NULL; + break; } - - counts = oldCounts; } - return shouldThisThreadKeepRunning; + RETURN result; } -DangerousNonHostedSpinLock ThreadpoolMgr::ThreadAdjustmentLock; - - -// -// This method must only be called if ShouldAdjustMaxWorkersActive has returned true, *and* -// ThreadAdjustmentLock is held. -// -void ThreadpoolMgr::AdjustMaxWorkersActive() +// Insert freed block in recycle list. If list is full, return to system heap +void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) { CONTRACTL { NOTHROW; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(ThreadAdjustmentLock.IsHeld()); - - LARGE_INTEGER startTime = CurrentSampleStartTime; - LARGE_INTEGER endTime; - QueryPerformanceCounter(&endTime); - - static LARGE_INTEGER freq; - if (freq.QuadPart == 0) - QueryPerformanceFrequency(&freq); - - double elapsed = (double)(endTime.QuadPart - startTime.QuadPart) / freq.QuadPart; - - // - // It's possible for the current sample to be reset while we're holding - // ThreadAdjustmentLock. This will result in a very short sample, possibly - // with completely bogus counts. We'll try to detect this by checking the sample - // interval; if it's very short, then we try again later. - // - if (elapsed*1000.0 >= (ThreadAdjustmentInterval/2)) + if(RecycledLists.IsInitialized()) { - DWORD currentTicks = GetTickCount(); - LONG totalNumCompletions = (LONG)Thread::GetTotalWorkerThreadPoolCompletionCount(); - LONG numCompletions = totalNumCompletions - VolatileLoad(&PriorCompletedWorkRequests); - ThreadCounter::Counts currentCounts = WorkerCounter.GetCleanCounts(); - - int newMax = HillClimbingInstance.Update( - currentCounts.MaxWorking, - elapsed, - numCompletions, - &ThreadAdjustmentInterval); - - while (newMax != currentCounts.MaxWorking) - { - ThreadCounter::Counts newCounts = currentCounts; - newCounts.MaxWorking = newMax; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, currentCounts); - if (oldCounts == currentCounts) - { - // - // If we're increasing the max, inject a thread. If that thread finds work, it will inject - // another thread, etc., until nobody finds work or we reach the new maximum. - // - // If we're reducing the max, whichever threads notice this first will retire themselves. - // - if (newMax > oldCounts.MaxWorking) - MaybeAddWorkingWorker(); - - break; - } - else - { - // we failed - maybe try again - if (oldCounts.MaxWorking > currentCounts.MaxWorking && - oldCounts.MaxWorking >= newMax) - { - // someone (probably the gate thread) increased the thread count more than - // we are about to do. Don't interfere. - break; - } + RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - currentCounts = oldCounts; - } + if(list.CanInsert()) + { + list.Insert( mem ); + return; } + } + + switch (memType) + { + case MEMTYPE_DelegateInfo: + delete (DelegateInfo*) mem; + break; + case MEMTYPE_AsyncCallback: + delete (AsyncCallback*) mem; + break; + case MEMTYPE_WorkRequest: + delete (WorkRequest*) mem; + break; + default: + _ASSERTE(!"Unknown Memtype"); - PriorCompletedWorkRequests = totalNumCompletions; - NextCompletedWorkRequestsTime = currentTicks + ThreadAdjustmentInterval; - MemoryBarrier(); // flush previous writes (especially NextCompletedWorkRequestsTime) - PriorCompletedWorkRequestsTime = currentTicks; - CurrentSampleStartTime = endTime;; } } - -void ThreadpoolMgr::MaybeAddWorkingWorker() +Thread* ThreadpoolMgr::CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread) { + STATIC_CONTRACT_NOTHROW; + if (GetThreadNULLOk()) { STATIC_CONTRACT_GC_TRIGGERS;} else {DISABLED(STATIC_CONTRACT_GC_NOTRIGGER);} + STATIC_CONTRACT_MODE_ANY; + /* cannot use contract because of SEH CONTRACTL { NOTHROW; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + GC_NOTRIGGER; MODE_ANY; } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - // counts volatile read paired with CompareExchangeCounts loop set - ThreadCounter::Counts counts = WorkerCounter.DangerousGetDirtyCounts(); - ThreadCounter::Counts newCounts; - while (true) - { - newCounts = counts; - newCounts.NumWorking = max(counts.NumWorking, min(counts.NumWorking + 1, counts.MaxWorking)); - newCounts.NumActive = max(counts.NumActive, newCounts.NumWorking); - newCounts.NumRetired = max(0, counts.NumRetired - (newCounts.NumActive - counts.NumActive)); - - if (newCounts == counts) - return; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - break; - - counts = oldCounts; - } - - int toUnretire = counts.NumRetired - newCounts.NumRetired; - int toCreate = (newCounts.NumActive - counts.NumActive) - toUnretire; - int toRelease = (newCounts.NumWorking - counts.NumWorking) - (toUnretire + toCreate); + CONTRACTL_END;*/ - _ASSERTE(toUnretire >= 0); - _ASSERTE(toCreate >= 0); - _ASSERTE(toRelease >= 0); - _ASSERTE(toUnretire + toCreate + toRelease <= 1); + Thread* pThread = NULL; - if (toUnretire > 0) - { - RetiredWorkerSemaphore->Release(toUnretire); + if (g_fEEStarted) { + *pIsCLRThread = TRUE; } - - if (toRelease > 0) - WorkerSemaphore->Release(toRelease); - - while (toCreate > 0) - { - if (CreateWorkerThread()) + else + *pIsCLRThread = FALSE; + if (*pIsCLRThread) { + EX_TRY { - toCreate--; + pThread = SetupUnstartedThread(); } - else + EX_CATCH { - // - // Uh-oh, we promised to create a new thread, but the creation failed. We have to renege on our - // promise. This may possibly result in no work getting done for a while, but the gate thread will - // eventually notice that no completions are happening and force the creation of a new thread. - // Of course, there's no guarantee *that* will work - but hopefully enough time will have passed - // to allow whoever's using all the memory right now to release some. - // - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - // - // If we said we would create a thread, we also said it would be working. So we need to - // decrement both NumWorking and NumActive by the number of threads we will no longer be creating. - // - newCounts = counts; - newCounts.NumWorking -= toCreate; - newCounts.NumActive -= toCreate; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - break; - - counts = oldCounts; - } - - toCreate = 0; + pThread = NULL; + } + EX_END_CATCH(SwallowAllExceptions); + if (pThread == NULL) { + return NULL; } } -} + DWORD threadId; + BOOL bOK = FALSE; + HANDLE threadHandle = NULL; -BOOL ThreadpoolMgr::PostQueuedCompletionStatus(LPOVERLAPPED lpOverlapped, - LPOVERLAPPED_COMPLETION_ROUTINE Function) -{ - CONTRACTL - { - THROWS; // EnsureInitialized can throw OOM - GC_TRIGGERS; - MODE_ANY; + if (*pIsCLRThread) { + // CreateNewThread takes care of reverting any impersonation - so dont do anything here. + bOK = pThread->CreateNewThread(0, // default stack size + lpStartAddress, + lpArgs, //arguments + W(".NET ThreadPool Worker")); } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - + else { #ifndef TARGET_UNIX - EnsureInitialized(); - - _ASSERTE(GlobalCompletionPort != NULL); - - if (!InitCompletionPortThreadpool) - InitCompletionPortThreadpool = TRUE; - - GrowCompletionPortThreadpoolIfNeeded(); - - // In order to allow external ETW listeners to correlate activities that use our IO completion port - // as a dispatch mechanism, we have to ensure the runtime's calls to ::PostQueuedCompletionStatus - // and ::GetQueuedCompletionStatus are "annotated" with ETW events representing to operations - // performed. - // There are currently 2 codepaths that post to the GlobalCompletionPort: - // 1. the managed API ThreadPool.UnsafeQueueNativeOverlapped(), calling CorPostQueuedCompletionStatus() - // which already fires the ETW event as needed - // 2. the managed API ThreadPool.RegisterWaitForSingleObject which needs to fire the ETW event - // at the time the managed API is called (on the orignial user thread), and not when the ::PQCS - // is called (from the dedicated wait thread). - // If additional codepaths appear they need to either fire the ETW event before calling this or ensure - // we do not fire an unmatched "dequeue" event in ThreadpoolMgr::CompletionPortThreadStart - // The current possible values for Function: - // - BindIoCompletionCallbackStub for ThreadPool.UnsafeQueueNativeOverlapped - // - WaitIOCompletionCallback for ThreadPool.RegisterWaitForSingleObject - - return ::PostQueuedCompletionStatus(GlobalCompletionPort, - 0, - (ULONG_PTR) Function, - lpOverlapped); -#else - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; + HandleHolder token; + BOOL bReverted = FALSE; + bOK = RevertIfImpersonated(&bReverted, &token); + if (bOK != TRUE) + return NULL; #endif // !TARGET_UNIX -} + threadHandle = CreateThread(NULL, // security descriptor + 0, // default stack size + lpStartAddress, + lpArgs, + CREATE_SUSPENDED, + &threadId); + SetThreadName(threadHandle, W(".NET ThreadPool Worker")); +#ifndef TARGET_UNIX + UndoRevert(bReverted, token); +#endif // !TARGET_UNIX + } -void ThreadpoolMgr::WaitIOCompletionCallback( - DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped) -{ - CONTRACTL + if (*pIsCLRThread && !bOK) { - THROWS; - MODE_ANY; + pThread->DecExternalCount(FALSE); + pThread = NULL; } - CONTRACTL_END; - if (dwErrorCode == ERROR_SUCCESS) - DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); + if (*pIsCLRThread) { + return pThread; + } + else + return (Thread*)threadHandle; } -extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - -// This is either made by a worker thread or a CP thread -// indicated by threadTypeStatus -void ThreadpoolMgr::EnsureGateThreadRunning() +// this should only be called by unmanaged thread (i.e. there should be no mgd +// caller on the stack) since we are swallowing terminal exceptions +DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) { - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (UsePortableThreadPool()) + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_PREEMPTIVE; + /* cannot use contract because of SEH + CONTRACTL { - GCX_COOP(); - MethodDescCallSite(METHOD__THREAD_POOL__ENSURE_GATE_THREAD_RUNNING).Call(NULL); - return; + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; } + CONTRACTL_END;*/ - while (true) + DWORD status = WAIT_TIMEOUT; + EX_TRY { - switch (GateThreadStatus) - { - case GATE_THREAD_STATUS_REQUESTED: - // - // No action needed; the gate thread is running, and someone else has already registered a request - // for it to stay. - // - return; - - case GATE_THREAD_STATUS_WAITING_FOR_REQUEST: - // - // Prevent the gate thread from exiting, if it hasn't already done so. If it has, we'll create it on the next iteration of - // this loop. - // - InterlockedCompareExchange(&GateThreadStatus, GATE_THREAD_STATUS_REQUESTED, GATE_THREAD_STATUS_WAITING_FOR_REQUEST); - break; - - case GATE_THREAD_STATUS_NOT_RUNNING: - // - // We need to create a new gate thread - // - if (InterlockedCompareExchange(&GateThreadStatus, GATE_THREAD_STATUS_REQUESTED, GATE_THREAD_STATUS_NOT_RUNNING) == GATE_THREAD_STATUS_NOT_RUNNING) - { - if (!CreateGateThread()) - { - // - // If we failed to create the gate thread, someone else will need to try again later. - // - GateThreadStatus = GATE_THREAD_STATUS_NOT_RUNNING; - } - return; - } - break; - - default: - _ASSERTE(!"Invalid value of ThreadpoolMgr::GateThreadStatus"); - } + status = ev->Wait(sleepTime,FALSE); } -} - -bool ThreadpoolMgr::NeedGateThreadForIOCompletions() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!InitCompletionPortThreadpool) + EX_CATCH { - return false; } - - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - return counts.NumActive <= counts.NumWorking; + EX_END_CATCH(SwallowAllExceptions) + return status; } -bool ThreadpoolMgr::ShouldGateThreadKeepRunning() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST || - GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); - - // - // Switch to WAITING_FOR_REQUEST, and see if we had a request since the last check. - // - LONG previousStatus = InterlockedExchange(&GateThreadStatus, GATE_THREAD_STATUS_WAITING_FOR_REQUEST); +/************************************************************************/ - if (previousStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST) - { - // - // No recent requests for the gate thread. Check to see if we're still needed. - // - - // - // Are there any free threads in the I/O completion pool? If there are, we don't need a gate thread. - // This implies that whenever we decrement NumFreeCPThreads to 0, we need to call EnsureGateThreadRunning(). - // - bool needGateThreadForCompletionPort = NeedGateThreadForIOCompletions(); - - // - // Are there any work requests in any worker queue? If so, we need a gate thread. - // This imples that whenever a work queue goes from empty to non-empty, we need to call EnsureGateThreadRunning(). - // - bool needGateThreadForWorkerThreads = PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains(); - - // - // If worker tracking is enabled, we need to fire periodic ETW events with active worker counts. This is - // done by the gate thread. - // We don't have to do anything special with EnsureGateThreadRunning() here, because this is only needed - // once work has been added to the queue for the first time (which is covered above). - // - bool needGateThreadForWorkerTracking = - 0 != CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking); - - if (!(needGateThreadForCompletionPort || - needGateThreadForWorkerThreads || - needGateThreadForWorkerTracking)) - { - // - // It looks like we shouldn't be running. But another thread may now tell us to run. If so, they will set GateThreadStatus - // back to GATE_THREAD_STATUS_REQUESTED. - // - previousStatus = InterlockedCompareExchange(&GateThreadStatus, GATE_THREAD_STATUS_NOT_RUNNING, GATE_THREAD_STATUS_WAITING_FOR_REQUEST); - if (previousStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST) - return false; - } - } +// if no wraparound that the timer is expired if duetime is less than current time +// if wraparound occurred, then the timer expired if dueTime was greater than last time or dueTime is less equal to current time +#define TimeExpired(last,now,duetime) ((last) <= (now) ? \ + ((duetime) <= (now) && (duetime) >= (last)): \ + ((duetime) >= (last) || (duetime) <= (now))) - _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST || - GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); - return true; -} - - - -//************************************************************************ -void ThreadpoolMgr::EnqueueWorkRequest(WorkRequest* workRequest) -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - AppendWorkRequest(workRequest); -} - -WorkRequest* ThreadpoolMgr::DequeueWorkRequest() -{ - WorkRequest* entry = NULL; - CONTRACT(WorkRequest*) - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - - POSTCONDITION(CheckPointer(entry, NULL_OK)); - } CONTRACT_END; +#define TimeInterval(end,start) ((end) > (start) ? ((end) - (start)) : ((0xffffffff - (start)) + (end) + 1)) - _ASSERTE(!UsePortableThreadPool()); +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (disable : 4716) +#else +#pragma warning (disable : 4715) +#endif +#endif +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:22008) // "Prefast integer overflow check on (0 + lval) is bogus. Tried local disable without luck, doing whole method." +#endif - entry = RemoveWorkRequest(); +#ifdef _PREFAST_ +#pragma warning(pop) +#endif - RETURN entry; -} +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (default : 4716) +#else +#pragma warning (default : 4715) +#endif +#endif -void ThreadpoolMgr::ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled) +void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, + unsigned index, + BOOL waitTimedOut + ) { + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_PREEMPTIVE; + /* cannot use contract because of SEH CONTRACTL { - THROWS; // QueueUserWorkItem can throw + THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); + CONTRACTL_END;*/ - IPerAppDomainTPCount* pAdCount; + AsyncCallback* asyncCallback = NULL; + EX_TRY{ + if ( waitInfo->flag & WAIT_SINGLE_EXECUTION) + { + DeactivateNthWait (waitInfo,index) ; + } + else + { // reactivate wait by resetting timer + waitInfo->timer.startTime = GetTickCount(); + } - LONG index = PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch(); + asyncCallback = MakeAsyncCallback(); + if (asyncCallback) + { + asyncCallback->wait = waitInfo; + asyncCallback->waitTimedOut = waitTimedOut; - if (index == 0) - { - *foundWork = false; - *wasNotRecalled = true; - return; - } + InterlockedIncrement(&waitInfo->refCount); - if (index == -1) - { - pAdCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); +#ifndef TARGET_UNIX + if (FALSE == PostQueuedCompletionStatus((LPOVERLAPPED)asyncCallback, (LPOVERLAPPED_COMPLETION_ROUTINE)WaitIOCompletionCallback)) +#else // TARGET_UNIX + if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) +#endif // !TARGET_UNIX + ReleaseAsyncCallback(asyncCallback); + } } - else - { + EX_CATCH { + if (asyncCallback) + ReleaseAsyncCallback(asyncCallback); - pAdCount = PerAppDomainTPCountList::GetPerAppdomainCount(TPIndex((DWORD)index)); - _ASSERTE(pAdCount); + EX_RETHROW; } - - pAdCount->DispatchWorkItem(foundWork, wasNotRecalled); + EX_END_CATCH(SwallowAllExceptions); } -//-------------------------------------------------------------------------- -//This function informs the thread scheduler that the first requests has been -//queued on an appdomain, or it's the first unmanaged TP request. -//Arguments: -// UnmanagedTP: Indicates that the request arises from the unmanaged -//part of Thread Pool. -//Assumptions: -// This function must be called under a per-appdomain lock or the -//correct lock under unmanaged TP queue. -// -BOOL ThreadpoolMgr::SetAppDomainRequestsActive(BOOL UnmanagedTP) + +DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) { CONTRACTL { - NOTHROW; - MODE_ANY; + THROWS; + MODE_PREEMPTIVE; GC_TRIGGERS; } CONTRACTL_END; - _ASSERTE(!UsePortableThreadPool()); - - BOOL fShouldSignalEvent = FALSE; - - IPerAppDomainTPCount* pAdCount; - - if(UnmanagedTP) - { - pAdCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - _ASSERTE(pAdCount); - } - else + Thread * pThread = GetThreadNULLOk(); + if (pThread == NULL) { - Thread* pCurThread = GetThread(); - AppDomain* pAppDomain = pCurThread->GetDomain(); - _ASSERTE(pAppDomain); + HRESULT hr = ERROR_SUCCESS; - TPIndex tpindex = pAppDomain->GetTPIndex(); - pAdCount = PerAppDomainTPCountList::GetPerAppdomainCount(tpindex); + ClrFlsSetThreadType(ThreadType_Threadpool_Worker); + pThread = SetupThreadNoThrow(&hr); - _ASSERTE(pAdCount); + if (pThread == NULL) + { + return hr; + } } - pAdCount->SetAppDomainRequestsActive(); - - return fShouldSignalEvent; -} - -void ThreadpoolMgr::ClearAppDomainRequestsActive(BOOL UnmanagedTP, LONG id) -//-------------------------------------------------------------------------- -//This function informs the thread scheduler that the kast request has been -//dequeued on an appdomain, or it's the last unmanaged TP request. -//Arguments: -// UnmanagedTP: Indicates that the request arises from the unmanaged -//part of Thread Pool. -// id: Indicates the id of the appdomain. The id is needed as this -//function can be called (indirectly) from the appdomain unload thread from -//unmanaged code to clear per-appdomain state during rude unload. -//Assumptions: -// This function must be called under a per-appdomain lock or the -//correct lock under unmanaged TP queue. -// -{ - CONTRACTL { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; + AsyncCallback * asyncCallback = (AsyncCallback*) pArgs; - _ASSERTE(!UsePortableThreadPool()); + WaitInfo * waitInfo = asyncCallback->wait; - IPerAppDomainTPCount* pAdCount; + AsyncCallbackHolder asyncCBHolder; + asyncCBHolder.Assign(asyncCallback); - if(UnmanagedTP) - { - pAdCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - _ASSERTE(pAdCount); - } - else - { - Thread* pCurThread = GetThread(); - AppDomain* pAppDomain = pCurThread->GetDomain(); - _ASSERTE(pAppDomain); + // We fire the "dequeue" ETW event here, before executing the user code, to enable correlation with + // the ThreadPoolIOEnqueue fired in ThreadpoolMgr::RegisterWaitForSingleObject + if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) + FireEtwThreadPoolIODequeue(waitInfo, reinterpret_cast(waitInfo->Callback), GetClrInstanceId()); - TPIndex tpindex = pAppDomain->GetTPIndex(); + // the user callback can throw, the host must be prepared to handle it. + // SQL is ok, since they have a top-level SEH handler. However, there's + // no easy way to verify it - pAdCount = PerAppDomainTPCountList::GetPerAppdomainCount(tpindex); + ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) + ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); - _ASSERTE(pAdCount); +#ifndef TARGET_UNIX + Thread::IncrementIOThreadPoolCompletionCount(pThread); +#endif } - pAdCount->ClearAppDomainRequestsActive(); + return ERROR_SUCCESS; } - -// Remove a block from the appropriate recycleList and return. -// If recycleList is empty, fall back to new. -LPVOID ThreadpoolMgr::GetRecycledMemory(enum MemType memType) +void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) { - LPVOID result = NULL; - CONTRACT(LPVOID) - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - POSTCONDITION(CheckPointer(result)); - } CONTRACT_END; + LIMITED_METHOD_CONTRACT; - if(RecycledLists.IsInitialized()) + ThreadCB* threadCB = waitInfo->threadCB; + DWORD endIndex = threadCB->NumActiveWaits-1; + DWORD index; + + for (index = 0; index <= endIndex; index++) { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); + LIST_ENTRY* head = &(threadCB->waitPointer[index]); + LIST_ENTRY* current = head; + do { + if (current->Flink == (PVOID) waitInfo) + goto FOUND; - result = list.Remove(); - } + current = (LIST_ENTRY*) current->Flink; - if(result == NULL) - { - switch (memType) - { - case MEMTYPE_DelegateInfo: - result = new DelegateInfo; - break; - case MEMTYPE_AsyncCallback: - result = new AsyncCallback; - break; - case MEMTYPE_WorkRequest: - result = new WorkRequest; - break; - default: - _ASSERTE(!"Unknown Memtype"); - result = NULL; - break; - } + } while (current != head); } - RETURN result; +FOUND: + _ASSERTE(index <= endIndex); + + DeactivateNthWait(waitInfo, index); } -// Insert freed block in recycle list. If list is full, return to system heap -void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) + +void ThreadpoolMgr::DeleteWait(WaitInfo* waitInfo) { CONTRACTL { - NOTHROW; - GC_NOTRIGGER; + if (waitInfo->ExternalEventSafeHandle != NULL) { THROWS;} else { NOTHROW; } MODE_ANY; + if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} } CONTRACTL_END; - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); + if(waitInfo->Context && (waitInfo->flag & WAIT_FREE_CONTEXT)) { + DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - if(list.CanInsert()) + // Since the delegate release destroys a handle, we need to be in + // co-operative mode { - list.Insert( mem ); - return; + GCX_COOP(); + pDelegate->Release(); } + + RecycleMemory( pDelegate, MEMTYPE_DelegateInfo ); } - switch (memType) + if (waitInfo->flag & WAIT_INTERNAL_COMPLETION) { - case MEMTYPE_DelegateInfo: - delete (DelegateInfo*) mem; - break; - case MEMTYPE_AsyncCallback: - delete (AsyncCallback*) mem; - break; - case MEMTYPE_WorkRequest: - delete (WorkRequest*) mem; - break; - default: - _ASSERTE(!"Unknown Memtype"); - + waitInfo->InternalCompletionEvent.Set(); + return; // waitInfo will be deleted by the thread that's waiting on this event } -} - -Thread* ThreadpoolMgr::CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread) -{ - STATIC_CONTRACT_NOTHROW; - if (GetThreadNULLOk()) { STATIC_CONTRACT_GC_TRIGGERS;} else {DISABLED(STATIC_CONTRACT_GC_NOTRIGGER);} - STATIC_CONTRACT_MODE_ANY; - /* cannot use contract because of SEH - CONTRACTL + else if (waitInfo->ExternalCompletionEvent != INVALID_HANDLE) { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END;*/ - - Thread* pThread = NULL; - - if (g_fEEStarted) { - *pIsCLRThread = TRUE; - } - else - *pIsCLRThread = FALSE; - if (*pIsCLRThread) { - EX_TRY - { - pThread = SetupUnstartedThread(); - } - EX_CATCH - { - pThread = NULL; - } - EX_END_CATCH(SwallowAllExceptions); - if (pThread == NULL) { - return NULL; - } - } - DWORD threadId; - BOOL bOK = FALSE; - HANDLE threadHandle = NULL; - - if (*pIsCLRThread) { - // CreateNewThread takes care of reverting any impersonation - so dont do anything here. - bOK = pThread->CreateNewThread(0, // default stack size - lpStartAddress, - lpArgs, //arguments - W(".NET ThreadPool Worker")); - } - else { -#ifndef TARGET_UNIX - HandleHolder token; - BOOL bReverted = FALSE; - bOK = RevertIfImpersonated(&bReverted, &token); - if (bOK != TRUE) - return NULL; -#endif // !TARGET_UNIX - threadHandle = CreateThread(NULL, // security descriptor - 0, // default stack size - lpStartAddress, - lpArgs, - CREATE_SUSPENDED, - &threadId); - - SetThreadName(threadHandle, W(".NET ThreadPool Worker")); -#ifndef TARGET_UNIX - UndoRevert(bReverted, token); -#endif // !TARGET_UNIX - } - - if (*pIsCLRThread && !bOK) - { - pThread->DecExternalCount(FALSE); - pThread = NULL; - } - - if (*pIsCLRThread) { - return pThread; - } - else - return (Thread*)threadHandle; -} - - -BOOL ThreadpoolMgr::CreateWorkerThread() -{ - CONTRACTL - { - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - NOTHROW; - MODE_ANY; // We may try to add a worker thread while queuing a work item thru an fcall - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - Thread *pThread; - BOOL fIsCLRThread; - if ((pThread = CreateUnimpersonatedThread(WorkerThreadStart, NULL, &fIsCLRThread)) != NULL) - { - if (fIsCLRThread) { - pThread->ChooseThreadCPUGroupAffinity(); - pThread->StartThread(); - } - else { - DWORD status; - status = ResumeThread((HANDLE)pThread); - _ASSERTE(status != (DWORD) (-1)); - CloseHandle((HANDLE)pThread); // we don't need this anymore - } - - return TRUE; - } - - return FALSE; -} - - -DWORD WINAPI ThreadpoolMgr::WorkerThreadStart(LPVOID lpArgs) -{ - ClrFlsSetThreadType (ThreadType_Threadpool_Worker); - - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPool()); - - Thread *pThread = NULL; - DWORD dwSwitchCount = 0; - BOOL fThreadInit = FALSE; - - ThreadCounter::Counts counts, oldCounts, newCounts; - bool foundWork = true, wasNotRecalled = true; - - counts = WorkerCounter.GetCleanCounts(); - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolWorkerThreadStart)) - FireEtwThreadPoolWorkerThreadStart(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - -#ifdef FEATURE_COMINTEROP - BOOL fCoInited = FALSE; - // Threadpool threads should be initialized as MTA. If we are unable to do so, - // return failure. - { - fCoInited = SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); - if (!fCoInited) - { - goto Exit; - } - } -#endif // FEATURE_COMINTEROP -Work: - - if (!fThreadInit) { - if (g_fEEStarted) { - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - __SwitchToThread(0, ++dwSwitchCount); - goto Work; - } - - // converted to CLRThread and added to ThreadStore, pick an group affinity for this thread - pThread->ChooseThreadCPUGroupAffinity(); - - #ifdef FEATURE_COMINTEROP - if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - newCounts = counts; - newCounts.NumActive--; - newCounts.NumWorking--; - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - break; - counts = oldCounts; - } - goto Exit; - } - #endif // FEATURE_COMINTEROP - - pThread->SetBackground(TRUE); - fThreadInit = TRUE; - } - } - - GCX_PREEMP_NO_DTOR(); - _ASSERTE(pThread == NULL || !pThread->PreemptiveGCDisabled()); - - // make sure there's really work. If not, go back to sleep - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - _ASSERTE(counts.NumActive > 0); - _ASSERTE(counts.NumWorking > 0); - - newCounts = counts; - - bool retired; - - if (counts.NumActive > counts.MaxWorking) - { - newCounts.NumActive--; - newCounts.NumRetired++; - retired = true; - } - else - { - retired = false; - - if (foundWork) - break; - } - - newCounts.NumWorking--; - - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - { - if (retired) - goto Retire; - else - goto WaitForWork; - } - - counts = oldCounts; - } - - if (GCHeapUtilities::IsGCInProgress(TRUE)) - { - // GC is imminent, so wait until GC is complete before executing next request. - // this reduces in-flight objects allocated right before GC, easing the GC's work - GCHeapUtilities::WaitForGCCompletion(TRUE); - } - - { - ThreadpoolMgr::UpdateLastDequeueTime(); - ThreadpoolMgr::ExecuteWorkRequest(&foundWork, &wasNotRecalled); - } - - if (foundWork) - { - // Reset TLS etc. for next WorkRequest. - if (pThread == NULL) - pThread = GetThreadNULLOk(); - - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } - } - - if (wasNotRecalled) - goto Work; - -Retire: - - counts = WorkerCounter.GetCleanCounts(); - FireEtwThreadPoolWorkerThreadRetirementStart(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - - // It's possible that some work came in just before we decremented the active thread count, in which - // case whoever queued that work may be expecting us to pick it up - so they would not have signalled - // the worker semaphore. If there are other threads waiting, they will never be woken up, because - // whoever queued the work expects that it's already been picked up. The solution is to signal the semaphore - // if there's any work available. - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains()) - MaybeAddWorkingWorker(); - - while (true) - { -RetryRetire: - if (RetiredWorkerSemaphore->Wait(WorkerTimeout)) - { - foundWork = true; - - counts = WorkerCounter.GetCleanCounts(); - FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - goto Work; - } - - if (!IsIoPending()) - { - // - // We're going to exit. There's a nasty race here. We're about to decrement NumRetired, - // since we're going to exit. Once we've done that, nobody will expect this thread - // to be waiting for RetiredWorkerSemaphore. But between now and then, other threads still - // think we're waiting on the semaphore, and they will happily do the following to try to - // wake us up: - // - // 1) Decrement NumRetired - // 2) Increment NumActive - // 3) Increment NumWorking - // 4) Signal RetiredWorkerSemaphore - // - // We will not receive that signal. If we don't do something special here, - // we will decrement NumRetired an extra time, and leave the world thinking there - // are fewer retired threads, and more working threads than reality. - // - // What can we do about this? First, we *need* to decrement NumRetired. If someone did it before us, - // it might go negative. This is the easiest way to tell that we've encountered this race. In that case, - // we will simply not commit the decrement, swallow the signal that was sent, and proceed as if we - // got WAIT_OBJECT_0 in the wait above. - // - // If we don't hit zero while decrementing NumRetired, we still may have encountered this race. But - // if we don't hit zero, then there's another retired thread that will pick up this signal. So it's ok - // to exit. - // - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - if (counts.NumRetired == 0) - goto RetryRetire; - - newCounts = counts; - newCounts.NumRetired--; - - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - { - counts = newCounts; - break; - } - counts = oldCounts; - } - - FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - goto Exit; - } - } - -WaitForWork: - - // It's possible that we decided we had no work just before some work came in, - // but reduced the worker count *after* the work came in. In this case, we might - // miss the notification of available work. So we make a sweep through the ADs here, - // and wake up a thread (maybe this one!) if there is work to do. - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains()) - { - foundWork = true; - MaybeAddWorkingWorker(); - } - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolWorkerThreadWait)) - FireEtwThreadPoolWorkerThreadWait(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - -RetryWaitForWork: - if (WorkerSemaphore->Wait(WorkerTimeout, WorkerThreadSpinLimit, NumberOfProcessors)) - { - foundWork = true; - goto Work; - } - - if (!IsIoPending()) - { - // - // We timed out, and are about to exit. This puts us in a very similar situation to the - // retirement case above - someone may think we're still waiting, and go ahead and: - // - // 1) Increment NumWorking - // 2) Signal WorkerSemaphore - // - // The solution is much like retirement; when we're decrementing NumActive, we need to make - // sure it doesn't drop below NumWorking. If it would, then we need to go back and wait - // again. - // - - DangerousNonHostedSpinLockHolder tal(&ThreadAdjustmentLock); - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - if (counts.NumActive == counts.NumWorking) - { - goto RetryWaitForWork; - } - - newCounts = counts; - newCounts.NumActive--; - - // if we timed out while active, then Hill Climbing needs to be told that we need fewer threads - newCounts.MaxWorking = max(MinLimitTotalWorkerThreads, min(newCounts.NumActive, newCounts.MaxWorking)); - - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - { - HillClimbingInstance.ForceChange(newCounts.MaxWorking, ThreadTimedOut); - goto Exit; - } - - counts = oldCounts; - } - } - else - { - goto RetryWaitForWork; - } - -Exit: - -#ifdef FEATURE_COMINTEROP - if (pThread) { - pThread->SetApartment(Thread::AS_Unknown); - pThread->CoUninitialize(); - } - - // Couninit the worker thread - if (fCoInited) - { - CoUninitialize(); - } -#endif - - if (pThread) { - pThread->ClearThreadCPUGroupAffinity(); - - DestroyThread(pThread); - } - - _ASSERTE(!IsIoPending()); - - counts = WorkerCounter.GetCleanCounts(); - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolWorkerThreadStop)) - FireEtwThreadPoolWorkerThreadStop(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - - return ERROR_SUCCESS; -} - -// this should only be called by unmanaged thread (i.e. there should be no mgd -// caller on the stack) since we are swallowing terminal exceptions -DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - DWORD status = WAIT_TIMEOUT; - EX_TRY - { - status = ev->Wait(sleepTime,FALSE); - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions) - return status; -} - -/************************************************************************/ - -BOOL ThreadpoolMgr::RegisterWaitForSingleObject(PHANDLE phNewWaitObject, - HANDLE hWaitObject, - WAITORTIMERCALLBACK Callback, - PVOID Context, - ULONG timeout, - DWORD dwFlag ) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - EnsureInitialized(); - - ThreadCB* threadCB; - { - CrstHolder csh(&WaitThreadsCriticalSection); - - threadCB = FindWaitThread(); - } - - *phNewWaitObject = NULL; - - if (threadCB) - { - WaitInfo* waitInfo = new (nothrow) WaitInfo; - - if (waitInfo == NULL) - return FALSE; - - waitInfo->waitHandle = hWaitObject; - waitInfo->Callback = Callback; - waitInfo->Context = Context; - waitInfo->timeout = timeout; - waitInfo->flag = dwFlag; - waitInfo->threadCB = threadCB; - waitInfo->state = 0; - waitInfo->refCount = 1; // safe to do this since no wait has yet been queued, so no other thread could be modifying this - waitInfo->ExternalCompletionEvent = INVALID_HANDLE; - waitInfo->ExternalEventSafeHandle = NULL; - - waitInfo->timer.startTime = GetTickCount(); - waitInfo->timer.remainingTime = timeout; - - *phNewWaitObject = waitInfo; - - // We fire the "enqueue" ETW event here, to "mark" the thread that had called the API, rather than the - // thread that will PostQueuedCompletionStatus (the dedicated WaitThread). - // This event correlates with ThreadPoolIODequeue in ThreadpoolMgr::AsyncCallbackCompletion - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIOEnqueue)) - FireEtwThreadPoolIOEnqueue((LPOVERLAPPED)waitInfo, reinterpret_cast(Callback), (dwFlag & WAIT_SINGLE_EXECUTION) == 0, GetClrInstanceId()); - - BOOL status = QueueUserAPC((PAPCFUNC)InsertNewWaitForSelf, threadCB->threadHandle, (size_t) waitInfo); - - if (status == FALSE) - { - *phNewWaitObject = NULL; - delete waitInfo; - } - - return status; - } - - return FALSE; -} - - -// Returns a wait thread that can accomodate another wait request. The -// caller is responsible for synchronizing access to the WaitThreadsHead -ThreadpoolMgr::ThreadCB* ThreadpoolMgr::FindWaitThread() -{ - CONTRACTL - { - THROWS; // CreateWaitThread can throw - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - do - { - for (LIST_ENTRY* Node = (LIST_ENTRY*) WaitThreadsHead.Flink ; - Node != &WaitThreadsHead ; - Node = (LIST_ENTRY*)Node->Flink) - { - _ASSERTE(offsetof(WaitThreadInfo,link) == 0); - - ThreadCB* threadCB = ((WaitThreadInfo*) Node)->threadCB; - - if (threadCB->NumWaitHandles < MAX_WAITHANDLES) // this test and following ... - - { - InterlockedIncrement(&threadCB->NumWaitHandles); // ... increment are protected by WaitThreadsCriticalSection. - // but there might be a concurrent decrement in DeactivateWait - // or InsertNewWaitForSelf, hence the interlock - return threadCB; - } - } - - // if reached here, there are no wait threads available, so need to create a new one - if (!CreateWaitThread()) - return NULL; - - - // Now loop back - } while (TRUE); - -} - -BOOL ThreadpoolMgr::CreateWaitThread() -{ - CONTRACTL - { - THROWS; // CLREvent::CreateAutoEvent can throw OOM - GC_TRIGGERS; - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - DWORD threadId; - - if (g_fEEShutDown & ShutDown_Finalize2){ - // The process is shutting down. Shutdown thread has ThreadStore lock, - // wait thread is blocked on the lock. - return FALSE; - } - - NewHolder waitThreadInfo(new (nothrow) WaitThreadInfo); - if (waitThreadInfo == NULL) - return FALSE; - - NewHolder threadCB(new (nothrow) ThreadCB); - - if (threadCB == NULL) - { - return FALSE; - } - - threadCB->startEvent.CreateAutoEvent(FALSE); - HANDLE threadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, WaitThreadStart, (LPVOID)threadCB, W(".NET ThreadPool Wait"), CREATE_SUSPENDED, &threadId); - - if (threadHandle == NULL) - { - threadCB->startEvent.CloseEvent(); - return FALSE; - } - - waitThreadInfo.SuppressRelease(); - threadCB.SuppressRelease(); - threadCB->threadHandle = threadHandle; - threadCB->threadId = threadId; // may be useful for debugging otherwise not used - threadCB->NumWaitHandles = 0; - threadCB->NumActiveWaits = 0; - for (int i=0; i< MAX_WAITHANDLES; i++) - { - InitializeListHead(&(threadCB->waitPointer[i])); - } - - waitThreadInfo->threadCB = threadCB; - - DWORD status = ResumeThread(threadHandle); - - { - // We will QueueUserAPC on the newly created thread. - // Let us wait until the thread starts running. - GCX_PREEMP(); - DWORD timeout=500; - while (TRUE) { - if (g_fEEShutDown & ShutDown_Finalize2){ - // The process is shutting down. Shutdown thread has ThreadStore lock, - // wait thread is blocked on the lock. - return FALSE; - } - DWORD wait_status = threadCB->startEvent.Wait(timeout, FALSE); - if (wait_status == WAIT_OBJECT_0) { - break; - } - } - } - threadCB->startEvent.CloseEvent(); - - // check to see if setup succeeded - if (threadCB->threadHandle == NULL) - return FALSE; - - InsertHeadList(&WaitThreadsHead,&waitThreadInfo->link); - - _ASSERTE(status != (DWORD) (-1)); - - return (status != (DWORD) (-1)); - -} - -// Executed as an APC on a WaitThread. Add the wait specified in pArg to the list of objects it is waiting on -void ThreadpoolMgr::InsertNewWaitForSelf(WaitInfo* pArgs) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - WaitInfo* waitInfo = pArgs; - - // the following is safe since only this thread is allowed to change the state - if (!(waitInfo->state & WAIT_DELETE)) - { - waitInfo->state = (WAIT_REGISTERED | WAIT_ACTIVE); - } - else - { - // some thread unregistered the wait - DeleteWait(waitInfo); - return; - } - - - ThreadCB* threadCB = waitInfo->threadCB; - - _ASSERTE(threadCB->NumActiveWaits <= threadCB->NumWaitHandles); - - int index = FindWaitIndex(threadCB, waitInfo->waitHandle); - _ASSERTE(index >= 0 && index <= threadCB->NumActiveWaits); - - if (index == threadCB->NumActiveWaits) - { - threadCB->waitHandle[threadCB->NumActiveWaits] = waitInfo->waitHandle; - threadCB->NumActiveWaits++; - } - else - { - // this is a duplicate waithandle, so the increment in FindWaitThread - // wasn't strictly necessary. This will avoid unnecessary thread creation. - InterlockedDecrement(&threadCB->NumWaitHandles); - } - - _ASSERTE(offsetof(WaitInfo, link) == 0); - InsertTailList(&(threadCB->waitPointer[index]), (&waitInfo->link)); - - return; -} - -// returns the index of the entry that matches waitHandle or next free entry if not found -int ThreadpoolMgr::FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHandle) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - for (int i=0;iNumActiveWaits; i++) - if (threadCB->waitHandle[i] == waitHandle) - return i; - - // else not found - return threadCB->NumActiveWaits; -} - - -// if no wraparound that the timer is expired if duetime is less than current time -// if wraparound occurred, then the timer expired if dueTime was greater than last time or dueTime is less equal to current time -#define TimeExpired(last,now,duetime) ((last) <= (now) ? \ - ((duetime) <= (now) && (duetime) >= (last)): \ - ((duetime) >= (last) || (duetime) <= (now))) - -#define TimeInterval(end,start) ((end) > (start) ? ((end) - (start)) : ((0xffffffff - (start)) + (end) + 1)) - -// Returns the minimum of the remaining time to reach a timeout among all the waits -DWORD ThreadpoolMgr::MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - unsigned int min = (unsigned int) -1; - DWORD currentTime = GetTickCount(); - - for (unsigned i=0; i < numWaits ; i++) - { - WaitInfo* waitInfoPtr = (WaitInfo*) (waitInfo[i].Flink); - PVOID waitInfoHead = &(waitInfo[i]); - do - { - if (waitInfoPtr->timeout != INFINITE) - { - // compute remaining time - DWORD elapsedTime = TimeInterval(currentTime,waitInfoPtr->timer.startTime ); - - __int64 remainingTime = (__int64) (waitInfoPtr->timeout) - (__int64) elapsedTime; - - // update remaining time - waitInfoPtr->timer.remainingTime = remainingTime > 0 ? (int) remainingTime : 0; - - // ... and min - if (waitInfoPtr->timer.remainingTime < min) - min = waitInfoPtr->timer.remainingTime; - } - - waitInfoPtr = (WaitInfo*) (waitInfoPtr->link.Flink); - - } while ((PVOID) waitInfoPtr != waitInfoHead); - - } - return min; -} - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif -#endif -#ifdef _PREFAST_ -#pragma warning(push) -#pragma warning(disable:22008) // "Prefast integer overflow check on (0 + lval) is bogus. Tried local disable without luck, doing whole method." -#endif - -DWORD WINAPI ThreadpoolMgr::WaitThreadStart(LPVOID lpArgs) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - ClrFlsSetThreadType (ThreadType_Wait); - - _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPool()); - - ThreadCB* threadCB = (ThreadCB*) lpArgs; - Thread* pThread = SetupThreadNoThrow(); - - if (pThread == NULL) - { - _ASSERTE(threadCB->threadHandle != NULL); - threadCB->threadHandle = NULL; - } - - threadCB->startEvent.Set(); - - if (pThread == NULL) - { - return 0; - } - - { - // wait threads never die. (Why?) - for (;;) - { - DWORD status; - DWORD timeout = 0; - - if (threadCB->NumActiveWaits == 0) - { - status = ClrSleepEx(INFINITE,TRUE); - _ASSERTE(status == WAIT_IO_COMPLETION); - } - else if (IsWaitThreadAPCPending()) - { - //Do a sleep if an APC is pending, This was done to solve the corner case where the wait is signaled, - //and APC to deregiter the wait never fires. That scenario leads to an infinite loop. This check would - //allow the thread to enter alertable wait and thus cause the APC to fire. - - ResetWaitThreadAPCPending(); - status = ClrSleepEx(0,TRUE); - continue; - } - else - { - // compute minimum timeout. this call also updates the remainingTime field for each wait - timeout = MinimumRemainingWait(threadCB->waitPointer,threadCB->NumActiveWaits); - - status = WaitForMultipleObjectsEx( threadCB->NumActiveWaits, - threadCB->waitHandle, - FALSE, // waitall - timeout, - TRUE ); // alertable - - _ASSERTE( (status == WAIT_TIMEOUT) || - (status == WAIT_IO_COMPLETION) || - //It could be that there are no waiters at this point, - //as the APC to deregister the wait may have run. - (status == WAIT_OBJECT_0) || - (status >= WAIT_OBJECT_0 && status < (DWORD)(WAIT_OBJECT_0 + threadCB->NumActiveWaits)) || - (status == WAIT_FAILED)); - - //It could be that the last waiter also got deregistered. - if (threadCB->NumActiveWaits == 0) - { - continue; - } - } - - if (status == WAIT_IO_COMPLETION) - continue; - - if (status == WAIT_TIMEOUT) - { - for (int i=0; i< threadCB->NumActiveWaits; i++) - { - WaitInfo* waitInfo = (WaitInfo*) (threadCB->waitPointer[i]).Flink; - PVOID waitInfoHead = &(threadCB->waitPointer[i]); - - do - { - _ASSERTE(waitInfo->timer.remainingTime >= timeout); - - WaitInfo* wTemp = (WaitInfo*) waitInfo->link.Flink; - - if (waitInfo->timer.remainingTime == timeout) - { - ProcessWaitCompletion(waitInfo,i,TRUE); - } - - waitInfo = wTemp; - - } while ((PVOID) waitInfo != waitInfoHead); - } - } - else if (status >= WAIT_OBJECT_0 && status < (DWORD)(WAIT_OBJECT_0 + threadCB->NumActiveWaits)) - { - unsigned index = status - WAIT_OBJECT_0; - WaitInfo* waitInfo = (WaitInfo*) (threadCB->waitPointer[index]).Flink; - PVOID waitInfoHead = &(threadCB->waitPointer[index]); - BOOL isAutoReset; - - // Setting to unconditional TRUE is inefficient since we will re-enter the wait and release - // the next waiter, but short of using undocumented NT apis is the only solution. - // Querying the state with a WaitForSingleObject is not an option as it will reset an - // auto reset event if it has been signalled since the previous wait. - isAutoReset = TRUE; - - do - { - WaitInfo* wTemp = (WaitInfo*) waitInfo->link.Flink; - ProcessWaitCompletion(waitInfo,index,FALSE); - - waitInfo = wTemp; - - } while (((PVOID) waitInfo != waitInfoHead) && !isAutoReset); - - // If an app registers a recurring wait for an event that is always signalled (!), - // then no apc's will be executed since the thread never enters the alertable state. - // This can be fixed by doing the following: - // SleepEx(0,TRUE); - // However, it causes an unnecessary context switch. It is not worth penalizing well - // behaved apps to protect poorly written apps. - - - } - else - { - _ASSERTE(status == WAIT_FAILED); - // wait failed: application error - // find out which wait handle caused the wait to fail - for (int i = 0; i < threadCB->NumActiveWaits; i++) - { - DWORD subRet = WaitForSingleObject(threadCB->waitHandle[i], 0); - - if (subRet != WAIT_FAILED) - continue; - - // remove all waits associated with this wait handle - - WaitInfo* waitInfo = (WaitInfo*) (threadCB->waitPointer[i]).Flink; - PVOID waitInfoHead = &(threadCB->waitPointer[i]); - - do - { - WaitInfo* temp = (WaitInfo*) waitInfo->link.Flink; - - DeactivateNthWait(waitInfo,i); - - - // Note, we cannot cleanup here since there is no way to suppress finalization - // we will just leak, and rely on the finalizer to clean up the memory - //if (InterlockedDecrement(&waitInfo->refCount) == 0) - // DeleteWait(waitInfo); - - - waitInfo = temp; - - } while ((PVOID) waitInfo != waitInfoHead); - - break; - } - } - } - } - - //This is unreachable...so no return required. -} -#ifdef _PREFAST_ -#pragma warning(pop) -#endif - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, - unsigned index, - BOOL waitTimedOut - ) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - AsyncCallback* asyncCallback = NULL; - EX_TRY{ - if ( waitInfo->flag & WAIT_SINGLE_EXECUTION) - { - DeactivateNthWait (waitInfo,index) ; - } - else - { // reactivate wait by resetting timer - waitInfo->timer.startTime = GetTickCount(); - } - - asyncCallback = MakeAsyncCallback(); - if (asyncCallback) - { - asyncCallback->wait = waitInfo; - asyncCallback->waitTimedOut = waitTimedOut; - - InterlockedIncrement(&waitInfo->refCount); - -#ifndef TARGET_UNIX - if (FALSE == PostQueuedCompletionStatus((LPOVERLAPPED)asyncCallback, (LPOVERLAPPED_COMPLETION_ROUTINE)WaitIOCompletionCallback)) -#else // TARGET_UNIX - if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) -#endif // !TARGET_UNIX - ReleaseAsyncCallback(asyncCallback); - } - } - EX_CATCH { - if (asyncCallback) - ReleaseAsyncCallback(asyncCallback); - - EX_RETHROW; - } - EX_END_CATCH(SwallowAllExceptions); -} - - -DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - MODE_PREEMPTIVE; - GC_TRIGGERS; - } - CONTRACTL_END; - - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - { - AsyncCallback * asyncCallback = (AsyncCallback*) pArgs; - - WaitInfo * waitInfo = asyncCallback->wait; - - AsyncCallbackHolder asyncCBHolder; - asyncCBHolder.Assign(asyncCallback); - - // We fire the "dequeue" ETW event here, before executing the user code, to enable correlation with - // the ThreadPoolIOEnqueue fired in ThreadpoolMgr::RegisterWaitForSingleObject - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) - FireEtwThreadPoolIODequeue(waitInfo, reinterpret_cast(waitInfo->Callback), GetClrInstanceId()); - - // the user callback can throw, the host must be prepared to handle it. - // SQL is ok, since they have a top-level SEH handler. However, there's - // no easy way to verify it - - ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) - ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); - -#ifndef TARGET_UNIX - Thread::IncrementIOThreadPoolCompletionCount(pThread); -#endif - } - - return ERROR_SUCCESS; -} - -void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) -{ - LIMITED_METHOD_CONTRACT; - - ThreadCB* threadCB = waitInfo->threadCB; - DWORD endIndex = threadCB->NumActiveWaits-1; - DWORD index; - - for (index = 0; index <= endIndex; index++) - { - LIST_ENTRY* head = &(threadCB->waitPointer[index]); - LIST_ENTRY* current = head; - do { - if (current->Flink == (PVOID) waitInfo) - goto FOUND; - - current = (LIST_ENTRY*) current->Flink; - - } while (current != head); - } - -FOUND: - _ASSERTE(index <= endIndex); - - DeactivateNthWait(waitInfo, index); -} - - -void ThreadpoolMgr::DeactivateNthWait(WaitInfo* waitInfo, DWORD index) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - ThreadCB* threadCB = waitInfo->threadCB; - - if (waitInfo->link.Flink != waitInfo->link.Blink) - { - RemoveEntryList(&(waitInfo->link)); - } - else - { - - ULONG EndIndex = threadCB->NumActiveWaits -1; - - // Move the remaining ActiveWaitArray left. - - ShiftWaitArray( threadCB, index+1, index,EndIndex - index ) ; - - // repair the blink and flink of the first and last elements in the list - for (unsigned int i = 0; i< EndIndex-index; i++) - { - WaitInfo* firstWaitInfo = (WaitInfo*) threadCB->waitPointer[index+i].Flink; - WaitInfo* lastWaitInfo = (WaitInfo*) threadCB->waitPointer[index+i].Blink; - firstWaitInfo->link.Blink = &(threadCB->waitPointer[index+i]); - lastWaitInfo->link.Flink = &(threadCB->waitPointer[index+i]); - } - // initialize the entry just freed - InitializeListHead(&(threadCB->waitPointer[EndIndex])); - - threadCB->NumActiveWaits-- ; - InterlockedDecrement(&threadCB->NumWaitHandles); - } - - waitInfo->state &= ~WAIT_ACTIVE ; - -} - -void ThreadpoolMgr::DeleteWait(WaitInfo* waitInfo) -{ - CONTRACTL - { - if (waitInfo->ExternalEventSafeHandle != NULL) { THROWS;} else { NOTHROW; } - MODE_ANY; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - if(waitInfo->Context && (waitInfo->flag & WAIT_FREE_CONTEXT)) { - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - // Since the delegate release destroys a handle, we need to be in - // co-operative mode - { - GCX_COOP(); - pDelegate->Release(); - } - - RecycleMemory( pDelegate, MEMTYPE_DelegateInfo ); - } - - if (waitInfo->flag & WAIT_INTERNAL_COMPLETION) - { - waitInfo->InternalCompletionEvent.Set(); - return; // waitInfo will be deleted by the thread that's waiting on this event - } - else if (waitInfo->ExternalCompletionEvent != INVALID_HANDLE) - { - SetEvent(waitInfo->ExternalCompletionEvent); - } - else if (waitInfo->ExternalEventSafeHandle != NULL) - { - // Release the safe handle and the GC handle holding it - ReleaseWaitInfo(waitInfo); - } - - delete waitInfo; - - -} - - - -/************************************************************************/ -BOOL ThreadpoolMgr::UnregisterWaitEx(HANDLE hWaitObject,HANDLE Event) -{ - CONTRACTL - { - THROWS; //NOTHROW; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(IsInitialized()); // cannot call unregister before first registering - - const BOOL Blocking = (Event == (HANDLE) -1); - WaitInfo* waitInfo = (WaitInfo*) hWaitObject; - - if (!hWaitObject) - { - return FALSE; - } - - // we do not allow callbacks to run in the wait thread, hence the assert - _ASSERTE(GetCurrentThreadId() != waitInfo->threadCB->threadId); - - - if (Blocking) - { - waitInfo->InternalCompletionEvent.CreateAutoEvent(FALSE); - waitInfo->flag |= WAIT_INTERNAL_COMPLETION; - - } - else - { - waitInfo->ExternalCompletionEvent = (Event ? Event : INVALID_HANDLE); - _ASSERTE((waitInfo->flag & WAIT_INTERNAL_COMPLETION) == 0); - // we still want to block until the wait has been deactivated - waitInfo->PartialCompletionEvent.CreateAutoEvent(FALSE); - } - - BOOL status = QueueDeregisterWait(waitInfo->threadCB->threadHandle, waitInfo); - - - if (status == 0) - { - STRESS_LOG1(LF_THREADPOOL, LL_ERROR, "Queue APC failed in UnregisterWaitEx %x", status); - - if (Blocking) - waitInfo->InternalCompletionEvent.CloseEvent(); - else - waitInfo->PartialCompletionEvent.CloseEvent(); - return FALSE; - } - - if (!Blocking) - { - waitInfo->PartialCompletionEvent.Wait(INFINITE,TRUE); - waitInfo->PartialCompletionEvent.CloseEvent(); - // we cannot do DeleteWait in DeregisterWait, since the DeleteWait could happen before - // we close the event. So, the code has been moved here. - if (InterlockedDecrement(&waitInfo->refCount) == 0) - { - DeleteWait(waitInfo); - } - } - - else // i.e. blocking - { - _ASSERTE(waitInfo->flag & WAIT_INTERNAL_COMPLETION); - _ASSERTE(waitInfo->ExternalEventSafeHandle == NULL); - - waitInfo->InternalCompletionEvent.Wait(INFINITE,TRUE); - waitInfo->InternalCompletionEvent.CloseEvent(); - delete waitInfo; // if WAIT_INTERNAL_COMPLETION is not set, waitInfo will be deleted in DeleteWait - } - return TRUE; -} - - -void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) -{ - WRAPPER_NO_CONTRACT; - - WaitInfo* waitInfo = pArgs; - - if ( ! (waitInfo->state & WAIT_REGISTERED) ) - { - // set state to deleted, so that it does not get registered - waitInfo->state |= WAIT_DELETE ; - - // since the wait has not even been registered, we dont need an interlock to decrease the RefCount - waitInfo->refCount--; - - if (waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - } - return; - } - - if (waitInfo->state & WAIT_ACTIVE) - { - DeactivateWait(waitInfo); - } - - if ( waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - return; // we cannot delete the wait here since the PartialCompletionEvent - // may not have been closed yet. so, we return and rely on the waiter of PartialCompletionEvent - // to do the close - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - { - DeleteWait(waitInfo); - } - return; -} - - -/* This gets called in a finalizer thread ONLY IF an app does not deregister the - the wait. Note that just because the registeredWaitHandle is collected by GC - does not mean it is safe to delete the wait. The refcount tells us when it is - safe. -*/ -void ThreadpoolMgr::WaitHandleCleanup(HANDLE hWaitObject) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(IsInitialized()); // cannot call cleanup before first registering - - WaitInfo* waitInfo = (WaitInfo*) hWaitObject; - _ASSERTE(waitInfo->refCount > 0); - - DWORD result = QueueDeregisterWait(waitInfo->threadCB->threadHandle, waitInfo); - - if (result == 0) - STRESS_LOG1(LF_THREADPOOL, LL_ERROR, "Queue APC failed in WaitHandleCleanup %x", result); - -} - -BOOL ThreadpoolMgr::CreateGateThread() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - HANDLE threadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, GateThreadStart, NULL, W(".NET ThreadPool Gate")); - - if (threadHandle) - { - CloseHandle(threadHandle); //we don't need this anymore - return TRUE; - } - - return FALSE; -} - - - -/************************************************************************/ - -BOOL ThreadpoolMgr::BindIoCompletionCallback(HANDLE FileHandle, - LPOVERLAPPED_COMPLETION_ROUTINE Function, - ULONG Flags, - DWORD& errCode) -{ - CONTRACTL - { - THROWS; // EnsureInitialized can throw - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - -#ifndef TARGET_UNIX - - errCode = S_OK; - - EnsureInitialized(); - - - _ASSERTE(GlobalCompletionPort != NULL); - - if (!InitCompletionPortThreadpool) - InitCompletionPortThreadpool = TRUE; - - GrowCompletionPortThreadpoolIfNeeded(); - - HANDLE h = CreateIoCompletionPort(FileHandle, - GlobalCompletionPort, - (ULONG_PTR) Function, - NumberOfProcessors); - if (h == NULL) - { - errCode = GetLastError(); - return FALSE; - } - - _ASSERTE(h == GlobalCompletionPort); - - return TRUE; -#else // TARGET_UNIX - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -#endif // !TARGET_UNIX -} - -#ifndef TARGET_UNIX -BOOL ThreadpoolMgr::CreateCompletionPortThread(LPVOID lpArgs) -{ - CONTRACTL - { - NOTHROW; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - Thread *pThread; - BOOL fIsCLRThread; - if ((pThread = CreateUnimpersonatedThread(CompletionPortThreadStart, lpArgs, &fIsCLRThread)) != NULL) - { - LastCPThreadCreation = GetTickCount(); // record this for use by logic to spawn additional threads - - if (fIsCLRThread) { - pThread->ChooseThreadCPUGroupAffinity(); - pThread->StartThread(); - } - else { - DWORD status; - status = ResumeThread((HANDLE)pThread); - _ASSERTE(status != (DWORD) (-1)); - CloseHandle((HANDLE)pThread); // we don't need this anymore - } - - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - FireEtwIOThreadCreate_V1(counts.NumActive + counts.NumRetired, counts.NumRetired, GetClrInstanceId()); - - return TRUE; - } - - - return FALSE; -} - -DWORD WINAPI ThreadpoolMgr::CompletionPortThreadStart(LPVOID lpArgs) -{ - ClrFlsSetThreadType (ThreadType_Threadpool_IOCompletion); - - CONTRACTL - { - THROWS; - if (GetThreadNULLOk()) { MODE_PREEMPTIVE;} else { DISABLED(MODE_ANY);} - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPoolForIO()); - - DWORD numBytes=0; - size_t key=0; - - LPOVERLAPPED pOverlapped = NULL; - DWORD errorCode; - PIOCompletionContext context; - BOOL fIsCompletionContext; - - const DWORD CP_THREAD_WAIT = 15000; /* milliseconds */ - - _ASSERTE(GlobalCompletionPort != NULL); - - BOOL fThreadInit = FALSE; - Thread *pThread = NULL; - - DWORD cpThreadWait = 0; - - if (g_fEEStarted) { - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - return 0; - } - - // converted to CLRThread and added to ThreadStore, pick an group affinity for this thread - pThread->ChooseThreadCPUGroupAffinity(); - - fThreadInit = TRUE; - } - -#ifdef FEATURE_COMINTEROP - // Threadpool threads should be initialized as MTA. If we are unable to do so, - // return failure. - BOOL fCoInited = FALSE; - { - fCoInited = SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); - if (!fCoInited) - { - goto Exit; - } - } - - if (pThread && pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // @todo: should we log the failure - goto Exit; - } -#endif // FEATURE_COMINTEROP - - ThreadCounter::Counts oldCounts; - ThreadCounter::Counts newCounts; - - cpThreadWait = CP_THREAD_WAIT; - for (;; ) - { -Top: - if (!fThreadInit) { - if (g_fEEStarted) { - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - break; - } - - // converted to CLRThread and added to ThreadStore, pick an group affinity for this thread - pThread->ChooseThreadCPUGroupAffinity(); - -#ifdef FEATURE_COMINTEROP - if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // @todo: should we log the failure - goto Exit; - } -#endif // FEATURE_COMINTEROP - - fThreadInit = TRUE; - } - } - - GCX_PREEMP_NO_DTOR(); - - // - // We're about to wait on the IOCP; mark ourselves as no longer "working." - // - while (true) - { - ThreadCounter::Counts oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - ThreadCounter::Counts newCounts = oldCounts; - newCounts.NumWorking--; - - // - // If we've only got one thread left, it won't be allowed to exit, because we need to keep - // one thread listening for completions. So there's no point in having a timeout; it will - // only use power unnecessarily. - // - cpThreadWait = (newCounts.NumActive == 1) ? INFINITE : CP_THREAD_WAIT; - - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - errorCode = S_OK; - - if (lpArgs == NULL) - { - CONTRACT_VIOLATION(ThrowsViolation); - - context = NULL; - fIsCompletionContext = FALSE; - - if (pThread == NULL) - { - pThread = GetThreadNULLOk(); - } - - if (pThread) - { - context = (PIOCompletionContext) pThread->GetIOCompletionContext(); - - if (context->lpOverlapped != NULL) - { - errorCode = context->ErrorCode; - numBytes = context->numBytesTransferred; - pOverlapped = context->lpOverlapped; - key = context->key; - - context->lpOverlapped = NULL; - fIsCompletionContext = TRUE; - } - } - - if((context == NULL) || (!fIsCompletionContext)) - { - _ASSERTE (context == NULL || context->lpOverlapped == NULL); - - BOOL status = GetQueuedCompletionStatus( - GlobalCompletionPort, - &numBytes, - (PULONG_PTR)&key, - &pOverlapped, - cpThreadWait - ); - - if (status == 0) - errorCode = GetLastError(); - } - } - else - { - QueuedStatus *CompletionStatus = (QueuedStatus*)lpArgs; - numBytes = CompletionStatus->numBytes; - key = (size_t)CompletionStatus->key; - pOverlapped = CompletionStatus->pOverlapped; - errorCode = CompletionStatus->errorCode; - delete CompletionStatus; - lpArgs = NULL; // one-time deal for initial CP packet - } - - // We fire IODequeue events whether the IO completion was retrieved in the above call to - // GetQueuedCompletionStatus or during an earlier call (e.g. in GateThreadStart, and passed here in lpArgs, - // or in CompletionPortDispatchWorkWithinAppDomain, and passed here through StoreOverlappedInfoInThread) - - // For the purposes of activity correlation we only fire ETW events here, if needed OR if not fired at a higher - // abstraction level (e.g. ThreadpoolMgr::RegisterWaitForSingleObject) - // Note: we still fire the event for managed async IO, despite the fact we don't have a paired IOEnqueue event - // for this case. We do this to "mark" the end of the previous workitem. When we provide full support at the higher - // abstraction level for managed IO we can remove the IODequeues fired here - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue) - && !AreEtwIOQueueEventsSpeciallyHandled((LPOVERLAPPED_COMPLETION_ROUTINE)key) && pOverlapped != NULL) - { - FireEtwThreadPoolIODequeue(pOverlapped, OverlappedDataObject::GetOverlappedForTracing(pOverlapped), GetClrInstanceId()); - } - - bool enterRetirement; - - while (true) - { - // - // When we reach this point, this thread is "active" but not "working." Depending on the result of the call to GetQueuedCompletionStatus, - // and the state of the rest of the IOCP threads, we need to figure out whether to de-activate (exit) this thread, retire this thread, - // or transition to "working." - // - - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - enterRetirement = false; - - if (errorCode == WAIT_TIMEOUT) - { - // - // We timed out, and are going to try to exit or retire. - // - newCounts.NumActive--; - - // - // We need at least one free thread, or we have no way of knowing if completions are being queued. - // - if (newCounts.NumWorking == newCounts.NumActive) - { - newCounts = oldCounts; - newCounts.NumWorking++; //not really working, but we'll decremented it at the top - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - goto Top; - else - continue; - } - - // - // We can't exit a thread that has pending I/O - we'll "retire" it instead. - // - if (IsIoPending()) - { - enterRetirement = true; - newCounts.NumRetired++; - } - } - else - { - // - // We have work to do - // - newCounts.NumWorking++; - } - - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - if (errorCode == WAIT_TIMEOUT) - { - if (!enterRetirement) - { - goto Exit; - } - else - { - // now in "retired mode" waiting for pending io to complete - FireEtwIOThreadRetire_V1(newCounts.NumActive + newCounts.NumRetired, newCounts.NumRetired, GetClrInstanceId()); - - for (;;) - { - DWORD status = SafeWait(RetiredCPWakeupEvent,CP_THREAD_PENDINGIO_WAIT,FALSE); - _ASSERTE(status == WAIT_TIMEOUT || status == WAIT_OBJECT_0); - - if (status == WAIT_TIMEOUT) - { - if (IsIoPending()) - { - continue; - } - else - { - // We can now exit; decrement the retired count. - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumRetired--; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - goto Exit; - } - } - else - { - // put back into rotation -- we need a thread - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumRetired--; - newCounts.NumActive++; - newCounts.NumWorking++; //we're not really working, but we'll decrement this before waiting for work. - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - FireEtwIOThreadUnretire_V1(newCounts.NumActive + newCounts.NumRetired, newCounts.NumRetired, GetClrInstanceId()); - goto Top; - } - } - } - } - - // we should not reach this point unless we have work to do - _ASSERTE(errorCode != WAIT_TIMEOUT && !enterRetirement); - - // if we have no more free threads, start the gate thread - if (newCounts.NumWorking >= newCounts.NumActive) - EnsureGateThreadRunning(); - - - // We can not assert here. If stdin/stdout/stderr of child process are redirected based on - // async io, GetQueuedCompletionStatus returns when child process operates on its stdin/stdout/stderr. - // Parent process does not issue any ReadFile/WriteFile, and hence pOverlapped is going to be NULL. - //_ASSERTE(pOverlapped != NULL); - - if (pOverlapped != NULL) - { - _ASSERTE(key != 0); // should be a valid function address - - if (key != 0) - { - if (GCHeapUtilities::IsGCInProgress(TRUE)) - { - //Indicate that this thread is free, and waiting on GC, not doing any user work. - //This helps in threads not getting injected when some threads have woken up from the - //GC event, and some have not. - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumWorking--; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - // GC is imminent, so wait until GC is complete before executing next request. - // this reduces in-flight objects allocated right before GC, easing the GC's work - GCHeapUtilities::WaitForGCCompletion(TRUE); - - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumWorking++; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } + SetEvent(waitInfo->ExternalCompletionEvent); + } + else if (waitInfo->ExternalEventSafeHandle != NULL) + { + // Release the safe handle and the GC handle holding it + ReleaseWaitInfo(waitInfo); + } - if (newCounts.NumWorking >= newCounts.NumActive) - EnsureGateThreadRunning(); - } - else - { - GrowCompletionPortThreadpoolIfNeeded(); - } + delete waitInfo; - { - CONTRACT_VIOLATION(ThrowsViolation); - ((LPOVERLAPPED_COMPLETION_ROUTINE) key)(errorCode, numBytes, pOverlapped); - } +} - Thread::IncrementIOThreadPoolCompletionCount(pThread); - if (pThread == NULL) - { - pThread = GetThreadNULLOk(); - } - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } - } - else - { - // Application bug - can't do much, just ignore it - } +/************************************************************************/ - } +void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) +{ + WRAPPER_NO_CONTRACT; - } // for (;;) + WaitInfo* waitInfo = pArgs; -Exit: + if ( ! (waitInfo->state & WAIT_REGISTERED) ) + { + // set state to deleted, so that it does not get registered + waitInfo->state |= WAIT_DELETE ; - oldCounts = CPThreadCounter.GetCleanCounts(); + // since the wait has not even been registered, we dont need an interlock to decrease the RefCount + waitInfo->refCount--; - // we should never destroy or retire all IOCP threads, because then we won't have any threads to notice incoming completions. - _ASSERTE(oldCounts.NumActive > 0); + if (waitInfo->PartialCompletionEvent.IsValid()) + { + waitInfo->PartialCompletionEvent.Set(); + } + return; + } - FireEtwIOThreadTerminate_V1(oldCounts.NumActive + oldCounts.NumRetired, oldCounts.NumRetired, GetClrInstanceId()); + if (waitInfo->state & WAIT_ACTIVE) + { + DeactivateWait(waitInfo); + } -#ifdef FEATURE_COMINTEROP - if (pThread) { - pThread->SetApartment(Thread::AS_Unknown); - pThread->CoUninitialize(); + if ( waitInfo->PartialCompletionEvent.IsValid()) + { + waitInfo->PartialCompletionEvent.Set(); + return; // we cannot delete the wait here since the PartialCompletionEvent + // may not have been closed yet. so, we return and rely on the waiter of PartialCompletionEvent + // to do the close } - // Couninit the worker thread - if (fCoInited) + + if (InterlockedDecrement(&waitInfo->refCount) == 0) { - CoUninitialize(); + DeleteWait(waitInfo); } -#endif + return; +} - if (pThread) { - pThread->ClearThreadCPUGroupAffinity(); - DestroyThread(pThread); - } +/************************************************************************/ - return 0; -} +#ifndef TARGET_UNIX LPOVERLAPPED ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain( Thread* pThread, @@ -3601,102 +1036,6 @@ void ThreadpoolMgr::StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCo context->key = key; } -BOOL ThreadpoolMgr::ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts counts) -{ - CONTRACTL - { - GC_NOTRIGGER; - NOTHROW; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (counts.NumWorking >= counts.NumActive - && (counts.NumActive == 0 || !GCHeapUtilities::IsGCInProgress(TRUE)) - ) - { - // adjust limit if needed - if (counts.NumRetired == 0) - { - if (counts.NumActive + counts.NumRetired < MaxLimitTotalCPThreads && - (counts.NumActive < MinLimitTotalCPThreads || cpuUtilization < CpuUtilizationLow)) - { - // add one more check to make sure that we haven't fired off a new - // thread since the last time time we checked the cpu utilization. - // However, don't bother if we haven't reached the MinLimit (2*number of cpus) - if ((counts.NumActive < MinLimitTotalCPThreads) || - SufficientDelaySinceLastSample(LastCPThreadCreation,counts.NumActive)) - { - return TRUE; - } - } - } - - if (counts.NumRetired > 0) - return TRUE; - } - return FALSE; -} - -void ThreadpoolMgr::GrowCompletionPortThreadpoolIfNeeded() -{ - CONTRACTL - { - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - NOTHROW; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - ThreadCounter::Counts oldCounts, newCounts; - while (true) - { - oldCounts = CPThreadCounter.GetCleanCounts(); - newCounts = oldCounts; - - if(!ShouldGrowCompletionPortThreadpool(oldCounts)) - { - break; - } - else - { - if (oldCounts.NumRetired > 0) - { - // wakeup retired thread instead - RetiredCPWakeupEvent->Set(); - return; - } - else - { - // create a new thread. New IOCP threads start as "active" and "working" - newCounts.NumActive++; - newCounts.NumWorking++; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - { - if (!CreateCompletionPortThread(NULL)) - { - // if thread creation failed, we have to adjust the counts back down. - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumActive--; - newCounts.NumWorking--; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - } - return; - } - } - } - } -} #endif // !TARGET_UNIX // Returns true if there is pending io on the thread. @@ -3898,329 +1237,6 @@ class GateThreadTimer } }; -DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) -{ - ClrFlsSetThreadType (ThreadType_Gate); - - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); - - GateThreadTimer timer; - - // TODO: do we need to do this? - timer.Wait(); // delay getting initial CPU reading - -#ifndef TARGET_UNIX - PROCESS_CPU_INFORMATION prevCPUInfo; - - if (!g_pufnNtQuerySystemInformation) - { - _ASSERT(!"NtQuerySystemInformation API not available!"); - return 0; - } - -#ifndef TARGET_UNIX - //GateThread can start before EESetup, so ensure CPU group information is initialized; - CPUGroupInfo::EnsureInitialized(); -#endif // !TARGET_UNIX - // initialize CPU usage information structure; - prevCPUInfo.idleTime.QuadPart = 0; - prevCPUInfo.kernelTime.QuadPart = 0; - prevCPUInfo.userTime.QuadPart = 0; - - PREFIX_ASSUME(NumberOfProcessors < 65536); - prevCPUInfo.numberOfProcessors = NumberOfProcessors; - - /* In following cases, affinity mask can be zero - * 1. hosted, the hosted process already uses multiple cpu groups. - * thus, during CLR initialization, GetCurrentProcessCpuCount() returns 64, and GC threads - * are created to fill up the initial CPU group. ==> use g_SystemInfo.dwNumberOfProcessors - * 2. GCCpuGroups=1, CLR creates GC threads for all processors in all CPU groups - * thus, the threadpool thread would use a whole CPU group (if Thread_UseAllCpuGroups is not set). - * ==> use g_SystemInfo.dwNumberOfProcessors. - * 3. !defined(TARGET_UNIX), GetCurrentProcessCpuCount() - * returns g_SystemInfo.dwNumberOfProcessors ==> use g_SystemInfo.dwNumberOfProcessors; - * Other cases: - * 1. Normal case: the mask is all or a subset of all processors in a CPU group; - * 2. GCCpuGroups=1 && Thread_UseAllCpuGroups = 1, the mask is not used - */ - prevCPUInfo.affinityMask = GetCurrentProcessCpuMask(); - if (prevCPUInfo.affinityMask == 0) - { // create a mask that has g_SystemInfo.dwNumberOfProcessors; - DWORD_PTR mask = 0, maskpos = 1; - for (unsigned int i=0; i < g_SystemInfo.dwNumberOfProcessors; i++) - { - mask |= maskpos; - maskpos <<= 1; - } - prevCPUInfo.affinityMask = mask; - } - - // in some cases GetCurrentProcessCpuCount() returns a number larger than - // g_SystemInfo.dwNumberOfProcessor when there are CPU groups, use the larger - // one to create buffer. This buffer must be cleared with 0's to get correct - // CPU usage statistics - int elementsNeeded = NumberOfProcessors > g_SystemInfo.dwNumberOfProcessors ? - NumberOfProcessors : g_SystemInfo.dwNumberOfProcessors; - - // - // When CLR threads are not using all groups, GetCPUBusyTime_NT will read element X from - // the "prevCPUInfo.usageBuffer" array if and only if "prevCPUInfo.affinityMask" contains a - // set bit in bit position X. This implies that GetCPUBusyTime_NT would read past the end - // of the "usageBuffer" array if the index of the highest set bit in "affinityMask" was - // ever larger than the index of the last array element. - // - // If necessary, expand "elementsNeeded" to ensure that the index of the last array element - // is always at least as large as the index of the highest set bit in the "affinityMask". - // - // This expansion is necessary in any case where the mask returned by GetCurrentProcessCpuMask - // (which is just a wrapper around the Win32 GetProcessAffinityMask API) contains set bits - // at indices greater than or equal to the larger of the basline CPU count values (i.e., - // ThreadpoolMgr::NumberOfProcessors and g_SystemInfo.dwNumberOfProcessors) that were - // captured earlier on (during ThreadpoolMgr::Initialize and during EEStartupHelper, - // respectively). Note that in the relevant scenario (i.e., when CLR threads are not using - // all groups) the mask and CPU counts are all collected via "group-unaware" APIs and are - // all "group-local" values as a result. - // - // Expansion is necessary in at least the following cases: - // - // - If the baseline CPU counts were captured while running in groups that contain fewer - // CPUs (in a multi-group system with heterogenous CPU counts across groups), but this code - // is now running in a group that contains a larger number of CPUs. - // - // - If CPUs were hot-added to the system and then added to the current process affinity - // mask at some point after the baseline CPU counts were captured. - // - if (!CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - { - int elementsNeededToCoverMask = 0; - DWORD_PTR remainingMask = prevCPUInfo.affinityMask; - while (remainingMask != 0) - { - elementsNeededToCoverMask++; - remainingMask >>= 1; - } - - if (elementsNeeded < elementsNeededToCoverMask) - { - elementsNeeded = elementsNeededToCoverMask; - } - } - - if (!ClrSafeInt::multiply(elementsNeeded, sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), - prevCPUInfo.usageBufferSize)) - return 0; - - prevCPUInfo.usageBuffer = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *)alloca(prevCPUInfo.usageBufferSize); - if (prevCPUInfo.usageBuffer == NULL) - return 0; - - memset((void *)prevCPUInfo.usageBuffer, 0, prevCPUInfo.usageBufferSize); //must clear it with 0s - - GetCPUBusyTime_NT(&prevCPUInfo); -#else // !TARGET_UNIX - PAL_IOCP_CPU_INFORMATION prevCPUInfo; - memset(&prevCPUInfo, 0, sizeof(prevCPUInfo)); - - GetCPUBusyTime_NT(&prevCPUInfo); // ignore return value the first time -#endif // !TARGET_UNIX - - BOOL IgnoreNextSample = FALSE; - - do - { - timer.Wait(); - - if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)) - FireEtwThreadPoolWorkingThreadCount(TakeMaxWorkingThreadCount(), GetClrInstanceId()); - -#ifdef DEBUGGING_SUPPORTED - // if we are stopped at a debug breakpoint, go back to sleep - if (CORDebuggerAttached() && g_pDebugInterface->IsStopped()) - continue; -#endif // DEBUGGING_SUPPORTED - - if (!GCHeapUtilities::IsGCInProgress(FALSE) ) - { - if (IgnoreNextSample) - { - IgnoreNextSample = FALSE; - int cpuUtilizationTemp = GetCPUBusyTime_NT(&prevCPUInfo); // updates prevCPUInfo as side effect - // don't artificially drive down average if cpu is high - if (cpuUtilizationTemp <= CpuUtilizationLow) - cpuUtilization = CpuUtilizationLow + 1; - else - cpuUtilization = cpuUtilizationTemp; - } - else - { - cpuUtilization = GetCPUBusyTime_NT(&prevCPUInfo); // updates prevCPUInfo as side effect - } - } - else - { - int cpuUtilizationTemp = GetCPUBusyTime_NT(&prevCPUInfo); // updates prevCPUInfo as side effect - // don't artificially drive down average if cpu is high - if (cpuUtilizationTemp <= CpuUtilizationLow) - cpuUtilization = CpuUtilizationLow + 1; - else - cpuUtilization = cpuUtilizationTemp; - IgnoreNextSample = TRUE; - } - - PerformGateActivities(cpuUtilization); - } - while (ShouldGateThreadKeepRunning()); - - return 0; -} - -void ThreadpoolMgr::PerformGateActivities(int cpuUtilization) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - ThreadpoolMgr::cpuUtilization = cpuUtilization; - -#ifndef TARGET_UNIX - // don't mess with CP thread pool settings if not initialized yet - if (InitCompletionPortThreadpool) - { - ThreadCounter::Counts oldCounts, newCounts; - oldCounts = CPThreadCounter.GetCleanCounts(); - - if (oldCounts.NumActive == oldCounts.NumWorking && - oldCounts.NumRetired == 0 && - oldCounts.NumActive < MaxLimitTotalCPThreads && - !GCHeapUtilities::IsGCInProgress(TRUE)) - - { - BOOL status; - DWORD numBytes; - size_t key; - LPOVERLAPPED pOverlapped; - DWORD errorCode; - - errorCode = S_OK; - - status = GetQueuedCompletionStatus( - GlobalCompletionPort, - &numBytes, - (PULONG_PTR)&key, - &pOverlapped, - 0 // immediate return - ); - - if (status == 0) - { - errorCode = GetLastError(); - } - - if (errorCode != WAIT_TIMEOUT) - { - QueuedStatus *CompletionStatus = NULL; - - // loop, retrying until memory is allocated. Under such conditions the gate - // thread is not useful anyway, so I feel comfortable with this behavior - do - { - // make sure to free mem later in thread - CompletionStatus = new (nothrow) QueuedStatus; - if (CompletionStatus == NULL) - { - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } - } - while (CompletionStatus == NULL); - - CompletionStatus->numBytes = numBytes; - CompletionStatus->key = (PULONG_PTR)key; - CompletionStatus->pOverlapped = pOverlapped; - CompletionStatus->errorCode = errorCode; - - // IOCP threads are created as "active" and "working" - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumActive++; - newCounts.NumWorking++; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - // loop, retrying until thread is created. - while (!CreateCompletionPortThread((LPVOID)CompletionStatus)) - { - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } - } - } - else if (cpuUtilization < CpuUtilizationLow) - { - // this could be an indication that threads might be getting blocked or there is no work - if (oldCounts.NumWorking == oldCounts.NumActive && // don't bump the limit if there are already free threads - oldCounts.NumRetired > 0) - { - RetiredCPWakeupEvent->Set(); - } - } - } -#endif // !TARGET_UNIX - - if (!UsePortableThreadPool() && - 0 == CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection)) - { - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() && SufficientDelaySinceLastDequeue()) - { - DangerousNonHostedSpinLockHolder tal(&ThreadAdjustmentLock); - - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - while (counts.NumActive < MaxLimitTotalWorkerThreads && //don't add a thread if we're at the max - counts.NumActive >= counts.MaxWorking) //don't add a thread if we're already in the process of adding threads - { - bool breakIntoDebugger = (0 != CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation)); - if (breakIntoDebugger) - { - OutputDebugStringW(W("The CLR ThreadPool detected work queue starvation!")); - DebugBreak(); - } - - ThreadCounter::Counts newCounts = counts; - newCounts.MaxWorking = newCounts.NumActive + 1; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - { - HillClimbingInstance.ForceChange(newCounts.MaxWorking, Starvation); - MaybeAddWorkingWorker(); - break; - } - else - { - counts = oldCounts; - } - } - } - } -} - // called by logic to spawn a new completion port thread. // return false if not enough time has elapsed since the last // time we sampled the cpu utilization. @@ -4252,35 +1268,6 @@ BOOL ThreadpoolMgr::SufficientDelaySinceLastSample(unsigned int LastThreadCreati } -// called by logic to spawn new worker threads, return true if it's been too long -// since the last dequeue operation - takes number of worker threads into account -// in deciding "too long" -BOOL ThreadpoolMgr::SufficientDelaySinceLastDequeue() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - #define DEQUEUE_DELAY_THRESHOLD (GATE_THREAD_DELAY * 2) - - unsigned delay = GetTickCount() - VolatileLoad(&LastDequeueTime); - unsigned tooLong; - - if(cpuUtilization < CpuUtilizationLow) - { - tooLong = GATE_THREAD_DELAY; - } - else - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - unsigned numThreads = counts.MaxWorking; - tooLong = numThreads * DEQUEUE_DELAY_THRESHOLD; - } - - return (delay > tooLong); - -} - - #ifdef _MSC_VER #ifdef HOST_64BIT #pragma warning (default : 4716) @@ -4565,19 +1552,10 @@ DWORD ThreadpoolMgr::FireTimers() InterlockedIncrement(&timerInfo->refCount); - if (UsePortableThreadPool()) - { - GCX_COOP(); + GCX_COOP(); - ARG_SLOT args[] = { PtrToArgSlot(AsyncTimerCallbackCompletion), PtrToArgSlot(timerInfo) }; - MethodDescCallSite(METHOD__THREAD_POOL__UNSAFE_QUEUE_UNMANAGED_WORK_ITEM).Call(args); - } - else - { - QueueUserWorkItem(AsyncTimerCallbackCompletion, - timerInfo, - QUEUE_ONLY /* TimerInfo take care of deleting*/); - } + ARG_SLOT args[] = { PtrToArgSlot(AsyncTimerCallbackCompletion), PtrToArgSlot(timerInfo) }; + MethodDescCallSite(METHOD__THREAD_POOL__UNSAFE_QUEUE_UNMANAGED_WORK_ITEM).Call(args); if (timerInfo->Period != 0 && timerInfo->Period != (ULONG)-1) { From be8de401b940b16e12be7e71cbc36bf0653dbe23 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Wed, 6 Jul 2022 20:25:49 -0700 Subject: [PATCH 02/20] Delete more unnecessary functions --- .../src/System/Threading/Overlapped.cs | 5 - .../Threading/ThreadPool.CoreCLR.Windows.cs | 37 +- .../System/Threading/ThreadPool.CoreCLR.cs | 322 ++---------------- src/coreclr/debug/ee/dactable.cpp | 1 - .../System/Threading/ThreadPool.Windows.cs | 2 +- src/coreclr/vm/CMakeLists.txt | 2 - src/coreclr/vm/appdomain.cpp | 7 - src/coreclr/vm/comthreadpool.cpp | 20 -- src/coreclr/vm/comthreadpool.h | 30 -- src/coreclr/vm/ecalllist.h | 21 -- src/coreclr/vm/hillclimbing.h | 96 ------ src/coreclr/vm/nativeoverlapped.h | 1 - src/coreclr/vm/qcallentrypoints.cpp | 3 - src/coreclr/vm/threadpoolrequest.cpp | 22 -- src/coreclr/vm/threadpoolrequest.h | 6 - src/coreclr/vm/threads.h | 2 - src/coreclr/vm/win32threadpool.cpp | 81 ----- src/coreclr/vm/win32threadpool.h | 28 -- .../System/Threading/PortableThreadPool.cs | 8 +- .../ThreadPoolBoundHandle.Windows.cs | 3 +- .../Threading/ThreadPool.Browser.Mono.cs | 2 +- 21 files changed, 37 insertions(+), 662 deletions(-) delete mode 100644 src/coreclr/vm/hillclimbing.h diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs index 2f7fd6721e67e7..aa48614ba8dcf7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -52,8 +52,6 @@ internal static void PerformIOCompletionCallback(uint errorCode, uint numBytes, ExecutionContext.RunInternal(helper._executionContext, IOCompletionCallback_Context_Delegate, helper); } - // Quickly check the VM again, to see if a packet has arrived. - OverlappedData.CheckVMForIOPacket(out pNativeOverlapped, out errorCode, out numBytes); } while (pNativeOverlapped != null); } } @@ -120,9 +118,6 @@ internal sealed unsafe class OverlappedData [MethodImpl(MethodImplOptions.InternalCall)] internal static extern OverlappedData GetOverlappedFromNative(NativeOverlapped* nativeOverlappedPtr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void CheckVMForIOPacket(out NativeOverlapped* pNativeOverlapped, out uint errorCode, out uint numBytes); } #endregion class OverlappedData diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs index 66b91d05c38d36..279abdf1baa204 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs @@ -18,32 +18,19 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); } - if (UsePortableThreadPoolForIO) - { - // OS doesn't signal handle, so do it here - overlapped->InternalLow = IntPtr.Zero; - - PortableThreadPool.ThreadPoolInstance.QueueNativeOverlapped(overlapped); - return true; - } + // OS doesn't signal handle, so do it here + overlapped->InternalLow = IntPtr.Zero; - return PostQueuedCompletionStatus(overlapped); + PortableThreadPool.ThreadPoolInstance.QueueNativeOverlapped(overlapped); + return true; } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe bool PostQueuedCompletionStatus(NativeOverlapped* overlapped); - [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] [SupportedOSPlatform("windows")] public static bool BindHandle(IntPtr osHandle) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle); - return true; - } - - return BindIOCompletionCallbackNative(osHandle); + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle); + return true; } [SupportedOSPlatform("windows")] @@ -56,13 +43,8 @@ public static bool BindHandle(SafeHandle osHandle) { osHandle.DangerousAddRef(ref mustReleaseSafeHandle); - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle()); - return true; - } - - return BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle()); + return true; } finally { @@ -70,8 +52,5 @@ public static bool BindHandle(SafeHandle osHandle) osHandle.DangerousRelease(); } } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 0906b97c6f5f35..ffb92841bbc44d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -47,13 +47,7 @@ internal void SetNativeRegisteredWaitHandle(IntPtr nativeRegisteredWaitHandle) internal void OnBeforeRegister() { - if (ThreadPool.UsePortableThreadPool) - { - GC.SuppressFinalize(this); - return; - } - - Handle.DangerousAddRef(ref _releaseHandle); + GC.SuppressFinalize(this); } /// @@ -67,71 +61,13 @@ internal void OnBeforeRegister() /// public bool Unregister(WaitHandle waitObject) { - if (ThreadPool.UsePortableThreadPool) - { - return UnregisterPortable(waitObject); - } - - s_callbackLock.Acquire(); - try - { - if (!IsValidHandle(_nativeRegisteredWaitHandle) || - !UnregisterWaitNative(_nativeRegisteredWaitHandle, waitObject?.SafeWaitHandle)) - { - return false; - } - _nativeRegisteredWaitHandle = InvalidHandleValue; - - if (_releaseHandle) - { - Handle.DangerousRelease(); - _releaseHandle = false; - } - } - finally - { - s_callbackLock.Release(); - } - - GC.SuppressFinalize(this); - return true; + return UnregisterPortable(waitObject); } ~RegisteredWaitHandle() { - if (ThreadPool.UsePortableThreadPool) - { - return; - } - - s_callbackLock.Acquire(); - try - { - if (!IsValidHandle(_nativeRegisteredWaitHandle)) - { - return; - } - - WaitHandleCleanupNative(_nativeRegisteredWaitHandle); - _nativeRegisteredWaitHandle = InvalidHandleValue; - - if (_releaseHandle) - { - Handle.DangerousRelease(); - _releaseHandle = false; - } - } - finally - { - s_callbackLock.Release(); - } + return; } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void WaitHandleCleanupNative(IntPtr handle); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle? waitObject); } internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem @@ -164,16 +100,10 @@ public static partial class ThreadPool { private static readonly byte UsePortableThreadPoolConfigValues = InitializeConfigAndDetermineUsePortableThreadPool(); - // SOS's ThreadPool command depends on the following names - internal static readonly bool UsePortableThreadPool = UsePortableThreadPoolConfigValues != 0; - internal static readonly bool UsePortableThreadPoolForIO = UsePortableThreadPoolConfigValues > 1; - // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work - internal static bool YieldFromDispatchLoop => !UsePortableThreadPool; + internal static bool YieldFromDispatchLoop => false; - // This needs to be initialized after UsePortableThreadPool above, as it may depend on UsePortableThreadPool and the - // config initialization private static readonly bool IsWorkerTrackingEnabledInConfig = GetEnableWorkerTracking(); private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() @@ -227,111 +157,31 @@ private static extern unsafe int GetNextConfigUInt32Value( out char* appContextConfigName); private static bool GetEnableWorkerTracking() => - UsePortableThreadPool - ? AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false) - : GetEnableWorkerTrackingNative(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool CanSetMinIOCompletionThreads(int ioCompletionThreads); - - internal static void SetMinIOCompletionThreads(int ioCompletionThreads) - { - Debug.Assert(UsePortableThreadPool); - Debug.Assert(!UsePortableThreadPoolForIO); - Debug.Assert(ioCompletionThreads >= 0); - - bool success = SetMinThreadsNative(1, ioCompletionThreads); // worker thread count is ignored - Debug.Assert(success); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool CanSetMaxIOCompletionThreads(int ioCompletionThreads); - - internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) - { - Debug.Assert(UsePortableThreadPool); - Debug.Assert(!UsePortableThreadPoolForIO); - Debug.Assert(ioCompletionThreads > 0); - - bool success = SetMaxThreadsNative(1, ioCompletionThreads); // worker thread count is ignored - Debug.Assert(success); - } + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { - if (UsePortableThreadPool) - { - return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); - } - - return - workerThreads >= 0 && - completionPortThreads >= 0 && - SetMaxThreadsNative(workerThreads, completionPortThreads); + return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); - } - else if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out _); - GetMaxThreadsNative(out _, out completionPortThreads); - } - else - { - GetMaxThreadsNative(out workerThreads, out completionPortThreads); - } + PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); } public static bool SetMinThreads(int workerThreads, int completionPortThreads) { - if (UsePortableThreadPool) - { - return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); - } - - return - workerThreads >= 0 && - completionPortThreads >= 0 && - SetMinThreadsNative(workerThreads, completionPortThreads); + return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); - } - else if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out _); - GetMinThreadsNative(out _, out completionPortThreads); - } - else - { - GetMinThreadsNative(out workerThreads, out completionPortThreads); - } + PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); } public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); - } - else if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out _); - GetAvailableThreadsNative(out _, out completionPortThreads); - } - else - { - GetAvailableThreadsNative(out workerThreads, out completionPortThreads); - } + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); } /// @@ -344,22 +194,10 @@ public static int ThreadCount { get { - int count = 0; - if (UsePortableThreadPool) - { - count += PortableThreadPool.ThreadPoolInstance.ThreadCount; - } - if (!UsePortableThreadPoolForIO) - { - count += GetThreadCount(); - } - return count; + return PortableThreadPool.ThreadPoolInstance.ThreadCount; } } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int GetThreadCount(); - /// /// Gets the number of work items that have been processed so far. /// @@ -370,23 +208,11 @@ public static long CompletedWorkItemCount { get { - long count = 0; - if (UsePortableThreadPool) - { - count += PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; - } - if (!UsePortableThreadPoolForIO) - { - count += GetCompletedWorkItemCount(); - } - return count; + return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; } } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_GetCompletedWorkItemCount")] - private static partial long GetCompletedWorkItemCount(); - - private static long PendingUnmanagedWorkItemCount => UsePortableThreadPool ? 0 : GetPendingUnmanagedWorkItemCount(); + private static long PendingUnmanagedWorkItemCount => 0; [MethodImpl(MethodImplOptions.InternalCall)] private static extern long GetPendingUnmanagedWorkItemCount(); @@ -410,46 +236,14 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( registeredWaitHandle.OnBeforeRegister(); - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); - } - else - { - IntPtr nativeRegisteredWaitHandle = - RegisterWaitForSingleObjectNative( - waitObject, - registeredWaitHandle.Callback, - (uint)registeredWaitHandle.TimeoutDurationMs, - !registeredWaitHandle.Repeating, - registeredWaitHandle); - registeredWaitHandle.SetNativeRegisteredWaitHandle(nativeRegisteredWaitHandle); - } + PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); return registeredWaitHandle; } internal static void RequestWorkerThread() { - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.RequestWorker(); - return; - } - - RequestWorkerThreadNative(); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_RequestWorkerThread")] - private static partial Interop.BOOL RequestWorkerThreadNative(); - - // Entry point from unmanaged code - private static void EnsureGateThreadRunning() - { - Debug.Assert(UsePortableThreadPool); - Debug.Assert(!UsePortableThreadPoolForIO); - - PortableThreadPool.EnsureGateThreadRunning(); + PortableThreadPool.ThreadPoolInstance.RequestWorker(); } /// @@ -459,110 +253,42 @@ private static void EnsureGateThreadRunning() /// True if the runtime still needs to perform gate activities, false otherwise internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) { - Debug.Assert(UsePortableThreadPool); - - if (UsePortableThreadPoolForIO) - { - return false; - } - - return PerformRuntimeSpecificGateActivitiesNative(cpuUtilization) != Interop.BOOL.FALSE; + return false; } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_PerformGateActivities")] - private static partial Interop.BOOL PerformRuntimeSpecificGateActivitiesNative(int cpuUtilization); - // Entry point from unmanaged code private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) { - Debug.Assert(UsePortableThreadPool); UnsafeQueueHighPriorityWorkItemInternal(new UnmanagedThreadPoolWorkItem(callback, state)); } - // Native methods: - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool SetMinThreadsNative(int workerThreads, int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool SetMaxThreadsNative(int workerThreads, int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetMinThreadsNative(out int workerThreads, out int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetMaxThreadsNative(out int workerThreads, out int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads); - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) { - if (UsePortableThreadPool) - { - return - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete( - threadLocalCompletionCountObject, - currentTimeMs); - } - - return NotifyWorkItemCompleteNative(); + return + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete( + threadLocalCompletionCountObject, + currentTimeMs); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool NotifyWorkItemCompleteNative(); - internal static void ReportThreadStatus(bool isWorking) { - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); - return; - } - - ReportThreadStatusNative(isWorking); + PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void ReportThreadStatusNative(bool isWorking); - internal static void NotifyWorkItemProgress() { - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); - return; - } - - NotifyWorkItemProgressNative(); + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void NotifyWorkItemProgressNative(); - - internal static bool NotifyThreadBlocked() => - UsePortableThreadPool && PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); + internal static bool NotifyThreadBlocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); internal static void NotifyThreadUnblocked() { - Debug.Assert(UsePortableThreadPool); PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked(); } internal static object? GetOrCreateThreadLocalCompletionCountObject() => - UsePortableThreadPool ? PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject() : null; - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool GetEnableWorkerTrackingNative(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr RegisterWaitForSingleObjectNative( - WaitHandle waitHandle, - object state, - uint timeOutInterval, - bool executeOnlyOnce, - RegisteredWaitHandle registeredWaitHandle - ); + PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); } } diff --git a/src/coreclr/debug/ee/dactable.cpp b/src/coreclr/debug/ee/dactable.cpp index ce77e65b2d2150..525bb0f4af3509 100644 --- a/src/coreclr/debug/ee/dactable.cpp +++ b/src/coreclr/debug/ee/dactable.cpp @@ -14,7 +14,6 @@ #include "../../vm/virtualcallstub.h" #include "../../vm/win32threadpool.h" -#include "../../vm/hillclimbing.h" #include "../../vm/codeman.h" #include "../../vm/eedbginterfaceimpl.h" #include "../../vm/common.h" diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs index 391148cf1cd07c..0fde8cabcea44f 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs @@ -423,7 +423,7 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); } - // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) + // OS doesn't signal handle, so do it here overlapped->InternalLow = (IntPtr)0; // Both types of callbacks are executed on the same thread pool return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index b5b9afb48b40e5..ef06b4363b03a7 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -82,7 +82,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON genericdict.cpp generics.cpp hash.cpp - hillclimbing.cpp ilinstrumentation.cpp ilstubcache.cpp ilstubresolver.cpp @@ -180,7 +179,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON gcheaputilities.h generics.h hash.h - hillclimbing.h ilinstrumentation.h ilstubcache.h ilstubresolver.h diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index b1e0160489fc9e..ebee5f7e9af39d 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1012,8 +1012,6 @@ void SystemDomain::Attach() CallCountingStubManager::Init(); #endif - PerAppDomainTPCountList::InitAppDomainIndexList(); - m_SystemDomainCrst.Init(CrstSystemDomain, (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); m_DelayedUnloadCrst.Init(CrstSystemDomainDelayedUnloadList, CRST_UNSAFE_COOPGC); @@ -1985,11 +1983,6 @@ void AppDomain::Init() SetStage( STAGE_CREATING); - //Allocate the threadpool entry before the appdomain id list. Otherwise, - //the thread pool list will be out of sync if insertion of id in - //the appdomain fails. - m_tpIndex = PerAppDomainTPCountList::AddNewTPIndex(); - BaseDomain::Init(); // Set up the binding caches diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index e0f89a8350448d..2be9e24c7a25c0 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -192,26 +192,6 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, } FCIMPLEND -/*****************************************************************************************************/ -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMinIOCompletionThreads, DWORD ioCompletionThreads) -{ - FCALL_CONTRACT; - - BOOL result = ThreadpoolMgr::CanSetMinIOCompletionThreads(ioCompletionThreads); - FC_RETURN_BOOL(result); -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMaxIOCompletionThreads, DWORD ioCompletionThreads) -{ - FCALL_CONTRACT; - - BOOL result = ThreadpoolMgr::CanSetMaxIOCompletionThreads(ioCompletionThreads); - FC_RETURN_BOOL(result); -} -FCIMPLEND - /*****************************************************************************************************/ struct RegisterWaitForSingleObjectCallback_Args diff --git a/src/coreclr/vm/comthreadpool.h b/src/coreclr/vm/comthreadpool.h index 1b700cd91fd810..f2375f89464d52 100644 --- a/src/coreclr/vm/comthreadpool.h +++ b/src/coreclr/vm/comthreadpool.h @@ -27,39 +27,9 @@ class ThreadPoolNative UINT32 *configValueRef, BOOL *isBooleanRef, LPCWSTR *appContextConfigNameRef); - static FCDECL1(FC_BOOL_RET, CorCanSetMinIOCompletionThreads, DWORD ioCompletionThreads); - static FCDECL1(FC_BOOL_RET, CorCanSetMaxIOCompletionThreads, DWORD ioCompletionThreads); - static FCDECL2(FC_BOOL_RET, CorSetMaxThreads, DWORD workerThreads, DWORD completionPortThreads); - static FCDECL2(VOID, CorGetMaxThreads, DWORD* workerThreads, DWORD* completionPortThreads); - static FCDECL2(FC_BOOL_RET, CorSetMinThreads, DWORD workerThreads, DWORD completionPortThreads); - static FCDECL2(VOID, CorGetMinThreads, DWORD* workerThreads, DWORD* completionPortThreads); - static FCDECL2(VOID, CorGetAvailableThreads, DWORD* workerThreads, DWORD* completionPortThreads); - static FCDECL0(INT32, GetThreadCount); static FCDECL0(INT64, GetPendingUnmanagedWorkItemCount); - - static FCDECL0(VOID, NotifyRequestProgress); - static FCDECL0(FC_BOOL_RET, NotifyRequestComplete); - - static FCDECL0(FC_BOOL_RET, GetEnableWorkerTracking); - - static FCDECL1(void, ReportThreadStatus, CLR_BOOL isWorking); - - static FCDECL5(LPVOID, CorRegisterWaitForSingleObject, - Object* waitObjectUNSAFE, - Object* stateUNSAFE, - UINT32 timeout, - CLR_BOOL executeOnlyOnce, - Object* registeredWaitObjectUNSAFE); - - static FCDECL1(FC_BOOL_RET, CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped); - static FCDECL2(FC_BOOL_RET, CorUnregisterWait, LPVOID WaitHandle, Object * objectToNotify); - static FCDECL1(void, CorWaitHandleCleanupNative, LPVOID WaitHandle); - static FCDECL1(FC_BOOL_RET, CorBindIoCompletionCallback, HANDLE fileHandle); }; -extern "C" INT64 QCALLTYPE ThreadPool_GetCompletedWorkItemCount(); -extern "C" BOOL QCALLTYPE ThreadPool_RequestWorkerThread(); -extern "C" BOOL QCALLTYPE ThreadPool_PerformGateActivities(INT32 cpuUtilization); extern "C" HANDLE QCALLTYPE AppDomainTimer_Create(INT32 dueTime, INT32 timerId); extern "C" BOOL QCALLTYPE AppDomainTimer_Change(HANDLE hTimer, INT32 dueTime); extern "C" BOOL QCALLTYPE AppDomainTimer_Delete(HANDLE hTimer); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index d2c3822f9de832..81ca5138dcff03 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -397,27 +397,7 @@ FCFuncEnd() FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetNextConfigUInt32Value", ThreadPoolNative::GetNextConfigUInt32Value) - FCFuncElement("PostQueuedCompletionStatus", ThreadPoolNative::CorPostQueuedCompletionStatus) - FCFuncElement("GetAvailableThreadsNative", ThreadPoolNative::CorGetAvailableThreads) - FCFuncElement("CanSetMinIOCompletionThreads", ThreadPoolNative::CorCanSetMinIOCompletionThreads) - FCFuncElement("CanSetMaxIOCompletionThreads", ThreadPoolNative::CorCanSetMaxIOCompletionThreads) - FCFuncElement("SetMinThreadsNative", ThreadPoolNative::CorSetMinThreads) - FCFuncElement("GetMinThreadsNative", ThreadPoolNative::CorGetMinThreads) - FCFuncElement("GetThreadCount", ThreadPoolNative::GetThreadCount) FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) - FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) - FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) - FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) - FCFuncElement("GetMaxThreadsNative", ThreadPoolNative::CorGetMaxThreads) - FCFuncElement("NotifyWorkItemCompleteNative", ThreadPoolNative::NotifyRequestComplete) - FCFuncElement("NotifyWorkItemProgressNative", ThreadPoolNative::NotifyRequestProgress) - FCFuncElement("GetEnableWorkerTrackingNative", ThreadPoolNative::GetEnableWorkerTracking) - FCFuncElement("ReportThreadStatusNative", ThreadPoolNative::ReportThreadStatus) -FCFuncEnd() - -FCFuncStart(gRegisteredWaitHandleFuncs) - FCFuncElement("UnregisterWaitNative", ThreadPoolNative::CorUnregisterWait) - FCFuncElement("WaitHandleCleanupNative", ThreadPoolNative::CorWaitHandleCleanupNative) FCFuncEnd() FCFuncStart(gWaitHandleFuncs) @@ -576,7 +556,6 @@ FCFuncEnd() FCFuncStart(gOverlappedFuncs) FCFuncElement("AllocateNativeOverlapped", AllocateNativeOverlapped) FCFuncElement("FreeNativeOverlapped", FreeNativeOverlapped) - FCFuncElement("CheckVMForIOPacket", CheckVMForIOPacket) FCFuncElement("GetOverlappedFromNative", GetOverlappedFromNative) FCFuncEnd() diff --git a/src/coreclr/vm/hillclimbing.h b/src/coreclr/vm/hillclimbing.h deleted file mode 100644 index 49bd023af113df..00000000000000 --- a/src/coreclr/vm/hillclimbing.h +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// HillClimbing.h -// -// Defines classes for the ThreadPool's HillClimbing concurrency-optimization -// algorithm. -// - -//========================================================================= - -#ifndef _HILLCLIMBING_H -#define _HILLCLIMBING_H - -#include "complex.h" -#include "random.h" - -enum HillClimbingStateTransition -{ - Warmup, - Initializing, - RandomMove, - ClimbingMove, - ChangePoint, - Stabilizing, - Starvation, //used by ThreadpoolMgr - ThreadTimedOut, //used by ThreadpoolMgr - Undefined, -}; - - -class HillClimbing -{ -private: - int m_wavePeriod; - int m_samplesToMeasure; - double m_targetThroughputRatio; - double m_targetSignalToNoiseRatio; - double m_maxChangePerSecond; - double m_maxChangePerSample; - int m_maxThreadWaveMagnitude; - DWORD m_sampleIntervalLow; - double m_threadMagnitudeMultiplier; - DWORD m_sampleIntervalHigh; - double m_throughputErrorSmoothingFactor; - double m_gainExponent; - double m_maxSampleError; - - double m_currentControlSetting; - LONGLONG m_totalSamples; - int m_lastThreadCount; - double m_elapsedSinceLastChange; //elapsed seconds since last thread count change - double m_completionsSinceLastChange; //number of completions since last thread count change - - double m_averageThroughputNoise; - - double* m_samples; //Circular buffer of the last m_samplesToMeasure samples - double* m_threadCounts; //Thread counts effective at each of m_samples - - unsigned int m_currentSampleInterval; - CLRRandom m_randomIntervalGenerator; - - int m_accumulatedCompletionCount; - double m_accumulatedSampleDuration; - - void ChangeThreadCount(int newThreadCount, HillClimbingStateTransition transition); - void LogTransition(int threadCount, double throughput, HillClimbingStateTransition transition); - - Complex GetWaveComponent(double* samples, int sampleCount, double period); - -public: - void Initialize(); - int Update(int currentThreadCount, double sampleDuration, int numCompletions, int* pNewSampleInterval); - void ForceChange(int newThreadCount, HillClimbingStateTransition transition); -}; - -#define HillClimbingLogCapacity 200 - -struct HillClimbingLogEntry -{ - DWORD TickCount; - HillClimbingStateTransition Transition; - int NewControlSetting; - int LastHistoryCount; - float LastHistoryMean; -}; - -GARY_DECL(HillClimbingLogEntry, HillClimbingLog, HillClimbingLogCapacity); -GVAL_DECL(int, HillClimbingLogFirstIndex); -GVAL_DECL(int, HillClimbingLogSize); -typedef DPTR(HillClimbingLogEntry) PTR_HillClimbingLogEntry; - -#endif diff --git a/src/coreclr/vm/nativeoverlapped.h b/src/coreclr/vm/nativeoverlapped.h index ff041cbbf0a86e..771f9fbf483270 100644 --- a/src/coreclr/vm/nativeoverlapped.h +++ b/src/coreclr/vm/nativeoverlapped.h @@ -73,7 +73,6 @@ typedef OverlappedDataObject* OVERLAPPEDDATAREF; #endif -FCDECL3(void, CheckVMForIOPacket, LPOVERLAPPED* lpOverlapped, DWORD* errorCode, DWORD* numBytes); FCDECL1(LPOVERLAPPED, AllocateNativeOverlapped, OverlappedDataObject* overlapped); FCDECL1(void, FreeNativeOverlapped, LPOVERLAPPED lpOverlapped); FCDECL1(OverlappedDataObject*, GetOverlappedFromNative, LPOVERLAPPED lpOverlapped); diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 92c36b3089458b..c1e44759a99d28 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -209,9 +209,6 @@ static const Entry s_QCall[] = DllImportEntry(ThreadNative_InformThreadNameChange) DllImportEntry(ThreadNative_YieldThread) DllImportEntry(ThreadNative_GetCurrentOSThreadId) - DllImportEntry(ThreadPool_GetCompletedWorkItemCount) - DllImportEntry(ThreadPool_RequestWorkerThread) - DllImportEntry(ThreadPool_PerformGateActivities) DllImportEntry(AppDomainTimer_Create) DllImportEntry(AppDomainTimer_Change) DllImportEntry(AppDomainTimer_Delete) diff --git a/src/coreclr/vm/threadpoolrequest.cpp b/src/coreclr/vm/threadpoolrequest.cpp index e2eb502324f03e..e28adc484b629c 100644 --- a/src/coreclr/vm/threadpoolrequest.cpp +++ b/src/coreclr/vm/threadpoolrequest.cpp @@ -35,28 +35,6 @@ DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) UnManagedPerAppDomainTPCount PerAppDomainTPC //The list of all per-appdomain work-request counts. ArrayListStatic PerAppDomainTPCountList::s_appDomainIndexList; -void PerAppDomainTPCountList::InitAppDomainIndexList() -{ - LIMITED_METHOD_CONTRACT; -} - - -//--------------------------------------------------------------------------- -//AddNewTPIndex adds and returns a per-appdomain TP entry whenever a new appdomain -//is created. Our list count should be equal to the max number of appdomains created -//in the system. -// -//Assumptions: -//This function needs to be called under the SystemDomain lock. -//The ArrayListStatic data dtructure allows traversing of the counts without a -//lock, but addition to the list requires synchronization. -// -TPIndex PerAppDomainTPCountList::AddNewTPIndex() -{ - STANDARD_VM_CONTRACT; - - return TPIndex(); -} //--------------------------------------------------------------------------- //ResetAppDomainIndex: Resets the AppDomain ID and the per-appdomain diff --git a/src/coreclr/vm/threadpoolrequest.h b/src/coreclr/vm/threadpoolrequest.h index f3cd4c49ccac60..84193983fa8761 100644 --- a/src/coreclr/vm/threadpoolrequest.h +++ b/src/coreclr/vm/threadpoolrequest.h @@ -179,9 +179,6 @@ class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { bool TakeActiveRequest(); - void QueueUnmanagedWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context); - PVOID DeQueueUnManagedWorkRequest(bool* lastOne); - void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); inline void SetTPIndexUnused() @@ -239,10 +236,7 @@ class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { //synchronization to indicate start/end of requests to the scheduler. class PerAppDomainTPCountList{ public: - static void InitAppDomainIndexList(); static void ResetAppDomainIndex(TPIndex index); - static bool AreRequestsPendingInAnyAppDomains(); - static LONG GetAppDomainIndexForThreadpoolDispatch(); static TPIndex AddNewTPIndex(); inline static IPerAppDomainTPCount* GetPerAppdomainCount(TPIndex index) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 99b547bf639d5d..9010c341b152ec 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -3528,8 +3528,6 @@ class Thread return GetOverflowCount(&s_ioThreadPoolCompletionCountOverflow); } - static UINT64 GetTotalThreadPoolCompletionCount(); - static void IncrementMonitorLockContentionCount(Thread *pThread) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp index bbd4dcda462d27..529902b44e3888 100644 --- a/src/coreclr/vm/win32threadpool.cpp +++ b/src/coreclr/vm/win32threadpool.cpp @@ -30,7 +30,6 @@ Revision History: #include "threads.h" #include "appdomain.inl" #include "nativeoverlapped.h" -#include "hillclimbing.h" #include "configuration.h" @@ -552,86 +551,6 @@ void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) } } -Thread* ThreadpoolMgr::CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread) -{ - STATIC_CONTRACT_NOTHROW; - if (GetThreadNULLOk()) { STATIC_CONTRACT_GC_TRIGGERS;} else {DISABLED(STATIC_CONTRACT_GC_NOTRIGGER);} - STATIC_CONTRACT_MODE_ANY; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END;*/ - - Thread* pThread = NULL; - - if (g_fEEStarted) { - *pIsCLRThread = TRUE; - } - else - *pIsCLRThread = FALSE; - if (*pIsCLRThread) { - EX_TRY - { - pThread = SetupUnstartedThread(); - } - EX_CATCH - { - pThread = NULL; - } - EX_END_CATCH(SwallowAllExceptions); - if (pThread == NULL) { - return NULL; - } - } - DWORD threadId; - BOOL bOK = FALSE; - HANDLE threadHandle = NULL; - - if (*pIsCLRThread) { - // CreateNewThread takes care of reverting any impersonation - so dont do anything here. - bOK = pThread->CreateNewThread(0, // default stack size - lpStartAddress, - lpArgs, //arguments - W(".NET ThreadPool Worker")); - } - else { -#ifndef TARGET_UNIX - HandleHolder token; - BOOL bReverted = FALSE; - bOK = RevertIfImpersonated(&bReverted, &token); - if (bOK != TRUE) - return NULL; -#endif // !TARGET_UNIX - threadHandle = CreateThread(NULL, // security descriptor - 0, // default stack size - lpStartAddress, - lpArgs, - CREATE_SUSPENDED, - &threadId); - - SetThreadName(threadHandle, W(".NET ThreadPool Worker")); -#ifndef TARGET_UNIX - UndoRevert(bReverted, token); -#endif // !TARGET_UNIX - } - - if (*pIsCLRThread && !bOK) - { - pThread->DecExternalCount(FALSE); - pThread = NULL; - } - - if (*pIsCLRThread) { - return pThread; - } - else - return (Thread*)threadHandle; -} - // this should only be called by unmanaged thread (i.e. there should be no mgd // caller on the stack) since we are swallowing terminal exceptions DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h index b08d7ca32e9c2f..ead529124888d7 100644 --- a/src/coreclr/vm/win32threadpool.h +++ b/src/coreclr/vm/win32threadpool.h @@ -208,8 +208,6 @@ class ThreadpoolMgr public: - static void ReportThreadStatus(bool isWorking); - // enumeration of different kinds of memory blocks that are recycled enum MemType { @@ -253,37 +251,11 @@ class ThreadpoolMgr static BOOL Initialize(); - static BOOL SetMaxThreadsHelper(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads); - - static bool CanSetMinIOCompletionThreads(DWORD ioCompletionThreads); - static bool CanSetMaxIOCompletionThreads(DWORD ioCompletionThreads); - - static BOOL SetMaxThreads(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads); - - static BOOL GetMaxThreads(DWORD* MaxWorkerThreads, - DWORD* MaxIOCompletionThreads); - - static BOOL SetMinThreads(DWORD MinWorkerThreads, - DWORD MinIOCompletionThreads); - - static BOOL GetMinThreads(DWORD* MinWorkerThreads, - DWORD* MinIOCompletionThreads); - - static BOOL GetAvailableThreads(DWORD* AvailableWorkerThreads, - DWORD* AvailableIOCompletionThreads); - - static INT32 GetThreadCount(); - static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, PVOID Context, ULONG Flags, BOOL UnmanagedTPRequest=TRUE); - static BOOL PostQueuedCompletionStatus(LPOVERLAPPED lpOverlapped, - LPOVERLAPPED_COMPLETION_ROUTINE Function); - inline static BOOL IsCompletionPortInitialized() { LIMITED_METHOD_CONTRACT; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 847a9e715ecad7..7ed9d611db71f8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -151,9 +151,7 @@ public bool SetMinThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO - ? ioCompletionThreads > _legacy_maxIOCompletionThreads - : !ThreadPool.CanSetMinIOCompletionThreads(ioCompletionThreads)) + if (ioCompletionThreads > _legacy_maxIOCompletionThreads) { return false; } @@ -243,9 +241,7 @@ public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO - ? ioCompletionThreads < _legacy_minIOCompletionThreads - : !ThreadPool.CanSetMaxIOCompletionThreads(ioCompletionThreads)) + if (ioCompletionThreads < _legacy_minIOCompletionThreads) { return false; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs index 097277e894d9db..f80107fcf1a66a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs @@ -17,8 +17,7 @@ private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle) try { Debug.Assert(OperatingSystem.IsWindows()); - // ThreadPool.BindHandle will always return true, otherwise, it throws. See the underlying FCall - // implementation in ThreadPoolNative::CorBindIoCompletionCallback to see the implementation. + // ThreadPool.BindHandle will always return true, otherwise, it throws. bool succeeded = ThreadPool.BindHandle(handle); Debug.Assert(succeeded); } diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index 4887cf220df47a..9ab712fc0543ed 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -129,7 +129,7 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); } - // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) + // OS doesn't signal handle, so do it here overlapped->InternalLow = (IntPtr)0; // Both types of callbacks are executed on the same thread pool return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); From 14e1757ccd65cf328b24fa9068faf5ca1627da6a Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 13:54:06 -0700 Subject: [PATCH 03/20] Delete win32threadpool.cpp and win32threadpool.h --- src/coreclr/vm/ecalllist.h | 3 + src/coreclr/vm/win32threadpool.cpp | 1967 ---------------------------- src/coreclr/vm/win32threadpool.h | 1088 --------------- 3 files changed, 3 insertions(+), 3055 deletions(-) delete mode 100644 src/coreclr/vm/win32threadpool.cpp delete mode 100644 src/coreclr/vm/win32threadpool.h diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 81ca5138dcff03..0ee86131aafd1e 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -400,6 +400,9 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncEnd() +FCFuncStart(gRegisteredWaitHandleFuncs) +FCFuncEnd() + FCFuncStart(gWaitHandleFuncs) FCFuncElement("WaitOneCore", WaitHandleNative::CorWaitOneNative) FCFuncElement("WaitMultipleIgnoringSyncContext", WaitHandleNative::CorWaitMultipleNative) diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp deleted file mode 100644 index 529902b44e3888..00000000000000 --- a/src/coreclr/vm/win32threadpool.cpp +++ /dev/null @@ -1,1967 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -/*++ - -Module Name: - - Win32ThreadPool.cpp - -Abstract: - - This module implements Threadpool support using Win32 APIs - - -Revision History: - December 1999 - Created - ---*/ - -#include "common.h" -#include "log.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" -#include "delegateinfo.h" -#include "eeconfig.h" -#include "dbginterface.h" -#include "corhost.h" -#include "eventtrace.h" -#include "threads.h" -#include "appdomain.inl" -#include "nativeoverlapped.h" -#include "configuration.h" - - -#ifndef TARGET_UNIX -#ifndef DACCESS_COMPILE - -// APIs that must be accessed through dynamic linking. -typedef int (WINAPI *NtQueryInformationThreadProc) ( - HANDLE ThreadHandle, - THREADINFOCLASS ThreadInformationClass, - PVOID ThreadInformation, - ULONG ThreadInformationLength, - PULONG ReturnLength); -NtQueryInformationThreadProc g_pufnNtQueryInformationThread = NULL; - -typedef int (WINAPI *NtQuerySystemInformationProc) ( - SYSTEM_INFORMATION_CLASS SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength OPTIONAL); -NtQuerySystemInformationProc g_pufnNtQuerySystemInformation = NULL; - -typedef HANDLE (WINAPI * CreateWaitableTimerExProc) ( - LPSECURITY_ATTRIBUTES lpTimerAttributes, - LPCTSTR lpTimerName, - DWORD dwFlags, - DWORD dwDesiredAccess); -CreateWaitableTimerExProc g_pufnCreateWaitableTimerEx = NULL; - -typedef BOOL (WINAPI * SetWaitableTimerExProc) ( - HANDLE hTimer, - const LARGE_INTEGER *lpDueTime, - LONG lPeriod, - PTIMERAPCROUTINE pfnCompletionRoutine, - LPVOID lpArgToCompletionRoutine, - void* WakeContext, //should be PREASON_CONTEXT, but it's not defined for us (and we don't use it) - ULONG TolerableDelay); -SetWaitableTimerExProc g_pufnSetWaitableTimerEx = NULL; - -#endif // !DACCESS_COMPILE -#endif // !TARGET_UNIX - -BOOL ThreadpoolMgr::InitCompletionPortThreadpool = FALSE; -HANDLE ThreadpoolMgr::GlobalCompletionPort; // used for binding io completions on file handles - -SVAL_IMPL(ThreadpoolMgr::ThreadCounter,ThreadpoolMgr,CPThreadCounter); - -SVAL_IMPL_INIT(LONG,ThreadpoolMgr,MaxLimitTotalCPThreads,1000); // = MaxLimitCPThreadsPerCPU * number of CPUS -SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalCPThreads); -SVAL_IMPL(LONG,ThreadpoolMgr,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_IMPL(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr, WorkerCounter); - -SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS -SVAL_IMPL(LONG,ThreadpoolMgr,MaxLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS - -SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization); - -HillClimbing ThreadpoolMgr::HillClimbingInstance; - -// Cacheline aligned, 3 hot variables updated in a group -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::PriorCompletedWorkRequests = 0; -DWORD ThreadpoolMgr::PriorCompletedWorkRequestsTime; -DWORD ThreadpoolMgr::NextCompletedWorkRequestsTime; - -LARGE_INTEGER ThreadpoolMgr::CurrentSampleStartTime; - -unsigned int ThreadpoolMgr::WorkerThreadSpinLimit; -bool ThreadpoolMgr::IsHillClimbingDisabled; -int ThreadpoolMgr::ThreadAdjustmentInterval; - -#define INVALID_HANDLE ((HANDLE) -1) -#define NEW_THREAD_THRESHOLD 7 // Number of requests outstanding before we start a new thread -#define CP_THREAD_PENDINGIO_WAIT 5000 // polling interval when thread is retired but has a pending io -#define GATE_THREAD_DELAY 500 /*milliseconds*/ -#define GATE_THREAD_DELAY_TOLERANCE 50 /*milliseconds*/ -#define DELAY_BETWEEN_SUSPENDS (5000 + GATE_THREAD_DELAY) // time to delay between suspensions - -Volatile ThreadpoolMgr::Initialization = 0; // indicator of whether the threadpool is initialized. - -bool ThreadpoolMgr::s_usePortableThreadPool = false; -bool ThreadpoolMgr::s_usePortableThreadPoolForIO = false; - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) unsigned int ThreadpoolMgr::LastDequeueTime; // used to determine if work items are getting thread starved - -SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestHead); // Head of work request queue -SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestTail); // Head of work request queue - -SVAL_IMPL(ThreadpoolMgr::LIST_ENTRY,ThreadpoolMgr,TimerQueue); // queue of timers - -//unsigned int ThreadpoolMgr::LastCpuSamplingTime=0; // last time cpu utilization was sampled by gate thread -unsigned int ThreadpoolMgr::LastCPThreadCreation=0; // last time a completion port thread was created -unsigned int ThreadpoolMgr::NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads - - -CrstStatic ThreadpoolMgr::WorkerCriticalSection; -CLREvent * ThreadpoolMgr::RetiredCPWakeupEvent; // wakeup event for completion port threads -CrstStatic ThreadpoolMgr::WaitThreadsCriticalSection; -ThreadpoolMgr::LIST_ENTRY ThreadpoolMgr::WaitThreadsHead; - -CLRLifoSemaphore* ThreadpoolMgr::WorkerSemaphore; -CLRLifoSemaphore* ThreadpoolMgr::RetiredWorkerSemaphore; - -CrstStatic ThreadpoolMgr::TimerQueueCriticalSection; -HANDLE ThreadpoolMgr::TimerThread=NULL; -Thread *ThreadpoolMgr::pTimerThread=NULL; - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) DWORD ThreadpoolMgr::LastTickCount; - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::GateThreadStatus=GATE_THREAD_STATUS_NOT_RUNNING; - -// Move out of from preceeding variables' cache line -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) ThreadpoolMgr::RecycledListsWrapper ThreadpoolMgr::RecycledLists; - -ThreadpoolMgr::TimerInfo *ThreadpoolMgr::TimerInfosToBeRecycled = NULL; - -BOOL ThreadpoolMgr::IsApcPendingOnWaitThread = FALSE; - -#ifndef DACCESS_COMPILE - -// Macros for inserting/deleting from doubly linked list - -#define InitializeListHead(ListHead) (\ - (ListHead)->Flink = (ListHead)->Blink = (ListHead)) - -// -// these are named the same as slightly different macros in the NT headers -// -#undef RemoveHeadList -#undef RemoveEntryList -#undef InsertTailList -#undef InsertHeadList - -#define RemoveHeadList(ListHead,FirstEntry) \ - {\ - FirstEntry = (LIST_ENTRY*) (ListHead)->Flink;\ - ((LIST_ENTRY*)FirstEntry->Flink)->Blink = (ListHead);\ - (ListHead)->Flink = FirstEntry->Flink;\ - } - -#define RemoveEntryList(Entry) {\ - LIST_ENTRY* _EX_Entry;\ - _EX_Entry = (Entry);\ - ((LIST_ENTRY*) _EX_Entry->Blink)->Flink = _EX_Entry->Flink;\ - ((LIST_ENTRY*) _EX_Entry->Flink)->Blink = _EX_Entry->Blink;\ - } - -#define InsertTailList(ListHead,Entry) \ - (Entry)->Flink = (ListHead);\ - (Entry)->Blink = (ListHead)->Blink;\ - ((LIST_ENTRY*)(ListHead)->Blink)->Flink = (Entry);\ - (ListHead)->Blink = (Entry); - -#define InsertHeadList(ListHead,Entry) {\ - LIST_ENTRY* _EX_Flink;\ - LIST_ENTRY* _EX_ListHead;\ - _EX_ListHead = (LIST_ENTRY*)(ListHead);\ - _EX_Flink = (LIST_ENTRY*) _EX_ListHead->Flink;\ - (Entry)->Flink = _EX_Flink;\ - (Entry)->Blink = _EX_ListHead;\ - _EX_Flink->Blink = (Entry);\ - _EX_ListHead->Flink = (Entry);\ - } - -#define IsListEmpty(ListHead) \ - ((ListHead)->Flink == (ListHead)) - -#define SetLastHRError(hr) \ - if (HRESULT_FACILITY(hr) == FACILITY_WIN32)\ - SetLastError(HRESULT_CODE(hr));\ - else \ - SetLastError(ERROR_INVALID_DATA);\ - -/************************************************************************/ - -void ThreadpoolMgr::RecycledListsWrapper::Initialize( unsigned int numProcs ) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - pRecycledListPerProcessor = new RecycledListInfo[numProcs][MEMTYPE_COUNT]; -} - -//--// - -void ThreadpoolMgr::EnsureInitialized() -{ - CONTRACTL - { - THROWS; // EnsureInitializedSlow can throw - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - if (IsInitialized()) - return; - - EnsureInitializedSlow(); -} - -NOINLINE void ThreadpoolMgr::EnsureInitializedSlow() -{ - CONTRACTL - { - THROWS; // Initialize can throw - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - DWORD dwSwitchCount = 0; - -retry: - if (InterlockedCompareExchange(&Initialization, 1, 0) == 0) - { - if (Initialize()) - Initialization = -1; - else - { - Initialization = 0; - COMPlusThrowOM(); - } - } - else // someone has already begun initializing. - { - // wait until it finishes - while (Initialization != -1) - { - __SwitchToThread(0, ++dwSwitchCount); - goto retry; - } - } -} - -BOOL ThreadpoolMgr::Initialize() -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - BOOL bRet = FALSE; - BOOL bExceptionCaught = FALSE; - - NumberOfProcessors = GetCurrentProcessCpuCount(); - InitPlatformVariables(); - - EX_TRY - { - TimerQueueCriticalSection.Init(CrstThreadpoolTimerQueue); - - // initialize TimerQueue - InitializeListHead(&TimerQueue); - -#ifndef TARGET_UNIX - //ThreadPool_CPUGroup - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - RecycledLists.Initialize( CPUGroupInfo::GetNumActiveProcessors() ); - else - RecycledLists.Initialize( g_SystemInfo.dwNumberOfProcessors ); -#else // !TARGET_UNIX - RecycledLists.Initialize( PAL_GetTotalCpuCount() ); -#endif // !TARGET_UNIX - } - EX_CATCH - { - // Note: It is fine to call Destroy on uninitialized critical sections - TimerQueueCriticalSection.Destroy(); - - bExceptionCaught = TRUE; - } - EX_END_CATCH(SwallowAllExceptions); - - if (bExceptionCaught) - { - goto end; - } - - bRet = TRUE; -end: - return bRet; -} - -void ThreadpoolMgr::InitPlatformVariables() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - HINSTANCE hNtDll; - HINSTANCE hCoreSynch = nullptr; - { - CONTRACT_VIOLATION(GCViolation|FaultViolation); - hNtDll = CLRLoadLibrary(W("ntdll.dll")); - _ASSERTE(hNtDll); - } - - // These APIs must be accessed via dynamic binding since they may be removed in future - // OS versions. - g_pufnNtQueryInformationThread = (NtQueryInformationThreadProc)GetProcAddress(hNtDll,"NtQueryInformationThread"); - g_pufnNtQuerySystemInformation = (NtQuerySystemInformationProc)GetProcAddress(hNtDll,"NtQuerySystemInformation"); - -#endif -} - -// -// WorkingThreadCounts tracks the number of worker threads currently doing user work, and the maximum number of such threads -// since the last time TakeMaxWorkingThreadCount was called. This information is for diagnostic purposes only, -// and is tracked only if the CLR config value INTERNAL_ThreadPool_EnableWorkerTracking is non-zero (this feature is off -// by default). -// -union WorkingThreadCounts -{ - struct - { - int currentWorking : 16; - int maxWorking : 16; - }; - - LONG asLong; -}; - -WorkingThreadCounts g_workingThreadCounts; - -// -// If worker tracking is enabled (see above) then this is called immediately before and after a worker thread executes -// each work item. -// -void ThreadpoolMgr::ReportThreadStatus(bool isWorking) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); // can't be here without requesting a thread first - _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); - - while (true) - { - WorkingThreadCounts currentCounts, newCounts; - currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); - - newCounts = currentCounts; - - if (isWorking) - newCounts.currentWorking++; - - if (newCounts.currentWorking > newCounts.maxWorking) - newCounts.maxWorking = newCounts.currentWorking; - - if (!isWorking) - newCounts.currentWorking--; - - if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) - break; - } -} - -// -// Returns the max working count since the previous call to TakeMaxWorkingThreadCount, and resets WorkingThreadCounts.maxWorking. -// -int TakeMaxWorkingThreadCount() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); - while (true) - { - WorkingThreadCounts currentCounts, newCounts; - currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); - - newCounts = currentCounts; - newCounts.maxWorking = 0; - - if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) - { - // If we haven't updated the counts since the last call to TakeMaxWorkingThreadCount, then we never updated maxWorking. - // In that case, the number of working threads for the whole period since the last TakeMaxWorkingThreadCount is the - // current number of working threads. - return currentCounts.maxWorking == 0 ? currentCounts.currentWorking : currentCounts.maxWorking; - } - } -} - - -/************************************************************************/ - -DangerousNonHostedSpinLock ThreadpoolMgr::ThreadAdjustmentLock; - -void ThreadpoolMgr::WaitIOCompletionCallback( - DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - } - CONTRACTL_END; - - if (dwErrorCode == ERROR_SUCCESS) - DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); -} - -extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - -// Remove a block from the appropriate recycleList and return. -// If recycleList is empty, fall back to new. -LPVOID ThreadpoolMgr::GetRecycledMemory(enum MemType memType) -{ - LPVOID result = NULL; - CONTRACT(LPVOID) - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - POSTCONDITION(CheckPointer(result)); - } CONTRACT_END; - - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - - result = list.Remove(); - } - - if(result == NULL) - { - switch (memType) - { - case MEMTYPE_DelegateInfo: - result = new DelegateInfo; - break; - case MEMTYPE_AsyncCallback: - result = new AsyncCallback; - break; - case MEMTYPE_WorkRequest: - result = new WorkRequest; - break; - default: - _ASSERTE(!"Unknown Memtype"); - result = NULL; - break; - } - } - - RETURN result; -} - -// Insert freed block in recycle list. If list is full, return to system heap -void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - - if(list.CanInsert()) - { - list.Insert( mem ); - return; - } - } - - switch (memType) - { - case MEMTYPE_DelegateInfo: - delete (DelegateInfo*) mem; - break; - case MEMTYPE_AsyncCallback: - delete (AsyncCallback*) mem; - break; - case MEMTYPE_WorkRequest: - delete (WorkRequest*) mem; - break; - default: - _ASSERTE(!"Unknown Memtype"); - - } -} - -// this should only be called by unmanaged thread (i.e. there should be no mgd -// caller on the stack) since we are swallowing terminal exceptions -DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - DWORD status = WAIT_TIMEOUT; - EX_TRY - { - status = ev->Wait(sleepTime,FALSE); - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions) - return status; -} - -/************************************************************************/ - - -// if no wraparound that the timer is expired if duetime is less than current time -// if wraparound occurred, then the timer expired if dueTime was greater than last time or dueTime is less equal to current time -#define TimeExpired(last,now,duetime) ((last) <= (now) ? \ - ((duetime) <= (now) && (duetime) >= (last)): \ - ((duetime) >= (last) || (duetime) <= (now))) - -#define TimeInterval(end,start) ((end) > (start) ? ((end) - (start)) : ((0xffffffff - (start)) + (end) + 1)) - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif -#endif -#ifdef _PREFAST_ -#pragma warning(push) -#pragma warning(disable:22008) // "Prefast integer overflow check on (0 + lval) is bogus. Tried local disable without luck, doing whole method." -#endif - -#ifdef _PREFAST_ -#pragma warning(pop) -#endif - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, - unsigned index, - BOOL waitTimedOut - ) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - AsyncCallback* asyncCallback = NULL; - EX_TRY{ - if ( waitInfo->flag & WAIT_SINGLE_EXECUTION) - { - DeactivateNthWait (waitInfo,index) ; - } - else - { // reactivate wait by resetting timer - waitInfo->timer.startTime = GetTickCount(); - } - - asyncCallback = MakeAsyncCallback(); - if (asyncCallback) - { - asyncCallback->wait = waitInfo; - asyncCallback->waitTimedOut = waitTimedOut; - - InterlockedIncrement(&waitInfo->refCount); - -#ifndef TARGET_UNIX - if (FALSE == PostQueuedCompletionStatus((LPOVERLAPPED)asyncCallback, (LPOVERLAPPED_COMPLETION_ROUTINE)WaitIOCompletionCallback)) -#else // TARGET_UNIX - if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) -#endif // !TARGET_UNIX - ReleaseAsyncCallback(asyncCallback); - } - } - EX_CATCH { - if (asyncCallback) - ReleaseAsyncCallback(asyncCallback); - - EX_RETHROW; - } - EX_END_CATCH(SwallowAllExceptions); -} - - -DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - MODE_PREEMPTIVE; - GC_TRIGGERS; - } - CONTRACTL_END; - - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - { - AsyncCallback * asyncCallback = (AsyncCallback*) pArgs; - - WaitInfo * waitInfo = asyncCallback->wait; - - AsyncCallbackHolder asyncCBHolder; - asyncCBHolder.Assign(asyncCallback); - - // We fire the "dequeue" ETW event here, before executing the user code, to enable correlation with - // the ThreadPoolIOEnqueue fired in ThreadpoolMgr::RegisterWaitForSingleObject - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) - FireEtwThreadPoolIODequeue(waitInfo, reinterpret_cast(waitInfo->Callback), GetClrInstanceId()); - - // the user callback can throw, the host must be prepared to handle it. - // SQL is ok, since they have a top-level SEH handler. However, there's - // no easy way to verify it - - ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) - ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); - -#ifndef TARGET_UNIX - Thread::IncrementIOThreadPoolCompletionCount(pThread); -#endif - } - - return ERROR_SUCCESS; -} - -void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) -{ - LIMITED_METHOD_CONTRACT; - - ThreadCB* threadCB = waitInfo->threadCB; - DWORD endIndex = threadCB->NumActiveWaits-1; - DWORD index; - - for (index = 0; index <= endIndex; index++) - { - LIST_ENTRY* head = &(threadCB->waitPointer[index]); - LIST_ENTRY* current = head; - do { - if (current->Flink == (PVOID) waitInfo) - goto FOUND; - - current = (LIST_ENTRY*) current->Flink; - - } while (current != head); - } - -FOUND: - _ASSERTE(index <= endIndex); - - DeactivateNthWait(waitInfo, index); -} - - -void ThreadpoolMgr::DeleteWait(WaitInfo* waitInfo) -{ - CONTRACTL - { - if (waitInfo->ExternalEventSafeHandle != NULL) { THROWS;} else { NOTHROW; } - MODE_ANY; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - if(waitInfo->Context && (waitInfo->flag & WAIT_FREE_CONTEXT)) { - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - // Since the delegate release destroys a handle, we need to be in - // co-operative mode - { - GCX_COOP(); - pDelegate->Release(); - } - - RecycleMemory( pDelegate, MEMTYPE_DelegateInfo ); - } - - if (waitInfo->flag & WAIT_INTERNAL_COMPLETION) - { - waitInfo->InternalCompletionEvent.Set(); - return; // waitInfo will be deleted by the thread that's waiting on this event - } - else if (waitInfo->ExternalCompletionEvent != INVALID_HANDLE) - { - SetEvent(waitInfo->ExternalCompletionEvent); - } - else if (waitInfo->ExternalEventSafeHandle != NULL) - { - // Release the safe handle and the GC handle holding it - ReleaseWaitInfo(waitInfo); - } - - delete waitInfo; - - -} - - - -/************************************************************************/ - -void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) -{ - WRAPPER_NO_CONTRACT; - - WaitInfo* waitInfo = pArgs; - - if ( ! (waitInfo->state & WAIT_REGISTERED) ) - { - // set state to deleted, so that it does not get registered - waitInfo->state |= WAIT_DELETE ; - - // since the wait has not even been registered, we dont need an interlock to decrease the RefCount - waitInfo->refCount--; - - if (waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - } - return; - } - - if (waitInfo->state & WAIT_ACTIVE) - { - DeactivateWait(waitInfo); - } - - if ( waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - return; // we cannot delete the wait here since the PartialCompletionEvent - // may not have been closed yet. so, we return and rely on the waiter of PartialCompletionEvent - // to do the close - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - { - DeleteWait(waitInfo); - } - return; -} - - -/************************************************************************/ - -#ifndef TARGET_UNIX - -LPOVERLAPPED ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain( - Thread* pThread, - DWORD* pErrorCode, - DWORD* pNumBytes, - size_t* pKey) -// -//This function is called just after dispatching the previous BindIO callback -//to Managed code. This is a perf optimization to do a quick call to -//GetQueuedCompletionStatus with a timeout of 0 ms. If there is work in the -//same appdomain, dispatch it back immediately. If not stick it in a well known -//place, and reenter the target domain. The timeout of zero is chosen so as to -//not delay appdomain unloads. -// -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - LPOVERLAPPED lpOverlapped=NULL; - - BOOL status=FALSE; - OVERLAPPEDDATAREF overlapped=NULL; - BOOL ManagedCallback=FALSE; - - *pErrorCode = S_OK; - - - //Very Very Important! - //Do not change the timeout for GetQueuedCompletionStatus to a non-zero value. - //Selecting a non-zero value can cause the thread to block, and lead to expensive context switches. - //In real life scenarios, we have noticed a packet to be not available immediately, but very shortly - //(after few 100's of instructions), and falling back to the VM is good in that case as compared to - //taking a context switch. Changing the timeout to non-zero can lead to perf degrades, that are very - //hard to diagnose. - - status = ::GetQueuedCompletionStatus( - GlobalCompletionPort, - pNumBytes, - (PULONG_PTR)pKey, - &lpOverlapped, - 0); - - DWORD lastError = GetLastError(); - - if (status == 0) - { - if (lpOverlapped != NULL) - { - *pErrorCode = lastError; - } - else - { - return NULL; - } - } - - if (((LPOVERLAPPED_COMPLETION_ROUTINE) *pKey) != BindIoCompletionCallbackStub) - { - //_ASSERTE(FALSE); - } - else - { - ManagedCallback = TRUE; - overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - } - - if (ManagedCallback) - { - _ASSERTE(*pKey != 0); // should be a valid function address - - if (*pKey ==0) - { - //Application Bug. - return NULL; - } - } - else - { - //Just retruned back from managed code, a Thread structure should exist. - _ASSERTE (pThread); - - //Oops, this is an overlapped fom a different appdomain. STick it in - //the thread. We will process it later. - - StoreOverlappedInfoInThread(pThread, *pErrorCode, *pNumBytes, *pKey, lpOverlapped); - - lpOverlapped = NULL; - } - -#ifndef DACCESS_COMPILE - return lpOverlapped; -#endif -} - -void ThreadpoolMgr::StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - _ASSERTE(pThread); - - PIOCompletionContext context; - - context = (PIOCompletionContext) pThread->GetIOCompletionContext(); - - _ASSERTE(context); - - context->ErrorCode = dwErrorCode; - context->numBytesTransferred = dwNumBytes; - context->lpOverlapped = lpOverlapped; - context->key = key; -} - -#endif // !TARGET_UNIX - -// Returns true if there is pending io on the thread. -BOOL ThreadpoolMgr::IsIoPending() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - int Status; - ULONG IsIoPending; - - if (g_pufnNtQueryInformationThread) - { - Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(), - ThreadIsIoPending, - &IsIoPending, - sizeof(IsIoPending), - NULL); - - - if ((Status < 0) || IsIoPending) - return TRUE; - else - return FALSE; - } - return TRUE; -#else - return FALSE; -#endif // !TARGET_UNIX -} - -#ifndef TARGET_UNIX - -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif - -int ThreadpoolMgr::GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo) -{ - LIMITED_METHOD_CONTRACT; - - PROCESS_CPU_INFORMATION newUsage; - newUsage.idleTime.QuadPart = 0; - newUsage.kernelTime.QuadPart = 0; - newUsage.userTime.QuadPart = 0; - - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - { -#if !defined(FEATURE_NATIVEAOT) && !defined(TARGET_UNIX) - FILETIME newIdleTime, newKernelTime, newUserTime; - - CPUGroupInfo::GetSystemTimes(&newIdleTime, &newKernelTime, &newUserTime); - newUsage.idleTime.u.LowPart = newIdleTime.dwLowDateTime; - newUsage.idleTime.u.HighPart = newIdleTime.dwHighDateTime; - newUsage.kernelTime.u.LowPart = newKernelTime.dwLowDateTime; - newUsage.kernelTime.u.HighPart = newKernelTime.dwHighDateTime; - newUsage.userTime.u.LowPart = newUserTime.dwLowDateTime; - newUsage.userTime.u.HighPart = newUserTime.dwHighDateTime; -#endif - } - else - { - (*g_pufnNtQuerySystemInformation)(SystemProcessorPerformanceInformation, - pOldInfo->usageBuffer, - pOldInfo->usageBufferSize, - NULL); - - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* pInfoArray = pOldInfo->usageBuffer; - DWORD_PTR pmask = pOldInfo->affinityMask; - - int proc_no = 0; - while (pmask) - { - if (pmask & 1) - { //should be good: 1CPU 28823 years, 256CPUs 100+years - newUsage.idleTime.QuadPart += pInfoArray[proc_no].IdleTime.QuadPart; - newUsage.kernelTime.QuadPart += pInfoArray[proc_no].KernelTime.QuadPart; - newUsage.userTime.QuadPart += pInfoArray[proc_no].UserTime.QuadPart; - } - - pmask >>=1; - proc_no++; - } - } - - __int64 cpuTotalTime, cpuBusyTime; - - cpuTotalTime = (newUsage.userTime.QuadPart - pOldInfo->userTime.QuadPart) + - (newUsage.kernelTime.QuadPart - pOldInfo->kernelTime.QuadPart); - cpuBusyTime = cpuTotalTime - - (newUsage.idleTime.QuadPart - pOldInfo->idleTime.QuadPart); - - // Preserve reading - pOldInfo->idleTime = newUsage.idleTime; - pOldInfo->kernelTime = newUsage.kernelTime; - pOldInfo->userTime = newUsage.userTime; - - __int64 reading = 0; - - if (cpuTotalTime > 0) - reading = ((cpuBusyTime * 100) / cpuTotalTime); - - _ASSERTE(FitsIn(reading)); - return (int)reading; -} - -#else // !TARGET_UNIX - -int ThreadpoolMgr::GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo) -{ - return PAL_GetCPUBusyTime(pOldInfo); -} - -#endif // !TARGET_UNIX - -// -// A timer that ticks every GATE_THREAD_DELAY milliseconds. -// On platforms that support it, we use a coalescable waitable timer object. -// For other platforms, we use Sleep, via __SwitchToThread. -// -class GateThreadTimer -{ -#ifndef TARGET_UNIX - HANDLE m_hTimer; - -public: - GateThreadTimer() - : m_hTimer(NULL) - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (g_pufnCreateWaitableTimerEx && g_pufnSetWaitableTimerEx) - { - m_hTimer = g_pufnCreateWaitableTimerEx(NULL, NULL, 0, TIMER_ALL_ACCESS); - if (m_hTimer) - { - // - // Set the timer to fire GATE_THREAD_DELAY milliseconds from now, then every GATE_THREAD_DELAY milliseconds thereafter. - // We also set the tolerance to GET_THREAD_DELAY_TOLERANCE, allowing the OS to coalesce this timer. - // - LARGE_INTEGER dueTime; - dueTime.QuadPart = MILLI_TO_100NANO(-(LONGLONG)GATE_THREAD_DELAY); //negative value indicates relative time - if (!g_pufnSetWaitableTimerEx(m_hTimer, &dueTime, GATE_THREAD_DELAY, NULL, NULL, NULL, GATE_THREAD_DELAY_TOLERANCE)) - { - CloseHandle(m_hTimer); - m_hTimer = NULL; - } - } - } - } - - ~GateThreadTimer() - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (m_hTimer) - { - CloseHandle(m_hTimer); - m_hTimer = NULL; - } - } - -#endif // !TARGET_UNIX - -public: - void Wait() - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - if (m_hTimer) - WaitForSingleObject(m_hTimer, INFINITE); - else -#endif // !TARGET_UNIX - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } -}; - -// called by logic to spawn a new completion port thread. -// return false if not enough time has elapsed since the last -// time we sampled the cpu utilization. -BOOL ThreadpoolMgr::SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, - unsigned NumThreads, // total number of threads of that type (worker or CP) - double throttleRate // the delay is increased by this percentage for each extra thread - ) -{ - LIMITED_METHOD_CONTRACT; - - unsigned dwCurrentTickCount = GetTickCount(); - - unsigned delaySinceLastThreadCreation = dwCurrentTickCount - LastThreadCreationTime; - - unsigned minWaitBetweenThreadCreation = GATE_THREAD_DELAY; - - if (throttleRate > 0.0) - { - _ASSERTE(throttleRate <= 1.0); - - unsigned adjustedThreadCount = NumThreads > NumberOfProcessors ? (NumThreads - NumberOfProcessors) : 0; - - minWaitBetweenThreadCreation = (unsigned) (GATE_THREAD_DELAY * pow((1.0 + throttleRate),(double)adjustedThreadCount)); - } - // the amount of time to wait should grow up as the number of threads is increased - - return (delaySinceLastThreadCreation > minWaitBetweenThreadCreation); - -} - - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -/************************************************************************/ - -struct CreateTimerThreadParams { - CLREvent event; - BOOL setupSucceeded; -}; - -BOOL ThreadpoolMgr::CreateTimerQueueTimer(PHANDLE phNewTimer, - WAITORTIMERCALLBACK Callback, - PVOID Parameter, - DWORD DueTime, - DWORD Period, - ULONG Flag) -{ - CONTRACTL - { - THROWS; // EnsureInitialized, CreateAutoEvent can throw - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} // There can be calls thru ICorThreadpool - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - EnsureInitialized(); - - // For now we use just one timer thread. Consider using multiple timer threads if - // number of timers in the queue exceeds a certain threshold. The logic and code - // would be similar to the one for creating wait threads. - if (NULL == TimerThread) - { - CrstHolder csh(&TimerQueueCriticalSection); - - // check again - if (NULL == TimerThread) - { - CreateTimerThreadParams params; - params.event.CreateAutoEvent(FALSE); - - params.setupSucceeded = FALSE; - - HANDLE TimerThreadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, TimerThreadStart, ¶ms, W(".NET Timer")); - - if (TimerThreadHandle == NULL) - { - params.event.CloseEvent(); - ThrowOutOfMemory(); - } - - { - GCX_PREEMP(); - for(;;) - { - // if a host throws because it couldnt allocate another thread, - // just retry the wait. - if (SafeWait(¶ms.event,INFINITE, FALSE) != WAIT_TIMEOUT) - break; - } - } - params.event.CloseEvent(); - - if (!params.setupSucceeded) - { - CloseHandle(TimerThreadHandle); - *phNewTimer = NULL; - return FALSE; - } - - TimerThread = TimerThreadHandle; - } - - } - - - NewHolder timerInfoHolder; - TimerInfo * timerInfo = new (nothrow) TimerInfo; - if (NULL == timerInfo) - ThrowOutOfMemory(); - - timerInfoHolder.Assign(timerInfo); - - timerInfo->FiringTime = DueTime; - timerInfo->Function = Callback; - timerInfo->Context = Parameter; - timerInfo->Period = Period; - timerInfo->state = 0; - timerInfo->flag = Flag; - timerInfo->ExternalCompletionEvent = INVALID_HANDLE; - timerInfo->ExternalEventSafeHandle = NULL; - - *phNewTimer = (HANDLE)timerInfo; - - BOOL status = QueueUserAPC((PAPCFUNC)InsertNewTimer,TimerThread,(size_t)timerInfo); - if (FALSE == status) - { - *phNewTimer = NULL; - return FALSE; - } - - timerInfoHolder.SuppressRelease(); - return TRUE; -} - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif -#endif -DWORD WINAPI ThreadpoolMgr::TimerThreadStart(LPVOID p) -{ - ClrFlsSetThreadType (ThreadType_Timer); - - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; // due to SetApartment - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - CreateTimerThreadParams* params = (CreateTimerThreadParams*)p; - - Thread* pThread = SetupThreadNoThrow(); - - params->setupSucceeded = (pThread == NULL) ? 0 : 1; - params->event.Set(); - - if (pThread == NULL) - return 0; - - pTimerThread = pThread; - // Timer threads never die - - LastTickCount = GetTickCount(); - -#ifdef FEATURE_COMINTEROP - if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // @todo: should we log the failure - return 0; - } -#endif // FEATURE_COMINTEROP - - for (;;) - { - // moved to its own function since EX_TRY consumes stack -#ifdef _MSC_VER -#pragma inline_depth (0) // the function containing EX_TRY can't be inlined here -#endif - TimerThreadFire(); -#ifdef _MSC_VER -#pragma inline_depth (20) -#endif - } - - // unreachable -} - -void ThreadpoolMgr::TimerThreadFire() -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - EX_TRY { - DWORD timeout = FireTimers(); - ClrSleepEx(timeout, TRUE); - - // the thread could wake up either because an APC completed or the sleep timeout - // in both case, we need to sweep the timer queue, firing timers, and readjusting - // the next firing time - - } - EX_CATCH { - // Assert on debug builds since a dead timer thread is a fatal error - _ASSERTE(FALSE); - EX_RETHROW; - } - EX_END_CATCH(SwallowAllExceptions); -} - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -// Executed as an APC in timer thread -void ThreadpoolMgr::InsertNewTimer(TimerInfo* pArg) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(pArg); - TimerInfo * timerInfo = pArg; - - if (timerInfo->state & TIMER_DELETE) - { // timer was deleted before it could be registered - DeleteTimer(timerInfo); - return; - } - - // set the firing time = current time + due time (note initially firing time = due time) - DWORD currentTime = GetTickCount(); - if (timerInfo->FiringTime == (ULONG) -1) - { - timerInfo->state = TIMER_REGISTERED; - timerInfo->refCount = 1; - - } - else - { - timerInfo->FiringTime += currentTime; - - timerInfo->state = (TIMER_REGISTERED | TIMER_ACTIVE); - timerInfo->refCount = 1; - - // insert the timer in the queue - InsertTailList(&TimerQueue,(&timerInfo->link)); - } - - return; -} - - -// executed by the Timer thread -// sweeps through the list of timers, readjusting the firing times, queueing APCs for -// those that have expired, and returns the next firing time interval -DWORD ThreadpoolMgr::FireTimers() -{ - CONTRACTL - { - THROWS; // QueueUserWorkItem can throw - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - if (GetThreadNULLOk()) { MODE_PREEMPTIVE;} else { DISABLED(MODE_ANY);} - } - CONTRACTL_END; - - DWORD currentTime = GetTickCount(); - DWORD nextFiringInterval = (DWORD) -1; - TimerInfo* timerInfo = NULL; - - EX_TRY - { - for (LIST_ENTRY* node = (LIST_ENTRY*) TimerQueue.Flink; - node != &TimerQueue; - ) - { - timerInfo = (TimerInfo*) node; - node = (LIST_ENTRY*) node->Flink; - - if (TimeExpired(LastTickCount, currentTime, timerInfo->FiringTime)) - { - if (timerInfo->Period == 0 || timerInfo->Period == (ULONG) -1) - { - DeactivateTimer(timerInfo); - } - - InterlockedIncrement(&timerInfo->refCount); - - GCX_COOP(); - - ARG_SLOT args[] = { PtrToArgSlot(AsyncTimerCallbackCompletion), PtrToArgSlot(timerInfo) }; - MethodDescCallSite(METHOD__THREAD_POOL__UNSAFE_QUEUE_UNMANAGED_WORK_ITEM).Call(args); - - if (timerInfo->Period != 0 && timerInfo->Period != (ULONG)-1) - { - ULONG nextFiringTime = timerInfo->FiringTime + timerInfo->Period; - DWORD firingInterval; - if (TimeExpired(timerInfo->FiringTime, currentTime, nextFiringTime)) - { - // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up with the short - // period, have it fire 1 ms from now to avoid spinning without a delay. - timerInfo->FiringTime = currentTime + 1; - firingInterval = 1; - } - else - { - timerInfo->FiringTime = nextFiringTime; - firingInterval = TimeInterval(nextFiringTime, currentTime); - } - - if (firingInterval < nextFiringInterval) - nextFiringInterval = firingInterval; - } - } - else - { - DWORD firingInterval = TimeInterval(timerInfo->FiringTime, currentTime); - if (firingInterval < nextFiringInterval) - nextFiringInterval = firingInterval; - } - } - } - EX_CATCH - { - // If QueueUserWorkItem throws OOM, swallow the exception and retry on - // the next call to FireTimers(), otherwise retrhow. - Exception *ex = GET_EXCEPTION(); - // undo the call to DeactivateTimer() - InterlockedDecrement(&timerInfo->refCount); - timerInfo->state = timerInfo->state & TIMER_ACTIVE; - InsertTailList(&TimerQueue, (&timerInfo->link)); - if (ex->GetHR() != E_OUTOFMEMORY) - { - EX_RETHROW; - } - } - EX_END_CATCH(RethrowTerminalExceptions); - - LastTickCount = currentTime; - - return nextFiringInterval; -} - -DWORD WINAPI ThreadpoolMgr::AsyncTimerCallbackCompletion(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - Thread* pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - { - TimerInfo* timerInfo = (TimerInfo*) pArgs; - ((WAITORTIMERCALLBACKFUNC) timerInfo->Function) (timerInfo->Context, TRUE) ; - - if (InterlockedDecrement(&timerInfo->refCount) == 0) - { - DeleteTimer(timerInfo); - } - } - - return ERROR_SUCCESS; -} - - -// removes the timer from the timer queue, thereby cancelling it -// there may still be pending callbacks that haven't completed -void ThreadpoolMgr::DeactivateTimer(TimerInfo* timerInfo) -{ - LIMITED_METHOD_CONTRACT; - - RemoveEntryList((LIST_ENTRY*) timerInfo); - - // This timer info could go into another linked list of timer infos - // waiting to be released. Reinitialize the list pointers - InitializeListHead(&timerInfo->link); - timerInfo->state = timerInfo->state & ~TIMER_ACTIVE; -} - -DWORD WINAPI ThreadpoolMgr::AsyncDeleteTimer(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - MODE_PREEMPTIVE; - GC_TRIGGERS; - } - CONTRACTL_END; - - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - DeleteTimer((TimerInfo*) pArgs); - - return ERROR_SUCCESS; -} - -void ThreadpoolMgr::DeleteTimer(TimerInfo* timerInfo) -{ - CONTRACTL - { - if (GetThreadNULLOk() == pTimerThread) { NOTHROW; } else { THROWS; } - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE((timerInfo->state & TIMER_ACTIVE) == 0); - - _ASSERTE(!(timerInfo->flag & WAIT_FREE_CONTEXT)); - - if (timerInfo->flag & WAIT_INTERNAL_COMPLETION) - { - timerInfo->InternalCompletionEvent.Set(); - return; // the timerInfo will be deleted by the thread that's waiting on InternalCompletionEvent - } - - // ExternalCompletionEvent comes from Host, ExternalEventSafeHandle from managed code. - // They are mutually exclusive. - _ASSERTE(!(timerInfo->ExternalCompletionEvent != INVALID_HANDLE && - timerInfo->ExternalEventSafeHandle != NULL)); - - if (timerInfo->ExternalCompletionEvent != INVALID_HANDLE) - { - SetEvent(timerInfo->ExternalCompletionEvent); - timerInfo->ExternalCompletionEvent = INVALID_HANDLE; - } - - // We cannot block the timer thread, so some cleanup is deferred to other threads. - if (GetThreadNULLOk() == pTimerThread) - { - // Notify the ExternalEventSafeHandle with an user work item - if (timerInfo->ExternalEventSafeHandle != NULL) - { - BOOL success = FALSE; - EX_TRY - { - if (QueueUserWorkItem(AsyncDeleteTimer, - timerInfo, - QUEUE_ONLY) != FALSE) - { - success = TRUE; - } - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions); - - // If unable to queue a user work item, fall back to queueing timer for release - // which will happen *sometime* in the future. - if (success == FALSE) - { - QueueTimerInfoForRelease(timerInfo); - } - - return; - } - - // Releasing GC handles can block. So we wont do this on the timer thread. - // We'll put it in a list which will be processed by a worker thread - if (timerInfo->Context != NULL) - { - QueueTimerInfoForRelease(timerInfo); - return; - } - } - - // To get here we are either not the Timer thread or there is no blocking work to be done - - if (timerInfo->Context != NULL) - { - GCX_COOP(); - delete (ThreadpoolMgr::TimerInfoContext*)timerInfo->Context; - } - - if (timerInfo->ExternalEventSafeHandle != NULL) - { - ReleaseTimerInfo(timerInfo); - } - - delete timerInfo; - -} - -// We add TimerInfos from deleted timers into a linked list. -// A worker thread will later release the handles held by the TimerInfo -// and recycle them if possible. -void ThreadpoolMgr::QueueTimerInfoForRelease(TimerInfo *pTimerInfo) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - // The synchronization in this method depends on the fact that - // - There is only one timer thread - // - The one and only timer thread is executing this method. - // - This function wont go into an alertable state. That could trigger another APC. - // Else two threads can be queueing timerinfos and a race could - // lead to leaked memory and handles - _ASSERTE(pTimerThread == GetThread()); - TimerInfo *pHead = NULL; - - // Make sure this timer info has been deactivated and removed from any other lists - _ASSERTE((pTimerInfo->state & TIMER_ACTIVE) == 0); - //_ASSERTE(pTimerInfo->link.Blink == &(pTimerInfo->link) && - // pTimerInfo->link.Flink == &(pTimerInfo->link)); - // Make sure "link" is the first field in TimerInfo - _ASSERTE(pTimerInfo == (PVOID)&pTimerInfo->link); - - // Grab any previously published list - if ((pHead = InterlockedExchangeT(&TimerInfosToBeRecycled, NULL)) != NULL) - { - // If there already is a list, just append - InsertTailList((LIST_ENTRY *)pHead, &pTimerInfo->link); - pTimerInfo = pHead; - } - else - // If this is the head, make its next and previous ptrs point to itself - InitializeListHead((LIST_ENTRY*)&pTimerInfo->link); - - // Publish the list - (void) InterlockedExchangeT(&TimerInfosToBeRecycled, pTimerInfo); - -} - -void ThreadpoolMgr::FlushQueueOfTimerInfos() -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - TimerInfo *pHeadTimerInfo = NULL, *pCurrTimerInfo = NULL; - LIST_ENTRY *pNextInfo = NULL; - - if ((pHeadTimerInfo = InterlockedExchangeT(&TimerInfosToBeRecycled, NULL)) == NULL) - return; - - do - { - RemoveHeadList((LIST_ENTRY *)pHeadTimerInfo, pNextInfo); - _ASSERTE(pNextInfo != NULL); - - pCurrTimerInfo = (TimerInfo *) pNextInfo; - - GCX_COOP(); - if (pCurrTimerInfo->Context != NULL) - { - delete (ThreadpoolMgr::TimerInfoContext*)pCurrTimerInfo->Context; - } - - if (pCurrTimerInfo->ExternalEventSafeHandle != NULL) - { - ReleaseTimerInfo(pCurrTimerInfo); - } - - delete pCurrTimerInfo; - - } - while ((TimerInfo *)pNextInfo != pHeadTimerInfo); -} - -/************************************************************************/ -BOOL ThreadpoolMgr::ChangeTimerQueueTimer( - HANDLE Timer, - ULONG DueTime, - ULONG Period) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); - _ASSERTE(Timer); // not possible to give invalid handle in managed code - - NewHolder updateInfoHolder; - TimerUpdateInfo *updateInfo = new TimerUpdateInfo; - updateInfoHolder.Assign(updateInfo); - - updateInfo->Timer = (TimerInfo*) Timer; - updateInfo->DueTime = DueTime; - updateInfo->Period = Period; - - BOOL status = QueueUserAPC((PAPCFUNC)UpdateTimer, - TimerThread, - (size_t) updateInfo); - - if (status) - updateInfoHolder.SuppressRelease(); - - return(status); -} - -void ThreadpoolMgr::UpdateTimer(TimerUpdateInfo* pArgs) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - TimerUpdateInfo* updateInfo = (TimerUpdateInfo*) pArgs; - TimerInfo* timerInfo = updateInfo->Timer; - - timerInfo->Period = updateInfo->Period; - - if (updateInfo->DueTime == (ULONG) -1) - { - if (timerInfo->state & TIMER_ACTIVE) - { - DeactivateTimer(timerInfo); - } - // else, noop (the timer was already inactive) - _ASSERTE((timerInfo->state & TIMER_ACTIVE) == 0); - - delete updateInfo; - return; - } - - DWORD currentTime = GetTickCount(); - timerInfo->FiringTime = currentTime + updateInfo->DueTime; - - delete updateInfo; - - if (! (timerInfo->state & TIMER_ACTIVE)) - { - // timer not active (probably a one shot timer that has expired), so activate it - timerInfo->state |= TIMER_ACTIVE; - _ASSERTE(timerInfo->refCount >= 1); - // insert the timer in the queue - InsertTailList(&TimerQueue,(&timerInfo->link)); - - } - - return; -} - -/************************************************************************/ -BOOL ThreadpoolMgr::DeleteTimerQueueTimer( - HANDLE Timer, - HANDLE Event) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); // cannot call delete before creating timer - _ASSERTE(Timer); // not possible to give invalid handle in managed code - - // make volatile to avoid compiler reordering check after async call. - // otherwise, DeregisterTimer could delete timerInfo before the comparison. - VolatilePtr timerInfo = (TimerInfo*) Timer; - - if (Event == (HANDLE) -1) - { - //CONTRACT_VIOLATION(ThrowsViolation); - timerInfo->InternalCompletionEvent.CreateAutoEvent(FALSE); - timerInfo->flag |= WAIT_INTERNAL_COMPLETION; - } - else if (Event) - { - timerInfo->ExternalCompletionEvent = Event; - } -#ifdef _DEBUG - else /* Event == NULL */ - { - _ASSERTE(timerInfo->ExternalCompletionEvent == INVALID_HANDLE); - } -#endif - - BOOL isBlocking = timerInfo->flag & WAIT_INTERNAL_COMPLETION; - - BOOL status = QueueUserAPC((PAPCFUNC)DeregisterTimer, - TimerThread, - (size_t)(TimerInfo*)timerInfo); - - if (FALSE == status) - { - if (isBlocking) - timerInfo->InternalCompletionEvent.CloseEvent(); - return FALSE; - } - - if (isBlocking) - { - _ASSERTE(timerInfo->ExternalEventSafeHandle == NULL); - _ASSERTE(timerInfo->ExternalCompletionEvent == INVALID_HANDLE); - _ASSERTE(GetThreadNULLOk() != pTimerThread); - - timerInfo->InternalCompletionEvent.Wait(INFINITE,TRUE /*alertable*/); - timerInfo->InternalCompletionEvent.CloseEvent(); - // Release handles and delete TimerInfo - _ASSERTE(timerInfo->refCount == 0); - // if WAIT_INTERNAL_COMPLETION flag is not set, timerInfo will be deleted in DeleteTimer. - timerInfo->flag &= ~WAIT_INTERNAL_COMPLETION; - DeleteTimer(timerInfo); - } - return status; -} - -void ThreadpoolMgr::DeregisterTimer(TimerInfo* pArgs) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - TimerInfo* timerInfo = (TimerInfo*) pArgs; - - if (! (timerInfo->state & TIMER_REGISTERED) ) - { - // set state to deleted, so that it does not get registered - timerInfo->state |= TIMER_DELETE ; - - // since the timer has not even been registered, we dont need an interlock to decrease the RefCount - timerInfo->refCount--; - - return; - } - - if (timerInfo->state & TIMER_ACTIVE) - { - DeactivateTimer(timerInfo); - } - - if (InterlockedDecrement(&timerInfo->refCount) == 0 ) - { - DeleteTimer(timerInfo); - } - return; -} - -#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h deleted file mode 100644 index ead529124888d7..00000000000000 --- a/src/coreclr/vm/win32threadpool.h +++ /dev/null @@ -1,1088 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -/*++ - -Module Name: - - Win32ThreadPool.h - -Abstract: - - This module is the header file for thread pools using Win32 APIs. - -Revision History: - - ---*/ - -#ifndef _WIN32THREADPOOL_H -#define _WIN32THREADPOOL_H - -#include "delegateinfo.h" -#include "util.hpp" -#include "nativeoverlapped.h" -#include "hillclimbing.h" - -#define MAX_WAITHANDLES 64 - -#define MAX_CACHED_EVENTS 40 // upper limit on number of wait events cached - -#define WAIT_REGISTERED 0x01 -#define WAIT_ACTIVE 0x02 -#define WAIT_DELETE 0x04 - -#define TIMER_REGISTERED 0x01 -#define TIMER_ACTIVE 0x02 -#define TIMER_DELETE 0x04 - -#define WAIT_SINGLE_EXECUTION 0x00000001 -#define WAIT_FREE_CONTEXT 0x00000002 -#define WAIT_INTERNAL_COMPLETION 0x00000004 - -#define QUEUE_ONLY 0x00000000 // do not attempt to call on the thread -#define CALL_OR_QUEUE 0x00000001 // call on the same thread if not too busy, else queue - -const int MaxLimitThreadsPerCPU=250; // upper limit on number of cp threads per CPU -const int MaxFreeCPThreadsPerCPU=2; // upper limit on number of free cp threads per CPU - -const int CpuUtilizationHigh=95; // remove threads when above this -const int CpuUtilizationLow =80; // inject more threads if below this - -#ifndef TARGET_UNIX -extern HANDLE (WINAPI *g_pufnCreateIoCompletionPort)(HANDLE FileHandle, - HANDLE ExistingCompletionPort, - ULONG_PTR CompletionKey, - DWORD NumberOfConcurrentThreads); - -extern int (WINAPI *g_pufnNtQueryInformationThread) (HANDLE ThreadHandle, - THREADINFOCLASS ThreadInformationClass, - PVOID ThreadInformation, - ULONG ThreadInformationLength, - PULONG ReturnLength); - -extern int (WINAPI * g_pufnNtQuerySystemInformation) (SYSTEM_INFORMATION_CLASS SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength OPTIONAL); -#endif // !TARGET_UNIX - -#define FILETIME_TO_INT64(t) (*(__int64*)&(t)) -#define MILLI_TO_100NANO(x) ((x) * 10000) // convert from milliseond to 100 nanosecond unit - -/** - * This type is supposed to be private to ThreadpoolMgr. - * It's at global scope because Strike needs to be able to access its - * definition. - */ -struct WorkRequest { - WorkRequest* next; - LPTHREAD_START_ROUTINE Function; - PVOID Context; - -}; - -typedef struct _IOCompletionContext -{ - DWORD ErrorCode; - DWORD numBytesTransferred; - LPOVERLAPPED lpOverlapped; - size_t key; -} IOCompletionContext, *PIOCompletionContext; - -typedef DPTR(WorkRequest) PTR_WorkRequest; -class ThreadpoolMgr -{ - friend class ClrDataAccess; - friend struct DelegateInfo; - friend class ThreadPoolNative; - friend class TimerNative; - friend class UnManagedPerAppDomainTPCount; - friend class ManagedPerAppDomainTPCount; - friend class PerAppDomainTPCountList; - friend class HillClimbing; - friend struct _DacGlobals; - -public: - struct ThreadCounter - { - static const int MaxPossibleCount = 0x7fff; - - // padding to ensure we get our own cache line - BYTE padding1[MAX_CACHE_LINE_SIZE]; - - union Counts - { - struct - { - // - // Note: these are signed rather than unsigned to allow us to detect under/overflow. - // - int MaxWorking : 16; //Determined by HillClimbing; adjusted elsewhere for timeouts, etc. - int NumActive : 16; //Active means working or waiting on WorkerSemaphore. These are "warm/hot" threads. - int NumWorking : 16; //Trying to get work from various queues. Not waiting on either semaphore. - int NumRetired : 16; //Not trying to get work; waiting on RetiredWorkerSemaphore. These are "cold" threads. - - // Note: the only reason we need "retired" threads at all is that it allows some threads to eventually time out - // even if other threads are getting work. If we ever make WorkerSemaphore a true LIFO semaphore, we will no longer - // need the concept of "retirement" - instead, the very "coldest" threads will naturally be the first to time out. - }; - - LONGLONG AsLongLong; - - bool operator==(Counts other) {LIMITED_METHOD_CONTRACT; return AsLongLong == other.AsLongLong;} - } counts; - - // padding to ensure we get our own cache line - BYTE padding2[MAX_CACHE_LINE_SIZE]; - - Counts GetCleanCounts() - { - LIMITED_METHOD_CONTRACT; -#ifdef HOST_64BIT - // VolatileLoad x64 bit read is atomic - return DangerousGetDirtyCounts(); -#else // !HOST_64BIT - // VolatileLoad may result in torn read - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, 0, 0); - ValidateCounts(result); -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; -#endif // !HOST_64BIT - } - - // - // This does a non-atomic read of the counts. The returned value is suitable only - // for use inside of a read-compare-exchange loop, where the compare-exhcange must succeed - // before any action is taken. Use GetCleanWorkerCounts for other needs, but keep in mind - // it's much slower. - // - Counts DangerousGetDirtyCounts() - { - LIMITED_METHOD_CONTRACT; - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = VolatileLoad(&counts.AsLongLong); -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; - } - - - Counts CompareExchangeCounts(Counts newCounts, Counts oldCounts) - { - LIMITED_METHOD_CONTRACT; - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, newCounts.AsLongLong, oldCounts.AsLongLong); - if (result == oldCounts) - { - // can only do validation on success; if we failed, it may have been due to a previous - // dirty read, which may contain invalid values. - ValidateCounts(result); - ValidateCounts(newCounts); - } -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; - } - - private: - static void ValidateCounts(Counts counts) - { - LIMITED_METHOD_CONTRACT; - _ASSERTE(counts.MaxWorking > 0); - _ASSERTE(counts.NumActive >= 0); - _ASSERTE(counts.NumWorking >= 0); - _ASSERTE(counts.NumRetired >= 0); - _ASSERTE(counts.NumWorking <= counts.NumActive); - } - }; - -public: - - // enumeration of different kinds of memory blocks that are recycled - enum MemType - { - MEMTYPE_AsyncCallback = 0, - MEMTYPE_DelegateInfo = 1, - MEMTYPE_WorkRequest = 2, - MEMTYPE_COUNT = 3, - }; - - typedef struct { - INT32 TimerId; - } TimerInfoContext; - -#ifndef DACCESS_COMPILE - static void StaticInitialize() - { - WRAPPER_NO_CONTRACT; - - s_usePortableThreadPool = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPool) != 0; -#ifdef TARGET_WINDOWS - s_usePortableThreadPoolForIO = - s_usePortableThreadPool && - CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPoolForIO) != 0; -#else // !TARGET_WINDOWS - s_usePortableThreadPoolForIO = s_usePortableThreadPool; -#endif // TARGET_WINDOWS - } -#endif // !DACCESS_COMPILE - - static bool UsePortableThreadPool() - { - LIMITED_METHOD_CONTRACT; - return s_usePortableThreadPool; - } - - static bool UsePortableThreadPoolForIO() - { - LIMITED_METHOD_CONTRACT; - return s_usePortableThreadPoolForIO; - } - - static BOOL Initialize(); - - static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, - PVOID Context, - ULONG Flags, - BOOL UnmanagedTPRequest=TRUE); - - inline static BOOL IsCompletionPortInitialized() - { - LIMITED_METHOD_CONTRACT; - return GlobalCompletionPort != NULL; - } - - static BOOL RegisterWaitForSingleObject(PHANDLE phNewWaitObject, - HANDLE hWaitObject, - WAITORTIMERCALLBACK Callback, - PVOID Context, - ULONG timeout, - DWORD dwFlag); - - static BOOL UnregisterWaitEx(HANDLE hWaitObject,HANDLE CompletionEvent); - static void WaitHandleCleanup(HANDLE hWaitObject); - - static BOOL WINAPI BindIoCompletionCallback(HANDLE FileHandle, - LPOVERLAPPED_COMPLETION_ROUTINE Function, - ULONG Flags, - DWORD& errorCode); - - static void WINAPI WaitIOCompletionCallback(DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - static BOOL SetAppDomainRequestsActive(BOOL UnmanagedTP = FALSE); - static void ClearAppDomainRequestsActive(BOOL UnmanagedTP = FALSE, LONG index = -1); - - static inline void UpdateLastDequeueTime() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&LastDequeueTime, (unsigned int)GetTickCount()); - } - - static BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, - WAITORTIMERCALLBACK Callback, - PVOID Parameter, - DWORD DueTime, - DWORD Period, - ULONG Flags); - - static BOOL ChangeTimerQueueTimer(HANDLE Timer, - ULONG DueTime, - ULONG Period); - static BOOL DeleteTimerQueueTimer(HANDLE Timer, - HANDLE CompletionEvent); - - static void RecycleMemory(LPVOID mem, enum MemType memType); - - static void FlushQueueOfTimerInfos(); - - static BOOL HaveTimerInfosToFlush() { return TimerInfosToBeRecycled != NULL; } - -#ifndef TARGET_UNIX - static LPOVERLAPPED CompletionPortDispatchWorkWithinAppDomain(Thread* pThread, DWORD* pErrorCode, DWORD* pNumBytes, size_t* pKey); - static void StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped); -#endif // !TARGET_UNIX - - // Enable filtering of correlation ETW events for cases handled at a higher abstraction level - -#ifndef DACCESS_COMPILE - static FORCEINLINE BOOL AreEtwQueueEventsSpeciallyHandled(LPTHREAD_START_ROUTINE Function) - { - // Timer events are handled at a higher abstraction level: in the managed Timer class - return (Function == ThreadpoolMgr::AsyncTimerCallbackCompletion); - } - - static FORCEINLINE BOOL AreEtwIOQueueEventsSpeciallyHandled(LPOVERLAPPED_COMPLETION_ROUTINE Function) - { - // We handle registered waits at a higher abstraction level - return Function == ThreadpoolMgr::WaitIOCompletionCallback; - } -#endif - -private: - -#ifndef DACCESS_COMPILE - - inline static void FreeWorkRequest(WorkRequest* workRequest) - { - RecycleMemory( workRequest, MEMTYPE_WorkRequest ); //delete workRequest; - } - - inline static WorkRequest* MakeWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context) - { - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END;; - - WorkRequest* wr = (WorkRequest*) GetRecycledMemory(MEMTYPE_WorkRequest); - _ASSERTE(wr); - if (NULL == wr) - return NULL; - wr->Function = function; - wr->Context = context; - wr->next = NULL; - return wr; - } - -#endif // #ifndef DACCESS_COMPILE - - typedef struct { - DWORD numBytes; - ULONG_PTR *key; - LPOVERLAPPED pOverlapped; - DWORD errorCode; - } QueuedStatus; - - typedef DPTR(struct _LIST_ENTRY) PTR_LIST_ENTRY; - typedef struct _LIST_ENTRY { - struct _LIST_ENTRY *Flink; - struct _LIST_ENTRY *Blink; - } LIST_ENTRY, *PLIST_ENTRY; - - struct WaitInfo; - - typedef struct { - HANDLE threadHandle; - DWORD threadId; - CLREvent startEvent; - LONG NumWaitHandles; // number of wait objects registered to the thread <=64 - LONG NumActiveWaits; // number of objects, thread is actually waiting on (this may be less than - // NumWaitHandles since the thread may not have activated some waits - HANDLE waitHandle[MAX_WAITHANDLES]; // array of wait handles (copied from waitInfo since - // we need them to be contiguous) - LIST_ENTRY waitPointer[MAX_WAITHANDLES]; // array of doubly linked list of corresponding waitinfo - } ThreadCB; - - - typedef struct { - ULONG startTime; // time at which wait was started - // endTime = startTime+timeout - ULONG remainingTime; // endTime - currentTime - } WaitTimerInfo; - - struct WaitInfo { - LIST_ENTRY link; // Win9x does not allow duplicate waithandles, so we need to - // group all waits on a single waithandle using this linked list - HANDLE waitHandle; - WAITORTIMERCALLBACK Callback; - PVOID Context; - ULONG timeout; - WaitTimerInfo timer; - DWORD flag; - DWORD state; - ThreadCB* threadCB; - LONG refCount; // when this reaches 0, the waitInfo can be safely deleted - CLREvent PartialCompletionEvent; // used to synchronize deactivation of a wait - CLREvent InternalCompletionEvent; // only one of InternalCompletion or ExternalCompletion is used - // but I cant make a union since CLREvent has a non-default constructor - HANDLE ExternalCompletionEvent; // they are signalled when all callbacks have completed (refCount=0) - OBJECTHANDLE ExternalEventSafeHandle; - - } ; - - // structure used to maintain global information about wait threads. Protected by WaitThreadsCriticalSection - typedef struct WaitThreadTag { - LIST_ENTRY link; - ThreadCB* threadCB; - } WaitThreadInfo; - - - struct AsyncCallback{ - WaitInfo* wait; - BOOL waitTimedOut; - } ; - -#ifndef DACCESS_COMPILE - - static VOID - AcquireAsyncCallback(AsyncCallback *pAsyncCB) - { - LIMITED_METHOD_CONTRACT; - } - - static VOID - ReleaseAsyncCallback(AsyncCallback *pAsyncCB) - { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - WaitInfo *waitInfo = pAsyncCB->wait; - ThreadpoolMgr::RecycleMemory((LPVOID*)pAsyncCB, ThreadpoolMgr::MEMTYPE_AsyncCallback); - - // if this was a single execution, we now need to stop rooting registeredWaitHandle - // in a GC handle. This will cause the finalizer to pick it up and call the cleanup - // routine. - if ( (waitInfo->flag & WAIT_SINGLE_EXECUTION) && (waitInfo->flag & WAIT_FREE_CONTEXT)) - { - - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - _ASSERTE(pDelegate->m_registeredWaitHandle); - - { - GCX_COOP(); - StoreObjectInHandle(pDelegate->m_registeredWaitHandle, NULL); - } - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - ThreadpoolMgr::DeleteWait(waitInfo); - - } - - typedef Holder AsyncCallbackHolder; - inline static AsyncCallback* MakeAsyncCallback() - { - WRAPPER_NO_CONTRACT; - return (AsyncCallback*) GetRecycledMemory(MEMTYPE_AsyncCallback); - } - - static VOID ReleaseInfo(OBJECTHANDLE& hndSafeHandle, - HANDLE hndNativeHandle) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END - -// Use of EX_TRY, GCPROTECT etc in the same function is causing prefast to complain about local variables with -// same name masking each other (#246). The error could not be suppressed with "#pragma PREFAST_SUPPRESS" -#ifndef _PREFAST_ - - if (hndSafeHandle != NULL) - { - - SAFEHANDLEREF refSH = NULL; - - GCX_COOP(); - GCPROTECT_BEGIN(refSH); - - { - EX_TRY - { - // Read the GC handle - refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(ObjectFromHandle(hndSafeHandle)); - - // Destroy the GC handle - DestroyHandle(hndSafeHandle); - - if (refSH != NULL) - { - SafeHandleHolder h(&refSH); - - HANDLE hEvent = refSH->GetHandle(); - if (hEvent != INVALID_HANDLE_VALUE) - { - SetEvent(hEvent); - } - } - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions); - } - - GCPROTECT_END(); - - hndSafeHandle = NULL; - } -#endif - } - -#endif // #ifndef DACCESS_COMPILE - - typedef struct { - LIST_ENTRY link; - HANDLE Handle; - } WaitEvent ; - - // Timer - typedef struct { - LIST_ENTRY link; // doubly linked list of timers - ULONG FiringTime; // TickCount of when to fire next - WAITORTIMERCALLBACK Function; // Function to call when timer fires - PVOID Context; // Context to pass to function when timer fires - ULONG Period; - DWORD flag; // How do we deal with the context - DWORD state; - LONG refCount; - HANDLE ExternalCompletionEvent; // only one of this is used, but cant do a union since CLREvent has a non-default constructor - CLREvent InternalCompletionEvent; // flags indicates which one is being used - OBJECTHANDLE ExternalEventSafeHandle; - } TimerInfo; - - static VOID AcquireWaitInfo(WaitInfo *pInfo) - { - } - static VOID ReleaseWaitInfo(WaitInfo *pInfo) - { - WRAPPER_NO_CONTRACT; -#ifndef DACCESS_COMPILE - ReleaseInfo(pInfo->ExternalEventSafeHandle, - pInfo->ExternalCompletionEvent); -#endif - } - static VOID AcquireTimerInfo(TimerInfo *pInfo) - { - } - static VOID ReleaseTimerInfo(TimerInfo *pInfo) - { - WRAPPER_NO_CONTRACT; -#ifndef DACCESS_COMPILE - ReleaseInfo(pInfo->ExternalEventSafeHandle, - pInfo->ExternalCompletionEvent); -#endif - } - - typedef Holder WaitInfoHolder; - typedef Holder TimerInfoHolder; - - typedef struct { - TimerInfo* Timer; // timer to be updated - ULONG DueTime ; // new due time - ULONG Period ; // new period - } TimerUpdateInfo; - - // Definitions and data structures to support recycling of high-frequency - // memory blocks. We use a spin-lock to access the list - - class RecycledListInfo - { - static const unsigned int MaxCachedEntries = 40; - - struct Entry - { - Entry* next; - }; - - Volatile lock; // this is the spin lock - DWORD count; // count of number of elements in the list - Entry* root; // ptr to first element of recycled list -#ifndef HOST_64BIT - DWORD filler; // Pad the structure to a multiple of the 16. -#endif - - //--// - -public: - RecycledListInfo() - { - LIMITED_METHOD_CONTRACT; - - lock = 0; - root = NULL; - count = 0; - } - - FORCEINLINE bool CanInsert() - { - LIMITED_METHOD_CONTRACT; - - return count < MaxCachedEntries; - } - - FORCEINLINE LPVOID Remove() - { - LIMITED_METHOD_CONTRACT; - - if(root == NULL) return NULL; // No need for acquiring the lock, there's nothing to remove. - - AcquireLock(); - - Entry* ret = (Entry*)root; - - if(ret) - { - root = ret->next; - count -= 1; - } - - ReleaseLock(); - - return ret; - } - - FORCEINLINE void Insert( LPVOID mem ) - { - LIMITED_METHOD_CONTRACT; - - AcquireLock(); - - Entry* entry = (Entry*)mem; - - entry->next = root; - - root = entry; - count += 1; - - ReleaseLock(); - } - - private: - FORCEINLINE void AcquireLock() - { - LIMITED_METHOD_CONTRACT; - - unsigned int rounds = 0; - - DWORD dwSwitchCount = 0; - - while(lock != 0 || InterlockedExchange( &lock, 1 ) != 0) - { - YieldProcessorNormalized(); // indicate to the processor that we are spinning - - rounds++; - - if((rounds % 32) == 0) - { - __SwitchToThread( 0, ++dwSwitchCount ); - } - } - } - - FORCEINLINE void ReleaseLock() - { - LIMITED_METHOD_CONTRACT; - - lock = 0; - } - }; - - // - // It's critical that we ensure these pointers are allocated by the linker away from - // variables that are modified a lot at runtime. - // - // The use of the CacheGuard is a temporary solution, - // the thread pool has to be refactor away from static variable and - // toward a single global structure, where we can control the locality of variables. - // - class RecycledListsWrapper - { - DWORD CacheGuardPre[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; - - RecycledListInfo (*pRecycledListPerProcessor)[MEMTYPE_COUNT]; // RecycledListInfo [numProc][MEMTYPE_COUNT] - - DWORD CacheGuardPost[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; - - public: - void Initialize( unsigned int numProcs ); - - FORCEINLINE bool IsInitialized() - { - LIMITED_METHOD_CONTRACT; - - return pRecycledListPerProcessor != NULL; - } - -#ifndef DACCESS_COMPILE - FORCEINLINE RecycledListInfo& GetRecycleMemoryInfo( enum MemType memType ) - { - LIMITED_METHOD_CONTRACT; - - DWORD processorNumber = 0; - -#ifndef TARGET_UNIX - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - processorNumber = CPUGroupInfo::CalculateCurrentProcessorNumber(); - else - // Turns out GetCurrentProcessorNumber can return a value greater than the number of processors reported by - // GetSystemInfo, if we're running in WOW64 on a machine with >32 processors. - processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; -#else // !TARGET_UNIX - if (PAL_HasGetCurrentProcessorNumber()) - { - // On linux, GetCurrentProcessorNumber which uses sched_getcpu() can return a value greater than the number - // of processors reported by sysconf(_SC_NPROCESSORS_ONLN) when using OpenVZ kernel. - processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; - } -#endif // !TARGET_UNIX - return pRecycledListPerProcessor[processorNumber][memType]; - } -#endif // DACCESS_COMPILE - }; - -#define GATE_THREAD_STATUS_NOT_RUNNING 0 // There is no gate thread -#define GATE_THREAD_STATUS_REQUESTED 1 // There is a gate thread, and someone has asked it to stick around recently -#define GATE_THREAD_STATUS_WAITING_FOR_REQUEST 2 // There is a gate thread, but nobody has asked it to stay. It may die soon - - // Private methods - - static Thread* CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread); - - static BOOL CreateWorkerThread(); - - static void EnqueueWorkRequest(WorkRequest* wr); - - static WorkRequest* DequeueWorkRequest(); - - static void ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled); - -#ifndef DACCESS_COMPILE - - inline static void AppendWorkRequest(WorkRequest* entry) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - if (WorkRequestTail) - { - _ASSERTE(WorkRequestHead != NULL); - WorkRequestTail->next = entry; - } - else - { - _ASSERTE(WorkRequestHead == NULL); - WorkRequestHead = entry; - } - - WorkRequestTail = entry; - _ASSERTE(WorkRequestTail->next == NULL); - } - - inline static WorkRequest* RemoveWorkRequest() - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - WorkRequest* entry = NULL; - if (WorkRequestHead) - { - entry = WorkRequestHead; - WorkRequestHead = entry->next; - if (WorkRequestHead == NULL) - WorkRequestTail = NULL; - } - return entry; - } - -public: - static void EnsureInitialized(); -private: - static void EnsureInitializedSlow(); - -public: - static void InitPlatformVariables(); - - inline static BOOL IsInitialized() - { - LIMITED_METHOD_CONTRACT; - return Initialization == -1; - } - - static void MaybeAddWorkingWorker(); - - static void NotifyWorkItemCompleted() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - Thread::IncrementWorkerThreadPoolCompletionCount(GetThreadNULLOk()); - UpdateLastDequeueTime(); - } - - static bool ShouldAdjustMaxWorkersActive() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - DWORD priorTime = PriorCompletedWorkRequestsTime; - MemoryBarrier(); // read fresh value for NextCompletedWorkRequestsTime below - DWORD requiredInterval = NextCompletedWorkRequestsTime - priorTime; - DWORD elapsedInterval = GetTickCount() - priorTime; - if (elapsedInterval >= requiredInterval) - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - if (counts.NumActive <= counts.MaxWorking) - return !IsHillClimbingDisabled; - } - - return false; - } - - static void AdjustMaxWorkersActive(); - static bool ShouldWorkerKeepRunning(); - - static DWORD SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable); - - static DWORD WINAPI WorkerThreadStart(LPVOID lpArgs); - - static BOOL AddWaitRequest(HANDLE waitHandle, WaitInfo* waitInfo); - - - static ThreadCB* FindWaitThread(); // returns a wait thread that can accomodate another wait request - - static BOOL CreateWaitThread(); - - static void WINAPI InsertNewWaitForSelf(WaitInfo* pArg); - - static int FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHandle); - - static DWORD MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits); - - static void ProcessWaitCompletion( WaitInfo* waitInfo, - unsigned index, // array index - BOOL waitTimedOut); - - static DWORD WINAPI WaitThreadStart(LPVOID lpArgs); - - static DWORD WINAPI AsyncCallbackCompletion(PVOID pArgs); - - static void QueueTimerInfoForRelease(TimerInfo *pTimerInfo); - - static void DeactivateWait(WaitInfo* waitInfo); - static void DeactivateNthWait(WaitInfo* waitInfo, DWORD index); - - static void DeleteWait(WaitInfo* waitInfo); - - - inline static void ShiftWaitArray( ThreadCB* threadCB, - ULONG SrcIndex, - ULONG DestIndex, - ULONG count) - { - LIMITED_METHOD_CONTRACT; - memmove(&threadCB->waitHandle[DestIndex], - &threadCB->waitHandle[SrcIndex], - count * sizeof(HANDLE)); - memmove(&threadCB->waitPointer[DestIndex], - &threadCB->waitPointer[SrcIndex], - count * sizeof(LIST_ENTRY)); - } - - static void WINAPI DeregisterWait(WaitInfo* pArgs); - -#ifndef TARGET_UNIX - // holds the aggregate of system cpu usage of all processors - typedef struct _PROCESS_CPU_INFORMATION - { - LARGE_INTEGER idleTime; - LARGE_INTEGER kernelTime; - LARGE_INTEGER userTime; - DWORD_PTR affinityMask; - int numberOfProcessors; - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* usageBuffer; - int usageBufferSize; - } PROCESS_CPU_INFORMATION; - - static int GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo); - static BOOL CreateCompletionPortThread(LPVOID lpArgs); - static DWORD WINAPI CompletionPortThreadStart(LPVOID lpArgs); -public: - inline static bool HaveNativeWork() - { - LIMITED_METHOD_CONTRACT; - return WorkRequestHead != NULL; - } - - static void GrowCompletionPortThreadpoolIfNeeded(); - static BOOL ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts counts); -#else - static int GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo); - -#endif // !TARGET_UNIX - - static void PerformGateActivities(int cpuUtilization); - static bool NeedGateThreadForIOCompletions(); - -private: - static BOOL IsIoPending(); - - static BOOL CreateGateThread(); - static void EnsureGateThreadRunning(); - static bool ShouldGateThreadKeepRunning(); - static DWORD WINAPI GateThreadStart(LPVOID lpArgs); - static BOOL SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, - unsigned NumThreads, // total number of threads of that type (worker or CP) - double throttleRate=0.0 // the delay is increased by this percentage for each extra thread - ); - static BOOL SufficientDelaySinceLastDequeue(); - - static LPVOID GetRecycledMemory(enum MemType memType); - - static DWORD WINAPI TimerThreadStart(LPVOID args); - static void TimerThreadFire(); // helper method used by TimerThreadStart - static void WINAPI InsertNewTimer(TimerInfo* pArg); - static DWORD FireTimers(); - static DWORD WINAPI AsyncTimerCallbackCompletion(PVOID pArgs); - static void DeactivateTimer(TimerInfo* timerInfo); - static DWORD WINAPI AsyncDeleteTimer(PVOID pArgs); - static void DeleteTimer(TimerInfo* timerInfo); - static void WINAPI UpdateTimer(TimerUpdateInfo* pArgs); - - static void WINAPI DeregisterTimer(TimerInfo* pArgs); - - inline static DWORD QueueDeregisterWait(HANDLE waitThread, WaitInfo* waitInfo) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - DWORD result = QueueUserAPC(reinterpret_cast(DeregisterWait), waitThread, reinterpret_cast(waitInfo)); - SetWaitThreadAPCPending(); - return result; - } - - - inline static void SetWaitThreadAPCPending() {IsApcPendingOnWaitThread = TRUE;} - inline static void ResetWaitThreadAPCPending() {IsApcPendingOnWaitThread = FALSE;} - inline static BOOL IsWaitThreadAPCPending() {return IsApcPendingOnWaitThread;} - -#endif // #ifndef DACCESS_COMPILE - // Private variables - - static Volatile Initialization; // indicator of whether the threadpool is initialized. - - static bool s_usePortableThreadPool; - static bool s_usePortableThreadPoolForIO; - - SVAL_DECL(LONG,MinLimitTotalWorkerThreads); // same as MinLimitTotalCPThreads - SVAL_DECL(LONG,MaxLimitTotalWorkerThreads); // same as MaxLimitTotalCPThreads - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static unsigned int LastDequeueTime; // used to determine if work items are getting thread starved - - static HillClimbing HillClimbingInstance; - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG PriorCompletedWorkRequests; - static DWORD PriorCompletedWorkRequestsTime; - static DWORD NextCompletedWorkRequestsTime; - - static LARGE_INTEGER CurrentSampleStartTime; - - static unsigned int WorkerThreadSpinLimit; - static bool IsHillClimbingDisabled; - static int ThreadAdjustmentInterval; - - SPTR_DECL(WorkRequest,WorkRequestHead); // Head of work request queue - SPTR_DECL(WorkRequest,WorkRequestTail); // Head of work request queue - - static unsigned int LastCPThreadCreation; // last time a completion port thread was created - static unsigned int NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads - - static BOOL IsApcPendingOnWaitThread; // Indicates if an APC is pending on the wait thread - - // This needs to be non-hosted, because worker threads can run prior to EE startup. - static DangerousNonHostedSpinLock ThreadAdjustmentLock; - -public: - static CrstStatic WorkerCriticalSection; - -private: - static const DWORD WorkerTimeout = 20 * 1000; - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_DECL(ThreadCounter,WorkerCounter); - - // - // WorkerSemaphore is an UnfairSemaphore because: - // 1) Threads enter and exit this semaphore very frequently, and thus benefit greatly from the spinning done by UnfairSemaphore - // 2) There is no functional reason why any particular thread should be preferred when waking workers. This only impacts performance, - // and un-fairness helps performance in this case. - // - static CLRLifoSemaphore* WorkerSemaphore; - - // - // RetiredWorkerSemaphore is a regular CLRSemaphore, not an UnfairSemaphore, because if a thread waits on this semaphore is it almost certainly - // NOT going to be released soon, so the spinning done in UnfairSemaphore only burns valuable CPU time. However, if UnfairSemaphore is ever - // implemented in terms of a Win32 IO Completion Port, we should reconsider this. The IOCP's LIFO unblocking behavior could help keep working set - // down, by constantly re-using the same small set of retired workers rather than round-robining between all of them as CLRSemaphore will do. - // If we go that route, we should add a "no-spin" option to UnfairSemaphore.Wait to avoid wasting CPU. - // - static CLRLifoSemaphore* RetiredWorkerSemaphore; - - static CLREvent * RetiredCPWakeupEvent; - - static CrstStatic WaitThreadsCriticalSection; - static LIST_ENTRY WaitThreadsHead; // queue of wait threads, each thread can handle upto 64 waits - - static TimerInfo *TimerInfosToBeRecycled; // list of delegate infos associated with deleted timers - static CrstStatic TimerQueueCriticalSection; // critical section to synchronize timer queue access - SVAL_DECL(LIST_ENTRY,TimerQueue); // queue of timers - static HANDLE TimerThread; // Currently we only have one timer thread - static Thread* pTimerThread; - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static DWORD LastTickCount; // the count just before timer thread goes to sleep - - static BOOL InitCompletionPortThreadpool; // flag indicating whether completion port threadpool has been initialized - static HANDLE GlobalCompletionPort; // used for binding io completions on file handles - -public: - SVAL_DECL(ThreadCounter,CPThreadCounter); - -private: - SVAL_DECL(LONG,MaxLimitTotalCPThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS - SVAL_DECL(LONG,MinLimitTotalCPThreads); - SVAL_DECL(LONG,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG GateThreadStatus; // See GateThreadStatus enumeration - - SVAL_DECL(LONG,cpuUtilization); - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static RecycledListsWrapper RecycledLists; -}; - - - - -#endif // _WIN32THREADPOOL_H From a5e1c9e86185b086943ee7a3f8d19ee440d61084 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 16:26:08 -0700 Subject: [PATCH 04/20] Add win32threadpool with other changes --- src/coreclr/debug/daccess/enummem.cpp | 1 - src/coreclr/debug/daccess/request.cpp | 1 - src/coreclr/debug/daccess/request_svr.cpp | 1 - src/coreclr/debug/ee/dactable.cpp | 1 - src/coreclr/vm/CMakeLists.txt | 2 - src/coreclr/vm/ceemain.cpp | 2 - src/coreclr/vm/comcache.cpp | 1 - src/coreclr/vm/comthreadpool.cpp | 1 - src/coreclr/vm/corhost.cpp | 1 - .../vm/eventing/eventpipe/ep-rt-coreclr.h | 1 - src/coreclr/vm/nativeoverlapped.cpp | 1 - src/coreclr/vm/threadpoolrequest.cpp | 6 - src/coreclr/vm/threads.cpp | 2 - src/coreclr/vm/tieredcompilation.cpp | 1 - src/coreclr/vm/win32threadpool.cpp | 1967 +++++++++++++++++ src/coreclr/vm/win32threadpool.h | 1088 +++++++++ 16 files changed, 3055 insertions(+), 22 deletions(-) create mode 100644 src/coreclr/vm/win32threadpool.cpp create mode 100644 src/coreclr/vm/win32threadpool.h diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 0fda825fe90a34..c62f7713753578 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -18,7 +18,6 @@ #include "typestring.h" #include "daccess.h" #include "binder.h" -#include "win32threadpool.h" #include "runtimeinfo.h" #ifdef FEATURE_COMWRAPPERS diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 4b7bee7cbd1d40..35035c1b85e8af 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -10,7 +10,6 @@ //***************************************************************************** #include "stdafx.h" -#include #include "typestring.h" #include diff --git a/src/coreclr/debug/daccess/request_svr.cpp b/src/coreclr/debug/daccess/request_svr.cpp index d913d0f5938ebb..36d21efb8df121 100644 --- a/src/coreclr/debug/daccess/request_svr.cpp +++ b/src/coreclr/debug/daccess/request_svr.cpp @@ -16,7 +16,6 @@ #if defined(FEATURE_SVR_GC) #include -#include #include "request_common.h" int GCHeapCount() diff --git a/src/coreclr/debug/ee/dactable.cpp b/src/coreclr/debug/ee/dactable.cpp index 525bb0f4af3509..6c2865b3359633 100644 --- a/src/coreclr/debug/ee/dactable.cpp +++ b/src/coreclr/debug/ee/dactable.cpp @@ -13,7 +13,6 @@ #include #include "../../vm/virtualcallstub.h" -#include "../../vm/win32threadpool.h" #include "../../vm/codeman.h" #include "../../vm/eedbginterfaceimpl.h" #include "../../vm/common.h" diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index ef06b4363b03a7..e0e02f579d601d 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -129,7 +129,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON vars.cpp versionresilienthashcode.cpp virtualcallstub.cpp - win32threadpool.cpp zapsig.cpp ) @@ -237,7 +236,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON vars.hpp versionresilienthashcode.h virtualcallstub.h - win32threadpool.h zapsig.h ) diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index aada53cd7e5658..97184c4e15ac49 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -175,8 +175,6 @@ #include "stacksampler.h" #endif -#include "win32threadpool.h" - #include #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/comcache.cpp b/src/coreclr/vm/comcache.cpp index c9afbbed923dd9..573ead1a96e355 100644 --- a/src/coreclr/vm/comcache.cpp +++ b/src/coreclr/vm/comcache.cpp @@ -9,7 +9,6 @@ #include "comcache.h" #include "runtimecallablewrapper.h" #include -#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT #include "olecontexthelpers.h" diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 2be9e24c7a25c0..97826d2e7f97cd 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -17,7 +17,6 @@ #include "comdelegate.h" #include "comthreadpool.h" #include "threadpoolrequest.h" -#include "win32threadpool.h" #include "class.h" #include "object.h" #include "field.h" diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index e1babc7f6228e4..db99db62b10c56 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -28,7 +28,6 @@ #include "dllimportcallback.h" #include "eventtrace.h" -#include "win32threadpool.h" #include "eventtrace.h" #include "finalizerthread.h" #include "threadsuspend.h" diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 9b940adc091bf6..f000317d7c2b84 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -11,7 +11,6 @@ #include #include "fstream.h" #include "typestring.h" -#include "win32threadpool.h" #include "clrversion.h" #undef EP_INFINITE_WAIT diff --git a/src/coreclr/vm/nativeoverlapped.cpp b/src/coreclr/vm/nativeoverlapped.cpp index 8c4a79be68b19b..4a9edb5ff780be 100644 --- a/src/coreclr/vm/nativeoverlapped.cpp +++ b/src/coreclr/vm/nativeoverlapped.cpp @@ -15,7 +15,6 @@ #include "fcall.h" #include "nativeoverlapped.h" #include "corhost.h" -#include "win32threadpool.h" #include "comsynchronizable.h" #include "comthreadpool.h" #include "marshalnative.h" diff --git a/src/coreclr/vm/threadpoolrequest.cpp b/src/coreclr/vm/threadpoolrequest.cpp index e28adc484b629c..32ab74a8766fc9 100644 --- a/src/coreclr/vm/threadpoolrequest.cpp +++ b/src/coreclr/vm/threadpoolrequest.cpp @@ -15,7 +15,6 @@ #include "comdelegate.h" #include "comthreadpool.h" #include "threadpoolrequest.h" -#include "win32threadpool.h" #include "class.h" #include "object.h" #include "field.h" @@ -63,8 +62,3 @@ void PerAppDomainTPCountList::ResetAppDomainIndex(TPIndex index) _ASSERTE(index.m_dwIndex == TPIndex().m_dwIndex); } - - -FORCEINLINE void ReleaseWorkRequest(WorkRequest *workRequest) { ThreadpoolMgr::RecycleMemory( workRequest, ThreadpoolMgr::MEMTYPE_WorkRequest ); } -typedef Wrapper< WorkRequest *, DoNothing, ReleaseWorkRequest > WorkRequestHolder; - diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index aadb6397c4fdb4..d151cc1ec77364 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -20,7 +20,6 @@ #include "eeprofinterfaces.h" #include "eeconfig.h" #include "corhost.h" -#include "win32threadpool.h" #include "jitinterface.h" #include "eventtrace.h" #include "comutilnative.h" @@ -34,7 +33,6 @@ #include "appdomain.inl" #include "vmholder.h" #include "exceptmacros.h" -#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP #include "runtimecallablewrapper.h" diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 4e4da913e86641..3b15100a34fb66 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -10,7 +10,6 @@ #include "common.h" #include "excep.h" #include "log.h" -#include "win32threadpool.h" #include "threadsuspend.h" #include "tieredcompilation.h" diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp new file mode 100644 index 00000000000000..529902b44e3888 --- /dev/null +++ b/src/coreclr/vm/win32threadpool.cpp @@ -0,0 +1,1967 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +/*++ + +Module Name: + + Win32ThreadPool.cpp + +Abstract: + + This module implements Threadpool support using Win32 APIs + + +Revision History: + December 1999 - Created + +--*/ + +#include "common.h" +#include "log.h" +#include "threadpoolrequest.h" +#include "win32threadpool.h" +#include "delegateinfo.h" +#include "eeconfig.h" +#include "dbginterface.h" +#include "corhost.h" +#include "eventtrace.h" +#include "threads.h" +#include "appdomain.inl" +#include "nativeoverlapped.h" +#include "configuration.h" + + +#ifndef TARGET_UNIX +#ifndef DACCESS_COMPILE + +// APIs that must be accessed through dynamic linking. +typedef int (WINAPI *NtQueryInformationThreadProc) ( + HANDLE ThreadHandle, + THREADINFOCLASS ThreadInformationClass, + PVOID ThreadInformation, + ULONG ThreadInformationLength, + PULONG ReturnLength); +NtQueryInformationThreadProc g_pufnNtQueryInformationThread = NULL; + +typedef int (WINAPI *NtQuerySystemInformationProc) ( + SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength OPTIONAL); +NtQuerySystemInformationProc g_pufnNtQuerySystemInformation = NULL; + +typedef HANDLE (WINAPI * CreateWaitableTimerExProc) ( + LPSECURITY_ATTRIBUTES lpTimerAttributes, + LPCTSTR lpTimerName, + DWORD dwFlags, + DWORD dwDesiredAccess); +CreateWaitableTimerExProc g_pufnCreateWaitableTimerEx = NULL; + +typedef BOOL (WINAPI * SetWaitableTimerExProc) ( + HANDLE hTimer, + const LARGE_INTEGER *lpDueTime, + LONG lPeriod, + PTIMERAPCROUTINE pfnCompletionRoutine, + LPVOID lpArgToCompletionRoutine, + void* WakeContext, //should be PREASON_CONTEXT, but it's not defined for us (and we don't use it) + ULONG TolerableDelay); +SetWaitableTimerExProc g_pufnSetWaitableTimerEx = NULL; + +#endif // !DACCESS_COMPILE +#endif // !TARGET_UNIX + +BOOL ThreadpoolMgr::InitCompletionPortThreadpool = FALSE; +HANDLE ThreadpoolMgr::GlobalCompletionPort; // used for binding io completions on file handles + +SVAL_IMPL(ThreadpoolMgr::ThreadCounter,ThreadpoolMgr,CPThreadCounter); + +SVAL_IMPL_INIT(LONG,ThreadpoolMgr,MaxLimitTotalCPThreads,1000); // = MaxLimitCPThreadsPerCPU * number of CPUS +SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalCPThreads); +SVAL_IMPL(LONG,ThreadpoolMgr,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS + +// Cacheline aligned, hot variable +DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_IMPL(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr, WorkerCounter); + +SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS +SVAL_IMPL(LONG,ThreadpoolMgr,MaxLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS + +SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization); + +HillClimbing ThreadpoolMgr::HillClimbingInstance; + +// Cacheline aligned, 3 hot variables updated in a group +DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::PriorCompletedWorkRequests = 0; +DWORD ThreadpoolMgr::PriorCompletedWorkRequestsTime; +DWORD ThreadpoolMgr::NextCompletedWorkRequestsTime; + +LARGE_INTEGER ThreadpoolMgr::CurrentSampleStartTime; + +unsigned int ThreadpoolMgr::WorkerThreadSpinLimit; +bool ThreadpoolMgr::IsHillClimbingDisabled; +int ThreadpoolMgr::ThreadAdjustmentInterval; + +#define INVALID_HANDLE ((HANDLE) -1) +#define NEW_THREAD_THRESHOLD 7 // Number of requests outstanding before we start a new thread +#define CP_THREAD_PENDINGIO_WAIT 5000 // polling interval when thread is retired but has a pending io +#define GATE_THREAD_DELAY 500 /*milliseconds*/ +#define GATE_THREAD_DELAY_TOLERANCE 50 /*milliseconds*/ +#define DELAY_BETWEEN_SUSPENDS (5000 + GATE_THREAD_DELAY) // time to delay between suspensions + +Volatile ThreadpoolMgr::Initialization = 0; // indicator of whether the threadpool is initialized. + +bool ThreadpoolMgr::s_usePortableThreadPool = false; +bool ThreadpoolMgr::s_usePortableThreadPoolForIO = false; + +// Cacheline aligned, hot variable +DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) unsigned int ThreadpoolMgr::LastDequeueTime; // used to determine if work items are getting thread starved + +SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestHead); // Head of work request queue +SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestTail); // Head of work request queue + +SVAL_IMPL(ThreadpoolMgr::LIST_ENTRY,ThreadpoolMgr,TimerQueue); // queue of timers + +//unsigned int ThreadpoolMgr::LastCpuSamplingTime=0; // last time cpu utilization was sampled by gate thread +unsigned int ThreadpoolMgr::LastCPThreadCreation=0; // last time a completion port thread was created +unsigned int ThreadpoolMgr::NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads + + +CrstStatic ThreadpoolMgr::WorkerCriticalSection; +CLREvent * ThreadpoolMgr::RetiredCPWakeupEvent; // wakeup event for completion port threads +CrstStatic ThreadpoolMgr::WaitThreadsCriticalSection; +ThreadpoolMgr::LIST_ENTRY ThreadpoolMgr::WaitThreadsHead; + +CLRLifoSemaphore* ThreadpoolMgr::WorkerSemaphore; +CLRLifoSemaphore* ThreadpoolMgr::RetiredWorkerSemaphore; + +CrstStatic ThreadpoolMgr::TimerQueueCriticalSection; +HANDLE ThreadpoolMgr::TimerThread=NULL; +Thread *ThreadpoolMgr::pTimerThread=NULL; + +// Cacheline aligned, hot variable +DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) DWORD ThreadpoolMgr::LastTickCount; + +// Cacheline aligned, hot variable +DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::GateThreadStatus=GATE_THREAD_STATUS_NOT_RUNNING; + +// Move out of from preceeding variables' cache line +DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) ThreadpoolMgr::RecycledListsWrapper ThreadpoolMgr::RecycledLists; + +ThreadpoolMgr::TimerInfo *ThreadpoolMgr::TimerInfosToBeRecycled = NULL; + +BOOL ThreadpoolMgr::IsApcPendingOnWaitThread = FALSE; + +#ifndef DACCESS_COMPILE + +// Macros for inserting/deleting from doubly linked list + +#define InitializeListHead(ListHead) (\ + (ListHead)->Flink = (ListHead)->Blink = (ListHead)) + +// +// these are named the same as slightly different macros in the NT headers +// +#undef RemoveHeadList +#undef RemoveEntryList +#undef InsertTailList +#undef InsertHeadList + +#define RemoveHeadList(ListHead,FirstEntry) \ + {\ + FirstEntry = (LIST_ENTRY*) (ListHead)->Flink;\ + ((LIST_ENTRY*)FirstEntry->Flink)->Blink = (ListHead);\ + (ListHead)->Flink = FirstEntry->Flink;\ + } + +#define RemoveEntryList(Entry) {\ + LIST_ENTRY* _EX_Entry;\ + _EX_Entry = (Entry);\ + ((LIST_ENTRY*) _EX_Entry->Blink)->Flink = _EX_Entry->Flink;\ + ((LIST_ENTRY*) _EX_Entry->Flink)->Blink = _EX_Entry->Blink;\ + } + +#define InsertTailList(ListHead,Entry) \ + (Entry)->Flink = (ListHead);\ + (Entry)->Blink = (ListHead)->Blink;\ + ((LIST_ENTRY*)(ListHead)->Blink)->Flink = (Entry);\ + (ListHead)->Blink = (Entry); + +#define InsertHeadList(ListHead,Entry) {\ + LIST_ENTRY* _EX_Flink;\ + LIST_ENTRY* _EX_ListHead;\ + _EX_ListHead = (LIST_ENTRY*)(ListHead);\ + _EX_Flink = (LIST_ENTRY*) _EX_ListHead->Flink;\ + (Entry)->Flink = _EX_Flink;\ + (Entry)->Blink = _EX_ListHead;\ + _EX_Flink->Blink = (Entry);\ + _EX_ListHead->Flink = (Entry);\ + } + +#define IsListEmpty(ListHead) \ + ((ListHead)->Flink == (ListHead)) + +#define SetLastHRError(hr) \ + if (HRESULT_FACILITY(hr) == FACILITY_WIN32)\ + SetLastError(HRESULT_CODE(hr));\ + else \ + SetLastError(ERROR_INVALID_DATA);\ + +/************************************************************************/ + +void ThreadpoolMgr::RecycledListsWrapper::Initialize( unsigned int numProcs ) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + + pRecycledListPerProcessor = new RecycledListInfo[numProcs][MEMTYPE_COUNT]; +} + +//--// + +void ThreadpoolMgr::EnsureInitialized() +{ + CONTRACTL + { + THROWS; // EnsureInitializedSlow can throw + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (IsInitialized()) + return; + + EnsureInitializedSlow(); +} + +NOINLINE void ThreadpoolMgr::EnsureInitializedSlow() +{ + CONTRACTL + { + THROWS; // Initialize can throw + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DWORD dwSwitchCount = 0; + +retry: + if (InterlockedCompareExchange(&Initialization, 1, 0) == 0) + { + if (Initialize()) + Initialization = -1; + else + { + Initialization = 0; + COMPlusThrowOM(); + } + } + else // someone has already begun initializing. + { + // wait until it finishes + while (Initialization != -1) + { + __SwitchToThread(0, ++dwSwitchCount); + goto retry; + } + } +} + +BOOL ThreadpoolMgr::Initialize() +{ + CONTRACTL + { + THROWS; + MODE_ANY; + GC_NOTRIGGER; + INJECT_FAULT(COMPlusThrowOM()); + } + CONTRACTL_END; + + BOOL bRet = FALSE; + BOOL bExceptionCaught = FALSE; + + NumberOfProcessors = GetCurrentProcessCpuCount(); + InitPlatformVariables(); + + EX_TRY + { + TimerQueueCriticalSection.Init(CrstThreadpoolTimerQueue); + + // initialize TimerQueue + InitializeListHead(&TimerQueue); + +#ifndef TARGET_UNIX + //ThreadPool_CPUGroup + if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) + RecycledLists.Initialize( CPUGroupInfo::GetNumActiveProcessors() ); + else + RecycledLists.Initialize( g_SystemInfo.dwNumberOfProcessors ); +#else // !TARGET_UNIX + RecycledLists.Initialize( PAL_GetTotalCpuCount() ); +#endif // !TARGET_UNIX + } + EX_CATCH + { + // Note: It is fine to call Destroy on uninitialized critical sections + TimerQueueCriticalSection.Destroy(); + + bExceptionCaught = TRUE; + } + EX_END_CATCH(SwallowAllExceptions); + + if (bExceptionCaught) + { + goto end; + } + + bRet = TRUE; +end: + return bRet; +} + +void ThreadpoolMgr::InitPlatformVariables() +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifndef TARGET_UNIX + HINSTANCE hNtDll; + HINSTANCE hCoreSynch = nullptr; + { + CONTRACT_VIOLATION(GCViolation|FaultViolation); + hNtDll = CLRLoadLibrary(W("ntdll.dll")); + _ASSERTE(hNtDll); + } + + // These APIs must be accessed via dynamic binding since they may be removed in future + // OS versions. + g_pufnNtQueryInformationThread = (NtQueryInformationThreadProc)GetProcAddress(hNtDll,"NtQueryInformationThread"); + g_pufnNtQuerySystemInformation = (NtQuerySystemInformationProc)GetProcAddress(hNtDll,"NtQuerySystemInformation"); + +#endif +} + +// +// WorkingThreadCounts tracks the number of worker threads currently doing user work, and the maximum number of such threads +// since the last time TakeMaxWorkingThreadCount was called. This information is for diagnostic purposes only, +// and is tracked only if the CLR config value INTERNAL_ThreadPool_EnableWorkerTracking is non-zero (this feature is off +// by default). +// +union WorkingThreadCounts +{ + struct + { + int currentWorking : 16; + int maxWorking : 16; + }; + + LONG asLong; +}; + +WorkingThreadCounts g_workingThreadCounts; + +// +// If worker tracking is enabled (see above) then this is called immediately before and after a worker thread executes +// each work item. +// +void ThreadpoolMgr::ReportThreadStatus(bool isWorking) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE(IsInitialized()); // can't be here without requesting a thread first + _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); + + while (true) + { + WorkingThreadCounts currentCounts, newCounts; + currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); + + newCounts = currentCounts; + + if (isWorking) + newCounts.currentWorking++; + + if (newCounts.currentWorking > newCounts.maxWorking) + newCounts.maxWorking = newCounts.currentWorking; + + if (!isWorking) + newCounts.currentWorking--; + + if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) + break; + } +} + +// +// Returns the max working count since the previous call to TakeMaxWorkingThreadCount, and resets WorkingThreadCounts.maxWorking. +// +int TakeMaxWorkingThreadCount() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); + while (true) + { + WorkingThreadCounts currentCounts, newCounts; + currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); + + newCounts = currentCounts; + newCounts.maxWorking = 0; + + if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) + { + // If we haven't updated the counts since the last call to TakeMaxWorkingThreadCount, then we never updated maxWorking. + // In that case, the number of working threads for the whole period since the last TakeMaxWorkingThreadCount is the + // current number of working threads. + return currentCounts.maxWorking == 0 ? currentCounts.currentWorking : currentCounts.maxWorking; + } + } +} + + +/************************************************************************/ + +DangerousNonHostedSpinLock ThreadpoolMgr::ThreadAdjustmentLock; + +void ThreadpoolMgr::WaitIOCompletionCallback( + DWORD dwErrorCode, + DWORD numBytesTransferred, + LPOVERLAPPED lpOverlapped) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + } + CONTRACTL_END; + + if (dwErrorCode == ERROR_SUCCESS) + DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); +} + +extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, + DWORD numBytesTransferred, + LPOVERLAPPED lpOverlapped); + + +// Remove a block from the appropriate recycleList and return. +// If recycleList is empty, fall back to new. +LPVOID ThreadpoolMgr::GetRecycledMemory(enum MemType memType) +{ + LPVOID result = NULL; + CONTRACT(LPVOID) + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM()); + POSTCONDITION(CheckPointer(result)); + } CONTRACT_END; + + if(RecycledLists.IsInitialized()) + { + RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); + + result = list.Remove(); + } + + if(result == NULL) + { + switch (memType) + { + case MEMTYPE_DelegateInfo: + result = new DelegateInfo; + break; + case MEMTYPE_AsyncCallback: + result = new AsyncCallback; + break; + case MEMTYPE_WorkRequest: + result = new WorkRequest; + break; + default: + _ASSERTE(!"Unknown Memtype"); + result = NULL; + break; + } + } + + RETURN result; +} + +// Insert freed block in recycle list. If list is full, return to system heap +void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(RecycledLists.IsInitialized()) + { + RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); + + if(list.CanInsert()) + { + list.Insert( mem ); + return; + } + } + + switch (memType) + { + case MEMTYPE_DelegateInfo: + delete (DelegateInfo*) mem; + break; + case MEMTYPE_AsyncCallback: + delete (AsyncCallback*) mem; + break; + case MEMTYPE_WorkRequest: + delete (WorkRequest*) mem; + break; + default: + _ASSERTE(!"Unknown Memtype"); + + } +} + +// this should only be called by unmanaged thread (i.e. there should be no mgd +// caller on the stack) since we are swallowing terminal exceptions +DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_PREEMPTIVE; + /* cannot use contract because of SEH + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } + CONTRACTL_END;*/ + + DWORD status = WAIT_TIMEOUT; + EX_TRY + { + status = ev->Wait(sleepTime,FALSE); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions) + return status; +} + +/************************************************************************/ + + +// if no wraparound that the timer is expired if duetime is less than current time +// if wraparound occurred, then the timer expired if dueTime was greater than last time or dueTime is less equal to current time +#define TimeExpired(last,now,duetime) ((last) <= (now) ? \ + ((duetime) <= (now) && (duetime) >= (last)): \ + ((duetime) >= (last) || (duetime) <= (now))) + +#define TimeInterval(end,start) ((end) > (start) ? ((end) - (start)) : ((0xffffffff - (start)) + (end) + 1)) + +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (disable : 4716) +#else +#pragma warning (disable : 4715) +#endif +#endif +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:22008) // "Prefast integer overflow check on (0 + lval) is bogus. Tried local disable without luck, doing whole method." +#endif + +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (default : 4716) +#else +#pragma warning (default : 4715) +#endif +#endif + +void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, + unsigned index, + BOOL waitTimedOut + ) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_MODE_PREEMPTIVE; + /* cannot use contract because of SEH + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END;*/ + + AsyncCallback* asyncCallback = NULL; + EX_TRY{ + if ( waitInfo->flag & WAIT_SINGLE_EXECUTION) + { + DeactivateNthWait (waitInfo,index) ; + } + else + { // reactivate wait by resetting timer + waitInfo->timer.startTime = GetTickCount(); + } + + asyncCallback = MakeAsyncCallback(); + if (asyncCallback) + { + asyncCallback->wait = waitInfo; + asyncCallback->waitTimedOut = waitTimedOut; + + InterlockedIncrement(&waitInfo->refCount); + +#ifndef TARGET_UNIX + if (FALSE == PostQueuedCompletionStatus((LPOVERLAPPED)asyncCallback, (LPOVERLAPPED_COMPLETION_ROUTINE)WaitIOCompletionCallback)) +#else // TARGET_UNIX + if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) +#endif // !TARGET_UNIX + ReleaseAsyncCallback(asyncCallback); + } + } + EX_CATCH { + if (asyncCallback) + ReleaseAsyncCallback(asyncCallback); + + EX_RETHROW; + } + EX_END_CATCH(SwallowAllExceptions); +} + + +DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) +{ + CONTRACTL + { + THROWS; + MODE_PREEMPTIVE; + GC_TRIGGERS; + } + CONTRACTL_END; + + Thread * pThread = GetThreadNULLOk(); + if (pThread == NULL) + { + HRESULT hr = ERROR_SUCCESS; + + ClrFlsSetThreadType(ThreadType_Threadpool_Worker); + pThread = SetupThreadNoThrow(&hr); + + if (pThread == NULL) + { + return hr; + } + } + + { + AsyncCallback * asyncCallback = (AsyncCallback*) pArgs; + + WaitInfo * waitInfo = asyncCallback->wait; + + AsyncCallbackHolder asyncCBHolder; + asyncCBHolder.Assign(asyncCallback); + + // We fire the "dequeue" ETW event here, before executing the user code, to enable correlation with + // the ThreadPoolIOEnqueue fired in ThreadpoolMgr::RegisterWaitForSingleObject + if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) + FireEtwThreadPoolIODequeue(waitInfo, reinterpret_cast(waitInfo->Callback), GetClrInstanceId()); + + // the user callback can throw, the host must be prepared to handle it. + // SQL is ok, since they have a top-level SEH handler. However, there's + // no easy way to verify it + + ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) + ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); + +#ifndef TARGET_UNIX + Thread::IncrementIOThreadPoolCompletionCount(pThread); +#endif + } + + return ERROR_SUCCESS; +} + +void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) +{ + LIMITED_METHOD_CONTRACT; + + ThreadCB* threadCB = waitInfo->threadCB; + DWORD endIndex = threadCB->NumActiveWaits-1; + DWORD index; + + for (index = 0; index <= endIndex; index++) + { + LIST_ENTRY* head = &(threadCB->waitPointer[index]); + LIST_ENTRY* current = head; + do { + if (current->Flink == (PVOID) waitInfo) + goto FOUND; + + current = (LIST_ENTRY*) current->Flink; + + } while (current != head); + } + +FOUND: + _ASSERTE(index <= endIndex); + + DeactivateNthWait(waitInfo, index); +} + + +void ThreadpoolMgr::DeleteWait(WaitInfo* waitInfo) +{ + CONTRACTL + { + if (waitInfo->ExternalEventSafeHandle != NULL) { THROWS;} else { NOTHROW; } + MODE_ANY; + if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + } + CONTRACTL_END; + + if(waitInfo->Context && (waitInfo->flag & WAIT_FREE_CONTEXT)) { + DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; + + // Since the delegate release destroys a handle, we need to be in + // co-operative mode + { + GCX_COOP(); + pDelegate->Release(); + } + + RecycleMemory( pDelegate, MEMTYPE_DelegateInfo ); + } + + if (waitInfo->flag & WAIT_INTERNAL_COMPLETION) + { + waitInfo->InternalCompletionEvent.Set(); + return; // waitInfo will be deleted by the thread that's waiting on this event + } + else if (waitInfo->ExternalCompletionEvent != INVALID_HANDLE) + { + SetEvent(waitInfo->ExternalCompletionEvent); + } + else if (waitInfo->ExternalEventSafeHandle != NULL) + { + // Release the safe handle and the GC handle holding it + ReleaseWaitInfo(waitInfo); + } + + delete waitInfo; + + +} + + + +/************************************************************************/ + +void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) +{ + WRAPPER_NO_CONTRACT; + + WaitInfo* waitInfo = pArgs; + + if ( ! (waitInfo->state & WAIT_REGISTERED) ) + { + // set state to deleted, so that it does not get registered + waitInfo->state |= WAIT_DELETE ; + + // since the wait has not even been registered, we dont need an interlock to decrease the RefCount + waitInfo->refCount--; + + if (waitInfo->PartialCompletionEvent.IsValid()) + { + waitInfo->PartialCompletionEvent.Set(); + } + return; + } + + if (waitInfo->state & WAIT_ACTIVE) + { + DeactivateWait(waitInfo); + } + + if ( waitInfo->PartialCompletionEvent.IsValid()) + { + waitInfo->PartialCompletionEvent.Set(); + return; // we cannot delete the wait here since the PartialCompletionEvent + // may not have been closed yet. so, we return and rely on the waiter of PartialCompletionEvent + // to do the close + } + + if (InterlockedDecrement(&waitInfo->refCount) == 0) + { + DeleteWait(waitInfo); + } + return; +} + + +/************************************************************************/ + +#ifndef TARGET_UNIX + +LPOVERLAPPED ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain( + Thread* pThread, + DWORD* pErrorCode, + DWORD* pNumBytes, + size_t* pKey) +// +//This function is called just after dispatching the previous BindIO callback +//to Managed code. This is a perf optimization to do a quick call to +//GetQueuedCompletionStatus with a timeout of 0 ms. If there is work in the +//same appdomain, dispatch it back immediately. If not stick it in a well known +//place, and reenter the target domain. The timeout of zero is chosen so as to +//not delay appdomain unloads. +// +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_ANY; + + LPOVERLAPPED lpOverlapped=NULL; + + BOOL status=FALSE; + OVERLAPPEDDATAREF overlapped=NULL; + BOOL ManagedCallback=FALSE; + + *pErrorCode = S_OK; + + + //Very Very Important! + //Do not change the timeout for GetQueuedCompletionStatus to a non-zero value. + //Selecting a non-zero value can cause the thread to block, and lead to expensive context switches. + //In real life scenarios, we have noticed a packet to be not available immediately, but very shortly + //(after few 100's of instructions), and falling back to the VM is good in that case as compared to + //taking a context switch. Changing the timeout to non-zero can lead to perf degrades, that are very + //hard to diagnose. + + status = ::GetQueuedCompletionStatus( + GlobalCompletionPort, + pNumBytes, + (PULONG_PTR)pKey, + &lpOverlapped, + 0); + + DWORD lastError = GetLastError(); + + if (status == 0) + { + if (lpOverlapped != NULL) + { + *pErrorCode = lastError; + } + else + { + return NULL; + } + } + + if (((LPOVERLAPPED_COMPLETION_ROUTINE) *pKey) != BindIoCompletionCallbackStub) + { + //_ASSERTE(FALSE); + } + else + { + ManagedCallback = TRUE; + overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); + } + + if (ManagedCallback) + { + _ASSERTE(*pKey != 0); // should be a valid function address + + if (*pKey ==0) + { + //Application Bug. + return NULL; + } + } + else + { + //Just retruned back from managed code, a Thread structure should exist. + _ASSERTE (pThread); + + //Oops, this is an overlapped fom a different appdomain. STick it in + //the thread. We will process it later. + + StoreOverlappedInfoInThread(pThread, *pErrorCode, *pNumBytes, *pKey, lpOverlapped); + + lpOverlapped = NULL; + } + +#ifndef DACCESS_COMPILE + return lpOverlapped; +#endif +} + +void ThreadpoolMgr::StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_ANY; + + _ASSERTE(pThread); + + PIOCompletionContext context; + + context = (PIOCompletionContext) pThread->GetIOCompletionContext(); + + _ASSERTE(context); + + context->ErrorCode = dwErrorCode; + context->numBytesTransferred = dwNumBytes; + context->lpOverlapped = lpOverlapped; + context->key = key; +} + +#endif // !TARGET_UNIX + +// Returns true if there is pending io on the thread. +BOOL ThreadpoolMgr::IsIoPending() +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifndef TARGET_UNIX + int Status; + ULONG IsIoPending; + + if (g_pufnNtQueryInformationThread) + { + Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(), + ThreadIsIoPending, + &IsIoPending, + sizeof(IsIoPending), + NULL); + + + if ((Status < 0) || IsIoPending) + return TRUE; + else + return FALSE; + } + return TRUE; +#else + return FALSE; +#endif // !TARGET_UNIX +} + +#ifndef TARGET_UNIX + +#ifdef HOST_64BIT +#pragma warning (disable : 4716) +#else +#pragma warning (disable : 4715) +#endif + +int ThreadpoolMgr::GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo) +{ + LIMITED_METHOD_CONTRACT; + + PROCESS_CPU_INFORMATION newUsage; + newUsage.idleTime.QuadPart = 0; + newUsage.kernelTime.QuadPart = 0; + newUsage.userTime.QuadPart = 0; + + if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) + { +#if !defined(FEATURE_NATIVEAOT) && !defined(TARGET_UNIX) + FILETIME newIdleTime, newKernelTime, newUserTime; + + CPUGroupInfo::GetSystemTimes(&newIdleTime, &newKernelTime, &newUserTime); + newUsage.idleTime.u.LowPart = newIdleTime.dwLowDateTime; + newUsage.idleTime.u.HighPart = newIdleTime.dwHighDateTime; + newUsage.kernelTime.u.LowPart = newKernelTime.dwLowDateTime; + newUsage.kernelTime.u.HighPart = newKernelTime.dwHighDateTime; + newUsage.userTime.u.LowPart = newUserTime.dwLowDateTime; + newUsage.userTime.u.HighPart = newUserTime.dwHighDateTime; +#endif + } + else + { + (*g_pufnNtQuerySystemInformation)(SystemProcessorPerformanceInformation, + pOldInfo->usageBuffer, + pOldInfo->usageBufferSize, + NULL); + + SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* pInfoArray = pOldInfo->usageBuffer; + DWORD_PTR pmask = pOldInfo->affinityMask; + + int proc_no = 0; + while (pmask) + { + if (pmask & 1) + { //should be good: 1CPU 28823 years, 256CPUs 100+years + newUsage.idleTime.QuadPart += pInfoArray[proc_no].IdleTime.QuadPart; + newUsage.kernelTime.QuadPart += pInfoArray[proc_no].KernelTime.QuadPart; + newUsage.userTime.QuadPart += pInfoArray[proc_no].UserTime.QuadPart; + } + + pmask >>=1; + proc_no++; + } + } + + __int64 cpuTotalTime, cpuBusyTime; + + cpuTotalTime = (newUsage.userTime.QuadPart - pOldInfo->userTime.QuadPart) + + (newUsage.kernelTime.QuadPart - pOldInfo->kernelTime.QuadPart); + cpuBusyTime = cpuTotalTime - + (newUsage.idleTime.QuadPart - pOldInfo->idleTime.QuadPart); + + // Preserve reading + pOldInfo->idleTime = newUsage.idleTime; + pOldInfo->kernelTime = newUsage.kernelTime; + pOldInfo->userTime = newUsage.userTime; + + __int64 reading = 0; + + if (cpuTotalTime > 0) + reading = ((cpuBusyTime * 100) / cpuTotalTime); + + _ASSERTE(FitsIn(reading)); + return (int)reading; +} + +#else // !TARGET_UNIX + +int ThreadpoolMgr::GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo) +{ + return PAL_GetCPUBusyTime(pOldInfo); +} + +#endif // !TARGET_UNIX + +// +// A timer that ticks every GATE_THREAD_DELAY milliseconds. +// On platforms that support it, we use a coalescable waitable timer object. +// For other platforms, we use Sleep, via __SwitchToThread. +// +class GateThreadTimer +{ +#ifndef TARGET_UNIX + HANDLE m_hTimer; + +public: + GateThreadTimer() + : m_hTimer(NULL) + { + CONTRACTL + { + NOTHROW; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + if (g_pufnCreateWaitableTimerEx && g_pufnSetWaitableTimerEx) + { + m_hTimer = g_pufnCreateWaitableTimerEx(NULL, NULL, 0, TIMER_ALL_ACCESS); + if (m_hTimer) + { + // + // Set the timer to fire GATE_THREAD_DELAY milliseconds from now, then every GATE_THREAD_DELAY milliseconds thereafter. + // We also set the tolerance to GET_THREAD_DELAY_TOLERANCE, allowing the OS to coalesce this timer. + // + LARGE_INTEGER dueTime; + dueTime.QuadPart = MILLI_TO_100NANO(-(LONGLONG)GATE_THREAD_DELAY); //negative value indicates relative time + if (!g_pufnSetWaitableTimerEx(m_hTimer, &dueTime, GATE_THREAD_DELAY, NULL, NULL, NULL, GATE_THREAD_DELAY_TOLERANCE)) + { + CloseHandle(m_hTimer); + m_hTimer = NULL; + } + } + } + } + + ~GateThreadTimer() + { + CONTRACTL + { + NOTHROW; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + if (m_hTimer) + { + CloseHandle(m_hTimer); + m_hTimer = NULL; + } + } + +#endif // !TARGET_UNIX + +public: + void Wait() + { + CONTRACTL + { + NOTHROW; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + +#ifndef TARGET_UNIX + if (m_hTimer) + WaitForSingleObject(m_hTimer, INFINITE); + else +#endif // !TARGET_UNIX + __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); + } +}; + +// called by logic to spawn a new completion port thread. +// return false if not enough time has elapsed since the last +// time we sampled the cpu utilization. +BOOL ThreadpoolMgr::SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, + unsigned NumThreads, // total number of threads of that type (worker or CP) + double throttleRate // the delay is increased by this percentage for each extra thread + ) +{ + LIMITED_METHOD_CONTRACT; + + unsigned dwCurrentTickCount = GetTickCount(); + + unsigned delaySinceLastThreadCreation = dwCurrentTickCount - LastThreadCreationTime; + + unsigned minWaitBetweenThreadCreation = GATE_THREAD_DELAY; + + if (throttleRate > 0.0) + { + _ASSERTE(throttleRate <= 1.0); + + unsigned adjustedThreadCount = NumThreads > NumberOfProcessors ? (NumThreads - NumberOfProcessors) : 0; + + minWaitBetweenThreadCreation = (unsigned) (GATE_THREAD_DELAY * pow((1.0 + throttleRate),(double)adjustedThreadCount)); + } + // the amount of time to wait should grow up as the number of threads is increased + + return (delaySinceLastThreadCreation > minWaitBetweenThreadCreation); + +} + + +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (default : 4716) +#else +#pragma warning (default : 4715) +#endif +#endif + +/************************************************************************/ + +struct CreateTimerThreadParams { + CLREvent event; + BOOL setupSucceeded; +}; + +BOOL ThreadpoolMgr::CreateTimerQueueTimer(PHANDLE phNewTimer, + WAITORTIMERCALLBACK Callback, + PVOID Parameter, + DWORD DueTime, + DWORD Period, + ULONG Flag) +{ + CONTRACTL + { + THROWS; // EnsureInitialized, CreateAutoEvent can throw + if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} // There can be calls thru ICorThreadpool + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM()); + } + CONTRACTL_END; + + EnsureInitialized(); + + // For now we use just one timer thread. Consider using multiple timer threads if + // number of timers in the queue exceeds a certain threshold. The logic and code + // would be similar to the one for creating wait threads. + if (NULL == TimerThread) + { + CrstHolder csh(&TimerQueueCriticalSection); + + // check again + if (NULL == TimerThread) + { + CreateTimerThreadParams params; + params.event.CreateAutoEvent(FALSE); + + params.setupSucceeded = FALSE; + + HANDLE TimerThreadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, TimerThreadStart, ¶ms, W(".NET Timer")); + + if (TimerThreadHandle == NULL) + { + params.event.CloseEvent(); + ThrowOutOfMemory(); + } + + { + GCX_PREEMP(); + for(;;) + { + // if a host throws because it couldnt allocate another thread, + // just retry the wait. + if (SafeWait(¶ms.event,INFINITE, FALSE) != WAIT_TIMEOUT) + break; + } + } + params.event.CloseEvent(); + + if (!params.setupSucceeded) + { + CloseHandle(TimerThreadHandle); + *phNewTimer = NULL; + return FALSE; + } + + TimerThread = TimerThreadHandle; + } + + } + + + NewHolder timerInfoHolder; + TimerInfo * timerInfo = new (nothrow) TimerInfo; + if (NULL == timerInfo) + ThrowOutOfMemory(); + + timerInfoHolder.Assign(timerInfo); + + timerInfo->FiringTime = DueTime; + timerInfo->Function = Callback; + timerInfo->Context = Parameter; + timerInfo->Period = Period; + timerInfo->state = 0; + timerInfo->flag = Flag; + timerInfo->ExternalCompletionEvent = INVALID_HANDLE; + timerInfo->ExternalEventSafeHandle = NULL; + + *phNewTimer = (HANDLE)timerInfo; + + BOOL status = QueueUserAPC((PAPCFUNC)InsertNewTimer,TimerThread,(size_t)timerInfo); + if (FALSE == status) + { + *phNewTimer = NULL; + return FALSE; + } + + timerInfoHolder.SuppressRelease(); + return TRUE; +} + +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (disable : 4716) +#else +#pragma warning (disable : 4715) +#endif +#endif +DWORD WINAPI ThreadpoolMgr::TimerThreadStart(LPVOID p) +{ + ClrFlsSetThreadType (ThreadType_Timer); + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_TRIGGERS; // due to SetApartment + STATIC_CONTRACT_MODE_PREEMPTIVE; + /* cannot use contract because of SEH + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } + CONTRACTL_END;*/ + + CreateTimerThreadParams* params = (CreateTimerThreadParams*)p; + + Thread* pThread = SetupThreadNoThrow(); + + params->setupSucceeded = (pThread == NULL) ? 0 : 1; + params->event.Set(); + + if (pThread == NULL) + return 0; + + pTimerThread = pThread; + // Timer threads never die + + LastTickCount = GetTickCount(); + +#ifdef FEATURE_COMINTEROP + if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) + { + // @todo: should we log the failure + return 0; + } +#endif // FEATURE_COMINTEROP + + for (;;) + { + // moved to its own function since EX_TRY consumes stack +#ifdef _MSC_VER +#pragma inline_depth (0) // the function containing EX_TRY can't be inlined here +#endif + TimerThreadFire(); +#ifdef _MSC_VER +#pragma inline_depth (20) +#endif + } + + // unreachable +} + +void ThreadpoolMgr::TimerThreadFire() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + EX_TRY { + DWORD timeout = FireTimers(); + ClrSleepEx(timeout, TRUE); + + // the thread could wake up either because an APC completed or the sleep timeout + // in both case, we need to sweep the timer queue, firing timers, and readjusting + // the next firing time + + } + EX_CATCH { + // Assert on debug builds since a dead timer thread is a fatal error + _ASSERTE(FALSE); + EX_RETHROW; + } + EX_END_CATCH(SwallowAllExceptions); +} + +#ifdef _MSC_VER +#ifdef HOST_64BIT +#pragma warning (default : 4716) +#else +#pragma warning (default : 4715) +#endif +#endif + +// Executed as an APC in timer thread +void ThreadpoolMgr::InsertNewTimer(TimerInfo* pArg) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE(pArg); + TimerInfo * timerInfo = pArg; + + if (timerInfo->state & TIMER_DELETE) + { // timer was deleted before it could be registered + DeleteTimer(timerInfo); + return; + } + + // set the firing time = current time + due time (note initially firing time = due time) + DWORD currentTime = GetTickCount(); + if (timerInfo->FiringTime == (ULONG) -1) + { + timerInfo->state = TIMER_REGISTERED; + timerInfo->refCount = 1; + + } + else + { + timerInfo->FiringTime += currentTime; + + timerInfo->state = (TIMER_REGISTERED | TIMER_ACTIVE); + timerInfo->refCount = 1; + + // insert the timer in the queue + InsertTailList(&TimerQueue,(&timerInfo->link)); + } + + return; +} + + +// executed by the Timer thread +// sweeps through the list of timers, readjusting the firing times, queueing APCs for +// those that have expired, and returns the next firing time interval +DWORD ThreadpoolMgr::FireTimers() +{ + CONTRACTL + { + THROWS; // QueueUserWorkItem can throw + if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + if (GetThreadNULLOk()) { MODE_PREEMPTIVE;} else { DISABLED(MODE_ANY);} + } + CONTRACTL_END; + + DWORD currentTime = GetTickCount(); + DWORD nextFiringInterval = (DWORD) -1; + TimerInfo* timerInfo = NULL; + + EX_TRY + { + for (LIST_ENTRY* node = (LIST_ENTRY*) TimerQueue.Flink; + node != &TimerQueue; + ) + { + timerInfo = (TimerInfo*) node; + node = (LIST_ENTRY*) node->Flink; + + if (TimeExpired(LastTickCount, currentTime, timerInfo->FiringTime)) + { + if (timerInfo->Period == 0 || timerInfo->Period == (ULONG) -1) + { + DeactivateTimer(timerInfo); + } + + InterlockedIncrement(&timerInfo->refCount); + + GCX_COOP(); + + ARG_SLOT args[] = { PtrToArgSlot(AsyncTimerCallbackCompletion), PtrToArgSlot(timerInfo) }; + MethodDescCallSite(METHOD__THREAD_POOL__UNSAFE_QUEUE_UNMANAGED_WORK_ITEM).Call(args); + + if (timerInfo->Period != 0 && timerInfo->Period != (ULONG)-1) + { + ULONG nextFiringTime = timerInfo->FiringTime + timerInfo->Period; + DWORD firingInterval; + if (TimeExpired(timerInfo->FiringTime, currentTime, nextFiringTime)) + { + // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up with the short + // period, have it fire 1 ms from now to avoid spinning without a delay. + timerInfo->FiringTime = currentTime + 1; + firingInterval = 1; + } + else + { + timerInfo->FiringTime = nextFiringTime; + firingInterval = TimeInterval(nextFiringTime, currentTime); + } + + if (firingInterval < nextFiringInterval) + nextFiringInterval = firingInterval; + } + } + else + { + DWORD firingInterval = TimeInterval(timerInfo->FiringTime, currentTime); + if (firingInterval < nextFiringInterval) + nextFiringInterval = firingInterval; + } + } + } + EX_CATCH + { + // If QueueUserWorkItem throws OOM, swallow the exception and retry on + // the next call to FireTimers(), otherwise retrhow. + Exception *ex = GET_EXCEPTION(); + // undo the call to DeactivateTimer() + InterlockedDecrement(&timerInfo->refCount); + timerInfo->state = timerInfo->state & TIMER_ACTIVE; + InsertTailList(&TimerQueue, (&timerInfo->link)); + if (ex->GetHR() != E_OUTOFMEMORY) + { + EX_RETHROW; + } + } + EX_END_CATCH(RethrowTerminalExceptions); + + LastTickCount = currentTime; + + return nextFiringInterval; +} + +DWORD WINAPI ThreadpoolMgr::AsyncTimerCallbackCompletion(PVOID pArgs) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + Thread* pThread = GetThreadNULLOk(); + if (pThread == NULL) + { + HRESULT hr = ERROR_SUCCESS; + + ClrFlsSetThreadType(ThreadType_Threadpool_Worker); + pThread = SetupThreadNoThrow(&hr); + + if (pThread == NULL) + { + return hr; + } + } + + { + TimerInfo* timerInfo = (TimerInfo*) pArgs; + ((WAITORTIMERCALLBACKFUNC) timerInfo->Function) (timerInfo->Context, TRUE) ; + + if (InterlockedDecrement(&timerInfo->refCount) == 0) + { + DeleteTimer(timerInfo); + } + } + + return ERROR_SUCCESS; +} + + +// removes the timer from the timer queue, thereby cancelling it +// there may still be pending callbacks that haven't completed +void ThreadpoolMgr::DeactivateTimer(TimerInfo* timerInfo) +{ + LIMITED_METHOD_CONTRACT; + + RemoveEntryList((LIST_ENTRY*) timerInfo); + + // This timer info could go into another linked list of timer infos + // waiting to be released. Reinitialize the list pointers + InitializeListHead(&timerInfo->link); + timerInfo->state = timerInfo->state & ~TIMER_ACTIVE; +} + +DWORD WINAPI ThreadpoolMgr::AsyncDeleteTimer(PVOID pArgs) +{ + CONTRACTL + { + THROWS; + MODE_PREEMPTIVE; + GC_TRIGGERS; + } + CONTRACTL_END; + + Thread * pThread = GetThreadNULLOk(); + if (pThread == NULL) + { + HRESULT hr = ERROR_SUCCESS; + + ClrFlsSetThreadType(ThreadType_Threadpool_Worker); + pThread = SetupThreadNoThrow(&hr); + + if (pThread == NULL) + { + return hr; + } + } + + DeleteTimer((TimerInfo*) pArgs); + + return ERROR_SUCCESS; +} + +void ThreadpoolMgr::DeleteTimer(TimerInfo* timerInfo) +{ + CONTRACTL + { + if (GetThreadNULLOk() == pTimerThread) { NOTHROW; } else { THROWS; } + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE((timerInfo->state & TIMER_ACTIVE) == 0); + + _ASSERTE(!(timerInfo->flag & WAIT_FREE_CONTEXT)); + + if (timerInfo->flag & WAIT_INTERNAL_COMPLETION) + { + timerInfo->InternalCompletionEvent.Set(); + return; // the timerInfo will be deleted by the thread that's waiting on InternalCompletionEvent + } + + // ExternalCompletionEvent comes from Host, ExternalEventSafeHandle from managed code. + // They are mutually exclusive. + _ASSERTE(!(timerInfo->ExternalCompletionEvent != INVALID_HANDLE && + timerInfo->ExternalEventSafeHandle != NULL)); + + if (timerInfo->ExternalCompletionEvent != INVALID_HANDLE) + { + SetEvent(timerInfo->ExternalCompletionEvent); + timerInfo->ExternalCompletionEvent = INVALID_HANDLE; + } + + // We cannot block the timer thread, so some cleanup is deferred to other threads. + if (GetThreadNULLOk() == pTimerThread) + { + // Notify the ExternalEventSafeHandle with an user work item + if (timerInfo->ExternalEventSafeHandle != NULL) + { + BOOL success = FALSE; + EX_TRY + { + if (QueueUserWorkItem(AsyncDeleteTimer, + timerInfo, + QUEUE_ONLY) != FALSE) + { + success = TRUE; + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + // If unable to queue a user work item, fall back to queueing timer for release + // which will happen *sometime* in the future. + if (success == FALSE) + { + QueueTimerInfoForRelease(timerInfo); + } + + return; + } + + // Releasing GC handles can block. So we wont do this on the timer thread. + // We'll put it in a list which will be processed by a worker thread + if (timerInfo->Context != NULL) + { + QueueTimerInfoForRelease(timerInfo); + return; + } + } + + // To get here we are either not the Timer thread or there is no blocking work to be done + + if (timerInfo->Context != NULL) + { + GCX_COOP(); + delete (ThreadpoolMgr::TimerInfoContext*)timerInfo->Context; + } + + if (timerInfo->ExternalEventSafeHandle != NULL) + { + ReleaseTimerInfo(timerInfo); + } + + delete timerInfo; + +} + +// We add TimerInfos from deleted timers into a linked list. +// A worker thread will later release the handles held by the TimerInfo +// and recycle them if possible. +void ThreadpoolMgr::QueueTimerInfoForRelease(TimerInfo *pTimerInfo) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // The synchronization in this method depends on the fact that + // - There is only one timer thread + // - The one and only timer thread is executing this method. + // - This function wont go into an alertable state. That could trigger another APC. + // Else two threads can be queueing timerinfos and a race could + // lead to leaked memory and handles + _ASSERTE(pTimerThread == GetThread()); + TimerInfo *pHead = NULL; + + // Make sure this timer info has been deactivated and removed from any other lists + _ASSERTE((pTimerInfo->state & TIMER_ACTIVE) == 0); + //_ASSERTE(pTimerInfo->link.Blink == &(pTimerInfo->link) && + // pTimerInfo->link.Flink == &(pTimerInfo->link)); + // Make sure "link" is the first field in TimerInfo + _ASSERTE(pTimerInfo == (PVOID)&pTimerInfo->link); + + // Grab any previously published list + if ((pHead = InterlockedExchangeT(&TimerInfosToBeRecycled, NULL)) != NULL) + { + // If there already is a list, just append + InsertTailList((LIST_ENTRY *)pHead, &pTimerInfo->link); + pTimerInfo = pHead; + } + else + // If this is the head, make its next and previous ptrs point to itself + InitializeListHead((LIST_ENTRY*)&pTimerInfo->link); + + // Publish the list + (void) InterlockedExchangeT(&TimerInfosToBeRecycled, pTimerInfo); + +} + +void ThreadpoolMgr::FlushQueueOfTimerInfos() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + TimerInfo *pHeadTimerInfo = NULL, *pCurrTimerInfo = NULL; + LIST_ENTRY *pNextInfo = NULL; + + if ((pHeadTimerInfo = InterlockedExchangeT(&TimerInfosToBeRecycled, NULL)) == NULL) + return; + + do + { + RemoveHeadList((LIST_ENTRY *)pHeadTimerInfo, pNextInfo); + _ASSERTE(pNextInfo != NULL); + + pCurrTimerInfo = (TimerInfo *) pNextInfo; + + GCX_COOP(); + if (pCurrTimerInfo->Context != NULL) + { + delete (ThreadpoolMgr::TimerInfoContext*)pCurrTimerInfo->Context; + } + + if (pCurrTimerInfo->ExternalEventSafeHandle != NULL) + { + ReleaseTimerInfo(pCurrTimerInfo); + } + + delete pCurrTimerInfo; + + } + while ((TimerInfo *)pNextInfo != pHeadTimerInfo); +} + +/************************************************************************/ +BOOL ThreadpoolMgr::ChangeTimerQueueTimer( + HANDLE Timer, + ULONG DueTime, + ULONG Period) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + GC_NOTRIGGER; + INJECT_FAULT(COMPlusThrowOM()); + } + CONTRACTL_END; + + _ASSERTE(IsInitialized()); + _ASSERTE(Timer); // not possible to give invalid handle in managed code + + NewHolder updateInfoHolder; + TimerUpdateInfo *updateInfo = new TimerUpdateInfo; + updateInfoHolder.Assign(updateInfo); + + updateInfo->Timer = (TimerInfo*) Timer; + updateInfo->DueTime = DueTime; + updateInfo->Period = Period; + + BOOL status = QueueUserAPC((PAPCFUNC)UpdateTimer, + TimerThread, + (size_t) updateInfo); + + if (status) + updateInfoHolder.SuppressRelease(); + + return(status); +} + +void ThreadpoolMgr::UpdateTimer(TimerUpdateInfo* pArgs) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + TimerUpdateInfo* updateInfo = (TimerUpdateInfo*) pArgs; + TimerInfo* timerInfo = updateInfo->Timer; + + timerInfo->Period = updateInfo->Period; + + if (updateInfo->DueTime == (ULONG) -1) + { + if (timerInfo->state & TIMER_ACTIVE) + { + DeactivateTimer(timerInfo); + } + // else, noop (the timer was already inactive) + _ASSERTE((timerInfo->state & TIMER_ACTIVE) == 0); + + delete updateInfo; + return; + } + + DWORD currentTime = GetTickCount(); + timerInfo->FiringTime = currentTime + updateInfo->DueTime; + + delete updateInfo; + + if (! (timerInfo->state & TIMER_ACTIVE)) + { + // timer not active (probably a one shot timer that has expired), so activate it + timerInfo->state |= TIMER_ACTIVE; + _ASSERTE(timerInfo->refCount >= 1); + // insert the timer in the queue + InsertTailList(&TimerQueue,(&timerInfo->link)); + + } + + return; +} + +/************************************************************************/ +BOOL ThreadpoolMgr::DeleteTimerQueueTimer( + HANDLE Timer, + HANDLE Event) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + GC_TRIGGERS; + } + CONTRACTL_END; + + _ASSERTE(IsInitialized()); // cannot call delete before creating timer + _ASSERTE(Timer); // not possible to give invalid handle in managed code + + // make volatile to avoid compiler reordering check after async call. + // otherwise, DeregisterTimer could delete timerInfo before the comparison. + VolatilePtr timerInfo = (TimerInfo*) Timer; + + if (Event == (HANDLE) -1) + { + //CONTRACT_VIOLATION(ThrowsViolation); + timerInfo->InternalCompletionEvent.CreateAutoEvent(FALSE); + timerInfo->flag |= WAIT_INTERNAL_COMPLETION; + } + else if (Event) + { + timerInfo->ExternalCompletionEvent = Event; + } +#ifdef _DEBUG + else /* Event == NULL */ + { + _ASSERTE(timerInfo->ExternalCompletionEvent == INVALID_HANDLE); + } +#endif + + BOOL isBlocking = timerInfo->flag & WAIT_INTERNAL_COMPLETION; + + BOOL status = QueueUserAPC((PAPCFUNC)DeregisterTimer, + TimerThread, + (size_t)(TimerInfo*)timerInfo); + + if (FALSE == status) + { + if (isBlocking) + timerInfo->InternalCompletionEvent.CloseEvent(); + return FALSE; + } + + if (isBlocking) + { + _ASSERTE(timerInfo->ExternalEventSafeHandle == NULL); + _ASSERTE(timerInfo->ExternalCompletionEvent == INVALID_HANDLE); + _ASSERTE(GetThreadNULLOk() != pTimerThread); + + timerInfo->InternalCompletionEvent.Wait(INFINITE,TRUE /*alertable*/); + timerInfo->InternalCompletionEvent.CloseEvent(); + // Release handles and delete TimerInfo + _ASSERTE(timerInfo->refCount == 0); + // if WAIT_INTERNAL_COMPLETION flag is not set, timerInfo will be deleted in DeleteTimer. + timerInfo->flag &= ~WAIT_INTERNAL_COMPLETION; + DeleteTimer(timerInfo); + } + return status; +} + +void ThreadpoolMgr::DeregisterTimer(TimerInfo* pArgs) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + TimerInfo* timerInfo = (TimerInfo*) pArgs; + + if (! (timerInfo->state & TIMER_REGISTERED) ) + { + // set state to deleted, so that it does not get registered + timerInfo->state |= TIMER_DELETE ; + + // since the timer has not even been registered, we dont need an interlock to decrease the RefCount + timerInfo->refCount--; + + return; + } + + if (timerInfo->state & TIMER_ACTIVE) + { + DeactivateTimer(timerInfo); + } + + if (InterlockedDecrement(&timerInfo->refCount) == 0 ) + { + DeleteTimer(timerInfo); + } + return; +} + +#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h new file mode 100644 index 00000000000000..ead529124888d7 --- /dev/null +++ b/src/coreclr/vm/win32threadpool.h @@ -0,0 +1,1088 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +/*++ + +Module Name: + + Win32ThreadPool.h + +Abstract: + + This module is the header file for thread pools using Win32 APIs. + +Revision History: + + +--*/ + +#ifndef _WIN32THREADPOOL_H +#define _WIN32THREADPOOL_H + +#include "delegateinfo.h" +#include "util.hpp" +#include "nativeoverlapped.h" +#include "hillclimbing.h" + +#define MAX_WAITHANDLES 64 + +#define MAX_CACHED_EVENTS 40 // upper limit on number of wait events cached + +#define WAIT_REGISTERED 0x01 +#define WAIT_ACTIVE 0x02 +#define WAIT_DELETE 0x04 + +#define TIMER_REGISTERED 0x01 +#define TIMER_ACTIVE 0x02 +#define TIMER_DELETE 0x04 + +#define WAIT_SINGLE_EXECUTION 0x00000001 +#define WAIT_FREE_CONTEXT 0x00000002 +#define WAIT_INTERNAL_COMPLETION 0x00000004 + +#define QUEUE_ONLY 0x00000000 // do not attempt to call on the thread +#define CALL_OR_QUEUE 0x00000001 // call on the same thread if not too busy, else queue + +const int MaxLimitThreadsPerCPU=250; // upper limit on number of cp threads per CPU +const int MaxFreeCPThreadsPerCPU=2; // upper limit on number of free cp threads per CPU + +const int CpuUtilizationHigh=95; // remove threads when above this +const int CpuUtilizationLow =80; // inject more threads if below this + +#ifndef TARGET_UNIX +extern HANDLE (WINAPI *g_pufnCreateIoCompletionPort)(HANDLE FileHandle, + HANDLE ExistingCompletionPort, + ULONG_PTR CompletionKey, + DWORD NumberOfConcurrentThreads); + +extern int (WINAPI *g_pufnNtQueryInformationThread) (HANDLE ThreadHandle, + THREADINFOCLASS ThreadInformationClass, + PVOID ThreadInformation, + ULONG ThreadInformationLength, + PULONG ReturnLength); + +extern int (WINAPI * g_pufnNtQuerySystemInformation) (SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength OPTIONAL); +#endif // !TARGET_UNIX + +#define FILETIME_TO_INT64(t) (*(__int64*)&(t)) +#define MILLI_TO_100NANO(x) ((x) * 10000) // convert from milliseond to 100 nanosecond unit + +/** + * This type is supposed to be private to ThreadpoolMgr. + * It's at global scope because Strike needs to be able to access its + * definition. + */ +struct WorkRequest { + WorkRequest* next; + LPTHREAD_START_ROUTINE Function; + PVOID Context; + +}; + +typedef struct _IOCompletionContext +{ + DWORD ErrorCode; + DWORD numBytesTransferred; + LPOVERLAPPED lpOverlapped; + size_t key; +} IOCompletionContext, *PIOCompletionContext; + +typedef DPTR(WorkRequest) PTR_WorkRequest; +class ThreadpoolMgr +{ + friend class ClrDataAccess; + friend struct DelegateInfo; + friend class ThreadPoolNative; + friend class TimerNative; + friend class UnManagedPerAppDomainTPCount; + friend class ManagedPerAppDomainTPCount; + friend class PerAppDomainTPCountList; + friend class HillClimbing; + friend struct _DacGlobals; + +public: + struct ThreadCounter + { + static const int MaxPossibleCount = 0x7fff; + + // padding to ensure we get our own cache line + BYTE padding1[MAX_CACHE_LINE_SIZE]; + + union Counts + { + struct + { + // + // Note: these are signed rather than unsigned to allow us to detect under/overflow. + // + int MaxWorking : 16; //Determined by HillClimbing; adjusted elsewhere for timeouts, etc. + int NumActive : 16; //Active means working or waiting on WorkerSemaphore. These are "warm/hot" threads. + int NumWorking : 16; //Trying to get work from various queues. Not waiting on either semaphore. + int NumRetired : 16; //Not trying to get work; waiting on RetiredWorkerSemaphore. These are "cold" threads. + + // Note: the only reason we need "retired" threads at all is that it allows some threads to eventually time out + // even if other threads are getting work. If we ever make WorkerSemaphore a true LIFO semaphore, we will no longer + // need the concept of "retirement" - instead, the very "coldest" threads will naturally be the first to time out. + }; + + LONGLONG AsLongLong; + + bool operator==(Counts other) {LIMITED_METHOD_CONTRACT; return AsLongLong == other.AsLongLong;} + } counts; + + // padding to ensure we get our own cache line + BYTE padding2[MAX_CACHE_LINE_SIZE]; + + Counts GetCleanCounts() + { + LIMITED_METHOD_CONTRACT; +#ifdef HOST_64BIT + // VolatileLoad x64 bit read is atomic + return DangerousGetDirtyCounts(); +#else // !HOST_64BIT + // VolatileLoad may result in torn read + Counts result; +#ifndef DACCESS_COMPILE + result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, 0, 0); + ValidateCounts(result); +#else + result.AsLongLong = 0; //prevents prefast warning for DAC builds +#endif + return result; +#endif // !HOST_64BIT + } + + // + // This does a non-atomic read of the counts. The returned value is suitable only + // for use inside of a read-compare-exchange loop, where the compare-exhcange must succeed + // before any action is taken. Use GetCleanWorkerCounts for other needs, but keep in mind + // it's much slower. + // + Counts DangerousGetDirtyCounts() + { + LIMITED_METHOD_CONTRACT; + Counts result; +#ifndef DACCESS_COMPILE + result.AsLongLong = VolatileLoad(&counts.AsLongLong); +#else + result.AsLongLong = 0; //prevents prefast warning for DAC builds +#endif + return result; + } + + + Counts CompareExchangeCounts(Counts newCounts, Counts oldCounts) + { + LIMITED_METHOD_CONTRACT; + Counts result; +#ifndef DACCESS_COMPILE + result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, newCounts.AsLongLong, oldCounts.AsLongLong); + if (result == oldCounts) + { + // can only do validation on success; if we failed, it may have been due to a previous + // dirty read, which may contain invalid values. + ValidateCounts(result); + ValidateCounts(newCounts); + } +#else + result.AsLongLong = 0; //prevents prefast warning for DAC builds +#endif + return result; + } + + private: + static void ValidateCounts(Counts counts) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(counts.MaxWorking > 0); + _ASSERTE(counts.NumActive >= 0); + _ASSERTE(counts.NumWorking >= 0); + _ASSERTE(counts.NumRetired >= 0); + _ASSERTE(counts.NumWorking <= counts.NumActive); + } + }; + +public: + + // enumeration of different kinds of memory blocks that are recycled + enum MemType + { + MEMTYPE_AsyncCallback = 0, + MEMTYPE_DelegateInfo = 1, + MEMTYPE_WorkRequest = 2, + MEMTYPE_COUNT = 3, + }; + + typedef struct { + INT32 TimerId; + } TimerInfoContext; + +#ifndef DACCESS_COMPILE + static void StaticInitialize() + { + WRAPPER_NO_CONTRACT; + + s_usePortableThreadPool = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPool) != 0; +#ifdef TARGET_WINDOWS + s_usePortableThreadPoolForIO = + s_usePortableThreadPool && + CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPoolForIO) != 0; +#else // !TARGET_WINDOWS + s_usePortableThreadPoolForIO = s_usePortableThreadPool; +#endif // TARGET_WINDOWS + } +#endif // !DACCESS_COMPILE + + static bool UsePortableThreadPool() + { + LIMITED_METHOD_CONTRACT; + return s_usePortableThreadPool; + } + + static bool UsePortableThreadPoolForIO() + { + LIMITED_METHOD_CONTRACT; + return s_usePortableThreadPoolForIO; + } + + static BOOL Initialize(); + + static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, + PVOID Context, + ULONG Flags, + BOOL UnmanagedTPRequest=TRUE); + + inline static BOOL IsCompletionPortInitialized() + { + LIMITED_METHOD_CONTRACT; + return GlobalCompletionPort != NULL; + } + + static BOOL RegisterWaitForSingleObject(PHANDLE phNewWaitObject, + HANDLE hWaitObject, + WAITORTIMERCALLBACK Callback, + PVOID Context, + ULONG timeout, + DWORD dwFlag); + + static BOOL UnregisterWaitEx(HANDLE hWaitObject,HANDLE CompletionEvent); + static void WaitHandleCleanup(HANDLE hWaitObject); + + static BOOL WINAPI BindIoCompletionCallback(HANDLE FileHandle, + LPOVERLAPPED_COMPLETION_ROUTINE Function, + ULONG Flags, + DWORD& errorCode); + + static void WINAPI WaitIOCompletionCallback(DWORD dwErrorCode, + DWORD numBytesTransferred, + LPOVERLAPPED lpOverlapped); + + static BOOL SetAppDomainRequestsActive(BOOL UnmanagedTP = FALSE); + static void ClearAppDomainRequestsActive(BOOL UnmanagedTP = FALSE, LONG index = -1); + + static inline void UpdateLastDequeueTime() + { + LIMITED_METHOD_CONTRACT; + VolatileStore(&LastDequeueTime, (unsigned int)GetTickCount()); + } + + static BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, + WAITORTIMERCALLBACK Callback, + PVOID Parameter, + DWORD DueTime, + DWORD Period, + ULONG Flags); + + static BOOL ChangeTimerQueueTimer(HANDLE Timer, + ULONG DueTime, + ULONG Period); + static BOOL DeleteTimerQueueTimer(HANDLE Timer, + HANDLE CompletionEvent); + + static void RecycleMemory(LPVOID mem, enum MemType memType); + + static void FlushQueueOfTimerInfos(); + + static BOOL HaveTimerInfosToFlush() { return TimerInfosToBeRecycled != NULL; } + +#ifndef TARGET_UNIX + static LPOVERLAPPED CompletionPortDispatchWorkWithinAppDomain(Thread* pThread, DWORD* pErrorCode, DWORD* pNumBytes, size_t* pKey); + static void StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped); +#endif // !TARGET_UNIX + + // Enable filtering of correlation ETW events for cases handled at a higher abstraction level + +#ifndef DACCESS_COMPILE + static FORCEINLINE BOOL AreEtwQueueEventsSpeciallyHandled(LPTHREAD_START_ROUTINE Function) + { + // Timer events are handled at a higher abstraction level: in the managed Timer class + return (Function == ThreadpoolMgr::AsyncTimerCallbackCompletion); + } + + static FORCEINLINE BOOL AreEtwIOQueueEventsSpeciallyHandled(LPOVERLAPPED_COMPLETION_ROUTINE Function) + { + // We handle registered waits at a higher abstraction level + return Function == ThreadpoolMgr::WaitIOCompletionCallback; + } +#endif + +private: + +#ifndef DACCESS_COMPILE + + inline static void FreeWorkRequest(WorkRequest* workRequest) + { + RecycleMemory( workRequest, MEMTYPE_WorkRequest ); //delete workRequest; + } + + inline static WorkRequest* MakeWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context) + { + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END;; + + WorkRequest* wr = (WorkRequest*) GetRecycledMemory(MEMTYPE_WorkRequest); + _ASSERTE(wr); + if (NULL == wr) + return NULL; + wr->Function = function; + wr->Context = context; + wr->next = NULL; + return wr; + } + +#endif // #ifndef DACCESS_COMPILE + + typedef struct { + DWORD numBytes; + ULONG_PTR *key; + LPOVERLAPPED pOverlapped; + DWORD errorCode; + } QueuedStatus; + + typedef DPTR(struct _LIST_ENTRY) PTR_LIST_ENTRY; + typedef struct _LIST_ENTRY { + struct _LIST_ENTRY *Flink; + struct _LIST_ENTRY *Blink; + } LIST_ENTRY, *PLIST_ENTRY; + + struct WaitInfo; + + typedef struct { + HANDLE threadHandle; + DWORD threadId; + CLREvent startEvent; + LONG NumWaitHandles; // number of wait objects registered to the thread <=64 + LONG NumActiveWaits; // number of objects, thread is actually waiting on (this may be less than + // NumWaitHandles since the thread may not have activated some waits + HANDLE waitHandle[MAX_WAITHANDLES]; // array of wait handles (copied from waitInfo since + // we need them to be contiguous) + LIST_ENTRY waitPointer[MAX_WAITHANDLES]; // array of doubly linked list of corresponding waitinfo + } ThreadCB; + + + typedef struct { + ULONG startTime; // time at which wait was started + // endTime = startTime+timeout + ULONG remainingTime; // endTime - currentTime + } WaitTimerInfo; + + struct WaitInfo { + LIST_ENTRY link; // Win9x does not allow duplicate waithandles, so we need to + // group all waits on a single waithandle using this linked list + HANDLE waitHandle; + WAITORTIMERCALLBACK Callback; + PVOID Context; + ULONG timeout; + WaitTimerInfo timer; + DWORD flag; + DWORD state; + ThreadCB* threadCB; + LONG refCount; // when this reaches 0, the waitInfo can be safely deleted + CLREvent PartialCompletionEvent; // used to synchronize deactivation of a wait + CLREvent InternalCompletionEvent; // only one of InternalCompletion or ExternalCompletion is used + // but I cant make a union since CLREvent has a non-default constructor + HANDLE ExternalCompletionEvent; // they are signalled when all callbacks have completed (refCount=0) + OBJECTHANDLE ExternalEventSafeHandle; + + } ; + + // structure used to maintain global information about wait threads. Protected by WaitThreadsCriticalSection + typedef struct WaitThreadTag { + LIST_ENTRY link; + ThreadCB* threadCB; + } WaitThreadInfo; + + + struct AsyncCallback{ + WaitInfo* wait; + BOOL waitTimedOut; + } ; + +#ifndef DACCESS_COMPILE + + static VOID + AcquireAsyncCallback(AsyncCallback *pAsyncCB) + { + LIMITED_METHOD_CONTRACT; + } + + static VOID + ReleaseAsyncCallback(AsyncCallback *pAsyncCB) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + WaitInfo *waitInfo = pAsyncCB->wait; + ThreadpoolMgr::RecycleMemory((LPVOID*)pAsyncCB, ThreadpoolMgr::MEMTYPE_AsyncCallback); + + // if this was a single execution, we now need to stop rooting registeredWaitHandle + // in a GC handle. This will cause the finalizer to pick it up and call the cleanup + // routine. + if ( (waitInfo->flag & WAIT_SINGLE_EXECUTION) && (waitInfo->flag & WAIT_FREE_CONTEXT)) + { + + DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; + + _ASSERTE(pDelegate->m_registeredWaitHandle); + + { + GCX_COOP(); + StoreObjectInHandle(pDelegate->m_registeredWaitHandle, NULL); + } + } + + if (InterlockedDecrement(&waitInfo->refCount) == 0) + ThreadpoolMgr::DeleteWait(waitInfo); + + } + + typedef Holder AsyncCallbackHolder; + inline static AsyncCallback* MakeAsyncCallback() + { + WRAPPER_NO_CONTRACT; + return (AsyncCallback*) GetRecycledMemory(MEMTYPE_AsyncCallback); + } + + static VOID ReleaseInfo(OBJECTHANDLE& hndSafeHandle, + HANDLE hndNativeHandle) + { + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_TRIGGERS; + } + CONTRACTL_END + +// Use of EX_TRY, GCPROTECT etc in the same function is causing prefast to complain about local variables with +// same name masking each other (#246). The error could not be suppressed with "#pragma PREFAST_SUPPRESS" +#ifndef _PREFAST_ + + if (hndSafeHandle != NULL) + { + + SAFEHANDLEREF refSH = NULL; + + GCX_COOP(); + GCPROTECT_BEGIN(refSH); + + { + EX_TRY + { + // Read the GC handle + refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(ObjectFromHandle(hndSafeHandle)); + + // Destroy the GC handle + DestroyHandle(hndSafeHandle); + + if (refSH != NULL) + { + SafeHandleHolder h(&refSH); + + HANDLE hEvent = refSH->GetHandle(); + if (hEvent != INVALID_HANDLE_VALUE) + { + SetEvent(hEvent); + } + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + } + + GCPROTECT_END(); + + hndSafeHandle = NULL; + } +#endif + } + +#endif // #ifndef DACCESS_COMPILE + + typedef struct { + LIST_ENTRY link; + HANDLE Handle; + } WaitEvent ; + + // Timer + typedef struct { + LIST_ENTRY link; // doubly linked list of timers + ULONG FiringTime; // TickCount of when to fire next + WAITORTIMERCALLBACK Function; // Function to call when timer fires + PVOID Context; // Context to pass to function when timer fires + ULONG Period; + DWORD flag; // How do we deal with the context + DWORD state; + LONG refCount; + HANDLE ExternalCompletionEvent; // only one of this is used, but cant do a union since CLREvent has a non-default constructor + CLREvent InternalCompletionEvent; // flags indicates which one is being used + OBJECTHANDLE ExternalEventSafeHandle; + } TimerInfo; + + static VOID AcquireWaitInfo(WaitInfo *pInfo) + { + } + static VOID ReleaseWaitInfo(WaitInfo *pInfo) + { + WRAPPER_NO_CONTRACT; +#ifndef DACCESS_COMPILE + ReleaseInfo(pInfo->ExternalEventSafeHandle, + pInfo->ExternalCompletionEvent); +#endif + } + static VOID AcquireTimerInfo(TimerInfo *pInfo) + { + } + static VOID ReleaseTimerInfo(TimerInfo *pInfo) + { + WRAPPER_NO_CONTRACT; +#ifndef DACCESS_COMPILE + ReleaseInfo(pInfo->ExternalEventSafeHandle, + pInfo->ExternalCompletionEvent); +#endif + } + + typedef Holder WaitInfoHolder; + typedef Holder TimerInfoHolder; + + typedef struct { + TimerInfo* Timer; // timer to be updated + ULONG DueTime ; // new due time + ULONG Period ; // new period + } TimerUpdateInfo; + + // Definitions and data structures to support recycling of high-frequency + // memory blocks. We use a spin-lock to access the list + + class RecycledListInfo + { + static const unsigned int MaxCachedEntries = 40; + + struct Entry + { + Entry* next; + }; + + Volatile lock; // this is the spin lock + DWORD count; // count of number of elements in the list + Entry* root; // ptr to first element of recycled list +#ifndef HOST_64BIT + DWORD filler; // Pad the structure to a multiple of the 16. +#endif + + //--// + +public: + RecycledListInfo() + { + LIMITED_METHOD_CONTRACT; + + lock = 0; + root = NULL; + count = 0; + } + + FORCEINLINE bool CanInsert() + { + LIMITED_METHOD_CONTRACT; + + return count < MaxCachedEntries; + } + + FORCEINLINE LPVOID Remove() + { + LIMITED_METHOD_CONTRACT; + + if(root == NULL) return NULL; // No need for acquiring the lock, there's nothing to remove. + + AcquireLock(); + + Entry* ret = (Entry*)root; + + if(ret) + { + root = ret->next; + count -= 1; + } + + ReleaseLock(); + + return ret; + } + + FORCEINLINE void Insert( LPVOID mem ) + { + LIMITED_METHOD_CONTRACT; + + AcquireLock(); + + Entry* entry = (Entry*)mem; + + entry->next = root; + + root = entry; + count += 1; + + ReleaseLock(); + } + + private: + FORCEINLINE void AcquireLock() + { + LIMITED_METHOD_CONTRACT; + + unsigned int rounds = 0; + + DWORD dwSwitchCount = 0; + + while(lock != 0 || InterlockedExchange( &lock, 1 ) != 0) + { + YieldProcessorNormalized(); // indicate to the processor that we are spinning + + rounds++; + + if((rounds % 32) == 0) + { + __SwitchToThread( 0, ++dwSwitchCount ); + } + } + } + + FORCEINLINE void ReleaseLock() + { + LIMITED_METHOD_CONTRACT; + + lock = 0; + } + }; + + // + // It's critical that we ensure these pointers are allocated by the linker away from + // variables that are modified a lot at runtime. + // + // The use of the CacheGuard is a temporary solution, + // the thread pool has to be refactor away from static variable and + // toward a single global structure, where we can control the locality of variables. + // + class RecycledListsWrapper + { + DWORD CacheGuardPre[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; + + RecycledListInfo (*pRecycledListPerProcessor)[MEMTYPE_COUNT]; // RecycledListInfo [numProc][MEMTYPE_COUNT] + + DWORD CacheGuardPost[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; + + public: + void Initialize( unsigned int numProcs ); + + FORCEINLINE bool IsInitialized() + { + LIMITED_METHOD_CONTRACT; + + return pRecycledListPerProcessor != NULL; + } + +#ifndef DACCESS_COMPILE + FORCEINLINE RecycledListInfo& GetRecycleMemoryInfo( enum MemType memType ) + { + LIMITED_METHOD_CONTRACT; + + DWORD processorNumber = 0; + +#ifndef TARGET_UNIX + if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) + processorNumber = CPUGroupInfo::CalculateCurrentProcessorNumber(); + else + // Turns out GetCurrentProcessorNumber can return a value greater than the number of processors reported by + // GetSystemInfo, if we're running in WOW64 on a machine with >32 processors. + processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; +#else // !TARGET_UNIX + if (PAL_HasGetCurrentProcessorNumber()) + { + // On linux, GetCurrentProcessorNumber which uses sched_getcpu() can return a value greater than the number + // of processors reported by sysconf(_SC_NPROCESSORS_ONLN) when using OpenVZ kernel. + processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; + } +#endif // !TARGET_UNIX + return pRecycledListPerProcessor[processorNumber][memType]; + } +#endif // DACCESS_COMPILE + }; + +#define GATE_THREAD_STATUS_NOT_RUNNING 0 // There is no gate thread +#define GATE_THREAD_STATUS_REQUESTED 1 // There is a gate thread, and someone has asked it to stick around recently +#define GATE_THREAD_STATUS_WAITING_FOR_REQUEST 2 // There is a gate thread, but nobody has asked it to stay. It may die soon + + // Private methods + + static Thread* CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread); + + static BOOL CreateWorkerThread(); + + static void EnqueueWorkRequest(WorkRequest* wr); + + static WorkRequest* DequeueWorkRequest(); + + static void ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled); + +#ifndef DACCESS_COMPILE + + inline static void AppendWorkRequest(WorkRequest* entry) + { + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(!UsePortableThreadPool()); + + if (WorkRequestTail) + { + _ASSERTE(WorkRequestHead != NULL); + WorkRequestTail->next = entry; + } + else + { + _ASSERTE(WorkRequestHead == NULL); + WorkRequestHead = entry; + } + + WorkRequestTail = entry; + _ASSERTE(WorkRequestTail->next == NULL); + } + + inline static WorkRequest* RemoveWorkRequest() + { + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(!UsePortableThreadPool()); + + WorkRequest* entry = NULL; + if (WorkRequestHead) + { + entry = WorkRequestHead; + WorkRequestHead = entry->next; + if (WorkRequestHead == NULL) + WorkRequestTail = NULL; + } + return entry; + } + +public: + static void EnsureInitialized(); +private: + static void EnsureInitializedSlow(); + +public: + static void InitPlatformVariables(); + + inline static BOOL IsInitialized() + { + LIMITED_METHOD_CONTRACT; + return Initialization == -1; + } + + static void MaybeAddWorkingWorker(); + + static void NotifyWorkItemCompleted() + { + WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); + + Thread::IncrementWorkerThreadPoolCompletionCount(GetThreadNULLOk()); + UpdateLastDequeueTime(); + } + + static bool ShouldAdjustMaxWorkersActive() + { + WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); + + DWORD priorTime = PriorCompletedWorkRequestsTime; + MemoryBarrier(); // read fresh value for NextCompletedWorkRequestsTime below + DWORD requiredInterval = NextCompletedWorkRequestsTime - priorTime; + DWORD elapsedInterval = GetTickCount() - priorTime; + if (elapsedInterval >= requiredInterval) + { + ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); + if (counts.NumActive <= counts.MaxWorking) + return !IsHillClimbingDisabled; + } + + return false; + } + + static void AdjustMaxWorkersActive(); + static bool ShouldWorkerKeepRunning(); + + static DWORD SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable); + + static DWORD WINAPI WorkerThreadStart(LPVOID lpArgs); + + static BOOL AddWaitRequest(HANDLE waitHandle, WaitInfo* waitInfo); + + + static ThreadCB* FindWaitThread(); // returns a wait thread that can accomodate another wait request + + static BOOL CreateWaitThread(); + + static void WINAPI InsertNewWaitForSelf(WaitInfo* pArg); + + static int FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHandle); + + static DWORD MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits); + + static void ProcessWaitCompletion( WaitInfo* waitInfo, + unsigned index, // array index + BOOL waitTimedOut); + + static DWORD WINAPI WaitThreadStart(LPVOID lpArgs); + + static DWORD WINAPI AsyncCallbackCompletion(PVOID pArgs); + + static void QueueTimerInfoForRelease(TimerInfo *pTimerInfo); + + static void DeactivateWait(WaitInfo* waitInfo); + static void DeactivateNthWait(WaitInfo* waitInfo, DWORD index); + + static void DeleteWait(WaitInfo* waitInfo); + + + inline static void ShiftWaitArray( ThreadCB* threadCB, + ULONG SrcIndex, + ULONG DestIndex, + ULONG count) + { + LIMITED_METHOD_CONTRACT; + memmove(&threadCB->waitHandle[DestIndex], + &threadCB->waitHandle[SrcIndex], + count * sizeof(HANDLE)); + memmove(&threadCB->waitPointer[DestIndex], + &threadCB->waitPointer[SrcIndex], + count * sizeof(LIST_ENTRY)); + } + + static void WINAPI DeregisterWait(WaitInfo* pArgs); + +#ifndef TARGET_UNIX + // holds the aggregate of system cpu usage of all processors + typedef struct _PROCESS_CPU_INFORMATION + { + LARGE_INTEGER idleTime; + LARGE_INTEGER kernelTime; + LARGE_INTEGER userTime; + DWORD_PTR affinityMask; + int numberOfProcessors; + SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* usageBuffer; + int usageBufferSize; + } PROCESS_CPU_INFORMATION; + + static int GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo); + static BOOL CreateCompletionPortThread(LPVOID lpArgs); + static DWORD WINAPI CompletionPortThreadStart(LPVOID lpArgs); +public: + inline static bool HaveNativeWork() + { + LIMITED_METHOD_CONTRACT; + return WorkRequestHead != NULL; + } + + static void GrowCompletionPortThreadpoolIfNeeded(); + static BOOL ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts counts); +#else + static int GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo); + +#endif // !TARGET_UNIX + + static void PerformGateActivities(int cpuUtilization); + static bool NeedGateThreadForIOCompletions(); + +private: + static BOOL IsIoPending(); + + static BOOL CreateGateThread(); + static void EnsureGateThreadRunning(); + static bool ShouldGateThreadKeepRunning(); + static DWORD WINAPI GateThreadStart(LPVOID lpArgs); + static BOOL SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, + unsigned NumThreads, // total number of threads of that type (worker or CP) + double throttleRate=0.0 // the delay is increased by this percentage for each extra thread + ); + static BOOL SufficientDelaySinceLastDequeue(); + + static LPVOID GetRecycledMemory(enum MemType memType); + + static DWORD WINAPI TimerThreadStart(LPVOID args); + static void TimerThreadFire(); // helper method used by TimerThreadStart + static void WINAPI InsertNewTimer(TimerInfo* pArg); + static DWORD FireTimers(); + static DWORD WINAPI AsyncTimerCallbackCompletion(PVOID pArgs); + static void DeactivateTimer(TimerInfo* timerInfo); + static DWORD WINAPI AsyncDeleteTimer(PVOID pArgs); + static void DeleteTimer(TimerInfo* timerInfo); + static void WINAPI UpdateTimer(TimerUpdateInfo* pArgs); + + static void WINAPI DeregisterTimer(TimerInfo* pArgs); + + inline static DWORD QueueDeregisterWait(HANDLE waitThread, WaitInfo* waitInfo) + { + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(!UsePortableThreadPool()); + + DWORD result = QueueUserAPC(reinterpret_cast(DeregisterWait), waitThread, reinterpret_cast(waitInfo)); + SetWaitThreadAPCPending(); + return result; + } + + + inline static void SetWaitThreadAPCPending() {IsApcPendingOnWaitThread = TRUE;} + inline static void ResetWaitThreadAPCPending() {IsApcPendingOnWaitThread = FALSE;} + inline static BOOL IsWaitThreadAPCPending() {return IsApcPendingOnWaitThread;} + +#endif // #ifndef DACCESS_COMPILE + // Private variables + + static Volatile Initialization; // indicator of whether the threadpool is initialized. + + static bool s_usePortableThreadPool; + static bool s_usePortableThreadPoolForIO; + + SVAL_DECL(LONG,MinLimitTotalWorkerThreads); // same as MinLimitTotalCPThreads + SVAL_DECL(LONG,MaxLimitTotalWorkerThreads); // same as MaxLimitTotalCPThreads + + DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static unsigned int LastDequeueTime; // used to determine if work items are getting thread starved + + static HillClimbing HillClimbingInstance; + + DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG PriorCompletedWorkRequests; + static DWORD PriorCompletedWorkRequestsTime; + static DWORD NextCompletedWorkRequestsTime; + + static LARGE_INTEGER CurrentSampleStartTime; + + static unsigned int WorkerThreadSpinLimit; + static bool IsHillClimbingDisabled; + static int ThreadAdjustmentInterval; + + SPTR_DECL(WorkRequest,WorkRequestHead); // Head of work request queue + SPTR_DECL(WorkRequest,WorkRequestTail); // Head of work request queue + + static unsigned int LastCPThreadCreation; // last time a completion port thread was created + static unsigned int NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads + + static BOOL IsApcPendingOnWaitThread; // Indicates if an APC is pending on the wait thread + + // This needs to be non-hosted, because worker threads can run prior to EE startup. + static DangerousNonHostedSpinLock ThreadAdjustmentLock; + +public: + static CrstStatic WorkerCriticalSection; + +private: + static const DWORD WorkerTimeout = 20 * 1000; + + DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_DECL(ThreadCounter,WorkerCounter); + + // + // WorkerSemaphore is an UnfairSemaphore because: + // 1) Threads enter and exit this semaphore very frequently, and thus benefit greatly from the spinning done by UnfairSemaphore + // 2) There is no functional reason why any particular thread should be preferred when waking workers. This only impacts performance, + // and un-fairness helps performance in this case. + // + static CLRLifoSemaphore* WorkerSemaphore; + + // + // RetiredWorkerSemaphore is a regular CLRSemaphore, not an UnfairSemaphore, because if a thread waits on this semaphore is it almost certainly + // NOT going to be released soon, so the spinning done in UnfairSemaphore only burns valuable CPU time. However, if UnfairSemaphore is ever + // implemented in terms of a Win32 IO Completion Port, we should reconsider this. The IOCP's LIFO unblocking behavior could help keep working set + // down, by constantly re-using the same small set of retired workers rather than round-robining between all of them as CLRSemaphore will do. + // If we go that route, we should add a "no-spin" option to UnfairSemaphore.Wait to avoid wasting CPU. + // + static CLRLifoSemaphore* RetiredWorkerSemaphore; + + static CLREvent * RetiredCPWakeupEvent; + + static CrstStatic WaitThreadsCriticalSection; + static LIST_ENTRY WaitThreadsHead; // queue of wait threads, each thread can handle upto 64 waits + + static TimerInfo *TimerInfosToBeRecycled; // list of delegate infos associated with deleted timers + static CrstStatic TimerQueueCriticalSection; // critical section to synchronize timer queue access + SVAL_DECL(LIST_ENTRY,TimerQueue); // queue of timers + static HANDLE TimerThread; // Currently we only have one timer thread + static Thread* pTimerThread; + DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static DWORD LastTickCount; // the count just before timer thread goes to sleep + + static BOOL InitCompletionPortThreadpool; // flag indicating whether completion port threadpool has been initialized + static HANDLE GlobalCompletionPort; // used for binding io completions on file handles + +public: + SVAL_DECL(ThreadCounter,CPThreadCounter); + +private: + SVAL_DECL(LONG,MaxLimitTotalCPThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS + SVAL_DECL(LONG,MinLimitTotalCPThreads); + SVAL_DECL(LONG,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS + + DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG GateThreadStatus; // See GateThreadStatus enumeration + + SVAL_DECL(LONG,cpuUtilization); + + DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static RecycledListsWrapper RecycledLists; +}; + + + + +#endif // _WIN32THREADPOOL_H From a2b65d185a2a74fb696adbae9e4b9697d57de802 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 16:40:55 -0700 Subject: [PATCH 05/20] Add win32threadpool header back --- src/coreclr/debug/daccess/enummem.cpp | 1 + src/coreclr/debug/daccess/request.cpp | 1 + src/coreclr/debug/daccess/request_svr.cpp | 1 + src/coreclr/debug/ee/dactable.cpp | 1 + src/coreclr/vm/CMakeLists.txt | 2 ++ src/coreclr/vm/ceemain.cpp | 2 ++ src/coreclr/vm/comcache.cpp | 1 + src/coreclr/vm/comthreadpool.cpp | 1 + src/coreclr/vm/corhost.cpp | 1 + src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h | 1 + src/coreclr/vm/nativeoverlapped.cpp | 1 + src/coreclr/vm/threadpoolrequest.cpp | 4 ++++ src/coreclr/vm/threads.cpp | 2 ++ src/coreclr/vm/tieredcompilation.cpp | 1 + 14 files changed, 20 insertions(+) diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index c62f7713753578..0fda825fe90a34 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -18,6 +18,7 @@ #include "typestring.h" #include "daccess.h" #include "binder.h" +#include "win32threadpool.h" #include "runtimeinfo.h" #ifdef FEATURE_COMWRAPPERS diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 35035c1b85e8af..8f1e0d8559cbf0 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -10,6 +10,7 @@ //***************************************************************************** #include "stdafx.h" +#include "win32threadpool.h" #include "typestring.h" #include diff --git a/src/coreclr/debug/daccess/request_svr.cpp b/src/coreclr/debug/daccess/request_svr.cpp index 36d21efb8df121..1ddd437e2da049 100644 --- a/src/coreclr/debug/daccess/request_svr.cpp +++ b/src/coreclr/debug/daccess/request_svr.cpp @@ -16,6 +16,7 @@ #if defined(FEATURE_SVR_GC) #include +#include "win32threadpool.h" #include "request_common.h" int GCHeapCount() diff --git a/src/coreclr/debug/ee/dactable.cpp b/src/coreclr/debug/ee/dactable.cpp index 6c2865b3359633..525bb0f4af3509 100644 --- a/src/coreclr/debug/ee/dactable.cpp +++ b/src/coreclr/debug/ee/dactable.cpp @@ -13,6 +13,7 @@ #include #include "../../vm/virtualcallstub.h" +#include "../../vm/win32threadpool.h" #include "../../vm/codeman.h" #include "../../vm/eedbginterfaceimpl.h" #include "../../vm/common.h" diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index e0e02f579d601d..ef06b4363b03a7 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -129,6 +129,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON vars.cpp versionresilienthashcode.cpp virtualcallstub.cpp + win32threadpool.cpp zapsig.cpp ) @@ -236,6 +237,7 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON vars.hpp versionresilienthashcode.h virtualcallstub.h + win32threadpool.h zapsig.h ) diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 97184c4e15ac49..aada53cd7e5658 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -175,6 +175,8 @@ #include "stacksampler.h" #endif +#include "win32threadpool.h" + #include #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/comcache.cpp b/src/coreclr/vm/comcache.cpp index 573ead1a96e355..c9afbbed923dd9 100644 --- a/src/coreclr/vm/comcache.cpp +++ b/src/coreclr/vm/comcache.cpp @@ -9,6 +9,7 @@ #include "comcache.h" #include "runtimecallablewrapper.h" #include +#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT #include "olecontexthelpers.h" diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 97826d2e7f97cd..2be9e24c7a25c0 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -17,6 +17,7 @@ #include "comdelegate.h" #include "comthreadpool.h" #include "threadpoolrequest.h" +#include "win32threadpool.h" #include "class.h" #include "object.h" #include "field.h" diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index db99db62b10c56..e1babc7f6228e4 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -28,6 +28,7 @@ #include "dllimportcallback.h" #include "eventtrace.h" +#include "win32threadpool.h" #include "eventtrace.h" #include "finalizerthread.h" #include "threadsuspend.h" diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index f000317d7c2b84..9b940adc091bf6 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -11,6 +11,7 @@ #include #include "fstream.h" #include "typestring.h" +#include "win32threadpool.h" #include "clrversion.h" #undef EP_INFINITE_WAIT diff --git a/src/coreclr/vm/nativeoverlapped.cpp b/src/coreclr/vm/nativeoverlapped.cpp index 4a9edb5ff780be..8c4a79be68b19b 100644 --- a/src/coreclr/vm/nativeoverlapped.cpp +++ b/src/coreclr/vm/nativeoverlapped.cpp @@ -15,6 +15,7 @@ #include "fcall.h" #include "nativeoverlapped.h" #include "corhost.h" +#include "win32threadpool.h" #include "comsynchronizable.h" #include "comthreadpool.h" #include "marshalnative.h" diff --git a/src/coreclr/vm/threadpoolrequest.cpp b/src/coreclr/vm/threadpoolrequest.cpp index 32ab74a8766fc9..13cf18d8b10136 100644 --- a/src/coreclr/vm/threadpoolrequest.cpp +++ b/src/coreclr/vm/threadpoolrequest.cpp @@ -15,6 +15,7 @@ #include "comdelegate.h" #include "comthreadpool.h" #include "threadpoolrequest.h" +#include "win32threadpool.h" #include "class.h" #include "object.h" #include "field.h" @@ -62,3 +63,6 @@ void PerAppDomainTPCountList::ResetAppDomainIndex(TPIndex index) _ASSERTE(index.m_dwIndex == TPIndex().m_dwIndex); } + +FORCEINLINE void ReleaseWorkRequest(WorkRequest *workRequest) { ThreadpoolMgr::RecycleMemory( workRequest, ThreadpoolMgr::MEMTYPE_WorkRequest ); } +typedef Wrapper< WorkRequest *, DoNothing, ReleaseWorkRequest > WorkRequestHolder; diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index d151cc1ec77364..aadb6397c4fdb4 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -20,6 +20,7 @@ #include "eeprofinterfaces.h" #include "eeconfig.h" #include "corhost.h" +#include "win32threadpool.h" #include "jitinterface.h" #include "eventtrace.h" #include "comutilnative.h" @@ -33,6 +34,7 @@ #include "appdomain.inl" #include "vmholder.h" #include "exceptmacros.h" +#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP #include "runtimecallablewrapper.h" diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 3b15100a34fb66..4e4da913e86641 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -10,6 +10,7 @@ #include "common.h" #include "excep.h" #include "log.h" +#include "win32threadpool.h" #include "threadsuspend.h" #include "tieredcompilation.h" From aa501403e3e6561de01c8bc1f564ed9f27043295 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 16:59:17 -0700 Subject: [PATCH 06/20] Current changes go until 381/577 during build native --- src/coreclr/vm/win32threadpool.cpp | 42 +----------------------------- src/coreclr/vm/win32threadpool.h | 1 - 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp index 529902b44e3888..c034523304e102 100644 --- a/src/coreclr/vm/win32threadpool.cpp +++ b/src/coreclr/vm/win32threadpool.cpp @@ -89,8 +89,6 @@ SVAL_IMPL(LONG,ThreadpoolMgr,MaxLimitTotalWorkerThreads); // = MaxLimitCP SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization); -HillClimbing ThreadpoolMgr::HillClimbingInstance; - // Cacheline aligned, 3 hot variables updated in a group DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::PriorCompletedWorkRequests = 0; DWORD ThreadpoolMgr::PriorCompletedWorkRequestsTime; @@ -373,44 +371,6 @@ union WorkingThreadCounts WorkingThreadCounts g_workingThreadCounts; -// -// If worker tracking is enabled (see above) then this is called immediately before and after a worker thread executes -// each work item. -// -void ThreadpoolMgr::ReportThreadStatus(bool isWorking) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); // can't be here without requesting a thread first - _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); - - while (true) - { - WorkingThreadCounts currentCounts, newCounts; - currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); - - newCounts = currentCounts; - - if (isWorking) - newCounts.currentWorking++; - - if (newCounts.currentWorking > newCounts.maxWorking) - newCounts.maxWorking = newCounts.currentWorking; - - if (!isWorking) - newCounts.currentWorking--; - - if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) - break; - } -} - // // Returns the max working count since the previous call to TakeMaxWorkingThreadCount, and resets WorkingThreadCounts.maxWorking. // @@ -651,7 +611,7 @@ void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, InterlockedIncrement(&waitInfo->refCount); #ifndef TARGET_UNIX - if (FALSE == PostQueuedCompletionStatus((LPOVERLAPPED)asyncCallback, (LPOVERLAPPED_COMPLETION_ROUTINE)WaitIOCompletionCallback)) + #else // TARGET_UNIX if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) #endif // !TARGET_UNIX diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h index ead529124888d7..614753a3b2900b 100644 --- a/src/coreclr/vm/win32threadpool.h +++ b/src/coreclr/vm/win32threadpool.h @@ -23,7 +23,6 @@ Revision History: #include "delegateinfo.h" #include "util.hpp" #include "nativeoverlapped.h" -#include "hillclimbing.h" #define MAX_WAITHANDLES 64 From 568477e114775f5964c6a575286e0eaf236db352 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 17:06:57 -0700 Subject: [PATCH 07/20] Delete GetHillClimbingLogEntry in request.cpp, Delete hillclimbing stuff in dacvars.h --- src/coreclr/debug/daccess/request.cpp | 19 ------------------- src/coreclr/inc/dacvars.h | 3 --- 2 files changed, 22 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 8f1e0d8559cbf0..5099ed3c9405a2 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -284,25 +284,6 @@ ClrDataAccess::GetWorkRequestData(CLRDATA_ADDRESS addr, struct DacpWorkRequestDa return hr; } -HRESULT -ClrDataAccess::GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *entry) -{ - if (addr == 0 || entry == NULL) - return E_INVALIDARG; - - SOSDacEnter(); - - HillClimbingLogEntry *pLogEntry = PTR_HillClimbingLogEntry(TO_TADDR(addr)); - entry->TickCount = pLogEntry->TickCount; - entry->NewControlSetting = pLogEntry->NewControlSetting; - entry->LastHistoryCount = pLogEntry->LastHistoryCount; - entry->LastHistoryMean = pLogEntry->LastHistoryMean; - entry->Transition = pLogEntry->Transition; - - SOSDacLeave(); - return hr; -} - HRESULT ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData) { diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 5ce668353c4fb1..c077e91311ffaa 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -111,9 +111,6 @@ DEFINE_DACVAR(LONG, ThreadpoolMgr__MaxFreeCPThreads, ThreadpoolMgr::MaxFreeCPThr DEFINE_DACVAR(LONG, ThreadpoolMgr__MaxLimitTotalCPThreads, ThreadpoolMgr::MaxLimitTotalCPThreads) DEFINE_DACVAR(LONG, ThreadpoolMgr__MinLimitTotalCPThreads, ThreadpoolMgr::MinLimitTotalCPThreads) DEFINE_DACVAR(LIST_ENTRY, ThreadpoolMgr__TimerQueue, ThreadpoolMgr::TimerQueue) -DEFINE_DACVAR_NO_DUMP(SIZE_T, dac__HillClimbingLog, ::HillClimbingLog) -DEFINE_DACVAR(int, dac__HillClimbingLogFirstIndex, ::HillClimbingLogFirstIndex) -DEFINE_DACVAR(int, dac__HillClimbingLogSize, ::HillClimbingLogSize) DEFINE_DACVAR(PTR_Thread, dac__g_pFinalizerThread, ::g_pFinalizerThread) DEFINE_DACVAR(PTR_Thread, dac__g_pSuspensionThread, ::g_pSuspensionThread) From be9e5d45ef2b95026954e5dfe5dfc355e6bd31e2 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 17:08:17 -0700 Subject: [PATCH 08/20] Delete hillclimbing stuff in function GetThreadpoolData in request.cpp --- src/coreclr/debug/daccess/request.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 5099ed3c9405a2..49f2bfd8441258 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -310,10 +310,6 @@ ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData) threadpoolData->FirstUnmanagedWorkRequest = HOST_CDADDR(ThreadpoolMgr::WorkRequestHead); - threadpoolData->HillClimbingLog = dac_cast(&HillClimbingLog); - threadpoolData->HillClimbingLogFirstIndex = HillClimbingLogFirstIndex; - threadpoolData->HillClimbingLogSize = HillClimbingLogSize; - // // Read ThreadpoolMgr::CPThreadCounter From 8064d92ca4379330a8ed49647ddf9692b2810366 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 17:11:56 -0700 Subject: [PATCH 09/20] Delete GetHillClimbingLogEntry in dacimpl.h, sospriv.idl, sospriv.h --- src/coreclr/debug/daccess/dacimpl.h | 1 - src/coreclr/inc/sospriv.idl | 1 - src/coreclr/pal/prebuilt/inc/sospriv.h | 12 ------------ 3 files changed, 14 deletions(-) diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index e4b09e40f60208..5012d98fc9d7f2 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1125,7 +1125,6 @@ class ClrDataAccess virtual HRESULT STDMETHODCALLTYPE GetHeapAnalyzeData(CLRDATA_ADDRESS addr,struct DacpGcHeapAnalyzeData *data); virtual HRESULT STDMETHODCALLTYPE GetHeapAnalyzeStaticData(struct DacpGcHeapAnalyzeData *data); virtual HRESULT STDMETHODCALLTYPE GetMethodDescTransparencyData(CLRDATA_ADDRESS methodDesc, struct DacpMethodDescTransparencyData *data); - virtual HRESULT STDMETHODCALLTYPE GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *data); virtual HRESULT STDMETHODCALLTYPE GetThreadLocalModuleData(CLRDATA_ADDRESS thread, unsigned int index, struct DacpThreadLocalModuleData *data); virtual HRESULT STDMETHODCALLTYPE GetRCWData(CLRDATA_ADDRESS addr, struct DacpRCWData *data); virtual HRESULT STDMETHODCALLTYPE GetRCWInterfaces(CLRDATA_ADDRESS rcw, unsigned int count, struct DacpCOMInterfacePointerData interfaces[], unsigned int *pNeeded); diff --git a/src/coreclr/inc/sospriv.idl b/src/coreclr/inc/sospriv.idl index b2497cdcc819b0..d7182c19ba440c 100644 --- a/src/coreclr/inc/sospriv.idl +++ b/src/coreclr/inc/sospriv.idl @@ -221,7 +221,6 @@ interface ISOSDacInterface : IUnknown // ThreadPool HRESULT GetThreadpoolData(struct DacpThreadpoolData *data); HRESULT GetWorkRequestData(CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data); - HRESULT GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *data); // Objects HRESULT GetObjectData(CLRDATA_ADDRESS objAddr, struct DacpObjectData *data); diff --git a/src/coreclr/pal/prebuilt/inc/sospriv.h b/src/coreclr/pal/prebuilt/inc/sospriv.h index 0276bffd8d1c08..aa88284915f67c 100644 --- a/src/coreclr/pal/prebuilt/inc/sospriv.h +++ b/src/coreclr/pal/prebuilt/inc/sospriv.h @@ -862,10 +862,6 @@ EXTERN_C const IID IID_ISOSDacInterface; CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data) = 0; - virtual HRESULT STDMETHODCALLTYPE GetHillClimbingLogEntry( - CLRDATA_ADDRESS addr, - struct DacpHillClimbingLogEntry *data) = 0; - virtual HRESULT STDMETHODCALLTYPE GetObjectData( CLRDATA_ADDRESS objAddr, struct DacpObjectData *data) = 0; @@ -1321,11 +1317,6 @@ EXTERN_C const IID IID_ISOSDacInterface; CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data); - HRESULT ( STDMETHODCALLTYPE *GetHillClimbingLogEntry )( - ISOSDacInterface * This, - CLRDATA_ADDRESS addr, - struct DacpHillClimbingLogEntry *data); - HRESULT ( STDMETHODCALLTYPE *GetObjectData )( ISOSDacInterface * This, CLRDATA_ADDRESS objAddr, @@ -1760,9 +1751,6 @@ EXTERN_C const IID IID_ISOSDacInterface; #define ISOSDacInterface_GetWorkRequestData(This,addrWorkRequest,data) \ ( (This)->lpVtbl -> GetWorkRequestData(This,addrWorkRequest,data) ) -#define ISOSDacInterface_GetHillClimbingLogEntry(This,addr,data) \ - ( (This)->lpVtbl -> GetHillClimbingLogEntry(This,addr,data) ) - #define ISOSDacInterface_GetObjectData(This,objAddr,data) \ ( (This)->lpVtbl -> GetObjectData(This,objAddr,data) ) From 9cf256cc5a791fd5b64103385930cdfc8e817ca7 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 17:15:15 -0700 Subject: [PATCH 10/20] Delete DacpHillClimbingLogEntry in dacprivate.h --- src/coreclr/inc/dacprivate.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/coreclr/inc/dacprivate.h b/src/coreclr/inc/dacprivate.h index 1e345810445c8a..3079f7877e50e7 100644 --- a/src/coreclr/inc/dacprivate.h +++ b/src/coreclr/inc/dacprivate.h @@ -649,20 +649,6 @@ struct MSLAYOUT DacpWorkRequestData } }; -struct MSLAYOUT DacpHillClimbingLogEntry -{ - DWORD TickCount = 0; - int Transition = 0; - int NewControlSetting = 0; - int LastHistoryCount = 0; - double LastHistoryMean = 0; - - HRESULT Request(ISOSDacInterface *sos, CLRDATA_ADDRESS entry) - { - return sos->GetHillClimbingLogEntry(entry, this); - } -}; - // Used for CLR versions >= 4.0 struct MSLAYOUT DacpThreadpoolData @@ -1070,7 +1056,6 @@ static_assert(sizeof(DacpMethodTableTransparencyData) == 0xc, "Dacp structs cann static_assert(sizeof(DacpThreadLocalModuleData) == 0x30, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpCOMInterfacePointerData) == 0x18, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpMethodDescTransparencyData) == 0xc, "Dacp structs cannot be modified due to backwards compatibility."); -static_assert(sizeof(DacpHillClimbingLogEntry) == 0x18, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpGenerationData) == 0x20, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpGcHeapDetails) == 0x120, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpOomData) == 0x38, "Dacp structs cannot be modified due to backwards compatibility."); From d24f5544c99dc681171bbc84a2d140e5c9b71197 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 17:23:35 -0700 Subject: [PATCH 11/20] Revert "Delete DacpHillClimbingLogEntry in dacprivate.h" This reverts commit 9cf256cc5a791fd5b64103385930cdfc8e817ca7. --- src/coreclr/inc/dacprivate.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/coreclr/inc/dacprivate.h b/src/coreclr/inc/dacprivate.h index 3079f7877e50e7..1e345810445c8a 100644 --- a/src/coreclr/inc/dacprivate.h +++ b/src/coreclr/inc/dacprivate.h @@ -649,6 +649,20 @@ struct MSLAYOUT DacpWorkRequestData } }; +struct MSLAYOUT DacpHillClimbingLogEntry +{ + DWORD TickCount = 0; + int Transition = 0; + int NewControlSetting = 0; + int LastHistoryCount = 0; + double LastHistoryMean = 0; + + HRESULT Request(ISOSDacInterface *sos, CLRDATA_ADDRESS entry) + { + return sos->GetHillClimbingLogEntry(entry, this); + } +}; + // Used for CLR versions >= 4.0 struct MSLAYOUT DacpThreadpoolData @@ -1056,6 +1070,7 @@ static_assert(sizeof(DacpMethodTableTransparencyData) == 0xc, "Dacp structs cann static_assert(sizeof(DacpThreadLocalModuleData) == 0x30, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpCOMInterfacePointerData) == 0x18, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpMethodDescTransparencyData) == 0xc, "Dacp structs cannot be modified due to backwards compatibility."); +static_assert(sizeof(DacpHillClimbingLogEntry) == 0x18, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpGenerationData) == 0x20, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpGcHeapDetails) == 0x120, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpOomData) == 0x38, "Dacp structs cannot be modified due to backwards compatibility."); From c3a8389d40db68d0206bb2e9cdb8e94d9aa5d5dd Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 17:24:00 -0700 Subject: [PATCH 12/20] Revert "Delete GetHillClimbingLogEntry in dacimpl.h, sospriv.idl, sospriv.h" This reverts commit 8064d92ca4379330a8ed49647ddf9692b2810366. --- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/inc/sospriv.idl | 1 + src/coreclr/pal/prebuilt/inc/sospriv.h | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 5012d98fc9d7f2..e4b09e40f60208 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1125,6 +1125,7 @@ class ClrDataAccess virtual HRESULT STDMETHODCALLTYPE GetHeapAnalyzeData(CLRDATA_ADDRESS addr,struct DacpGcHeapAnalyzeData *data); virtual HRESULT STDMETHODCALLTYPE GetHeapAnalyzeStaticData(struct DacpGcHeapAnalyzeData *data); virtual HRESULT STDMETHODCALLTYPE GetMethodDescTransparencyData(CLRDATA_ADDRESS methodDesc, struct DacpMethodDescTransparencyData *data); + virtual HRESULT STDMETHODCALLTYPE GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *data); virtual HRESULT STDMETHODCALLTYPE GetThreadLocalModuleData(CLRDATA_ADDRESS thread, unsigned int index, struct DacpThreadLocalModuleData *data); virtual HRESULT STDMETHODCALLTYPE GetRCWData(CLRDATA_ADDRESS addr, struct DacpRCWData *data); virtual HRESULT STDMETHODCALLTYPE GetRCWInterfaces(CLRDATA_ADDRESS rcw, unsigned int count, struct DacpCOMInterfacePointerData interfaces[], unsigned int *pNeeded); diff --git a/src/coreclr/inc/sospriv.idl b/src/coreclr/inc/sospriv.idl index d7182c19ba440c..b2497cdcc819b0 100644 --- a/src/coreclr/inc/sospriv.idl +++ b/src/coreclr/inc/sospriv.idl @@ -221,6 +221,7 @@ interface ISOSDacInterface : IUnknown // ThreadPool HRESULT GetThreadpoolData(struct DacpThreadpoolData *data); HRESULT GetWorkRequestData(CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data); + HRESULT GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *data); // Objects HRESULT GetObjectData(CLRDATA_ADDRESS objAddr, struct DacpObjectData *data); diff --git a/src/coreclr/pal/prebuilt/inc/sospriv.h b/src/coreclr/pal/prebuilt/inc/sospriv.h index aa88284915f67c..0276bffd8d1c08 100644 --- a/src/coreclr/pal/prebuilt/inc/sospriv.h +++ b/src/coreclr/pal/prebuilt/inc/sospriv.h @@ -862,6 +862,10 @@ EXTERN_C const IID IID_ISOSDacInterface; CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data) = 0; + virtual HRESULT STDMETHODCALLTYPE GetHillClimbingLogEntry( + CLRDATA_ADDRESS addr, + struct DacpHillClimbingLogEntry *data) = 0; + virtual HRESULT STDMETHODCALLTYPE GetObjectData( CLRDATA_ADDRESS objAddr, struct DacpObjectData *data) = 0; @@ -1317,6 +1321,11 @@ EXTERN_C const IID IID_ISOSDacInterface; CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data); + HRESULT ( STDMETHODCALLTYPE *GetHillClimbingLogEntry )( + ISOSDacInterface * This, + CLRDATA_ADDRESS addr, + struct DacpHillClimbingLogEntry *data); + HRESULT ( STDMETHODCALLTYPE *GetObjectData )( ISOSDacInterface * This, CLRDATA_ADDRESS objAddr, @@ -1751,6 +1760,9 @@ EXTERN_C const IID IID_ISOSDacInterface; #define ISOSDacInterface_GetWorkRequestData(This,addrWorkRequest,data) \ ( (This)->lpVtbl -> GetWorkRequestData(This,addrWorkRequest,data) ) +#define ISOSDacInterface_GetHillClimbingLogEntry(This,addr,data) \ + ( (This)->lpVtbl -> GetHillClimbingLogEntry(This,addr,data) ) + #define ISOSDacInterface_GetObjectData(This,objAddr,data) \ ( (This)->lpVtbl -> GetObjectData(This,objAddr,data) ) From 566b4d74f97f7be947495479f66b90e117d7a68c Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 18:00:19 -0700 Subject: [PATCH 13/20] Change GetThreadpoolData in request.cpp to return E_NOTIMPL --- src/coreclr/debug/daccess/request.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 49f2bfd8441258..c735492cffaea8 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -339,8 +339,7 @@ ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData) } threadpoolData->AsyncTimerCallbackCompletionFPtr = (CLRDATA_ADDRESS) GFN_TADDR(ThreadpoolMgr__AsyncTimerCallbackCompletion); - SOSDacLeave(); - return hr; + return E_NOTIMPL; } HRESULT ClrDataAccess::GetThreadStoreData(struct DacpThreadStoreData *threadStoreData) From ed44b47384a5d7b78d4ff0364d82d24774772bb6 Mon Sep 17 00:00:00 2001 From: Eduardo Manuel Velarde Polar Date: Fri, 8 Jul 2022 18:04:28 -0700 Subject: [PATCH 14/20] Keep SOSDacLeave(); in GetThreadpoolData in request.cpp --- src/coreclr/debug/daccess/request.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index c735492cffaea8..ea56e218f05a73 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -339,6 +339,7 @@ ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData) } threadpoolData->AsyncTimerCallbackCompletionFPtr = (CLRDATA_ADDRESS) GFN_TADDR(ThreadpoolMgr__AsyncTimerCallbackCompletion); + SOSDacLeave(); return E_NOTIMPL; } From bdb28c71d937aaf278df9a597d7e71985b883851 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 9 Jul 2022 10:46:26 -0700 Subject: [PATCH 15/20] Delete mode unused code --- .../src/System/Threading/Overlapped.cs | 33 - .../src/System/Threading/Thread.CoreCLR.cs | 7 - .../System/Threading/ThreadPool.CoreCLR.cs | 89 +- src/coreclr/debug/daccess/enummem.cpp | 1 - src/coreclr/debug/daccess/request.cpp | 62 +- src/coreclr/debug/daccess/request_svr.cpp | 1 - src/coreclr/debug/ee/dactable.cpp | 1 - src/coreclr/inc/clrconfigvalues.h | 2 - src/coreclr/inc/dacvars.h | 11 - .../System/Threading/ThreadPool.NativeAot.cs | 2 - src/coreclr/pal/inc/pal.h | 27 - src/coreclr/pal/src/thread/process.cpp | 243 --- src/coreclr/pal/tests/palsuite/CMakeLists.txt | 1 - .../pal/tests/palsuite/compilableTests.txt | 1 - .../pal/tests/palsuite/paltestlist.txt | 1 - .../threading/GetProcessTimes/test2/test2.cpp | 121 -- src/coreclr/vm/CMakeLists.txt | 4 - src/coreclr/vm/appdomain.cpp | 8 - src/coreclr/vm/appdomain.hpp | 9 - src/coreclr/vm/ceemain.cpp | 3 - src/coreclr/vm/comcache.cpp | 1 - src/coreclr/vm/comthreadpool.cpp | 276 --- src/coreclr/vm/comthreadpool.h | 13 - src/coreclr/vm/corelib.h | 13 - src/coreclr/vm/corhost.cpp | 1 - src/coreclr/vm/delegateinfo.h | 62 - src/coreclr/vm/ecalllist.h | 1 - .../vm/eventing/eventpipe/ep-rt-coreclr.h | 10 - src/coreclr/vm/metasig.h | 5 - src/coreclr/vm/nativeoverlapped.cpp | 1 - src/coreclr/vm/threadpoolrequest.cpp | 68 - src/coreclr/vm/threadpoolrequest.h | 263 --- src/coreclr/vm/threads.cpp | 46 - src/coreclr/vm/threads.h | 18 - src/coreclr/vm/tieredcompilation.cpp | 1 - src/coreclr/vm/win32threadpool.cpp | 1911 ----------------- src/coreclr/vm/win32threadpool.h | 1001 --------- .../PortableThreadPool.GateThread.cs | 7 +- .../System/Threading/PortableThreadPool.cs | 24 +- .../RegisteredWaitHandle.Portable.cs | 4 - .../Threading/ThreadPool.Portable.Windows.cs | 2 - .../System/Threading/ThreadPool.Portable.cs | 11 - .../System/Threading/ThreadPoolWorkQueue.cs | 2 +- .../System/Threading/TimerQueue.Portable.cs | 2 +- .../src/System/Threading/ThreadPool.Mono.cs | 2 - src/mono/mono/eventpipe/ep-rt-mono.h | 9 - src/native/eventpipe/ep-rt.h | 4 - 47 files changed, 26 insertions(+), 4359 deletions(-) delete mode 100644 src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp delete mode 100644 src/coreclr/vm/delegateinfo.h delete mode 100644 src/coreclr/vm/threadpoolrequest.cpp delete mode 100644 src/coreclr/vm/threadpoolrequest.h delete mode 100644 src/coreclr/vm/win32threadpool.cpp delete mode 100644 src/coreclr/vm/win32threadpool.h diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs index aa48614ba8dcf7..141aa3a7290791 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -25,39 +25,6 @@ namespace System.Threading { - #region class _IOCompletionCallback - - internal sealed unsafe partial class _IOCompletionCallback - { - // call back helper - internal static void PerformIOCompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* pNativeOverlapped) - { - do - { - OverlappedData overlapped = OverlappedData.GetOverlappedFromNative(pNativeOverlapped); - - if (overlapped._callback is IOCompletionCallback iocb) - { - // We got here because of UnsafePack (or) Pack with EC flow suppressed - iocb(errorCode, numBytes, pNativeOverlapped); - } - else - { - // We got here because of Pack - var helper = (_IOCompletionCallback?)overlapped._callback; - Debug.Assert(helper != null, "Should only be receiving a completion callback if a delegate was provided."); - helper._errorCode = errorCode; - helper._numBytes = numBytes; - helper._pNativeOverlapped = pNativeOverlapped; - ExecutionContext.RunInternal(helper._executionContext, IOCompletionCallback_Context_Delegate, helper); - } - - } while (pNativeOverlapped != null); - } - } - - #endregion class _IOCompletionCallback - #region class OverlappedData internal sealed unsafe class OverlappedData diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index fda212fdf08227..e44eb3b3d74e4c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -356,13 +356,6 @@ internal void ResetThreadPoolThread() Debug.Assert(this == CurrentThread); Debug.Assert(IsThreadPoolThread); - if (!ThreadPool.UsePortableThreadPool) - { - // Currently implemented in unmanaged method Thread::InternalReset and - // called internally from the ThreadPool in NotifyWorkItemComplete. - return; - } - if (_mayNeedResetForThreadPool) { ResetThreadPoolThreadSlow(); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index ffb92841bbc44d..131947348a0193 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -18,58 +18,6 @@ namespace System.Threading { - // - // This type is necessary because VS 2010's debugger looks for a method named _ThreadPoolWaitCallbacck.PerformWaitCallback - // on the stack to determine if a thread is a ThreadPool thread or not. We have a better way to do this for .NET 4.5, but - // still need to maintain compatibility with VS 2010. When compat with VS 2010 is no longer an issue, this type may be - // removed. - // - internal static class _ThreadPoolWaitCallback - { - internal static bool PerformWaitCallback() => ThreadPoolWorkQueue.Dispatch(); - } - - public sealed partial class RegisteredWaitHandle : MarshalByRefObject - { - private IntPtr _nativeRegisteredWaitHandle = InvalidHandleValue; - private bool _releaseHandle; - - private static bool IsValidHandle(IntPtr handle) => handle != InvalidHandleValue && handle != IntPtr.Zero; - - internal void SetNativeRegisteredWaitHandle(IntPtr nativeRegisteredWaitHandle) - { - Debug.Assert(!ThreadPool.UsePortableThreadPool); - Debug.Assert(IsValidHandle(nativeRegisteredWaitHandle)); - Debug.Assert(!IsValidHandle(_nativeRegisteredWaitHandle)); - - _nativeRegisteredWaitHandle = nativeRegisteredWaitHandle; - } - - internal void OnBeforeRegister() - { - GC.SuppressFinalize(this); - } - - /// - /// Unregisters this wait handle registration from the wait threads. - /// - /// The event to signal when the handle is unregistered. - /// If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event. - /// - /// This method will only return true on the first call. - /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. - /// - public bool Unregister(WaitHandle waitObject) - { - return UnregisterPortable(waitObject); - } - - ~RegisteredWaitHandle() - { - return; - } - } - internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem { void IThreadPoolWorkItem.Execute() => CompleteWait(); @@ -77,7 +25,6 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt // Entry point from unmanaged code private void CompleteWait() { - Debug.Assert(ThreadPool.UsePortableThreadPool); PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut); } } @@ -98,7 +45,12 @@ public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state) public static partial class ThreadPool { - private static readonly byte UsePortableThreadPoolConfigValues = InitializeConfigAndDetermineUsePortableThreadPool(); + internal static bool EnsureConfigInitialized() + { + return s_initialized; + } + + private static bool s_initialized = InitializeConfig(); // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work @@ -106,9 +58,8 @@ public static partial class ThreadPool private static readonly bool IsWorkerTrackingEnabledInConfig = GetEnableWorkerTracking(); - private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() + private static unsafe bool InitializeConfig() { - byte usePortableThreadPoolConfigValues = 0; int configVariableIndex = 0; while (true) { @@ -131,7 +82,6 @@ private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() // Special case for UsePortableThreadPool and similar, which don't go into the AppContext Debug.Assert(configValue != 0); Debug.Assert(!isBoolean); - usePortableThreadPoolConfigValues = (byte)configValue; continue; } @@ -146,7 +96,7 @@ private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() } } - return usePortableThreadPoolConfigValues; + return true; } [MethodImpl(MethodImplOptions.InternalCall)] @@ -212,11 +162,6 @@ public static long CompletedWorkItemCount } } - private static long PendingUnmanagedWorkItemCount => 0; - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern long GetPendingUnmanagedWorkItemCount(); - private static RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject, WaitOrTimerCallback callBack, @@ -234,8 +179,6 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( (int)millisecondsTimeOutInterval, !executeOnlyOnce); - registeredWaitHandle.OnBeforeRegister(); - PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); return registeredWaitHandle; @@ -246,22 +189,6 @@ internal static void RequestWorkerThread() PortableThreadPool.ThreadPoolInstance.RequestWorker(); } - /// - /// Called from the gate thread periodically to perform runtime-specific gate activities - /// - /// CPU utilization as a percentage since the last call - /// True if the runtime still needs to perform gate activities, false otherwise - internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) - { - return false; - } - - // Entry point from unmanaged code - private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) - { - UnsafeQueueHighPriorityWorkItemInternal(new UnmanagedThreadPoolWorkItem(callback, state)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) { diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 0fda825fe90a34..c62f7713753578 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -18,7 +18,6 @@ #include "typestring.h" #include "daccess.h" #include "binder.h" -#include "win32threadpool.h" #include "runtimeinfo.h" #ifdef FEATURE_COMWRAPPERS diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index a06bdb056e0013..2c4a7c4979daee 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -10,7 +10,6 @@ //***************************************************************************** #include "stdafx.h" -#include "win32threadpool.h" #include "typestring.h" #include @@ -268,67 +267,20 @@ VOID GetJITMethodInfo (EECodeInfo * pCodeInfo, JITTypes *pJITType, CLRDATA_ADDRE } HRESULT -ClrDataAccess::GetWorkRequestData(CLRDATA_ADDRESS addr, struct DacpWorkRequestData *workRequestData) +ClrDataAccess::GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry* entry) { - if (addr == 0 || workRequestData == NULL) - return E_INVALIDARG; - - SOSDacEnter(); - - WorkRequest *pRequest = PTR_WorkRequest(TO_TADDR(addr)); - workRequestData->Function = (TADDR)(pRequest->Function); - workRequestData->Context = (TADDR)(pRequest->Context); - workRequestData->NextWorkRequest = (TADDR)(pRequest->next); + return E_NOTIMPL; +} - SOSDacLeave(); - return hr; +HRESULT +ClrDataAccess::GetWorkRequestData(CLRDATA_ADDRESS addr, struct DacpWorkRequestData *workRequestData) +{ + return E_NOTIMPL; } HRESULT ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData) { - if (threadpoolData == NULL) - return E_INVALIDARG; - - SOSDacEnter(); - - threadpoolData->cpuUtilization = ThreadpoolMgr::cpuUtilization; - threadpoolData->MinLimitTotalWorkerThreads = ThreadpoolMgr::MinLimitTotalWorkerThreads; - threadpoolData->MaxLimitTotalWorkerThreads = ThreadpoolMgr::MaxLimitTotalWorkerThreads; - - // - // Read ThreadpoolMgr::WorkerCounter - // - TADDR pCounter = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::WorkerCounter,true); - ThreadpoolMgr::ThreadCounter counter; - DacReadAll(pCounter,&counter,sizeof(ThreadpoolMgr::ThreadCounter),true); - ThreadpoolMgr::ThreadCounter::Counts counts = counter.counts; - - threadpoolData->NumWorkingWorkerThreads = counts.NumWorking; - threadpoolData->NumIdleWorkerThreads = counts.NumActive - counts.NumWorking; - threadpoolData->NumRetiredWorkerThreads = counts.NumRetired; - - threadpoolData->FirstUnmanagedWorkRequest = HOST_CDADDR(ThreadpoolMgr::WorkRequestHead); - - - // - // Read ThreadpoolMgr::CPThreadCounter - // - pCounter = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::CPThreadCounter,true); - DacReadAll(pCounter,&counter,sizeof(ThreadpoolMgr::ThreadCounter),true); - counts = counter.counts; - - threadpoolData->NumCPThreads = (LONG)(counts.NumActive + counts.NumRetired); - threadpoolData->NumFreeCPThreads = (LONG)(counts.NumActive - counts.NumWorking); - threadpoolData->MaxFreeCPThreads = ThreadpoolMgr::MaxFreeCPThreads; - threadpoolData->NumRetiredCPThreads = (LONG)(counts.NumRetired); - threadpoolData->MaxLimitTotalCPThreads = ThreadpoolMgr::MaxLimitTotalCPThreads; - threadpoolData->CurrentLimitTotalCPThreads = (LONG)(counts.NumActive); //legacy: currently has no meaning - threadpoolData->MinLimitTotalCPThreads = ThreadpoolMgr::MinLimitTotalCPThreads; - threadpoolData->NumTimers = 0; - threadpoolData->AsyncTimerCallbackCompletionFPtr = NULL; - - SOSDacLeave(); return E_NOTIMPL; } diff --git a/src/coreclr/debug/daccess/request_svr.cpp b/src/coreclr/debug/daccess/request_svr.cpp index 1ddd437e2da049..36d21efb8df121 100644 --- a/src/coreclr/debug/daccess/request_svr.cpp +++ b/src/coreclr/debug/daccess/request_svr.cpp @@ -16,7 +16,6 @@ #if defined(FEATURE_SVR_GC) #include -#include "win32threadpool.h" #include "request_common.h" int GCHeapCount() diff --git a/src/coreclr/debug/ee/dactable.cpp b/src/coreclr/debug/ee/dactable.cpp index 525bb0f4af3509..6c2865b3359633 100644 --- a/src/coreclr/debug/ee/dactable.cpp +++ b/src/coreclr/debug/ee/dactable.cpp @@ -13,7 +13,6 @@ #include #include "../../vm/virtualcallstub.h" -#include "../../vm/win32threadpool.h" #include "../../vm/codeman.h" #include "../../vm/eedbginterfaceimpl.h" #include "../../vm/common.h" diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 105a6504db4d01..73acc7145e2650 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -534,8 +534,6 @@ RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_ProcessorCount, W("PROCESSOR_COUNT"), 0, "S /// /// Threadpool /// -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UsePortableThreadPool, W("ThreadPool_UsePortableThreadPool"), 1, "Uses the managed portable thread pool implementation instead of the unmanaged one.") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UsePortableThreadPoolForIO, W("ThreadPool_UsePortableThreadPoolForIO"), 1, "Uses the managed portable thread pool implementation instead of the unmanaged one for async IO.") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMinWorkerThreads, W("ThreadPool_ForceMinWorkerThreads"), 0, "Overrides the MinThreads setting for the ThreadPool worker pool") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMaxWorkerThreads, W("ThreadPool_ForceMaxWorkerThreads"), 0, "Overrides the MaxThreads setting for the ThreadPool worker pool") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_DisableStarvationDetection, W("ThreadPool_DisableStarvationDetection"), 0, "Disables the ThreadPool feature that forces new threads to be added when workitems run for too long") diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 41a1edda49fa89..e7a1e8b5e82457 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -100,17 +100,6 @@ DEFINE_DACVAR(PTR_CallCountingStubManager, CallCountingStubManager__g_pManager, DEFINE_DACVAR(PTR_ThreadStore, ThreadStore__s_pThreadStore, ThreadStore::s_pThreadStore) -DEFINE_DACVAR(int, ThreadpoolMgr__cpuUtilization, ThreadpoolMgr::cpuUtilization) -DEFINE_DACVAR(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr__WorkerCounter, ThreadpoolMgr::WorkerCounter) -DEFINE_DACVAR(int, ThreadpoolMgr__MinLimitTotalWorkerThreads, ThreadpoolMgr::MinLimitTotalWorkerThreads) -DEFINE_DACVAR(DWORD, ThreadpoolMgr__MaxLimitTotalWorkerThreads, ThreadpoolMgr::MaxLimitTotalWorkerThreads) -DEFINE_DACVAR(UNKNOWN_POINTER_TYPE /*PTR_WorkRequest*/, ThreadpoolMgr__WorkRequestHead, ThreadpoolMgr::WorkRequestHead) // PTR_WorkRequest is not defined. So use a pointer type -DEFINE_DACVAR(UNKNOWN_POINTER_TYPE /*PTR_WorkRequest*/, ThreadpoolMgr__WorkRequestTail, ThreadpoolMgr::WorkRequestTail) // -DEFINE_DACVAR(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr__CPThreadCounter, ThreadpoolMgr::CPThreadCounter) -DEFINE_DACVAR(LONG, ThreadpoolMgr__MaxFreeCPThreads, ThreadpoolMgr::MaxFreeCPThreads) -DEFINE_DACVAR(LONG, ThreadpoolMgr__MaxLimitTotalCPThreads, ThreadpoolMgr::MaxLimitTotalCPThreads) -DEFINE_DACVAR(LONG, ThreadpoolMgr__MinLimitTotalCPThreads, ThreadpoolMgr::MinLimitTotalCPThreads) - DEFINE_DACVAR(PTR_Thread, dac__g_pFinalizerThread, ::g_pFinalizerThread) DEFINE_DACVAR(PTR_Thread, dac__g_pSuspensionThread, ::g_pSuspensionThread) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs index 1811bfad804062..67b66bd0e18484 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs @@ -23,7 +23,5 @@ public static partial class ThreadPool internal static void ReportThreadStatus(bool isWorking) { } - - private static long PendingUnmanagedWorkItemCount => 0; } } diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 124e355560ceee..152b1311c7c993 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -1269,16 +1269,6 @@ GetExitCodeProcess( IN HANDLE hProcess, IN LPDWORD lpExitCode); -PALIMPORT -BOOL -PALAPI -GetProcessTimes( - IN HANDLE hProcess, - OUT LPFILETIME lpCreationTime, - OUT LPFILETIME lpExitTime, - OUT LPFILETIME lpKernelTime, - OUT LPFILETIME lpUserTime); - #define MAXIMUM_WAIT_OBJECTS 64 #define WAIT_OBJECT_0 0 #define WAIT_ABANDONED 0x00000080 @@ -4551,23 +4541,6 @@ PALIMPORT DLLEXPORT int __cdecl _putenv(const char *); PALIMPORT WCHAR __cdecl PAL_ToUpperInvariant(WCHAR); PALIMPORT WCHAR __cdecl PAL_ToLowerInvariant(WCHAR); -/******************* PAL-specific I/O completion port *****************/ - -typedef struct _PAL_IOCP_CPU_INFORMATION { - union { - FILETIME ftLastRecordedIdleTime; - FILETIME ftLastRecordedCurrentTime; - } LastRecordedTime; - FILETIME ftLastRecordedKernelTime; - FILETIME ftLastRecordedUserTime; -} PAL_IOCP_CPU_INFORMATION; - -PALIMPORT -INT -PALAPI -PAL_GetCPUBusyTime( - IN OUT PAL_IOCP_CPU_INFORMATION *lpPrevCPUInfo); - /****************PAL Perf functions for PInvoke*********************/ #if PAL_PERF PALIMPORT diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 7fd95e4c9d4ace..0be2e4af3c77c5 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -1819,249 +1819,6 @@ PAL_GetTransportPipeName( suffix); } -/*++ -Function: - GetProcessTimes - -See MSDN doc. ---*/ -BOOL -PALAPI -GetProcessTimes( - IN HANDLE hProcess, - OUT LPFILETIME lpCreationTime, - OUT LPFILETIME lpExitTime, - OUT LPFILETIME lpKernelTime, - OUT LPFILETIME lpUserTime) -{ - BOOL retval = FALSE; - struct rusage resUsage; - UINT64 calcTime; - const UINT64 SECS_TO_100NS = 10000000ULL; // 10^7 - const UINT64 USECS_TO_100NS = 10ULL; // 10 - const UINT64 EPOCH_DIFF = 11644473600ULL; // number of seconds from 1 Jan. 1601 00:00 to 1 Jan 1970 00:00 UTC - - PERF_ENTRY(GetProcessTimes); - ENTRY("GetProcessTimes(hProcess=%p, lpExitTime=%p, lpKernelTime=%p," - "lpUserTime=%p)\n", - hProcess, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime ); - - /* Make sure hProcess is the current process, this is the only supported - case */ - if(PROCGetProcessIDFromHandle(hProcess)!=GetCurrentProcessId()) - { - ASSERT("GetProcessTimes() does not work on a process other than the " - "current process.\n"); - SetLastError(ERROR_INVALID_HANDLE); - goto GetProcessTimesExit; - } - - /* First, we need to actually retrieve the relevant statistics from the - OS */ - if (getrusage (RUSAGE_SELF, &resUsage) == -1) - { - ASSERT("Unable to get resource usage information for the current " - "process\n"); - SetLastError(ERROR_INTERNAL_ERROR); - goto GetProcessTimesExit; - } - - TRACE ("getrusage User: %ld sec,%ld microsec. Kernel: %ld sec,%ld" - " microsec\n", - resUsage.ru_utime.tv_sec, resUsage.ru_utime.tv_usec, - resUsage.ru_stime.tv_sec, resUsage.ru_stime.tv_usec); - - if (lpCreationTime) - { - // The IBC profile data uses this, instead of the actual - // process creation time we just return the current time - - struct timeval tv; - if (gettimeofday(&tv, NULL) == -1) - { - ASSERT("gettimeofday() failed; errno is %d (%s)\n", errno, strerror(errno)); - - // Assign zero to lpCreationTime - lpCreationTime->dwLowDateTime = 0; - lpCreationTime->dwHighDateTime = 0; - } - else - { - calcTime = EPOCH_DIFF; - calcTime += (UINT64)tv.tv_sec; - calcTime *= SECS_TO_100NS; - calcTime += ((UINT64)tv.tv_usec * USECS_TO_100NS); - - // Assign the time into lpCreationTime - lpCreationTime->dwLowDateTime = (DWORD)calcTime; - lpCreationTime->dwHighDateTime = (DWORD)(calcTime >> 32); - } - } - - if (lpExitTime) - { - // Assign zero to lpExitTime - lpExitTime->dwLowDateTime = 0; - lpExitTime->dwHighDateTime = 0; - } - - if (lpUserTime) - { - /* Get the time of user mode execution, in 100s of nanoseconds */ - calcTime = (UINT64)resUsage.ru_utime.tv_sec * SECS_TO_100NS; - calcTime += (UINT64)resUsage.ru_utime.tv_usec * USECS_TO_100NS; - - /* Assign the time into lpUserTime */ - lpUserTime->dwLowDateTime = (DWORD)calcTime; - lpUserTime->dwHighDateTime = (DWORD)(calcTime >> 32); - } - - if (lpKernelTime) - { - /* Get the time of kernel mode execution, in 100s of nanoseconds */ - calcTime = (UINT64)resUsage.ru_stime.tv_sec * SECS_TO_100NS; - calcTime += (UINT64)resUsage.ru_stime.tv_usec * USECS_TO_100NS; - - /* Assign the time into lpUserTime */ - lpKernelTime->dwLowDateTime = (DWORD)calcTime; - lpKernelTime->dwHighDateTime = (DWORD)(calcTime >> 32); - } - - retval = TRUE; - - -GetProcessTimesExit: - LOGEXIT("GetProcessTimes returns BOOL %d\n", retval); - PERF_EXIT(GetProcessTimes); - return (retval); -} - -#define FILETIME_TO_ULONGLONG(f) \ - (((ULONGLONG)(f).dwHighDateTime << 32) | ((ULONGLONG)(f).dwLowDateTime)) - -/*++ -Function: - PAL_GetCPUBusyTime - -The main purpose of this function is to compute the overall CPU utilization -for the CLR thread pool to regulate the number of I/O completion port -worker threads. -Since there is no consistent API on Unix to get the CPU utilization -from a user process, getrusage and gettimeofday are used to -compute the current process's CPU utilization instead. -This function emulates the ThreadpoolMgr::GetCPUBusyTime_NT function in -win32threadpool.cpp of the CLR. - -See MSDN doc for GetSystemTimes. ---*/ -INT -PALAPI -PAL_GetCPUBusyTime( - IN OUT PAL_IOCP_CPU_INFORMATION *lpPrevCPUInfo) -{ - ULONGLONG nLastRecordedCurrentTime = 0; - ULONGLONG nLastRecordedUserTime = 0; - ULONGLONG nLastRecordedKernelTime = 0; - ULONGLONG nKernelTime = 0; - ULONGLONG nUserTime = 0; - ULONGLONG nCurrentTime = 0; - ULONGLONG nCpuBusyTime = 0; - ULONGLONG nCpuTotalTime = 0; - DWORD nReading = 0; - struct rusage resUsage; - struct timeval tv; - static DWORD dwNumberOfProcessors = 0; - - if (dwNumberOfProcessors <= 0) - { - SYSTEM_INFO SystemInfo; - GetSystemInfo(&SystemInfo); - dwNumberOfProcessors = SystemInfo.dwNumberOfProcessors; - if (dwNumberOfProcessors <= 0) - { - return 0; - } - - UINT cpuLimit; - if (PAL_GetCpuLimit(&cpuLimit) && cpuLimit < dwNumberOfProcessors) - { - dwNumberOfProcessors = cpuLimit; - } - } - - if (getrusage(RUSAGE_SELF, &resUsage) == -1) - { - ASSERT("getrusage() failed; errno is %d (%s)\n", errno, strerror(errno)); - return 0; - } - else - { - nKernelTime = (ULONGLONG)resUsage.ru_stime.tv_sec*tccSecondsTo100NanoSeconds + - resUsage.ru_stime.tv_usec*tccMicroSecondsTo100NanoSeconds; - nUserTime = (ULONGLONG)resUsage.ru_utime.tv_sec*tccSecondsTo100NanoSeconds + - resUsage.ru_utime.tv_usec*tccMicroSecondsTo100NanoSeconds; - } - - if (gettimeofday(&tv, NULL) == -1) - { - ASSERT("gettimeofday() failed; errno is %d (%s)\n", errno, strerror(errno)); - return 0; - } - else - { - nCurrentTime = (ULONGLONG)tv.tv_sec*tccSecondsTo100NanoSeconds + - tv.tv_usec*tccMicroSecondsTo100NanoSeconds; - } - - nLastRecordedCurrentTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime); - nLastRecordedUserTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->ftLastRecordedUserTime); - nLastRecordedKernelTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->ftLastRecordedKernelTime); - - if (nCurrentTime > nLastRecordedCurrentTime) - { - nCpuTotalTime = (nCurrentTime - nLastRecordedCurrentTime); -#if HAVE_THREAD_SELF || HAVE__LWP_SELF || HAVE_VM_READ - // For systems that run multiple threads of a process on multiple processors, - // the accumulated userTime and kernelTime of this process may exceed - // the elapsed time. In this case, the cpuTotalTime needs to be adjusted - // according to number of processors so that the cpu utilization - // will not be greater than 100. - nCpuTotalTime *= dwNumberOfProcessors; -#endif // HAVE_THREAD_SELF || HAVE__LWP_SELF || HAVE_VM_READ - } - - if (nUserTime >= nLastRecordedUserTime && - nKernelTime >= nLastRecordedKernelTime) - { - nCpuBusyTime = - (nUserTime - nLastRecordedUserTime)+ - (nKernelTime - nLastRecordedKernelTime); - } - - if (nCpuTotalTime > 0 && nCpuBusyTime > 0) - { - nReading = (DWORD)((nCpuBusyTime*100)/nCpuTotalTime); - TRACE("PAL_GetCPUBusyTime: nCurrentTime=%lld, nKernelTime=%lld, nUserTime=%lld, nReading=%d\n", - nCurrentTime, nKernelTime, nUserTime, nReading); - } - - if (nReading > 100) - { - ERROR("cpu utilization(%d) > 100\n", nReading); - } - - lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime.dwLowDateTime = (DWORD)nCurrentTime; - lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime.dwHighDateTime = (DWORD)(nCurrentTime >> 32); - - lpPrevCPUInfo->ftLastRecordedUserTime.dwLowDateTime = (DWORD)nUserTime; - lpPrevCPUInfo->ftLastRecordedUserTime.dwHighDateTime = (DWORD)(nUserTime >> 32); - - lpPrevCPUInfo->ftLastRecordedKernelTime.dwLowDateTime = (DWORD)nKernelTime; - lpPrevCPUInfo->ftLastRecordedKernelTime.dwHighDateTime = (DWORD)(nKernelTime >> 32); - - return (DWORD)nReading; -} - /*++ Function: GetCommandLineW diff --git a/src/coreclr/pal/tests/palsuite/CMakeLists.txt b/src/coreclr/pal/tests/palsuite/CMakeLists.txt index 4cef1073a89cbe..c88f4effb42ee4 100644 --- a/src/coreclr/pal/tests/palsuite/CMakeLists.txt +++ b/src/coreclr/pal/tests/palsuite/CMakeLists.txt @@ -848,7 +848,6 @@ add_executable_clr(paltests threading/GetCurrentThreadId/test1/threadId.cpp threading/GetExitCodeProcess/test1/childProcess.cpp threading/GetExitCodeProcess/test1/test1.cpp - threading/GetProcessTimes/test2/test2.cpp threading/GetThreadTimes/test1/test1.cpp threading/NamedMutex/test1/namedmutex.cpp threading/NamedMutex/test1/nopal.cpp diff --git a/src/coreclr/pal/tests/palsuite/compilableTests.txt b/src/coreclr/pal/tests/palsuite/compilableTests.txt index 7f2d2804c41331..1cd7c5b964395b 100644 --- a/src/coreclr/pal/tests/palsuite/compilableTests.txt +++ b/src/coreclr/pal/tests/palsuite/compilableTests.txt @@ -738,7 +738,6 @@ threading/GetCurrentThread/test1/paltest_getcurrentthread_test1 threading/GetCurrentThread/test2/paltest_getcurrentthread_test2 threading/GetCurrentThreadId/test1/paltest_getcurrentthreadid_test1 threading/GetExitCodeProcess/test1/paltest_getexitcodeprocess_test1 -threading/GetProcessTimes/test2/paltest_getprocesstimes_test2 threading/GetThreadTimes/test1/paltest_getthreadtimes_test1 threading/NamedMutex/test1/paltest_namedmutex_test1 threading/OpenEventW/test1/paltest_openeventw_test1 diff --git a/src/coreclr/pal/tests/palsuite/paltestlist.txt b/src/coreclr/pal/tests/palsuite/paltestlist.txt index 95cf097c591930..607f8c7d686417 100644 --- a/src/coreclr/pal/tests/palsuite/paltestlist.txt +++ b/src/coreclr/pal/tests/palsuite/paltestlist.txt @@ -649,7 +649,6 @@ threading/ExitThread/test1/paltest_exitthread_test1 threading/GetCurrentProcessId/test1/paltest_getcurrentprocessid_test1 threading/GetCurrentThread/test1/paltest_getcurrentthread_test1 threading/GetCurrentThread/test2/paltest_getcurrentthread_test2 -threading/GetProcessTimes/test2/paltest_getprocesstimes_test2 threading/GetThreadTimes/test1/paltest_getthreadtimes_test1 threading/NamedMutex/test1/paltest_namedmutex_test1 threading/QueryThreadCycleTime/test1/paltest_querythreadcycletime_test1 diff --git a/src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp b/src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp deleted file mode 100644 index 7c1c7a10f65691..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================================= -** -** Source: test2.c -** -** Purpose: Test to ensure GetProcessTimes works properly. -** -** Dependencies: PAL_Initialize -** PAL_Terminate -** Fail -** ZeroMemory -** CompareFileTime -** GetLastError -** - -** -**===========================================================================*/ -#include - - -PALTEST(threading_GetProcessTimes_test2_paltest_getprocesstimes_test2, "threading/GetProcessTimes/test2/paltest_getprocesstimes_test2") - -{ - int i, j, k; - int *total; - - HANDLE hProcess; - FILETIME createTime; - FILETIME exitTime; - FILETIME kernelTime1; - FILETIME userTime1; - FILETIME kernelTime2; - FILETIME userTime2; - - DWORD dwError; - - /* initialize the PAL */ - if( PAL_Initialize(argc, argv) != 0 ) - { - return( FAIL ); - } - - /* get our own process handle */ - hProcess = GetCurrentProcess(); - if( hProcess == NULL ) - { - Fail( "GetCurrentProcess() returned a NULL handle.\n" ); - } - - /* zero our time structures */ - ZeroMemory( &createTime, sizeof(createTime) ); - ZeroMemory( &exitTime, sizeof(exitTime) ); - ZeroMemory( &kernelTime1, sizeof(kernelTime1) ); - ZeroMemory( &userTime1, sizeof(userTime1) ); - ZeroMemory( &kernelTime2, sizeof(kernelTime2) ); - ZeroMemory( &userTime2, sizeof(userTime2) ); - - /* check the process times for the child process */ - if( ! GetProcessTimes( hProcess, - &createTime, - &exitTime, - &kernelTime1, - &userTime1 ) ) - { - dwError = GetLastError(); - Fail( "GetProcessTimes() call failed with error code %d\n", - dwError ); - } - - - /* simulate some activity */ - for( i=0; i<1000; i++ ) - { - for( j=0; j<1000; j++ ) - { - /* do kernel work to increase system usage counters */ - total = (int*)malloc(1024 * 1024); - - *total = j * i; - for( k=0; k<1000; k++ ) - { - *total += k + i; - } - - free(total); - } - } - - /* check the process times for the child process */ - if( ! GetProcessTimes( hProcess, - &createTime, - &exitTime, - &kernelTime2, - &userTime2 ) ) - { - dwError = GetLastError(); - Fail( "GetProcessTimes() call failed with error code %d\n", - dwError ); - } - - - /* very simple logical checking of the results */ - if( CompareFileTime( &kernelTime1, &kernelTime2 ) > 0 ) - { - Fail( "Unexpected kernel time value reported.\n" ); - } - - if( CompareFileTime( &userTime1, &userTime2 ) > 0 ) - { - Fail( "Unexpected user time value reported.\n" ); - } - - - /* terminate the PAL */ - PAL_Terminate(); - - /* return success */ - return PASS; -} diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index ef06b4363b03a7..c59090a44f4405 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -115,7 +115,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON stublink.cpp stubmgr.cpp syncblk.cpp - threadpoolrequest.cpp threads.cpp threadstatics.cpp tieredcompilation.cpp @@ -129,7 +128,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON vars.cpp versionresilienthashcode.cpp virtualcallstub.cpp - win32threadpool.cpp zapsig.cpp ) @@ -220,7 +218,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON stubmgr.h syncblk.h syncblk.inl - threadpoolrequest.h threads.h threads.inl threadstatics.h @@ -237,7 +234,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON vars.hpp versionresilienthashcode.h virtualcallstub.h - win32threadpool.h zapsig.h ) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 61debf052e2ecf..6e302b25e575d5 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -51,7 +51,6 @@ #include "appdomain.inl" #include "typeparse.h" -#include "threadpoolrequest.h" #include "nativeoverlapped.h" @@ -1961,14 +1960,7 @@ AppDomain::~AppDomain() } CONTRACTL_END; - - // release the TPIndex. note that since TPIndex values are recycled the TPIndex - // can only be released once all threads in the AppDomain have exited. - if (GetTPIndex().m_dwIndex != 0) - PerAppDomainTPCountList::ResetAppDomainIndex(GetTPIndex()); - m_AssemblyCache.Clear(); - } //***************************************************************************** diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index 212a5e9994100f..1447d376a67f40 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -1965,12 +1965,6 @@ class AppDomain : public BaseDomain RCWRefCache *GetRCWRefCache(); #endif // FEATURE_COMWRAPPERS - TPIndex GetTPIndex() - { - LIMITED_METHOD_CONTRACT; - return m_tpIndex; - } - DefaultAssemblyBinder *CreateDefaultBinder(); void SetIgnoreUnhandledExceptions() @@ -2219,9 +2213,6 @@ class AppDomain : public BaseDomain RCWRefCache *m_pRCWRefCache; #endif // FEATURE_COMWRAPPERS - // The thread-pool index of this app domain among existing app domains (starting from 1) - TPIndex m_tpIndex; - Volatile m_Stage; ArrayList m_failedAssemblies; diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index aada53cd7e5658..587fdd38643530 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -175,8 +175,6 @@ #include "stacksampler.h" #endif -#include "win32threadpool.h" - #include #ifdef FEATURE_COMINTEROP @@ -644,7 +642,6 @@ void EEStartupHelper() IfFailGo(ExecutableAllocator::StaticInitialize(FatalErrorHandler)); Thread::StaticInitialize(); - ThreadpoolMgr::StaticInitialize(); JITInlineTrackingMap::StaticInitialize(); MethodDescBackpatchInfoTracker::StaticInitialize(); diff --git a/src/coreclr/vm/comcache.cpp b/src/coreclr/vm/comcache.cpp index c9afbbed923dd9..573ead1a96e355 100644 --- a/src/coreclr/vm/comcache.cpp +++ b/src/coreclr/vm/comcache.cpp @@ -9,7 +9,6 @@ #include "comcache.h" #include "runtimecallablewrapper.h" #include -#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT #include "olecontexthelpers.h" diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 843e089275858d..981b075eeb0a32 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -16,8 +16,6 @@ #include "common.h" #include "comdelegate.h" #include "comthreadpool.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" #include "class.h" #include "object.h" #include "field.h" @@ -28,99 +26,6 @@ #include "comsynchronizable.h" #include "callhelpers.h" #include "appdomain.inl" -/*****************************************************************************************************/ -#ifdef _DEBUG -void LogCall(MethodDesc* pMD, LPCUTF8 api) -{ - LIMITED_METHOD_CONTRACT; - - LPCUTF8 cls = pMD->GetMethodTable()->GetDebugClassName(); - LPCUTF8 name = pMD->GetName(); - - LOG((LF_THREADPOOL,LL_INFO1000,"%s: ", api)); - LOG((LF_THREADPOOL, LL_INFO1000, - " calling %s.%s\n", cls, name)); -} -#else -#define LogCall(pMd,api) -#endif - -VOID -AcquireDelegateInfo(DelegateInfo *pDelInfo) -{ - LIMITED_METHOD_CONTRACT; -} - -VOID -ReleaseDelegateInfo(DelegateInfo *pDelInfo) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - } CONTRACTL_END; - - // The release methods of holders can be called with preemptive GC enabled. Ensure we're in cooperative mode - // before calling pDelInfo->Release(), since that requires coop mode. - GCX_COOP(); - - pDelInfo->Release(); - ThreadpoolMgr::RecycleMemory( pDelInfo, ThreadpoolMgr::MEMTYPE_DelegateInfo ); -} - -//typedef Holder DelegateInfoHolder; - -typedef Wrapper DelegateInfoHolder; - -/*****************************************************************************************************/ -// Caller has to GC protect Objectrefs being passed in -DelegateInfo *DelegateInfo::MakeDelegateInfo(OBJECTREF *state, - OBJECTREF *waitEvent, - OBJECTREF *registeredWaitHandle) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - if (state != NULL || waitEvent != NULL || registeredWaitHandle != NULL) - { - MODE_COOPERATIVE; - } - else - { - MODE_ANY; - } - PRECONDITION(state == NULL || IsProtectedByGCFrame(state)); - PRECONDITION(waitEvent == NULL || IsProtectedByGCFrame(waitEvent)); - PRECONDITION(registeredWaitHandle == NULL || IsProtectedByGCFrame(registeredWaitHandle)); - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - DelegateInfoHolder delegateInfo = (DelegateInfo*) ThreadpoolMgr::GetRecycledMemory(ThreadpoolMgr::MEMTYPE_DelegateInfo); - - AppDomain* pAppDomain = ::GetAppDomain(); - - if (state != NULL) - delegateInfo->m_stateHandle = pAppDomain->CreateHandle(*state); - else - delegateInfo->m_stateHandle = NULL; - - if (waitEvent != NULL) - delegateInfo->m_eventHandle = pAppDomain->CreateHandle(*waitEvent); - else - delegateInfo->m_eventHandle = NULL; - - if (registeredWaitHandle != NULL) - delegateInfo->m_registeredWaitHandle = pAppDomain->CreateHandle(*registeredWaitHandle); - else - delegateInfo->m_registeredWaitHandle = NULL; - - delegateInfo.SuppressRelease(); - - return delegateInfo; -} /*****************************************************************************************************/ // Enumerates some runtime config variables that are used by CoreLib for initialization. The config variable index should start @@ -191,184 +96,3 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, } } FCIMPLEND - -/*****************************************************************************************************/ - -struct RegisterWaitForSingleObjectCallback_Args -{ - DelegateInfo *delegateInfo; - BOOLEAN TimerOrWaitFired; -}; - -static VOID -RegisterWaitForSingleObjectCallback_Worker(LPVOID ptr) -{ - CONTRACTL - { - GC_TRIGGERS; - THROWS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - OBJECTREF orState = NULL; - - GCPROTECT_BEGIN( orState ); - - RegisterWaitForSingleObjectCallback_Args *args = (RegisterWaitForSingleObjectCallback_Args *) ptr; - orState = ObjectFromHandle(((DelegateInfo*) args->delegateInfo)->m_stateHandle); - -#ifdef _DEBUG - MethodDesc *pMeth = CoreLibBinder::GetMethod(METHOD__TPWAITORTIMER_HELPER__PERFORM_WAITORTIMER_CALLBACK); - LogCall(pMeth,"RWSOCallback"); -#endif - - // Caution: the args are not protected, we have to garantee there's no GC from here till - // the managed call happens. - PREPARE_NONVIRTUAL_CALLSITE(METHOD__TPWAITORTIMER_HELPER__PERFORM_WAITORTIMER_CALLBACK); - DECLARE_ARGHOLDER_ARRAY(arg, 2); - arg[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(orState); - arg[ARGNUM_1] = DWORD_TO_ARGHOLDER(args->TimerOrWaitFired); - - // Call the method... - CALL_MANAGED_METHOD_NORET(arg); - - GCPROTECT_END(); -} - -VOID NTAPI RegisterWaitForSingleObjectCallback(PVOID delegateInfo, BOOLEAN TimerOrWaitFired) -{ - Thread* pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - return; - } - } - - CONTRACTL - { - MODE_PREEMPTIVE; // Worker thread will be in preempt mode. We switch to coop below. - THROWS; - GC_TRIGGERS; - - PRECONDITION(CheckPointer(delegateInfo)); - } - CONTRACTL_END; - - GCX_COOP(); - - RegisterWaitForSingleObjectCallback_Args args = { ((DelegateInfo*) delegateInfo), TimerOrWaitFired }; - - ManagedThreadBase::ThreadPool(RegisterWaitForSingleObjectCallback_Worker, &args); -} - -VOID QueueUserWorkItemManagedCallback(PVOID pArg) -{ - CONTRACTL - { - GC_TRIGGERS; - THROWS; - MODE_COOPERATIVE; - } CONTRACTL_END; - - _ASSERTE(NULL != pArg); - - bool* wasNotRecalled = (bool*)pArg; - - MethodDescCallSite dispatch(METHOD__TP_WAIT_CALLBACK__PERFORM_WAIT_CALLBACK); - *wasNotRecalled = dispatch.Call_RetBool(NULL); -} - -/********************************************************************************************************************/ - -struct BindIoCompletion_Args -{ - DWORD ErrorCode; - DWORD numBytesTransferred; - LPOVERLAPPED lpOverlapped; -}; - -VOID BindIoCompletionCallBack_Worker(LPVOID args) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_ANY; - - DWORD ErrorCode = ((BindIoCompletion_Args *)args)->ErrorCode; - DWORD numBytesTransferred = ((BindIoCompletion_Args *)args)->numBytesTransferred; - LPOVERLAPPED lpOverlapped = ((BindIoCompletion_Args *)args)->lpOverlapped; - - OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - - GCPROTECT_BEGIN(overlapped); - // we set processed to TRUE, now it's our responsibility to guarantee proper cleanup - -#ifdef _DEBUG - MethodDesc *pMeth = CoreLibBinder::GetMethod(METHOD__IOCB_HELPER__PERFORM_IOCOMPLETION_CALLBACK); - LogCall(pMeth,"IOCallback"); -#endif - - if (overlapped->m_callback != NULL) - { - // Caution: the args are not protected, we have to garantee there's no GC from here till - PREPARE_NONVIRTUAL_CALLSITE(METHOD__IOCB_HELPER__PERFORM_IOCOMPLETION_CALLBACK); - DECLARE_ARGHOLDER_ARRAY(arg, 3); - arg[ARGNUM_0] = DWORD_TO_ARGHOLDER(ErrorCode); - arg[ARGNUM_1] = DWORD_TO_ARGHOLDER(numBytesTransferred); - arg[ARGNUM_2] = PTR_TO_ARGHOLDER(lpOverlapped); - - // Call the method... - CALL_MANAGED_METHOD_NORET(arg); - } - else - { - // no user delegate to callback - _ASSERTE((overlapped->m_callback == NULL) || !"This is benign, but should be optimized"); - } - GCPROTECT_END(); -} - -void __stdcall BindIoCompletionCallbackStubEx(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped, - BOOL setStack) -{ - Thread* pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - // TODO: how do we notify user of OOM here? - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - return; - } - } - - CONTRACTL - { - THROWS; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - LOG((LF_INTEROP, LL_INFO10000, "In IO_CallBackStub thread 0x%x retCode 0x%x, overlap 0x%x\n", pThread, ErrorCode, lpOverlapped)); - - GCX_COOP(); - - BindIoCompletion_Args args = {ErrorCode, numBytesTransferred, lpOverlapped}; - ManagedThreadBase::ThreadPool(BindIoCompletionCallBack_Worker, &args); - - LOG((LF_INTEROP, LL_INFO10000, "Leaving IO_CallBackStub thread 0x%x retCode 0x%x, overlap 0x%x\n", pThread, ErrorCode, lpOverlapped)); -} - -void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped) -{ - WRAPPER_NO_CONTRACT; - BindIoCompletionCallbackStubEx(ErrorCode, numBytesTransferred, lpOverlapped, TRUE); -} diff --git a/src/coreclr/vm/comthreadpool.h b/src/coreclr/vm/comthreadpool.h index 141d4284bebbef..5b21de54b6b5d1 100644 --- a/src/coreclr/vm/comthreadpool.h +++ b/src/coreclr/vm/comthreadpool.h @@ -15,29 +15,16 @@ #ifndef _COMTHREADPOOL_H #define _COMTHREADPOOL_H -#include "delegateinfo.h" #include "nativeoverlapped.h" class ThreadPoolNative { - public: static FCDECL4(INT32, GetNextConfigUInt32Value, INT32 configVariableIndex, UINT32 *configValueRef, BOOL *isBooleanRef, LPCWSTR *appContextConfigNameRef); - static FCDECL0(INT64, GetPendingUnmanagedWorkItemCount); }; - -VOID QueueUserWorkItemManagedCallback(PVOID pArg); -void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); -void SetAsyncResultProperties( - OVERLAPPEDDATAREF overlapped, - DWORD dwErrorCode, - DWORD dwNumBytes); - #endif diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 0e73fbccbf2e4a..71edc2ff8c4413 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -897,19 +897,6 @@ DEFINE_METHOD(AUTORELEASEPOOL, CREATEAUTORELEASEPOOL, CreateAutoreleasePoo DEFINE_METHOD(AUTORELEASEPOOL, DRAINAUTORELEASEPOOL, DrainAutoreleasePool, SM_RetVoid) #endif // FEATURE_OBJCMARSHAL -DEFINE_CLASS(IOCB_HELPER, Threading, _IOCompletionCallback) -DEFINE_METHOD(IOCB_HELPER, PERFORM_IOCOMPLETION_CALLBACK, PerformIOCompletionCallback, SM_UInt_UInt_PtrNativeOverlapped_RetVoid) - -DEFINE_CLASS(TPWAITORTIMER_HELPER, Threading, _ThreadPoolWaitOrTimerCallback) -DEFINE_METHOD(TPWAITORTIMER_HELPER, PERFORM_WAITORTIMER_CALLBACK, PerformWaitOrTimerCallback, SM__ThreadPoolWaitOrTimerCallback_Bool_RetVoid) - -DEFINE_CLASS(TP_WAIT_CALLBACK, Threading, _ThreadPoolWaitCallback) -DEFINE_METHOD(TP_WAIT_CALLBACK, PERFORM_WAIT_CALLBACK, PerformWaitCallback, SM_RetBool) - -DEFINE_CLASS(THREAD_POOL, Threading, ThreadPool) -DEFINE_METHOD(THREAD_POOL, ENSURE_GATE_THREAD_RUNNING, EnsureGateThreadRunning, SM_RetVoid) -DEFINE_METHOD(THREAD_POOL, UNSAFE_QUEUE_UNMANAGED_WORK_ITEM, UnsafeQueueUnmanagedWorkItem, SM_IntPtr_IntPtr_RetVoid) - DEFINE_CLASS(TIMESPAN, System, TimeSpan) diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index e1babc7f6228e4..db99db62b10c56 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -28,7 +28,6 @@ #include "dllimportcallback.h" #include "eventtrace.h" -#include "win32threadpool.h" #include "eventtrace.h" #include "finalizerthread.h" #include "threadsuspend.h" diff --git a/src/coreclr/vm/delegateinfo.h b/src/coreclr/vm/delegateinfo.h deleted file mode 100644 index 2f9f2d71860248..00000000000000 --- a/src/coreclr/vm/delegateinfo.h +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Header: DelegateInfo.h -** -** -** Purpose: Native methods on System.ThreadPool -** and its inner classes -** - -** -===========================================================*/ -#ifndef DELEGATE_INFO -#define DELEGATE_INFO - -struct DelegateInfo; -typedef DelegateInfo* DelegateInfoPtr; - -struct DelegateInfo -{ - OBJECTHANDLE m_stateHandle; - OBJECTHANDLE m_eventHandle; - OBJECTHANDLE m_registeredWaitHandle; - -#ifndef DACCESS_COMPILE - void Release() - { - CONTRACTL { - // m_compressedStack->Release() can actually throw today because it has got a call - // to new down the stack. However that is recent and the semantic of that api is such - // it should not throw. I am expecting clenup of that function to take care of that - // so I am adding this comment to make sure the issue is document. - // Remove this comment once that work is done - NOTHROW; - GC_TRIGGERS; - MODE_COOPERATIVE; - FORBID_FAULT; - } - CONTRACTL_END; - - - if (m_stateHandle) - DestroyHandle(m_stateHandle); - if (m_eventHandle) - DestroyHandle(m_eventHandle); - if (m_registeredWaitHandle) - DestroyHandle(m_registeredWaitHandle); - } -#endif - - static DelegateInfo *MakeDelegateInfo(OBJECTREF *state, - OBJECTREF *waitEvent, - OBJECTREF *registeredWaitObject); -}; - - - - - -#endif // DELEGATE_INFO diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 0ee86131aafd1e..4bce261c0624a8 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -397,7 +397,6 @@ FCFuncEnd() FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetNextConfigUInt32Value", ThreadPoolNative::GetNextConfigUInt32Value) - FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncEnd() FCFuncStart(gRegisteredWaitHandleFuncs) diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 9b940adc091bf6..ba785b5a0f6983 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -11,7 +11,6 @@ #include #include "fstream.h" #include "typestring.h" -#include "win32threadpool.h" #include "clrversion.h" #undef EP_INFINITE_WAIT @@ -1661,15 +1660,6 @@ ep_rt_config_value_get_output_streaming (void) return CLRConfig::GetConfigValue (CLRConfig::INTERNAL_EventPipeOutputStreaming) != 0; } -static -inline -bool -ep_rt_config_value_get_use_portable_thread_pool (void) -{ - STATIC_CONTRACT_NOTHROW; - return ThreadpoolMgr::UsePortableThreadPool (); -} - /* * EventPipeSampleProfiler. */ diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 5b5bfc2d42746d..bcfb97357d2d68 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -374,8 +374,6 @@ DEFINE_METASIG(SM(ArrByte_RetObj, a(b), j)) DEFINE_METASIG(SM(ArrByte_Bool_RetObj, a(b) F, j)) DEFINE_METASIG(SM(ArrByte_ArrByte_RefObj_RetObj, a(b) a(b) r(j), j)) -DEFINE_METASIG_T(SM(UInt_UInt_PtrNativeOverlapped_RetVoid, K K P(g(NATIVEOVERLAPPED)), v)) - DEFINE_METASIG(IM(Long_RetVoid, l, v)) DEFINE_METASIG(IM(IntPtr_Int_RetVoid, I i, v)) DEFINE_METASIG(IM(IntInt_RetArrByte, i i, a(b))) @@ -559,9 +557,6 @@ DEFINE_METASIG_T(SM(IntPtr_AssemblyName_RetAssemblyBase, I C(ASSEMBLY_NAME), C(A DEFINE_METASIG_T(SM(Str_AssemblyBase_IntPtr_RetIntPtr, s C(ASSEMBLYBASE) I, I)) DEFINE_METASIG_T(SM(Str_AssemblyBase_Bool_UInt_RetIntPtr, s C(ASSEMBLYBASE) F K, I)) -// ThreadPool -DEFINE_METASIG_T(SM(_ThreadPoolWaitOrTimerCallback_Bool_RetVoid, C(TPWAITORTIMER_HELPER) F, v)) - // For FailFast DEFINE_METASIG(SM(Str_RetVoid, s, v)) DEFINE_METASIG_T(SM(Str_Exception_RetVoid, s C(EXCEPTION), v)) diff --git a/src/coreclr/vm/nativeoverlapped.cpp b/src/coreclr/vm/nativeoverlapped.cpp index 8c4a79be68b19b..4a9edb5ff780be 100644 --- a/src/coreclr/vm/nativeoverlapped.cpp +++ b/src/coreclr/vm/nativeoverlapped.cpp @@ -15,7 +15,6 @@ #include "fcall.h" #include "nativeoverlapped.h" #include "corhost.h" -#include "win32threadpool.h" #include "comsynchronizable.h" #include "comthreadpool.h" #include "marshalnative.h" diff --git a/src/coreclr/vm/threadpoolrequest.cpp b/src/coreclr/vm/threadpoolrequest.cpp deleted file mode 100644 index 13cf18d8b10136..00000000000000 --- a/src/coreclr/vm/threadpoolrequest.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// ThreadPoolRequest.cpp -// - -// -// -//========================================================================= - -#include "common.h" -#include "comdelegate.h" -#include "comthreadpool.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" -#include "class.h" -#include "object.h" -#include "field.h" -#include "excep.h" -#include "eeconfig.h" -#include "corhost.h" -#include "nativeoverlapped.h" -#include "appdomain.inl" - -BYTE PerAppDomainTPCountList::s_padding[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; -// Make this point to unmanaged TP in case, no appdomains have initialized yet. -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG PerAppDomainTPCountList::s_ADHint = -1; - -// Move out of from preceeding variables' cache line -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) UnManagedPerAppDomainTPCount PerAppDomainTPCountList::s_unmanagedTPCount; -//The list of all per-appdomain work-request counts. -ArrayListStatic PerAppDomainTPCountList::s_appDomainIndexList; - - -//--------------------------------------------------------------------------- -//ResetAppDomainIndex: Resets the AppDomain ID and the per-appdomain -// thread pool counts -// -//Arguments: -//index - The index into the s_appDomainIndexList for the AppDomain we're -// trying to clear (the AD being unloaded) -// -//Assumptions: -//This function needs to be called from the AD unload thread after all domain -//bound objects have been finalized when it's safe to recycle the TPIndex. -//ClearAppDomainRequestsActive can be called from this function because no -// managed code is running (If managed code is running, this function needs -//to be called under a managed per-appdomain lock). -// -void PerAppDomainTPCountList::ResetAppDomainIndex(TPIndex index) -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(index.m_dwIndex == TPIndex().m_dwIndex); -} - -FORCEINLINE void ReleaseWorkRequest(WorkRequest *workRequest) { ThreadpoolMgr::RecycleMemory( workRequest, ThreadpoolMgr::MEMTYPE_WorkRequest ); } -typedef Wrapper< WorkRequest *, DoNothing, ReleaseWorkRequest > WorkRequestHolder; diff --git a/src/coreclr/vm/threadpoolrequest.h b/src/coreclr/vm/threadpoolrequest.h deleted file mode 100644 index 84193983fa8761..00000000000000 --- a/src/coreclr/vm/threadpoolrequest.h +++ /dev/null @@ -1,263 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// ThreadPoolRequest.h -// - -// -// This file contains definitions of classes needed to mainain per-appdomain -// thread pool work requests. This is needed as unmanaged and managed work -// requests are allocted, managed and dispatched in drastically different ways. -// However, the scheduler need be aware of these differences, and it should -// simply talk to a common interface for managing work request counts. -// -//========================================================================= - -#ifndef _THREADPOOL_REQUEST_H -#define _THREADPOOL_REQUEST_H - -#include "util.hpp" - -#define TP_QUANTUM 2 -#define UNUSED_THREADPOOL_INDEX (DWORD)-1 - -//-------------------------------------------------------------------------- -//IPerAppDomainTPCount is an interface for implementing per-appdomain thread -//pool state. It's implementation should include logic to maintain work-counts, -//notify thread pool class when work arrives or no work is left. Finally -//there is logic to dipatch work items correctly in the right domain. -// -//Notes: -//This class was designed to support both the managed and unmanaged uses -//of thread pool. The unmananged part may directly be used through com -//interfaces. The differences between the actual management of counts and -//dispatching of work is quite different between the two. This interface -//hides these differences to the thread scheduler implemented by the thread pool -//class. -// - -class IPerAppDomainTPCount{ -public: - virtual void ResetState() = 0; - virtual BOOL IsRequestPending() = 0; - - //This functions marks the beginning of requests queued for the domain. - //It needs to notify the scheduler of work-arrival among other things. - virtual void SetAppDomainRequestsActive() = 0; - - //This functions marks the end of requests queued for this domain. - virtual void ClearAppDomainRequestsActive() = 0; - - //Clears the "active" flag if it was set, and returns whether it was set. - virtual bool TakeActiveRequest() = 0; - - //Takes care of dispatching requests in the right domain. - virtual void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) = 0; - virtual void SetTPIndexUnused() = 0; - virtual BOOL IsTPIndexUnused() = 0; - virtual void SetTPIndex(TPIndex index) = 0; -}; - -typedef DPTR(IPerAppDomainTPCount) PTR_IPerAppDomainTPCount; - -#ifdef _MSC_VER -// Disable this warning - we intentionally want __declspec(align()) to insert padding for us -#pragma warning(disable: 4324) // structure was padded due to __declspec(align()) -#endif - -//-------------------------------------------------------------------------- -//ManagedPerAppDomainTPCount maintains per-appdomain thread pool state. -//This class maintains the count of per-appdomain work-items queued by -//ThreadPool.QueueUserWorkItem. It also dispatches threads in the appdomain -//correctly by setting up the right exception handling frames etc. -// -//Note: The counts are not accurate, and neither do they need to be. The -//actual work queue is in managed (implemented in threadpool.cs). This class -//just provides heuristics to the thread pool scheduler, along with -//synchronization to indicate start/end of requests to the scheduler. -class ManagedPerAppDomainTPCount : public IPerAppDomainTPCount { -public: - - ManagedPerAppDomainTPCount(TPIndex index) {ResetState(); m_index = index;} - - inline void ResetState() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&m_numRequestsPending, (LONG)0); - } - - inline BOOL IsRequestPending() - { - LIMITED_METHOD_CONTRACT; - - LONG count = VolatileLoad(&m_numRequestsPending); - return count > 0; - } - - void SetAppDomainRequestsActive(); - void ClearAppDomainRequestsActive(); - bool TakeActiveRequest(); - - inline void SetTPIndex(TPIndex index) - { - LIMITED_METHOD_CONTRACT; - //This function should be called during appdomain creation when no managed code - //has started running yet. That implies, no requests should be pending - //or dispatched to this structure yet. - - _ASSERTE(m_index.m_dwIndex == UNUSED_THREADPOOL_INDEX); - - m_index = index; - } - - inline BOOL IsTPIndexUnused() - { - LIMITED_METHOD_CONTRACT; - if (m_index.m_dwIndex == UNUSED_THREADPOOL_INDEX) - { - return TRUE; - } - - return FALSE; - } - - inline void SetTPIndexUnused() - { - WRAPPER_NO_CONTRACT; - m_index.m_dwIndex = UNUSED_THREADPOOL_INDEX; - } - - void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); - -private: - TPIndex m_index; - struct DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) { - BYTE m_padding1[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; - // Only use with VolatileLoad+VolatileStore+InterlockedCompareExchange - LONG m_numRequestsPending; - BYTE m_padding2[MAX_CACHE_LINE_SIZE]; - }; -}; - -//-------------------------------------------------------------------------- -//UnManagedPerAppDomainTPCount maintains the thread pool state/counts for -//unmanaged work requests. From thread pool point of view we treat unmanaged -//requests as a special "appdomain". This helps in scheduling policies, and -//follow same fairness policies as requests in other appdomains. -class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { -public: - - UnManagedPerAppDomainTPCount() - { - LIMITED_METHOD_CONTRACT; - ResetState(); - } - - inline void ResetState() - { - LIMITED_METHOD_CONTRACT; - m_NumRequests = 0; - VolatileStore(&m_outstandingThreadRequestCount, (LONG)0); - } - - inline BOOL IsRequestPending() - { - LIMITED_METHOD_CONTRACT; - return VolatileLoad(&m_outstandingThreadRequestCount) != (LONG)0 ? TRUE : FALSE; - } - - void SetAppDomainRequestsActive(); - - inline void ClearAppDomainRequestsActive() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&m_outstandingThreadRequestCount, (LONG)0); - } - - bool TakeActiveRequest(); - - void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); - - inline void SetTPIndexUnused() - { - WRAPPER_NO_CONTRACT; - _ASSERT(FALSE); - } - - inline BOOL IsTPIndexUnused() - { - WRAPPER_NO_CONTRACT; - _ASSERT(FALSE); - return FALSE; - } - - inline void SetTPIndex(TPIndex index) - { - WRAPPER_NO_CONTRACT; - _ASSERT(FALSE); - } - - inline ULONG GetNumRequests() - { - LIMITED_METHOD_CONTRACT; - return VolatileLoad(&m_NumRequests); - } - -private: - SpinLock m_lock; - ULONG m_NumRequests; - struct DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) { - BYTE m_padding1[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; - // Only use with VolatileLoad+VolatileStore+InterlockedCompareExchange - LONG m_outstandingThreadRequestCount; - BYTE m_padding2[MAX_CACHE_LINE_SIZE]; - }; -}; - -#ifdef _MSC_VER -#pragma warning(default: 4324) // structure was padded due to __declspec(align()) -#endif - -//-------------------------------------------------------------------------- -//PerAppDomainTPCountList maintains the collection of per-appdomain thread -//pool states. Per appdomain counts are added to the list during appdomain -//creation inside the sdomain lock. The counts are reset during appdomain -//unload after all the threads have -//This class maintains the count of per-appdomain work-items queued by -//ThreadPool.QueueUserWorkItem. It also dispatches threads in the appdomain -//correctly by setting up the right exception handling frames etc. -// -//Note: The counts are not accurate, and neither do they need to be. The -//actual work queue is in managed (implemented in threadpool.cs). This class -//just provides heuristics to the thread pool scheduler, along with -//synchronization to indicate start/end of requests to the scheduler. -class PerAppDomainTPCountList{ -public: - static void ResetAppDomainIndex(TPIndex index); - static TPIndex AddNewTPIndex(); - - inline static IPerAppDomainTPCount* GetPerAppdomainCount(TPIndex index) - { - return dac_cast(s_appDomainIndexList.Get(index.m_dwIndex-1)); - } - - inline static UnManagedPerAppDomainTPCount* GetUnmanagedTPCount() - { - return &s_unmanagedTPCount; - } - -private: - static DWORD FindFirstFreeTpEntry(); - - static BYTE s_padding[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG s_ADHint; - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static UnManagedPerAppDomainTPCount s_unmanagedTPCount; - - //The list of all per-appdomain work-request counts. - static ArrayListStatic s_appDomainIndexList; -}; - -#endif //_THREADPOOL_REQUEST_H diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index c55ea8bdcebad4..a5af90f8e2aeed 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -20,7 +20,6 @@ #include "eeprofinterfaces.h" #include "eeconfig.h" #include "corhost.h" -#include "win32threadpool.h" #include "jitinterface.h" #include "eventtrace.h" #include "comutilnative.h" @@ -34,7 +33,6 @@ #include "appdomain.inl" #include "vmholder.h" #include "exceptmacros.h" -#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP #include "runtimecallablewrapper.h" @@ -1541,8 +1539,6 @@ Thread::Thread() m_AbortRequestLock = 0; m_fRudeAbortInitiated = FALSE; - m_pIOCompletionContext = NULL; - #ifdef _DEBUG m_fRudeAborted = FALSE; m_dwAbortPoint = 0; @@ -1776,12 +1772,6 @@ void Thread::InitThread() ThrowOutOfMemory(); } } - - ret = Thread::AllocateIOCompletionContext(); - if (!ret) - { - ThrowOutOfMemory(); - } } // Allocate all the handles. When we are kicking of a new thread, we can call @@ -1976,33 +1966,6 @@ BOOL Thread::HasStarted() return FALSE; } -BOOL Thread::AllocateIOCompletionContext() -{ - WRAPPER_NO_CONTRACT; - PIOCompletionContext pIOC = new (nothrow) IOCompletionContext; - - if(pIOC != NULL) - { - pIOC->lpOverlapped = NULL; - m_pIOCompletionContext = pIOC; - return TRUE; - } - else - { - return FALSE; - } -} - -VOID Thread::FreeIOCompletionContext() -{ - WRAPPER_NO_CONTRACT; - if (m_pIOCompletionContext != NULL) - { - PIOCompletionContext pIOC = (PIOCompletionContext) m_pIOCompletionContext; - delete pIOC; - m_pIOCompletionContext = NULL; - } -} void Thread::HandleThreadStartupFailure() { @@ -2676,8 +2639,6 @@ Thread::~Thread() m_EventWait.CloseEvent(); } - FreeIOCompletionContext(); - if (m_OSContext) delete m_OSContext; @@ -7581,13 +7542,6 @@ void ManagedThreadBase::KickOff(ADCallBackFcnType pTarget, LPVOID args) ManagedThreadBase_FullTransition(pTarget, args, ManagedThread); } -// The IOCompletion, QueueUserWorkItem, RegisterWaitForSingleObject cases in the ThreadPool -void ManagedThreadBase::ThreadPool(ADCallBackFcnType pTarget, LPVOID args) -{ - WRAPPER_NO_CONTRACT; - ManagedThreadBase_FullTransition(pTarget, args, ThreadPoolThread); -} - // The Finalizer thread establishes exception handling at its base, but defers all the AppDomain // transitions. void ManagedThreadBase::FinalizerBase(ADCallBackFcnType pTarget) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 4911673ae9ce7f..8bc5177d89d0eb 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -4182,20 +4182,6 @@ class Thread m_fAllowProfilerCallbacks = fValue; } -private: - // - //This context is used for optimizations on I/O thread pool thread. In case the - //overlapped structure is from a different appdomain, it is stored in this structure - //to be processed later correctly by entering the right domain. - PVOID m_pIOCompletionContext; - BOOL AllocateIOCompletionContext(); - VOID FreeIOCompletionContext(); -public: - inline PVOID GetIOCompletionContext() - { - return m_pIOCompletionContext; - } - private: // Inside a host, we don't own a thread handle, and we avoid DuplicateHandle call. // If a thread is dying after we obtain the thread handle, our SuspendThread may fail @@ -6000,10 +5986,6 @@ struct ManagedThreadBase static void KickOff(ADCallBackFcnType pTarget, LPVOID args); - // The IOCompletion, QueueUserWorkItem, RegisterWaitForSingleObject cases in - // the ThreadPool - static void ThreadPool(ADCallBackFcnType pTarget, LPVOID args); - // The Finalizer thread uses this path static void FinalizerBase(ADCallBackFcnType pTarget); }; diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 4e4da913e86641..3b15100a34fb66 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -10,7 +10,6 @@ #include "common.h" #include "excep.h" #include "log.h" -#include "win32threadpool.h" #include "threadsuspend.h" #include "tieredcompilation.h" diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp deleted file mode 100644 index 1fff0378f9a3e0..00000000000000 --- a/src/coreclr/vm/win32threadpool.cpp +++ /dev/null @@ -1,1911 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -/*++ - -Module Name: - - Win32ThreadPool.cpp - -Abstract: - - This module implements Threadpool support using Win32 APIs - - -Revision History: - December 1999 - Created - ---*/ - -#include "common.h" -#include "log.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" -#include "delegateinfo.h" -#include "eeconfig.h" -#include "dbginterface.h" -#include "corhost.h" -#include "eventtrace.h" -#include "threads.h" -#include "appdomain.inl" -#include "nativeoverlapped.h" -#include "configuration.h" - - -#ifndef TARGET_UNIX -#ifndef DACCESS_COMPILE - -// APIs that must be accessed through dynamic linking. -typedef int (WINAPI *NtQueryInformationThreadProc) ( - HANDLE ThreadHandle, - THREADINFOCLASS ThreadInformationClass, - PVOID ThreadInformation, - ULONG ThreadInformationLength, - PULONG ReturnLength); -NtQueryInformationThreadProc g_pufnNtQueryInformationThread = NULL; - -typedef int (WINAPI *NtQuerySystemInformationProc) ( - SYSTEM_INFORMATION_CLASS SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength OPTIONAL); -NtQuerySystemInformationProc g_pufnNtQuerySystemInformation = NULL; - -typedef HANDLE (WINAPI * CreateWaitableTimerExProc) ( - LPSECURITY_ATTRIBUTES lpTimerAttributes, - LPCTSTR lpTimerName, - DWORD dwFlags, - DWORD dwDesiredAccess); -CreateWaitableTimerExProc g_pufnCreateWaitableTimerEx = NULL; - -typedef BOOL (WINAPI * SetWaitableTimerExProc) ( - HANDLE hTimer, - const LARGE_INTEGER *lpDueTime, - LONG lPeriod, - PTIMERAPCROUTINE pfnCompletionRoutine, - LPVOID lpArgToCompletionRoutine, - void* WakeContext, //should be PREASON_CONTEXT, but it's not defined for us (and we don't use it) - ULONG TolerableDelay); -SetWaitableTimerExProc g_pufnSetWaitableTimerEx = NULL; - -#endif // !DACCESS_COMPILE -#endif // !TARGET_UNIX - -BOOL ThreadpoolMgr::InitCompletionPortThreadpool = FALSE; -HANDLE ThreadpoolMgr::GlobalCompletionPort; // used for binding io completions on file handles - -SVAL_IMPL(ThreadpoolMgr::ThreadCounter,ThreadpoolMgr,CPThreadCounter); - -SVAL_IMPL_INIT(LONG,ThreadpoolMgr,MaxLimitTotalCPThreads,1000); // = MaxLimitCPThreadsPerCPU * number of CPUS -SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalCPThreads); -SVAL_IMPL(LONG,ThreadpoolMgr,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_IMPL(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr, WorkerCounter); - -SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS -SVAL_IMPL(LONG,ThreadpoolMgr,MaxLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS - -SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization); - -// Cacheline aligned, 3 hot variables updated in a group -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::PriorCompletedWorkRequests = 0; -DWORD ThreadpoolMgr::PriorCompletedWorkRequestsTime; -DWORD ThreadpoolMgr::NextCompletedWorkRequestsTime; - -LARGE_INTEGER ThreadpoolMgr::CurrentSampleStartTime; - -unsigned int ThreadpoolMgr::WorkerThreadSpinLimit; -bool ThreadpoolMgr::IsHillClimbingDisabled; -int ThreadpoolMgr::ThreadAdjustmentInterval; - -#define INVALID_HANDLE ((HANDLE) -1) -#define NEW_THREAD_THRESHOLD 7 // Number of requests outstanding before we start a new thread -#define CP_THREAD_PENDINGIO_WAIT 5000 // polling interval when thread is retired but has a pending io -#define GATE_THREAD_DELAY 500 /*milliseconds*/ -#define GATE_THREAD_DELAY_TOLERANCE 50 /*milliseconds*/ -#define DELAY_BETWEEN_SUSPENDS (5000 + GATE_THREAD_DELAY) // time to delay between suspensions - -Volatile ThreadpoolMgr::Initialization = 0; // indicator of whether the threadpool is initialized. - -bool ThreadpoolMgr::s_usePortableThreadPool = false; -bool ThreadpoolMgr::s_usePortableThreadPoolForIO = false; - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) unsigned int ThreadpoolMgr::LastDequeueTime; // used to determine if work items are getting thread starved - -SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestHead); // Head of work request queue -SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestTail); // Head of work request queue - -//unsigned int ThreadpoolMgr::LastCpuSamplingTime=0; // last time cpu utilization was sampled by gate thread -unsigned int ThreadpoolMgr::LastCPThreadCreation=0; // last time a completion port thread was created -unsigned int ThreadpoolMgr::NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads - - -CrstStatic ThreadpoolMgr::WorkerCriticalSection; -CLREvent * ThreadpoolMgr::RetiredCPWakeupEvent; // wakeup event for completion port threads -CrstStatic ThreadpoolMgr::WaitThreadsCriticalSection; -ThreadpoolMgr::LIST_ENTRY ThreadpoolMgr::WaitThreadsHead; - -CLRLifoSemaphore* ThreadpoolMgr::WorkerSemaphore; -CLRLifoSemaphore* ThreadpoolMgr::RetiredWorkerSemaphore; - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::GateThreadStatus=GATE_THREAD_STATUS_NOT_RUNNING; - -// Move out of from preceeding variables' cache line -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) ThreadpoolMgr::RecycledListsWrapper ThreadpoolMgr::RecycledLists; - -BOOL ThreadpoolMgr::IsApcPendingOnWaitThread = FALSE; - -#ifndef DACCESS_COMPILE - -// Macros for inserting/deleting from doubly linked list - -#define InitializeListHead(ListHead) (\ - (ListHead)->Flink = (ListHead)->Blink = (ListHead)) - -// -// these are named the same as slightly different macros in the NT headers -// -#undef RemoveHeadList -#undef RemoveEntryList -#undef InsertTailList -#undef InsertHeadList - -#define RemoveHeadList(ListHead,FirstEntry) \ - {\ - FirstEntry = (LIST_ENTRY*) (ListHead)->Flink;\ - ((LIST_ENTRY*)FirstEntry->Flink)->Blink = (ListHead);\ - (ListHead)->Flink = FirstEntry->Flink;\ - } - -#define RemoveEntryList(Entry) {\ - LIST_ENTRY* _EX_Entry;\ - _EX_Entry = (Entry);\ - ((LIST_ENTRY*) _EX_Entry->Blink)->Flink = _EX_Entry->Flink;\ - ((LIST_ENTRY*) _EX_Entry->Flink)->Blink = _EX_Entry->Blink;\ - } - -#define InsertTailList(ListHead,Entry) \ - (Entry)->Flink = (ListHead);\ - (Entry)->Blink = (ListHead)->Blink;\ - ((LIST_ENTRY*)(ListHead)->Blink)->Flink = (Entry);\ - (ListHead)->Blink = (Entry); - -#define InsertHeadList(ListHead,Entry) {\ - LIST_ENTRY* _EX_Flink;\ - LIST_ENTRY* _EX_ListHead;\ - _EX_ListHead = (LIST_ENTRY*)(ListHead);\ - _EX_Flink = (LIST_ENTRY*) _EX_ListHead->Flink;\ - (Entry)->Flink = _EX_Flink;\ - (Entry)->Blink = _EX_ListHead;\ - _EX_Flink->Blink = (Entry);\ - _EX_ListHead->Flink = (Entry);\ - } - -#define IsListEmpty(ListHead) \ - ((ListHead)->Flink == (ListHead)) - -#define SetLastHRError(hr) \ - if (HRESULT_FACILITY(hr) == FACILITY_WIN32)\ - SetLastError(HRESULT_CODE(hr));\ - else \ - SetLastError(ERROR_INVALID_DATA);\ - -/************************************************************************/ - -void ThreadpoolMgr::RecycledListsWrapper::Initialize( unsigned int numProcs ) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - pRecycledListPerProcessor = new RecycledListInfo[numProcs][MEMTYPE_COUNT]; -} - -//--// - -void ThreadpoolMgr::EnsureInitialized() -{ - CONTRACTL - { - THROWS; // EnsureInitializedSlow can throw - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - if (IsInitialized()) - return; - - EnsureInitializedSlow(); -} - -NOINLINE void ThreadpoolMgr::EnsureInitializedSlow() -{ - CONTRACTL - { - THROWS; // Initialize can throw - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - DWORD dwSwitchCount = 0; - -retry: - if (InterlockedCompareExchange(&Initialization, 1, 0) == 0) - { - if (Initialize()) - Initialization = -1; - else - { - Initialization = 0; - COMPlusThrowOM(); - } - } - else // someone has already begun initializing. - { - // wait until it finishes - while (Initialization != -1) - { - __SwitchToThread(0, ++dwSwitchCount); - goto retry; - } - } -} - -BOOL ThreadpoolMgr::Initialize() -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - BOOL bRet = FALSE; - BOOL bExceptionCaught = FALSE; - - NumberOfProcessors = GetCurrentProcessCpuCount(); - InitPlatformVariables(); - - EX_TRY - { -#ifndef TARGET_UNIX - //ThreadPool_CPUGroup - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - RecycledLists.Initialize( CPUGroupInfo::GetNumActiveProcessors() ); - else - RecycledLists.Initialize( g_SystemInfo.dwNumberOfProcessors ); -#else // !TARGET_UNIX - RecycledLists.Initialize( PAL_GetTotalCpuCount() ); -#endif // !TARGET_UNIX - } - EX_CATCH - { - bExceptionCaught = TRUE; - } - EX_END_CATCH(SwallowAllExceptions); - - if (bExceptionCaught) - { - goto end; - } - - bRet = TRUE; -end: - return bRet; -} - -void ThreadpoolMgr::InitPlatformVariables() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - HINSTANCE hNtDll; - HINSTANCE hCoreSynch = nullptr; - { - CONTRACT_VIOLATION(GCViolation|FaultViolation); - hNtDll = CLRLoadLibrary(W("ntdll.dll")); - _ASSERTE(hNtDll); - } - - // These APIs must be accessed via dynamic binding since they may be removed in future - // OS versions. - g_pufnNtQueryInformationThread = (NtQueryInformationThreadProc)GetProcAddress(hNtDll,"NtQueryInformationThread"); - g_pufnNtQuerySystemInformation = (NtQuerySystemInformationProc)GetProcAddress(hNtDll,"NtQuerySystemInformation"); - -#endif -} - -// -// WorkingThreadCounts tracks the number of worker threads currently doing user work, and the maximum number of such threads -// since the last time TakeMaxWorkingThreadCount was called. This information is for diagnostic purposes only, -// and is tracked only if the CLR config value INTERNAL_ThreadPool_EnableWorkerTracking is non-zero (this feature is off -// by default). -// -union WorkingThreadCounts -{ - struct - { - int currentWorking : 16; - int maxWorking : 16; - }; - - LONG asLong; -}; - -WorkingThreadCounts g_workingThreadCounts; - -// -// Returns the max working count since the previous call to TakeMaxWorkingThreadCount, and resets WorkingThreadCounts.maxWorking. -// -int TakeMaxWorkingThreadCount() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); - while (true) - { - WorkingThreadCounts currentCounts, newCounts; - currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); - - newCounts = currentCounts; - newCounts.maxWorking = 0; - - if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) - { - // If we haven't updated the counts since the last call to TakeMaxWorkingThreadCount, then we never updated maxWorking. - // In that case, the number of working threads for the whole period since the last TakeMaxWorkingThreadCount is the - // current number of working threads. - return currentCounts.maxWorking == 0 ? currentCounts.currentWorking : currentCounts.maxWorking; - } - } -} - - -/************************************************************************/ - -DangerousNonHostedSpinLock ThreadpoolMgr::ThreadAdjustmentLock; - -void ThreadpoolMgr::WaitIOCompletionCallback( - DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - } - CONTRACTL_END; - - if (dwErrorCode == ERROR_SUCCESS) - DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); -} - -extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - -// Remove a block from the appropriate recycleList and return. -// If recycleList is empty, fall back to new. -LPVOID ThreadpoolMgr::GetRecycledMemory(enum MemType memType) -{ - LPVOID result = NULL; - CONTRACT(LPVOID) - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - POSTCONDITION(CheckPointer(result)); - } CONTRACT_END; - - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - - result = list.Remove(); - } - - if(result == NULL) - { - switch (memType) - { - case MEMTYPE_DelegateInfo: - result = new DelegateInfo; - break; - case MEMTYPE_AsyncCallback: - result = new AsyncCallback; - break; - case MEMTYPE_WorkRequest: - result = new WorkRequest; - break; - default: - _ASSERTE(!"Unknown Memtype"); - result = NULL; - break; - } - } - - RETURN result; -} - -// Insert freed block in recycle list. If list is full, return to system heap -void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - - if(list.CanInsert()) - { - list.Insert( mem ); - return; - } - } - - switch (memType) - { - case MEMTYPE_DelegateInfo: - delete (DelegateInfo*) mem; - break; - case MEMTYPE_AsyncCallback: - delete (AsyncCallback*) mem; - break; - case MEMTYPE_WorkRequest: - delete (WorkRequest*) mem; - break; - default: - _ASSERTE(!"Unknown Memtype"); - - } -} - -// this should only be called by unmanaged thread (i.e. there should be no mgd -// caller on the stack) since we are swallowing terminal exceptions -DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - DWORD status = WAIT_TIMEOUT; - EX_TRY - { - status = ev->Wait(sleepTime,FALSE); - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions) - return status; -} - -/************************************************************************/ - - -// if no wraparound that the timer is expired if duetime is less than current time -// if wraparound occurred, then the timer expired if dueTime was greater than last time or dueTime is less equal to current time -#define TimeExpired(last,now,duetime) ((last) <= (now) ? \ - ((duetime) <= (now) && (duetime) >= (last)): \ - ((duetime) >= (last) || (duetime) <= (now))) - -#define TimeInterval(end,start) ((end) > (start) ? ((end) - (start)) : ((0xffffffff - (start)) + (end) + 1)) - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif -#endif -#ifdef _PREFAST_ -#pragma warning(push) -#pragma warning(disable:22008) // "Prefast integer overflow check on (0 + lval) is bogus. Tried local disable without luck, doing whole method." -#endif - -#ifdef _PREFAST_ -#pragma warning(pop) -#endif - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, - unsigned index, - BOOL waitTimedOut - ) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - AsyncCallback* asyncCallback = NULL; - EX_TRY{ - if ( waitInfo->flag & WAIT_SINGLE_EXECUTION) - { - DeactivateNthWait (waitInfo,index) ; - } - else - { // reactivate wait by resetting timer - waitInfo->timer.startTime = GetTickCount(); - } - - asyncCallback = MakeAsyncCallback(); - if (asyncCallback) - { - asyncCallback->wait = waitInfo; - asyncCallback->waitTimedOut = waitTimedOut; - - InterlockedIncrement(&waitInfo->refCount); - -#ifndef TARGET_UNIX - -#else // TARGET_UNIX - if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) -#endif // !TARGET_UNIX - ReleaseAsyncCallback(asyncCallback); - } - } - EX_CATCH { - if (asyncCallback) - ReleaseAsyncCallback(asyncCallback); - - EX_RETHROW; - } - EX_END_CATCH(SwallowAllExceptions); -} - - -DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - MODE_PREEMPTIVE; - GC_TRIGGERS; - } - CONTRACTL_END; - - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - { - AsyncCallback * asyncCallback = (AsyncCallback*) pArgs; - - WaitInfo * waitInfo = asyncCallback->wait; - - AsyncCallbackHolder asyncCBHolder; - asyncCBHolder.Assign(asyncCallback); - - // We fire the "dequeue" ETW event here, before executing the user code, to enable correlation with - // the ThreadPoolIOEnqueue fired in ThreadpoolMgr::RegisterWaitForSingleObject - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) - FireEtwThreadPoolIODequeue(waitInfo, reinterpret_cast(waitInfo->Callback), GetClrInstanceId()); - - // the user callback can throw, the host must be prepared to handle it. - // SQL is ok, since they have a top-level SEH handler. However, there's - // no easy way to verify it - - ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) - ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); - -#ifndef TARGET_UNIX - Thread::IncrementIOThreadPoolCompletionCount(pThread); -#endif - } - - return ERROR_SUCCESS; -} - -void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) -{ - LIMITED_METHOD_CONTRACT; - - ThreadCB* threadCB = waitInfo->threadCB; - DWORD endIndex = threadCB->NumActiveWaits-1; - DWORD index; - - for (index = 0; index <= endIndex; index++) - { - LIST_ENTRY* head = &(threadCB->waitPointer[index]); - LIST_ENTRY* current = head; - do { - if (current->Flink == (PVOID) waitInfo) - goto FOUND; - - current = (LIST_ENTRY*) current->Flink; - - } while (current != head); - } - -FOUND: - _ASSERTE(index <= endIndex); - - DeactivateNthWait(waitInfo, index); -} - - -void ThreadpoolMgr::DeleteWait(WaitInfo* waitInfo) -{ - CONTRACTL - { - if (waitInfo->ExternalEventSafeHandle != NULL) { THROWS;} else { NOTHROW; } - MODE_ANY; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - if(waitInfo->Context && (waitInfo->flag & WAIT_FREE_CONTEXT)) { - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - // Since the delegate release destroys a handle, we need to be in - // co-operative mode - { - GCX_COOP(); - pDelegate->Release(); - } - - RecycleMemory( pDelegate, MEMTYPE_DelegateInfo ); - } - - if (waitInfo->flag & WAIT_INTERNAL_COMPLETION) - { - waitInfo->InternalCompletionEvent.Set(); - return; // waitInfo will be deleted by the thread that's waiting on this event - } - else if (waitInfo->ExternalCompletionEvent != INVALID_HANDLE) - { - SetEvent(waitInfo->ExternalCompletionEvent); - } - else if (waitInfo->ExternalEventSafeHandle != NULL) - { - // Release the safe handle and the GC handle holding it - ReleaseWaitInfo(waitInfo); - } - - delete waitInfo; - - -} - - - -/************************************************************************/ - -void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) -{ - WRAPPER_NO_CONTRACT; - - WaitInfo* waitInfo = pArgs; - - if ( ! (waitInfo->state & WAIT_REGISTERED) ) - { - // set state to deleted, so that it does not get registered - waitInfo->state |= WAIT_DELETE ; - - // since the wait has not even been registered, we dont need an interlock to decrease the RefCount - waitInfo->refCount--; - - if (waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - } - return; - } - - if (waitInfo->state & WAIT_ACTIVE) - { - DeactivateWait(waitInfo); - } - - if ( waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - return; // we cannot delete the wait here since the PartialCompletionEvent - // may not have been closed yet. so, we return and rely on the waiter of PartialCompletionEvent - // to do the close - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - { - DeleteWait(waitInfo); - } - return; -} - - -/************************************************************************/ - -#ifndef TARGET_UNIX - -LPOVERLAPPED ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain( - Thread* pThread, - DWORD* pErrorCode, - DWORD* pNumBytes, - size_t* pKey) -// -//This function is called just after dispatching the previous BindIO callback -//to Managed code. This is a perf optimization to do a quick call to -//GetQueuedCompletionStatus with a timeout of 0 ms. If there is work in the -//same appdomain, dispatch it back immediately. If not stick it in a well known -//place, and reenter the target domain. The timeout of zero is chosen so as to -//not delay appdomain unloads. -// -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - LPOVERLAPPED lpOverlapped=NULL; - - BOOL status=FALSE; - OVERLAPPEDDATAREF overlapped=NULL; - BOOL ManagedCallback=FALSE; - - *pErrorCode = S_OK; - - - //Very Very Important! - //Do not change the timeout for GetQueuedCompletionStatus to a non-zero value. - //Selecting a non-zero value can cause the thread to block, and lead to expensive context switches. - //In real life scenarios, we have noticed a packet to be not available immediately, but very shortly - //(after few 100's of instructions), and falling back to the VM is good in that case as compared to - //taking a context switch. Changing the timeout to non-zero can lead to perf degrades, that are very - //hard to diagnose. - - status = ::GetQueuedCompletionStatus( - GlobalCompletionPort, - pNumBytes, - (PULONG_PTR)pKey, - &lpOverlapped, - 0); - - DWORD lastError = GetLastError(); - - if (status == 0) - { - if (lpOverlapped != NULL) - { - *pErrorCode = lastError; - } - else - { - return NULL; - } - } - - if (((LPOVERLAPPED_COMPLETION_ROUTINE) *pKey) != BindIoCompletionCallbackStub) - { - //_ASSERTE(FALSE); - } - else - { - ManagedCallback = TRUE; - overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - } - - if (ManagedCallback) - { - _ASSERTE(*pKey != 0); // should be a valid function address - - if (*pKey ==0) - { - //Application Bug. - return NULL; - } - } - else - { - //Just retruned back from managed code, a Thread structure should exist. - _ASSERTE (pThread); - - //Oops, this is an overlapped fom a different appdomain. STick it in - //the thread. We will process it later. - - StoreOverlappedInfoInThread(pThread, *pErrorCode, *pNumBytes, *pKey, lpOverlapped); - - lpOverlapped = NULL; - } - -#ifndef DACCESS_COMPILE - return lpOverlapped; -#endif -} - -void ThreadpoolMgr::StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - _ASSERTE(pThread); - - PIOCompletionContext context; - - context = (PIOCompletionContext) pThread->GetIOCompletionContext(); - - _ASSERTE(context); - - context->ErrorCode = dwErrorCode; - context->numBytesTransferred = dwNumBytes; - context->lpOverlapped = lpOverlapped; - context->key = key; -} - -#endif // !TARGET_UNIX - -// Returns true if there is pending io on the thread. -BOOL ThreadpoolMgr::IsIoPending() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - int Status; - ULONG IsIoPending; - - if (g_pufnNtQueryInformationThread) - { - Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(), - ThreadIsIoPending, - &IsIoPending, - sizeof(IsIoPending), - NULL); - - - if ((Status < 0) || IsIoPending) - return TRUE; - else - return FALSE; - } - return TRUE; -#else - return FALSE; -#endif // !TARGET_UNIX -} - -#ifndef TARGET_UNIX - -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif - -int ThreadpoolMgr::GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo) -{ - LIMITED_METHOD_CONTRACT; - - PROCESS_CPU_INFORMATION newUsage; - newUsage.idleTime.QuadPart = 0; - newUsage.kernelTime.QuadPart = 0; - newUsage.userTime.QuadPart = 0; - - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - { -#if !defined(FEATURE_NATIVEAOT) && !defined(TARGET_UNIX) - FILETIME newIdleTime, newKernelTime, newUserTime; - - CPUGroupInfo::GetSystemTimes(&newIdleTime, &newKernelTime, &newUserTime); - newUsage.idleTime.u.LowPart = newIdleTime.dwLowDateTime; - newUsage.idleTime.u.HighPart = newIdleTime.dwHighDateTime; - newUsage.kernelTime.u.LowPart = newKernelTime.dwLowDateTime; - newUsage.kernelTime.u.HighPart = newKernelTime.dwHighDateTime; - newUsage.userTime.u.LowPart = newUserTime.dwLowDateTime; - newUsage.userTime.u.HighPart = newUserTime.dwHighDateTime; -#endif - } - else - { - (*g_pufnNtQuerySystemInformation)(SystemProcessorPerformanceInformation, - pOldInfo->usageBuffer, - pOldInfo->usageBufferSize, - NULL); - - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* pInfoArray = pOldInfo->usageBuffer; - DWORD_PTR pmask = pOldInfo->affinityMask; - - int proc_no = 0; - while (pmask) - { - if (pmask & 1) - { //should be good: 1CPU 28823 years, 256CPUs 100+years - newUsage.idleTime.QuadPart += pInfoArray[proc_no].IdleTime.QuadPart; - newUsage.kernelTime.QuadPart += pInfoArray[proc_no].KernelTime.QuadPart; - newUsage.userTime.QuadPart += pInfoArray[proc_no].UserTime.QuadPart; - } - - pmask >>=1; - proc_no++; - } - } - - __int64 cpuTotalTime, cpuBusyTime; - - cpuTotalTime = (newUsage.userTime.QuadPart - pOldInfo->userTime.QuadPart) + - (newUsage.kernelTime.QuadPart - pOldInfo->kernelTime.QuadPart); - cpuBusyTime = cpuTotalTime - - (newUsage.idleTime.QuadPart - pOldInfo->idleTime.QuadPart); - - // Preserve reading - pOldInfo->idleTime = newUsage.idleTime; - pOldInfo->kernelTime = newUsage.kernelTime; - pOldInfo->userTime = newUsage.userTime; - - __int64 reading = 0; - - if (cpuTotalTime > 0) - reading = ((cpuBusyTime * 100) / cpuTotalTime); - - _ASSERTE(FitsIn(reading)); - return (int)reading; -} - -#else // !TARGET_UNIX - -int ThreadpoolMgr::GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo) -{ - return PAL_GetCPUBusyTime(pOldInfo); -} - -#endif // !TARGET_UNIX - -// -// A timer that ticks every GATE_THREAD_DELAY milliseconds. -// On platforms that support it, we use a coalescable waitable timer object. -// For other platforms, we use Sleep, via __SwitchToThread. -// -class GateThreadTimer -{ -#ifndef TARGET_UNIX - HANDLE m_hTimer; - -public: - GateThreadTimer() - : m_hTimer(NULL) - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (g_pufnCreateWaitableTimerEx && g_pufnSetWaitableTimerEx) - { - m_hTimer = g_pufnCreateWaitableTimerEx(NULL, NULL, 0, TIMER_ALL_ACCESS); - if (m_hTimer) - { - // - // Set the timer to fire GATE_THREAD_DELAY milliseconds from now, then every GATE_THREAD_DELAY milliseconds thereafter. - // We also set the tolerance to GET_THREAD_DELAY_TOLERANCE, allowing the OS to coalesce this timer. - // - LARGE_INTEGER dueTime; - dueTime.QuadPart = MILLI_TO_100NANO(-(LONGLONG)GATE_THREAD_DELAY); //negative value indicates relative time - if (!g_pufnSetWaitableTimerEx(m_hTimer, &dueTime, GATE_THREAD_DELAY, NULL, NULL, NULL, GATE_THREAD_DELAY_TOLERANCE)) - { - CloseHandle(m_hTimer); - m_hTimer = NULL; - } - } - } - } - - ~GateThreadTimer() - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (m_hTimer) - { - CloseHandle(m_hTimer); - m_hTimer = NULL; - } - } - -#endif // !TARGET_UNIX - -public: - void Wait() - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - if (m_hTimer) - WaitForSingleObject(m_hTimer, INFINITE); - else -#endif // !TARGET_UNIX - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } -}; - -// called by logic to spawn a new completion port thread. -// return false if not enough time has elapsed since the last -// time we sampled the cpu utilization. -BOOL ThreadpoolMgr::SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, - unsigned NumThreads, // total number of threads of that type (worker or CP) - double throttleRate // the delay is increased by this percentage for each extra thread - ) -{ - LIMITED_METHOD_CONTRACT; - - unsigned dwCurrentTickCount = GetTickCount(); - - unsigned delaySinceLastThreadCreation = dwCurrentTickCount - LastThreadCreationTime; - - unsigned minWaitBetweenThreadCreation = GATE_THREAD_DELAY; - - if (throttleRate > 0.0) - { - _ASSERTE(throttleRate <= 1.0); - - unsigned adjustedThreadCount = NumThreads > NumberOfProcessors ? (NumThreads - NumberOfProcessors) : 0; - - minWaitBetweenThreadCreation = (unsigned) (GATE_THREAD_DELAY * pow((1.0 + throttleRate),(double)adjustedThreadCount)); - } - // the amount of time to wait should grow up as the number of threads is increased - - return (delaySinceLastThreadCreation > minWaitBetweenThreadCreation); - -} - - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -<<<<<<< HEAD -/************************************************************************/ - -struct CreateTimerThreadParams { - CLREvent event; - BOOL setupSucceeded; -}; - -BOOL ThreadpoolMgr::CreateTimerQueueTimer(PHANDLE phNewTimer, - WAITORTIMERCALLBACK Callback, - PVOID Parameter, - DWORD DueTime, - DWORD Period, - ULONG Flag) -{ - CONTRACTL - { - THROWS; // EnsureInitialized, CreateAutoEvent can throw - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} // There can be calls thru ICorThreadpool - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - EnsureInitialized(); - - // For now we use just one timer thread. Consider using multiple timer threads if - // number of timers in the queue exceeds a certain threshold. The logic and code - // would be similar to the one for creating wait threads. - if (NULL == TimerThread) - { - CrstHolder csh(&TimerQueueCriticalSection); - - // check again - if (NULL == TimerThread) - { - CreateTimerThreadParams params; - params.event.CreateAutoEvent(FALSE); - - params.setupSucceeded = FALSE; - - HANDLE TimerThreadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, TimerThreadStart, ¶ms, W(".NET Timer")); - - if (TimerThreadHandle == NULL) - { - params.event.CloseEvent(); - ThrowOutOfMemory(); - } - - { - GCX_PREEMP(); - for(;;) - { - // if a host throws because it couldnt allocate another thread, - // just retry the wait. - if (SafeWait(¶ms.event,INFINITE, FALSE) != WAIT_TIMEOUT) - break; - } - } - params.event.CloseEvent(); - - if (!params.setupSucceeded) - { - CloseHandle(TimerThreadHandle); - *phNewTimer = NULL; - return FALSE; - } - - TimerThread = TimerThreadHandle; - } - - } - - - NewHolder timerInfoHolder; - TimerInfo * timerInfo = new (nothrow) TimerInfo; - if (NULL == timerInfo) - ThrowOutOfMemory(); - - timerInfoHolder.Assign(timerInfo); - - timerInfo->FiringTime = DueTime; - timerInfo->Function = Callback; - timerInfo->Context = Parameter; - timerInfo->Period = Period; - timerInfo->state = 0; - timerInfo->flag = Flag; - timerInfo->ExternalCompletionEvent = INVALID_HANDLE; - timerInfo->ExternalEventSafeHandle = NULL; - - *phNewTimer = (HANDLE)timerInfo; - - BOOL status = QueueUserAPC((PAPCFUNC)InsertNewTimer,TimerThread,(size_t)timerInfo); - if (FALSE == status) - { - *phNewTimer = NULL; - return FALSE; - } - - timerInfoHolder.SuppressRelease(); - return TRUE; -} - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif -#endif -DWORD WINAPI ThreadpoolMgr::TimerThreadStart(LPVOID p) -{ - ClrFlsSetThreadType (ThreadType_Timer); - - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; // due to SetApartment - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - CreateTimerThreadParams* params = (CreateTimerThreadParams*)p; - - Thread* pThread = SetupThreadNoThrow(); - - params->setupSucceeded = (pThread == NULL) ? 0 : 1; - params->event.Set(); - - if (pThread == NULL) - return 0; - - pTimerThread = pThread; - // Timer threads never die - - LastTickCount = GetTickCount(); - -#ifdef FEATURE_COMINTEROP - if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // @todo: should we log the failure - return 0; - } -#endif // FEATURE_COMINTEROP - - for (;;) - { - // moved to its own function since EX_TRY consumes stack -#ifdef _MSC_VER -#pragma inline_depth (0) // the function containing EX_TRY can't be inlined here -#endif - TimerThreadFire(); -#ifdef _MSC_VER -#pragma inline_depth (20) -#endif - } - - // unreachable -} - -void ThreadpoolMgr::TimerThreadFire() -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - EX_TRY { - DWORD timeout = FireTimers(); - ClrSleepEx(timeout, TRUE); - - // the thread could wake up either because an APC completed or the sleep timeout - // in both case, we need to sweep the timer queue, firing timers, and readjusting - // the next firing time - - } - EX_CATCH { - // Assert on debug builds since a dead timer thread is a fatal error - _ASSERTE(FALSE); - EX_RETHROW; - } - EX_END_CATCH(SwallowAllExceptions); -} - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -// Executed as an APC in timer thread -void ThreadpoolMgr::InsertNewTimer(TimerInfo* pArg) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(pArg); - TimerInfo * timerInfo = pArg; - - if (timerInfo->state & TIMER_DELETE) - { // timer was deleted before it could be registered - DeleteTimer(timerInfo); - return; - } - - // set the firing time = current time + due time (note initially firing time = due time) - DWORD currentTime = GetTickCount(); - if (timerInfo->FiringTime == (ULONG) -1) - { - timerInfo->state = TIMER_REGISTERED; - timerInfo->refCount = 1; - - } - else - { - timerInfo->FiringTime += currentTime; - - timerInfo->state = (TIMER_REGISTERED | TIMER_ACTIVE); - timerInfo->refCount = 1; - - // insert the timer in the queue - InsertTailList(&TimerQueue,(&timerInfo->link)); - } - - return; -} - - -// executed by the Timer thread -// sweeps through the list of timers, readjusting the firing times, queueing APCs for -// those that have expired, and returns the next firing time interval -DWORD ThreadpoolMgr::FireTimers() -{ - CONTRACTL - { - THROWS; // QueueUserWorkItem can throw - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - if (GetThreadNULLOk()) { MODE_PREEMPTIVE;} else { DISABLED(MODE_ANY);} - } - CONTRACTL_END; - - DWORD currentTime = GetTickCount(); - DWORD nextFiringInterval = (DWORD) -1; - TimerInfo* timerInfo = NULL; - - EX_TRY - { - for (LIST_ENTRY* node = (LIST_ENTRY*) TimerQueue.Flink; - node != &TimerQueue; - ) - { - timerInfo = (TimerInfo*) node; - node = (LIST_ENTRY*) node->Flink; - - if (TimeExpired(LastTickCount, currentTime, timerInfo->FiringTime)) - { - if (timerInfo->Period == 0 || timerInfo->Period == (ULONG) -1) - { - DeactivateTimer(timerInfo); - } - - InterlockedIncrement(&timerInfo->refCount); - - GCX_COOP(); - - ARG_SLOT args[] = { PtrToArgSlot(AsyncTimerCallbackCompletion), PtrToArgSlot(timerInfo) }; - MethodDescCallSite(METHOD__THREAD_POOL__UNSAFE_QUEUE_UNMANAGED_WORK_ITEM).Call(args); - - if (timerInfo->Period != 0 && timerInfo->Period != (ULONG)-1) - { - ULONG nextFiringTime = timerInfo->FiringTime + timerInfo->Period; - DWORD firingInterval; - if (TimeExpired(timerInfo->FiringTime, currentTime, nextFiringTime)) - { - // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up with the short - // period, have it fire 1 ms from now to avoid spinning without a delay. - timerInfo->FiringTime = currentTime + 1; - firingInterval = 1; - } - else - { - timerInfo->FiringTime = nextFiringTime; - firingInterval = TimeInterval(nextFiringTime, currentTime); - } - - if (firingInterval < nextFiringInterval) - nextFiringInterval = firingInterval; - } - } - else - { - DWORD firingInterval = TimeInterval(timerInfo->FiringTime, currentTime); - if (firingInterval < nextFiringInterval) - nextFiringInterval = firingInterval; - } - } - } - EX_CATCH - { - // If QueueUserWorkItem throws OOM, swallow the exception and retry on - // the next call to FireTimers(), otherwise retrhow. - Exception *ex = GET_EXCEPTION(); - // undo the call to DeactivateTimer() - InterlockedDecrement(&timerInfo->refCount); - timerInfo->state = timerInfo->state & TIMER_ACTIVE; - InsertTailList(&TimerQueue, (&timerInfo->link)); - if (ex->GetHR() != E_OUTOFMEMORY) - { - EX_RETHROW; - } - } - EX_END_CATCH(RethrowTerminalExceptions); - - LastTickCount = currentTime; - - return nextFiringInterval; -} - -DWORD WINAPI ThreadpoolMgr::AsyncTimerCallbackCompletion(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - Thread* pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - { - TimerInfo* timerInfo = (TimerInfo*) pArgs; - ((WAITORTIMERCALLBACKFUNC) timerInfo->Function) (timerInfo->Context, TRUE) ; - - if (InterlockedDecrement(&timerInfo->refCount) == 0) - { - DeleteTimer(timerInfo); - } - } - - return ERROR_SUCCESS; -} - - -// removes the timer from the timer queue, thereby cancelling it -// there may still be pending callbacks that haven't completed -void ThreadpoolMgr::DeactivateTimer(TimerInfo* timerInfo) -{ - LIMITED_METHOD_CONTRACT; - - RemoveEntryList((LIST_ENTRY*) timerInfo); - - // This timer info could go into another linked list of timer infos - // waiting to be released. Reinitialize the list pointers - InitializeListHead(&timerInfo->link); - timerInfo->state = timerInfo->state & ~TIMER_ACTIVE; -} - -DWORD WINAPI ThreadpoolMgr::AsyncDeleteTimer(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - MODE_PREEMPTIVE; - GC_TRIGGERS; - } - CONTRACTL_END; - - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - DeleteTimer((TimerInfo*) pArgs); - - return ERROR_SUCCESS; -} - -void ThreadpoolMgr::DeleteTimer(TimerInfo* timerInfo) -{ - CONTRACTL - { - if (GetThreadNULLOk() == pTimerThread) { NOTHROW; } else { THROWS; } - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE((timerInfo->state & TIMER_ACTIVE) == 0); - - _ASSERTE(!(timerInfo->flag & WAIT_FREE_CONTEXT)); - - if (timerInfo->flag & WAIT_INTERNAL_COMPLETION) - { - timerInfo->InternalCompletionEvent.Set(); - return; // the timerInfo will be deleted by the thread that's waiting on InternalCompletionEvent - } - - // ExternalCompletionEvent comes from Host, ExternalEventSafeHandle from managed code. - // They are mutually exclusive. - _ASSERTE(!(timerInfo->ExternalCompletionEvent != INVALID_HANDLE && - timerInfo->ExternalEventSafeHandle != NULL)); - - if (timerInfo->ExternalCompletionEvent != INVALID_HANDLE) - { - SetEvent(timerInfo->ExternalCompletionEvent); - timerInfo->ExternalCompletionEvent = INVALID_HANDLE; - } - - // We cannot block the timer thread, so some cleanup is deferred to other threads. - if (GetThreadNULLOk() == pTimerThread) - { - // Notify the ExternalEventSafeHandle with an user work item - if (timerInfo->ExternalEventSafeHandle != NULL) - { - BOOL success = FALSE; - EX_TRY - { - if (QueueUserWorkItem(AsyncDeleteTimer, - timerInfo, - QUEUE_ONLY) != FALSE) - { - success = TRUE; - } - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions); - - // If unable to queue a user work item, fall back to queueing timer for release - // which will happen *sometime* in the future. - if (success == FALSE) - { - QueueTimerInfoForRelease(timerInfo); - } - - return; - } - - // Releasing GC handles can block. So we wont do this on the timer thread. - // We'll put it in a list which will be processed by a worker thread - if (timerInfo->Context != NULL) - { - QueueTimerInfoForRelease(timerInfo); - return; - } - } - - // To get here we are either not the Timer thread or there is no blocking work to be done - - if (timerInfo->Context != NULL) - { - GCX_COOP(); - delete (ThreadpoolMgr::TimerInfoContext*)timerInfo->Context; - } - - if (timerInfo->ExternalEventSafeHandle != NULL) - { - ReleaseTimerInfo(timerInfo); - } - - delete timerInfo; - -} - -// We add TimerInfos from deleted timers into a linked list. -// A worker thread will later release the handles held by the TimerInfo -// and recycle them if possible. -void ThreadpoolMgr::QueueTimerInfoForRelease(TimerInfo *pTimerInfo) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - // The synchronization in this method depends on the fact that - // - There is only one timer thread - // - The one and only timer thread is executing this method. - // - This function wont go into an alertable state. That could trigger another APC. - // Else two threads can be queueing timerinfos and a race could - // lead to leaked memory and handles - _ASSERTE(pTimerThread == GetThread()); - TimerInfo *pHead = NULL; - - // Make sure this timer info has been deactivated and removed from any other lists - _ASSERTE((pTimerInfo->state & TIMER_ACTIVE) == 0); - //_ASSERTE(pTimerInfo->link.Blink == &(pTimerInfo->link) && - // pTimerInfo->link.Flink == &(pTimerInfo->link)); - // Make sure "link" is the first field in TimerInfo - _ASSERTE(pTimerInfo == (PVOID)&pTimerInfo->link); - - // Grab any previously published list - if ((pHead = InterlockedExchangeT(&TimerInfosToBeRecycled, NULL)) != NULL) - { - // If there already is a list, just append - InsertTailList((LIST_ENTRY *)pHead, &pTimerInfo->link); - pTimerInfo = pHead; - } - else - // If this is the head, make its next and previous ptrs point to itself - InitializeListHead((LIST_ENTRY*)&pTimerInfo->link); - - // Publish the list - (void) InterlockedExchangeT(&TimerInfosToBeRecycled, pTimerInfo); - -} - -void ThreadpoolMgr::FlushQueueOfTimerInfos() -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - TimerInfo *pHeadTimerInfo = NULL, *pCurrTimerInfo = NULL; - LIST_ENTRY *pNextInfo = NULL; - - if ((pHeadTimerInfo = InterlockedExchangeT(&TimerInfosToBeRecycled, NULL)) == NULL) - return; - - do - { - RemoveHeadList((LIST_ENTRY *)pHeadTimerInfo, pNextInfo); - _ASSERTE(pNextInfo != NULL); - - pCurrTimerInfo = (TimerInfo *) pNextInfo; - - GCX_COOP(); - if (pCurrTimerInfo->Context != NULL) - { - delete (ThreadpoolMgr::TimerInfoContext*)pCurrTimerInfo->Context; - } - - if (pCurrTimerInfo->ExternalEventSafeHandle != NULL) - { - ReleaseTimerInfo(pCurrTimerInfo); - } - - delete pCurrTimerInfo; - - } - while ((TimerInfo *)pNextInfo != pHeadTimerInfo); -} - -/************************************************************************/ -BOOL ThreadpoolMgr::ChangeTimerQueueTimer( - HANDLE Timer, - ULONG DueTime, - ULONG Period) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); - _ASSERTE(Timer); // not possible to give invalid handle in managed code - - NewHolder updateInfoHolder; - TimerUpdateInfo *updateInfo = new TimerUpdateInfo; - updateInfoHolder.Assign(updateInfo); - - updateInfo->Timer = (TimerInfo*) Timer; - updateInfo->DueTime = DueTime; - updateInfo->Period = Period; - - BOOL status = QueueUserAPC((PAPCFUNC)UpdateTimer, - TimerThread, - (size_t) updateInfo); - - if (status) - updateInfoHolder.SuppressRelease(); - - return(status); -} - -void ThreadpoolMgr::UpdateTimer(TimerUpdateInfo* pArgs) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - TimerUpdateInfo* updateInfo = (TimerUpdateInfo*) pArgs; - TimerInfo* timerInfo = updateInfo->Timer; - - timerInfo->Period = updateInfo->Period; - - if (updateInfo->DueTime == (ULONG) -1) - { - if (timerInfo->state & TIMER_ACTIVE) - { - DeactivateTimer(timerInfo); - } - // else, noop (the timer was already inactive) - _ASSERTE((timerInfo->state & TIMER_ACTIVE) == 0); - - delete updateInfo; - return; - } - - DWORD currentTime = GetTickCount(); - timerInfo->FiringTime = currentTime + updateInfo->DueTime; - - delete updateInfo; - - if (! (timerInfo->state & TIMER_ACTIVE)) - { - // timer not active (probably a one shot timer that has expired), so activate it - timerInfo->state |= TIMER_ACTIVE; - _ASSERTE(timerInfo->refCount >= 1); - // insert the timer in the queue - InsertTailList(&TimerQueue,(&timerInfo->link)); - - } - - return; -} - -/************************************************************************/ -BOOL ThreadpoolMgr::DeleteTimerQueueTimer( - HANDLE Timer, - HANDLE Event) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); // cannot call delete before creating timer - _ASSERTE(Timer); // not possible to give invalid handle in managed code - - // make volatile to avoid compiler reordering check after async call. - // otherwise, DeregisterTimer could delete timerInfo before the comparison. - VolatilePtr timerInfo = (TimerInfo*) Timer; - - if (Event == (HANDLE) -1) - { - //CONTRACT_VIOLATION(ThrowsViolation); - timerInfo->InternalCompletionEvent.CreateAutoEvent(FALSE); - timerInfo->flag |= WAIT_INTERNAL_COMPLETION; - } - else if (Event) - { - timerInfo->ExternalCompletionEvent = Event; - } -#ifdef _DEBUG - else /* Event == NULL */ - { - _ASSERTE(timerInfo->ExternalCompletionEvent == INVALID_HANDLE); - } -#endif - - BOOL isBlocking = timerInfo->flag & WAIT_INTERNAL_COMPLETION; - - BOOL status = QueueUserAPC((PAPCFUNC)DeregisterTimer, - TimerThread, - (size_t)(TimerInfo*)timerInfo); - - if (FALSE == status) - { - if (isBlocking) - timerInfo->InternalCompletionEvent.CloseEvent(); - return FALSE; - } - - if (isBlocking) - { - _ASSERTE(timerInfo->ExternalEventSafeHandle == NULL); - _ASSERTE(timerInfo->ExternalCompletionEvent == INVALID_HANDLE); - _ASSERTE(GetThreadNULLOk() != pTimerThread); - - timerInfo->InternalCompletionEvent.Wait(INFINITE,TRUE /*alertable*/); - timerInfo->InternalCompletionEvent.CloseEvent(); - // Release handles and delete TimerInfo - _ASSERTE(timerInfo->refCount == 0); - // if WAIT_INTERNAL_COMPLETION flag is not set, timerInfo will be deleted in DeleteTimer. - timerInfo->flag &= ~WAIT_INTERNAL_COMPLETION; - DeleteTimer(timerInfo); - } - return status; -} - -void ThreadpoolMgr::DeregisterTimer(TimerInfo* pArgs) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - TimerInfo* timerInfo = (TimerInfo*) pArgs; - - if (! (timerInfo->state & TIMER_REGISTERED) ) - { - // set state to deleted, so that it does not get registered - timerInfo->state |= TIMER_DELETE ; - - // since the timer has not even been registered, we dont need an interlock to decrease the RefCount - timerInfo->refCount--; - - return; - } - - if (timerInfo->state & TIMER_ACTIVE) - { - DeactivateTimer(timerInfo); - } - - if (InterlockedDecrement(&timerInfo->refCount) == 0 ) - { - DeleteTimer(timerInfo); - } - return; -} - -======= ->>>>>>> main -#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h deleted file mode 100644 index 8219920c8286b8..00000000000000 --- a/src/coreclr/vm/win32threadpool.h +++ /dev/null @@ -1,1001 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -/*++ - -Module Name: - - Win32ThreadPool.h - -Abstract: - - This module is the header file for thread pools using Win32 APIs. - -Revision History: - - ---*/ - -#ifndef _WIN32THREADPOOL_H -#define _WIN32THREADPOOL_H - -#include "delegateinfo.h" -#include "util.hpp" -#include "nativeoverlapped.h" - -#define MAX_WAITHANDLES 64 - -#define MAX_CACHED_EVENTS 40 // upper limit on number of wait events cached - -#define WAIT_REGISTERED 0x01 -#define WAIT_ACTIVE 0x02 -#define WAIT_DELETE 0x04 - -#define WAIT_SINGLE_EXECUTION 0x00000001 -#define WAIT_FREE_CONTEXT 0x00000002 -#define WAIT_INTERNAL_COMPLETION 0x00000004 - -#define QUEUE_ONLY 0x00000000 // do not attempt to call on the thread -#define CALL_OR_QUEUE 0x00000001 // call on the same thread if not too busy, else queue - -const int MaxLimitThreadsPerCPU=250; // upper limit on number of cp threads per CPU -const int MaxFreeCPThreadsPerCPU=2; // upper limit on number of free cp threads per CPU - -const int CpuUtilizationHigh=95; // remove threads when above this -const int CpuUtilizationLow =80; // inject more threads if below this - -#ifndef TARGET_UNIX -extern HANDLE (WINAPI *g_pufnCreateIoCompletionPort)(HANDLE FileHandle, - HANDLE ExistingCompletionPort, - ULONG_PTR CompletionKey, - DWORD NumberOfConcurrentThreads); - -extern int (WINAPI *g_pufnNtQueryInformationThread) (HANDLE ThreadHandle, - THREADINFOCLASS ThreadInformationClass, - PVOID ThreadInformation, - ULONG ThreadInformationLength, - PULONG ReturnLength); - -extern int (WINAPI * g_pufnNtQuerySystemInformation) (SYSTEM_INFORMATION_CLASS SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength OPTIONAL); -#endif // !TARGET_UNIX - -#define FILETIME_TO_INT64(t) (*(__int64*)&(t)) -#define MILLI_TO_100NANO(x) ((x) * 10000) // convert from milliseond to 100 nanosecond unit - -/** - * This type is supposed to be private to ThreadpoolMgr. - * It's at global scope because Strike needs to be able to access its - * definition. - */ -struct WorkRequest { - WorkRequest* next; - LPTHREAD_START_ROUTINE Function; - PVOID Context; - -}; - -typedef struct _IOCompletionContext -{ - DWORD ErrorCode; - DWORD numBytesTransferred; - LPOVERLAPPED lpOverlapped; - size_t key; -} IOCompletionContext, *PIOCompletionContext; - -typedef DPTR(WorkRequest) PTR_WorkRequest; -class ThreadpoolMgr -{ - friend class ClrDataAccess; - friend struct DelegateInfo; - friend class ThreadPoolNative; - friend class UnManagedPerAppDomainTPCount; - friend class ManagedPerAppDomainTPCount; - friend class PerAppDomainTPCountList; - friend class HillClimbing; - friend struct _DacGlobals; - -public: - struct ThreadCounter - { - static const int MaxPossibleCount = 0x7fff; - - // padding to ensure we get our own cache line - BYTE padding1[MAX_CACHE_LINE_SIZE]; - - union Counts - { - struct - { - // - // Note: these are signed rather than unsigned to allow us to detect under/overflow. - // - int MaxWorking : 16; //Determined by HillClimbing; adjusted elsewhere for timeouts, etc. - int NumActive : 16; //Active means working or waiting on WorkerSemaphore. These are "warm/hot" threads. - int NumWorking : 16; //Trying to get work from various queues. Not waiting on either semaphore. - int NumRetired : 16; //Not trying to get work; waiting on RetiredWorkerSemaphore. These are "cold" threads. - - // Note: the only reason we need "retired" threads at all is that it allows some threads to eventually time out - // even if other threads are getting work. If we ever make WorkerSemaphore a true LIFO semaphore, we will no longer - // need the concept of "retirement" - instead, the very "coldest" threads will naturally be the first to time out. - }; - - LONGLONG AsLongLong; - - bool operator==(Counts other) {LIMITED_METHOD_CONTRACT; return AsLongLong == other.AsLongLong;} - } counts; - - // padding to ensure we get our own cache line - BYTE padding2[MAX_CACHE_LINE_SIZE]; - - Counts GetCleanCounts() - { - LIMITED_METHOD_CONTRACT; -#ifdef HOST_64BIT - // VolatileLoad x64 bit read is atomic - return DangerousGetDirtyCounts(); -#else // !HOST_64BIT - // VolatileLoad may result in torn read - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, 0, 0); - ValidateCounts(result); -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; -#endif // !HOST_64BIT - } - - // - // This does a non-atomic read of the counts. The returned value is suitable only - // for use inside of a read-compare-exchange loop, where the compare-exhcange must succeed - // before any action is taken. Use GetCleanWorkerCounts for other needs, but keep in mind - // it's much slower. - // - Counts DangerousGetDirtyCounts() - { - LIMITED_METHOD_CONTRACT; - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = VolatileLoad(&counts.AsLongLong); -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; - } - - - Counts CompareExchangeCounts(Counts newCounts, Counts oldCounts) - { - LIMITED_METHOD_CONTRACT; - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, newCounts.AsLongLong, oldCounts.AsLongLong); - if (result == oldCounts) - { - // can only do validation on success; if we failed, it may have been due to a previous - // dirty read, which may contain invalid values. - ValidateCounts(result); - ValidateCounts(newCounts); - } -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; - } - - private: - static void ValidateCounts(Counts counts) - { - LIMITED_METHOD_CONTRACT; - _ASSERTE(counts.MaxWorking > 0); - _ASSERTE(counts.NumActive >= 0); - _ASSERTE(counts.NumWorking >= 0); - _ASSERTE(counts.NumRetired >= 0); - _ASSERTE(counts.NumWorking <= counts.NumActive); - } - }; - -public: - - // enumeration of different kinds of memory blocks that are recycled - enum MemType - { - MEMTYPE_AsyncCallback = 0, - MEMTYPE_DelegateInfo = 1, - MEMTYPE_WorkRequest = 2, - MEMTYPE_COUNT = 3, - }; - -#ifndef DACCESS_COMPILE - static void StaticInitialize() - { - WRAPPER_NO_CONTRACT; - - s_usePortableThreadPool = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPool) != 0; -#ifdef TARGET_WINDOWS - s_usePortableThreadPoolForIO = - s_usePortableThreadPool && - CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPoolForIO) != 0; -#else // !TARGET_WINDOWS - s_usePortableThreadPoolForIO = s_usePortableThreadPool; -#endif // TARGET_WINDOWS - } -#endif // !DACCESS_COMPILE - - static bool UsePortableThreadPool() - { - LIMITED_METHOD_CONTRACT; - return s_usePortableThreadPool; - } - - static bool UsePortableThreadPoolForIO() - { - LIMITED_METHOD_CONTRACT; - return s_usePortableThreadPoolForIO; - } - - static BOOL Initialize(); - - static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, - PVOID Context, - ULONG Flags, - BOOL UnmanagedTPRequest=TRUE); - - inline static BOOL IsCompletionPortInitialized() - { - LIMITED_METHOD_CONTRACT; - return GlobalCompletionPort != NULL; - } - - static BOOL RegisterWaitForSingleObject(PHANDLE phNewWaitObject, - HANDLE hWaitObject, - WAITORTIMERCALLBACK Callback, - PVOID Context, - ULONG timeout, - DWORD dwFlag); - - static BOOL UnregisterWaitEx(HANDLE hWaitObject,HANDLE CompletionEvent); - static void WaitHandleCleanup(HANDLE hWaitObject); - - static BOOL WINAPI BindIoCompletionCallback(HANDLE FileHandle, - LPOVERLAPPED_COMPLETION_ROUTINE Function, - ULONG Flags, - DWORD& errorCode); - - static void WINAPI WaitIOCompletionCallback(DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - static BOOL SetAppDomainRequestsActive(BOOL UnmanagedTP = FALSE); - static void ClearAppDomainRequestsActive(BOOL UnmanagedTP = FALSE, LONG index = -1); - - static inline void UpdateLastDequeueTime() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&LastDequeueTime, (unsigned int)GetTickCount()); - } - - static void RecycleMemory(LPVOID mem, enum MemType memType); - -#ifndef TARGET_UNIX - static LPOVERLAPPED CompletionPortDispatchWorkWithinAppDomain(Thread* pThread, DWORD* pErrorCode, DWORD* pNumBytes, size_t* pKey); - static void StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped); -#endif // !TARGET_UNIX - - // Enable filtering of correlation ETW events for cases handled at a higher abstraction level - -#ifndef DACCESS_COMPILE - static FORCEINLINE BOOL AreEtwIOQueueEventsSpeciallyHandled(LPOVERLAPPED_COMPLETION_ROUTINE Function) - { - // We handle registered waits at a higher abstraction level - return Function == ThreadpoolMgr::WaitIOCompletionCallback; - } -#endif - -private: - -#ifndef DACCESS_COMPILE - - inline static void FreeWorkRequest(WorkRequest* workRequest) - { - RecycleMemory( workRequest, MEMTYPE_WorkRequest ); //delete workRequest; - } - - inline static WorkRequest* MakeWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context) - { - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END;; - - WorkRequest* wr = (WorkRequest*) GetRecycledMemory(MEMTYPE_WorkRequest); - _ASSERTE(wr); - if (NULL == wr) - return NULL; - wr->Function = function; - wr->Context = context; - wr->next = NULL; - return wr; - } - -#endif // #ifndef DACCESS_COMPILE - - typedef struct { - DWORD numBytes; - ULONG_PTR *key; - LPOVERLAPPED pOverlapped; - DWORD errorCode; - } QueuedStatus; - - typedef DPTR(struct _LIST_ENTRY) PTR_LIST_ENTRY; - typedef struct _LIST_ENTRY { - struct _LIST_ENTRY *Flink; - struct _LIST_ENTRY *Blink; - } LIST_ENTRY, *PLIST_ENTRY; - - struct WaitInfo; - - typedef struct { - HANDLE threadHandle; - DWORD threadId; - CLREvent startEvent; - LONG NumWaitHandles; // number of wait objects registered to the thread <=64 - LONG NumActiveWaits; // number of objects, thread is actually waiting on (this may be less than - // NumWaitHandles since the thread may not have activated some waits - HANDLE waitHandle[MAX_WAITHANDLES]; // array of wait handles (copied from waitInfo since - // we need them to be contiguous) - LIST_ENTRY waitPointer[MAX_WAITHANDLES]; // array of doubly linked list of corresponding waitinfo - } ThreadCB; - - - typedef struct { - ULONG startTime; // time at which wait was started - // endTime = startTime+timeout - ULONG remainingTime; // endTime - currentTime - } WaitTimerInfo; - - struct WaitInfo { - LIST_ENTRY link; // Win9x does not allow duplicate waithandles, so we need to - // group all waits on a single waithandle using this linked list - HANDLE waitHandle; - WAITORTIMERCALLBACK Callback; - PVOID Context; - ULONG timeout; - WaitTimerInfo timer; - DWORD flag; - DWORD state; - ThreadCB* threadCB; - LONG refCount; // when this reaches 0, the waitInfo can be safely deleted - CLREvent PartialCompletionEvent; // used to synchronize deactivation of a wait - CLREvent InternalCompletionEvent; // only one of InternalCompletion or ExternalCompletion is used - // but I cant make a union since CLREvent has a non-default constructor - HANDLE ExternalCompletionEvent; // they are signalled when all callbacks have completed (refCount=0) - OBJECTHANDLE ExternalEventSafeHandle; - - } ; - - // structure used to maintain global information about wait threads. Protected by WaitThreadsCriticalSection - typedef struct WaitThreadTag { - LIST_ENTRY link; - ThreadCB* threadCB; - } WaitThreadInfo; - - - struct AsyncCallback{ - WaitInfo* wait; - BOOL waitTimedOut; - } ; - -#ifndef DACCESS_COMPILE - - static VOID - AcquireAsyncCallback(AsyncCallback *pAsyncCB) - { - LIMITED_METHOD_CONTRACT; - } - - static VOID - ReleaseAsyncCallback(AsyncCallback *pAsyncCB) - { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - WaitInfo *waitInfo = pAsyncCB->wait; - ThreadpoolMgr::RecycleMemory((LPVOID*)pAsyncCB, ThreadpoolMgr::MEMTYPE_AsyncCallback); - - // if this was a single execution, we now need to stop rooting registeredWaitHandle - // in a GC handle. This will cause the finalizer to pick it up and call the cleanup - // routine. - if ( (waitInfo->flag & WAIT_SINGLE_EXECUTION) && (waitInfo->flag & WAIT_FREE_CONTEXT)) - { - - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - _ASSERTE(pDelegate->m_registeredWaitHandle); - - { - GCX_COOP(); - StoreObjectInHandle(pDelegate->m_registeredWaitHandle, NULL); - } - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - ThreadpoolMgr::DeleteWait(waitInfo); - - } - - typedef Holder AsyncCallbackHolder; - inline static AsyncCallback* MakeAsyncCallback() - { - WRAPPER_NO_CONTRACT; - return (AsyncCallback*) GetRecycledMemory(MEMTYPE_AsyncCallback); - } - - static VOID ReleaseInfo(OBJECTHANDLE& hndSafeHandle, - HANDLE hndNativeHandle) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END - -// Use of EX_TRY, GCPROTECT etc in the same function is causing prefast to complain about local variables with -// same name masking each other (#246). The error could not be suppressed with "#pragma PREFAST_SUPPRESS" -#ifndef _PREFAST_ - - if (hndSafeHandle != NULL) - { - - SAFEHANDLEREF refSH = NULL; - - GCX_COOP(); - GCPROTECT_BEGIN(refSH); - - { - EX_TRY - { - // Read the GC handle - refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(ObjectFromHandle(hndSafeHandle)); - - // Destroy the GC handle - DestroyHandle(hndSafeHandle); - - if (refSH != NULL) - { - SafeHandleHolder h(&refSH); - - HANDLE hEvent = refSH->GetHandle(); - if (hEvent != INVALID_HANDLE_VALUE) - { - SetEvent(hEvent); - } - } - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions); - } - - GCPROTECT_END(); - - hndSafeHandle = NULL; - } -#endif - } - -#endif // #ifndef DACCESS_COMPILE - - typedef struct { - LIST_ENTRY link; - HANDLE Handle; - } WaitEvent ; - - static VOID AcquireWaitInfo(WaitInfo *pInfo) - { - } - static VOID ReleaseWaitInfo(WaitInfo *pInfo) - { - WRAPPER_NO_CONTRACT; -#ifndef DACCESS_COMPILE - ReleaseInfo(pInfo->ExternalEventSafeHandle, - pInfo->ExternalCompletionEvent); -#endif - } - - typedef Holder WaitInfoHolder; - - // Definitions and data structures to support recycling of high-frequency - // memory blocks. We use a spin-lock to access the list - - class RecycledListInfo - { - static const unsigned int MaxCachedEntries = 40; - - struct Entry - { - Entry* next; - }; - - Volatile lock; // this is the spin lock - DWORD count; // count of number of elements in the list - Entry* root; // ptr to first element of recycled list -#ifndef HOST_64BIT - DWORD filler; // Pad the structure to a multiple of the 16. -#endif - - //--// - -public: - RecycledListInfo() - { - LIMITED_METHOD_CONTRACT; - - lock = 0; - root = NULL; - count = 0; - } - - FORCEINLINE bool CanInsert() - { - LIMITED_METHOD_CONTRACT; - - return count < MaxCachedEntries; - } - - FORCEINLINE LPVOID Remove() - { - LIMITED_METHOD_CONTRACT; - - if(root == NULL) return NULL; // No need for acquiring the lock, there's nothing to remove. - - AcquireLock(); - - Entry* ret = (Entry*)root; - - if(ret) - { - root = ret->next; - count -= 1; - } - - ReleaseLock(); - - return ret; - } - - FORCEINLINE void Insert( LPVOID mem ) - { - LIMITED_METHOD_CONTRACT; - - AcquireLock(); - - Entry* entry = (Entry*)mem; - - entry->next = root; - - root = entry; - count += 1; - - ReleaseLock(); - } - - private: - FORCEINLINE void AcquireLock() - { - LIMITED_METHOD_CONTRACT; - - unsigned int rounds = 0; - - DWORD dwSwitchCount = 0; - - while(lock != 0 || InterlockedExchange( &lock, 1 ) != 0) - { - YieldProcessorNormalized(); // indicate to the processor that we are spinning - - rounds++; - - if((rounds % 32) == 0) - { - __SwitchToThread( 0, ++dwSwitchCount ); - } - } - } - - FORCEINLINE void ReleaseLock() - { - LIMITED_METHOD_CONTRACT; - - lock = 0; - } - }; - - // - // It's critical that we ensure these pointers are allocated by the linker away from - // variables that are modified a lot at runtime. - // - // The use of the CacheGuard is a temporary solution, - // the thread pool has to be refactor away from static variable and - // toward a single global structure, where we can control the locality of variables. - // - class RecycledListsWrapper - { - DWORD CacheGuardPre[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; - - RecycledListInfo (*pRecycledListPerProcessor)[MEMTYPE_COUNT]; // RecycledListInfo [numProc][MEMTYPE_COUNT] - - DWORD CacheGuardPost[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; - - public: - void Initialize( unsigned int numProcs ); - - FORCEINLINE bool IsInitialized() - { - LIMITED_METHOD_CONTRACT; - - return pRecycledListPerProcessor != NULL; - } - -#ifndef DACCESS_COMPILE - FORCEINLINE RecycledListInfo& GetRecycleMemoryInfo( enum MemType memType ) - { - LIMITED_METHOD_CONTRACT; - - DWORD processorNumber = 0; - -#ifndef TARGET_UNIX - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - processorNumber = CPUGroupInfo::CalculateCurrentProcessorNumber(); - else - // Turns out GetCurrentProcessorNumber can return a value greater than the number of processors reported by - // GetSystemInfo, if we're running in WOW64 on a machine with >32 processors. - processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; -#else // !TARGET_UNIX - if (PAL_HasGetCurrentProcessorNumber()) - { - // On linux, GetCurrentProcessorNumber which uses sched_getcpu() can return a value greater than the number - // of processors reported by sysconf(_SC_NPROCESSORS_ONLN) when using OpenVZ kernel. - processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; - } -#endif // !TARGET_UNIX - return pRecycledListPerProcessor[processorNumber][memType]; - } -#endif // DACCESS_COMPILE - }; - -#define GATE_THREAD_STATUS_NOT_RUNNING 0 // There is no gate thread -#define GATE_THREAD_STATUS_REQUESTED 1 // There is a gate thread, and someone has asked it to stick around recently -#define GATE_THREAD_STATUS_WAITING_FOR_REQUEST 2 // There is a gate thread, but nobody has asked it to stay. It may die soon - - // Private methods - - static Thread* CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread); - - static BOOL CreateWorkerThread(); - - static void EnqueueWorkRequest(WorkRequest* wr); - - static WorkRequest* DequeueWorkRequest(); - - static void ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled); - -#ifndef DACCESS_COMPILE - - inline static void AppendWorkRequest(WorkRequest* entry) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - if (WorkRequestTail) - { - _ASSERTE(WorkRequestHead != NULL); - WorkRequestTail->next = entry; - } - else - { - _ASSERTE(WorkRequestHead == NULL); - WorkRequestHead = entry; - } - - WorkRequestTail = entry; - _ASSERTE(WorkRequestTail->next == NULL); - } - - inline static WorkRequest* RemoveWorkRequest() - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - WorkRequest* entry = NULL; - if (WorkRequestHead) - { - entry = WorkRequestHead; - WorkRequestHead = entry->next; - if (WorkRequestHead == NULL) - WorkRequestTail = NULL; - } - return entry; - } - -public: - static void EnsureInitialized(); -private: - static void EnsureInitializedSlow(); - -public: - static void InitPlatformVariables(); - - inline static BOOL IsInitialized() - { - LIMITED_METHOD_CONTRACT; - return Initialization == -1; - } - - static void MaybeAddWorkingWorker(); - - static void NotifyWorkItemCompleted() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - Thread::IncrementWorkerThreadPoolCompletionCount(GetThreadNULLOk()); - UpdateLastDequeueTime(); - } - - static bool ShouldAdjustMaxWorkersActive() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - DWORD priorTime = PriorCompletedWorkRequestsTime; - MemoryBarrier(); // read fresh value for NextCompletedWorkRequestsTime below - DWORD requiredInterval = NextCompletedWorkRequestsTime - priorTime; - DWORD elapsedInterval = GetTickCount() - priorTime; - if (elapsedInterval >= requiredInterval) - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - if (counts.NumActive <= counts.MaxWorking) - return !IsHillClimbingDisabled; - } - - return false; - } - - static void AdjustMaxWorkersActive(); - static bool ShouldWorkerKeepRunning(); - - static DWORD SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable); - - static DWORD WINAPI WorkerThreadStart(LPVOID lpArgs); - - static BOOL AddWaitRequest(HANDLE waitHandle, WaitInfo* waitInfo); - - - static ThreadCB* FindWaitThread(); // returns a wait thread that can accomodate another wait request - - static BOOL CreateWaitThread(); - - static void WINAPI InsertNewWaitForSelf(WaitInfo* pArg); - - static int FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHandle); - - static DWORD MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits); - - static void ProcessWaitCompletion( WaitInfo* waitInfo, - unsigned index, // array index - BOOL waitTimedOut); - - static DWORD WINAPI WaitThreadStart(LPVOID lpArgs); - - static DWORD WINAPI AsyncCallbackCompletion(PVOID pArgs); - - static void DeactivateWait(WaitInfo* waitInfo); - static void DeactivateNthWait(WaitInfo* waitInfo, DWORD index); - - static void DeleteWait(WaitInfo* waitInfo); - - - inline static void ShiftWaitArray( ThreadCB* threadCB, - ULONG SrcIndex, - ULONG DestIndex, - ULONG count) - { - LIMITED_METHOD_CONTRACT; - memmove(&threadCB->waitHandle[DestIndex], - &threadCB->waitHandle[SrcIndex], - count * sizeof(HANDLE)); - memmove(&threadCB->waitPointer[DestIndex], - &threadCB->waitPointer[SrcIndex], - count * sizeof(LIST_ENTRY)); - } - - static void WINAPI DeregisterWait(WaitInfo* pArgs); - -#ifndef TARGET_UNIX - // holds the aggregate of system cpu usage of all processors - typedef struct _PROCESS_CPU_INFORMATION - { - LARGE_INTEGER idleTime; - LARGE_INTEGER kernelTime; - LARGE_INTEGER userTime; - DWORD_PTR affinityMask; - int numberOfProcessors; - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* usageBuffer; - int usageBufferSize; - } PROCESS_CPU_INFORMATION; - - static int GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo); - static BOOL CreateCompletionPortThread(LPVOID lpArgs); - static DWORD WINAPI CompletionPortThreadStart(LPVOID lpArgs); -public: - inline static bool HaveNativeWork() - { - LIMITED_METHOD_CONTRACT; - return WorkRequestHead != NULL; - } - - static void GrowCompletionPortThreadpoolIfNeeded(); - static BOOL ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts counts); -#else - static int GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo); - -#endif // !TARGET_UNIX - - static void PerformGateActivities(int cpuUtilization); - static bool NeedGateThreadForIOCompletions(); - -private: - static BOOL IsIoPending(); - - static BOOL CreateGateThread(); - static void EnsureGateThreadRunning(); - static bool ShouldGateThreadKeepRunning(); - static DWORD WINAPI GateThreadStart(LPVOID lpArgs); - static BOOL SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, - unsigned NumThreads, // total number of threads of that type (worker or CP) - double throttleRate=0.0 // the delay is increased by this percentage for each extra thread - ); - static BOOL SufficientDelaySinceLastDequeue(); - - static LPVOID GetRecycledMemory(enum MemType memType); - - inline static DWORD QueueDeregisterWait(HANDLE waitThread, WaitInfo* waitInfo) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - DWORD result = QueueUserAPC(reinterpret_cast(DeregisterWait), waitThread, reinterpret_cast(waitInfo)); - SetWaitThreadAPCPending(); - return result; - } - - - inline static void SetWaitThreadAPCPending() {IsApcPendingOnWaitThread = TRUE;} - inline static void ResetWaitThreadAPCPending() {IsApcPendingOnWaitThread = FALSE;} - inline static BOOL IsWaitThreadAPCPending() {return IsApcPendingOnWaitThread;} - -#endif // #ifndef DACCESS_COMPILE - // Private variables - - static Volatile Initialization; // indicator of whether the threadpool is initialized. - - static bool s_usePortableThreadPool; - static bool s_usePortableThreadPoolForIO; - - SVAL_DECL(LONG,MinLimitTotalWorkerThreads); // same as MinLimitTotalCPThreads - SVAL_DECL(LONG,MaxLimitTotalWorkerThreads); // same as MaxLimitTotalCPThreads - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static unsigned int LastDequeueTime; // used to determine if work items are getting thread starved - - static HillClimbing HillClimbingInstance; - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG PriorCompletedWorkRequests; - static DWORD PriorCompletedWorkRequestsTime; - static DWORD NextCompletedWorkRequestsTime; - - static LARGE_INTEGER CurrentSampleStartTime; - - static unsigned int WorkerThreadSpinLimit; - static bool IsHillClimbingDisabled; - static int ThreadAdjustmentInterval; - - SPTR_DECL(WorkRequest,WorkRequestHead); // Head of work request queue - SPTR_DECL(WorkRequest,WorkRequestTail); // Head of work request queue - - static unsigned int LastCPThreadCreation; // last time a completion port thread was created - static unsigned int NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads - - static BOOL IsApcPendingOnWaitThread; // Indicates if an APC is pending on the wait thread - - // This needs to be non-hosted, because worker threads can run prior to EE startup. - static DangerousNonHostedSpinLock ThreadAdjustmentLock; - -public: - static CrstStatic WorkerCriticalSection; - -private: - static const DWORD WorkerTimeout = 20 * 1000; - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_DECL(ThreadCounter,WorkerCounter); - - // - // WorkerSemaphore is an UnfairSemaphore because: - // 1) Threads enter and exit this semaphore very frequently, and thus benefit greatly from the spinning done by UnfairSemaphore - // 2) There is no functional reason why any particular thread should be preferred when waking workers. This only impacts performance, - // and un-fairness helps performance in this case. - // - static CLRLifoSemaphore* WorkerSemaphore; - - // - // RetiredWorkerSemaphore is a regular CLRSemaphore, not an UnfairSemaphore, because if a thread waits on this semaphore is it almost certainly - // NOT going to be released soon, so the spinning done in UnfairSemaphore only burns valuable CPU time. However, if UnfairSemaphore is ever - // implemented in terms of a Win32 IO Completion Port, we should reconsider this. The IOCP's LIFO unblocking behavior could help keep working set - // down, by constantly re-using the same small set of retired workers rather than round-robining between all of them as CLRSemaphore will do. - // If we go that route, we should add a "no-spin" option to UnfairSemaphore.Wait to avoid wasting CPU. - // - static CLRLifoSemaphore* RetiredWorkerSemaphore; - - static CLREvent * RetiredCPWakeupEvent; - - static CrstStatic WaitThreadsCriticalSection; - static LIST_ENTRY WaitThreadsHead; // queue of wait threads, each thread can handle upto 64 waits - - static BOOL InitCompletionPortThreadpool; // flag indicating whether completion port threadpool has been initialized - static HANDLE GlobalCompletionPort; // used for binding io completions on file handles - -public: - SVAL_DECL(ThreadCounter,CPThreadCounter); - -private: - SVAL_DECL(LONG,MaxLimitTotalCPThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS - SVAL_DECL(LONG,MinLimitTotalCPThreads); - SVAL_DECL(LONG,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG GateThreadStatus; // See GateThreadStatus enumeration - - SVAL_DECL(LONG,cpuUtilization); - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static RecycledListsWrapper RecycledLists; -}; - - - - -#endif // _WIN32THREADPOOL_H diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 2843f99347ca50..b206e6c227cebb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -105,8 +105,6 @@ private static void GateThreadStart() int cpuUtilization = cpuUtilizationReader.CurrentUtilization; threadPoolInstance._cpuUtilization = cpuUtilization; - bool needGateThreadForRuntime = ThreadPool.PerformRuntimeSpecificGateActivities(cpuUtilization); - if (!disableStarvationDetection && threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None && threadPoolInstance._separated.numRequestedWorkers > 0 && @@ -164,8 +162,7 @@ private static void GateThreadStart() } } - if (!needGateThreadForRuntime && - threadPoolInstance._separated.numRequestedWorkers <= 0 && + if (threadPoolInstance._separated.numRequestedWorkers <= 0 && threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None && Interlocked.Decrement(ref threadPoolInstance._separated.gateThreadRunningState) <= GetRunningStateForNumRuns(0)) { @@ -334,7 +331,5 @@ public bool HasBlockingAdjustmentDelayElapsed(int currentTimeMs, bool wasSignale } } } - - internal static void EnsureGateThreadRunning() => GateThread.EnsureRunning(ThreadPoolInstance); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 7ed9d611db71f8..c89de3c51589cf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -29,6 +29,12 @@ internal sealed partial class PortableThreadPool private const int CpuUtilizationHigh = 95; private const int CpuUtilizationLow = 80; +#if CORECLR +#pragma warning disable CA1823 + private static readonly bool s_initialized = ThreadPool.EnsureConfigInitialized(); +#pragma warning restore CA1823 +#endif + private static readonly short ForcedMinWorkerThreads = AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MinThreads", 0, false); private static readonly short ForcedMaxWorkerThreads = @@ -161,14 +167,7 @@ public bool SetMinThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO) - { - _legacy_minIOCompletionThreads = (short)Math.Max(1, ioCompletionThreads); - } - else - { - ThreadPool.SetMinIOCompletionThreads(ioCompletionThreads); - } + _legacy_minIOCompletionThreads = (short)Math.Max(1, ioCompletionThreads); short newMinThreads = (short)Math.Max(1, workerThreads); if (newMinThreads == _minThreads) @@ -251,14 +250,7 @@ public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO) - { - _legacy_maxIOCompletionThreads = (short)Math.Min(ioCompletionThreads, MaxPossibleThreadCount); - } - else - { - ThreadPool.SetMaxIOCompletionThreads(ioCompletionThreads); - } + _legacy_maxIOCompletionThreads = (short)Math.Min(ioCompletionThreads, MaxPossibleThreadCount); short newMaxThreads = (short)Math.Min(workerThreads, MaxPossibleThreadCount); if (newMaxThreads == _maxThreads) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs index de31539b74b0fa..19d55909abd63d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs @@ -106,11 +106,7 @@ internal void RestartTimeout() /// internal PortableThreadPool.WaitThread? WaitThread { get; set; } -#if CORECLR - private bool UnregisterPortable(WaitHandle waitObject) -#else public bool Unregister(WaitHandle waitObject) -#endif { // The registered wait handle must have been registered by this time, otherwise the instance is not handed out to // the caller of the public variants of RegisterWaitForSingleObject diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs index da8e83c5b009e9..024110ea93b925 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs @@ -14,8 +14,6 @@ public static partial class ThreadPool [SupportedOSPlatform("windows")] public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) { - Debug.Assert(UsePortableThreadPoolForIO); - if (overlapped == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index c831d39e28466f..39e1d6453263e7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -17,8 +17,6 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt public static partial class ThreadPool { - internal static bool UsePortableThreadPoolForIO => true; - // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work internal static bool YieldFromDispatchLoop => false; @@ -75,15 +73,6 @@ public static void GetAvailableThreads(out int workerThreads, out int completion /// internal static void RequestWorkerThread() => PortableThreadPool.ThreadPoolInstance.RequestWorker(); - /// - /// Called from the gate thread periodically to perform runtime-specific gate activities - /// - /// CPU utilization as a percentage since the last call - /// True if the runtime still needs to perform gate activities, false otherwise -#pragma warning disable IDE0060 - internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) => false; -#pragma warning restore IDE0060 - internal static void NotifyWorkItemProgress() => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs index bc49c5549f4f58..78d8b9294caa21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -1658,7 +1658,7 @@ public static long PendingWorkItemCount get { ThreadPoolWorkQueue workQueue = s_workQueue; - return ThreadPoolWorkQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount; + return ThreadPoolWorkQueue.LocalCount + workQueue.GlobalCount; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs index 2816f0ab6a858e..0e7892d36f3d92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs @@ -7,7 +7,7 @@ namespace System.Threading { // - // Unix-specific implementation of Timer + // Portable implementation of Timer // internal sealed partial class TimerQueue : IThreadPoolWorkItem { diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs index fb700981fb26e3..6a6dd5b82e0060 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs @@ -13,7 +13,5 @@ public static partial class ThreadPool internal static void ReportThreadStatus(bool isWorking) { } - - private static long PendingUnmanagedWorkItemCount => 0; } } diff --git a/src/mono/mono/eventpipe/ep-rt-mono.h b/src/mono/mono/eventpipe/ep-rt-mono.h index 095266b20d1f18..718336601ec987 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono.h +++ b/src/mono/mono/eventpipe/ep-rt-mono.h @@ -981,15 +981,6 @@ ep_rt_config_value_get_output_streaming (void) return enable; } -static -inline -bool -ep_rt_config_value_get_use_portable_thread_pool (void) -{ - // Only supports portable thread pool. - return true; -} - static inline uint32_t diff --git a/src/native/eventpipe/ep-rt.h b/src/native/eventpipe/ep-rt.h index efd681fdeec108..26a6b8714aebe4 100644 --- a/src/native/eventpipe/ep-rt.h +++ b/src/native/eventpipe/ep-rt.h @@ -392,10 +392,6 @@ inline bool ep_rt_config_value_get_output_streaming (void); -static -bool -ep_rt_config_value_get_use_portable_thread_pool (void); - /* * EventPipeSampleProfiler. */ From 98fe65e4c10c6e3d31d119163b5247c8f7308005 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 9 Jul 2022 10:52:41 -0700 Subject: [PATCH 16/20] Delete TPIndex --- src/coreclr/vm/vars.hpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index d6f8022940281c..2a26078ef485b4 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -621,25 +621,6 @@ GVAL_DECL(SIZE_T, g_runtimeVirtualSize); #define MAXULONGLONG UI64(0xffffffffffffffff) #endif -struct TPIndex -{ - DWORD m_dwIndex; - TPIndex () - : m_dwIndex(0) - {} - explicit TPIndex (DWORD id) - : m_dwIndex(id) - {} - BOOL operator==(const TPIndex& tpindex) const - { - return m_dwIndex == tpindex.m_dwIndex; - } - BOOL operator!=(const TPIndex& tpindex) const - { - return m_dwIndex != tpindex.m_dwIndex; - } -}; - // Every Module is assigned a ModuleIndex, regardless of whether the Module is domain // neutral or domain specific. When a domain specific Module is unloaded, its ModuleIndex // can be reused. From 43efcf439ad3f081d0b19320f9176593c382b595 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 9 Jul 2022 18:17:13 -0700 Subject: [PATCH 17/20] Update src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs Co-authored-by: Stephen Toub --- .../src/System/Threading/ThreadPool.CoreCLR.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 131947348a0193..da2d2a7399236f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -50,7 +50,7 @@ internal static bool EnsureConfigInitialized() return s_initialized; } - private static bool s_initialized = InitializeConfig(); + private static readonly bool s_initialized = InitializeConfig(); // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work From afdd6c859038f9ee4a4e1a83d1f7f9c855b06e6c Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 20 Aug 2022 18:21:20 +0200 Subject: [PATCH 18/20] CR feedback --- .../System/Threading/ThreadPool.CoreCLR.cs | 10 +- src/coreclr/inc/CrstTypes.def | 16 +--- src/coreclr/inc/crsttypes.h | 91 +++++++++---------- src/coreclr/vm/comthreadpool.cpp | 6 -- src/coreclr/vm/ecalllist.h | 5 - src/coreclr/vm/threads.cpp | 10 -- src/coreclr/vm/threads.h | 34 ------- 7 files changed, 45 insertions(+), 127 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index da2d2a7399236f..031ff0cda2c800 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -60,7 +60,7 @@ internal static bool EnsureConfigInitialized() private static unsafe bool InitializeConfig() { - int configVariableIndex = 0; + int configVariableIndex = 1; while (true) { int nextConfigVariableIndex = @@ -77,13 +77,7 @@ private static unsafe bool InitializeConfig() Debug.Assert(nextConfigVariableIndex > configVariableIndex); configVariableIndex = nextConfigVariableIndex; - if (appContextConfigNameUnsafe == null) - { - // Special case for UsePortableThreadPool and similar, which don't go into the AppContext - Debug.Assert(configValue != 0); - Debug.Assert(!isBoolean); - continue; - } + Debug.Assert(appContextConfigNameUnsafe != null); var appContextConfigName = new string(appContextConfigNameUnsafe); if (isBoolean) diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index 3d2cdcf8203b18..987ed684e622fe 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -292,7 +292,7 @@ End Crst JumpStubCache AcquiredBefore ExecuteManRangeLock LoaderHeap SingleUseLock AcquiredAfter AppDomainCache - ILStubGen ThreadpoolTimerQueue ThreadpoolWaitThreads + ILStubGen TypeIDMap BaseDomain AssemblyLoader End @@ -476,18 +476,6 @@ End Crst ThreadIdDispenser End -Crst ThreadpoolTimerQueue - AcquiredBefore UniqueStack -End - -Crst ThreadpoolWaitThreads - AcquiredBefore UniqueStack -End - -Crst ThreadpoolWorker - AcquiredBefore ThreadIdDispenser ThreadStore -End - Crst ThreadStore AcquiredBefore AvailableParamTypes DeadlockDetection DebuggerController DebuggerHeapLock DebuggerJitInfo DynamicIL ExecuteManRangeLock HandleTable IbcProfile @@ -561,7 +549,7 @@ End Crst TieredCompilation AcquiredAfter CodeVersioning - AcquiredBefore ThreadpoolTimerQueue + AcquiredBefore End Crst COMCallWrapper diff --git a/src/coreclr/inc/crsttypes.h b/src/coreclr/inc/crsttypes.h index b5e52aef0a4afb..5e01dd866bf5ec 100644 --- a/src/coreclr/inc/crsttypes.h +++ b/src/coreclr/inc/crsttypes.h @@ -120,21 +120,18 @@ enum CrstType CrstSystemDomain = 102, CrstSystemDomainDelayedUnloadList = 103, CrstThreadIdDispenser = 104, - CrstThreadpoolTimerQueue = 105, - CrstThreadpoolWaitThreads = 106, - CrstThreadpoolWorker = 107, - CrstThreadStore = 108, - CrstTieredCompilation = 109, - CrstTypeEquivalenceMap = 110, - CrstTypeIDMap = 111, - CrstUMEntryThunkCache = 112, - CrstUMEntryThunkFreeListLock = 113, - CrstUniqueStack = 114, - CrstUnresolvedClassLock = 115, - CrstUnwindInfoTableLock = 116, - CrstVSDIndirectionCellLock = 117, - CrstWrapperTemplate = 118, - kNumberOfCrstTypes = 119 + CrstThreadStore = 105, + CrstTieredCompilation = 106, + CrstTypeEquivalenceMap = 107, + CrstTypeIDMap = 108, + CrstUMEntryThunkCache = 109, + CrstUMEntryThunkFreeListLock = 110, + CrstUniqueStack = 111, + CrstUnresolvedClassLock = 112, + CrstUnwindInfoTableLock = 113, + CrstVSDIndirectionCellLock = 114, + CrstWrapperTemplate = 115, + kNumberOfCrstTypes = 116 }; #endif // __CRST_TYPES_INCLUDED @@ -145,19 +142,19 @@ enum CrstType // An array mapping CrstType to level. int g_rgCrstLevelMap[] = { - 10, // CrstAppDomainCache + 9, // CrstAppDomainCache 3, // CrstArgBasedStubCache 0, // CrstAssemblyList - 12, // CrstAssemblyLoader + 11, // CrstAssemblyLoader 4, // CrstAvailableClass 5, // CrstAvailableParamTypes 7, // CrstBaseDomain -1, // CrstCCompRC - 13, // CrstClassFactInfoHash - 11, // CrstClassInit + 12, // CrstClassFactInfoHash + 10, // CrstClassInit -1, // CrstClrNotification 6, // CrstCodeFragmentHeap - 9, // CrstCodeVersioning + 8, // CrstCodeVersioning 0, // CrstCOMCallWrapper 5, // CrstCOMWrapperCache 3, // CrstDataTest1 @@ -169,13 +166,13 @@ int g_rgCrstLevelMap[] = 0, // CrstDebuggerHeapExecMemLock 0, // CrstDebuggerHeapLock 4, // CrstDebuggerJitInfo - 10, // CrstDebuggerMutex + 9, // CrstDebuggerMutex 0, // CrstDelegateToFPtrHash - 16, // CrstDomainLocalBlock + 15, // CrstDomainLocalBlock 0, // CrstDynamicIL 3, // CrstDynamicMT 0, // CrstEtwTypeLogHash - 18, // CrstEventPipe + 17, // CrstEventPipe 0, // CrstEventStore 0, // CrstException 0, // CrstExecutableAllocatorLock @@ -183,55 +180,55 @@ int g_rgCrstLevelMap[] = 0, // CrstExternalObjectContextCache 4, // CrstFCall 7, // CrstFuncPtrStubs - 10, // CrstFusionAppCtx - 10, // CrstGCCover - 15, // CrstGlobalStrLiteralMap + 9, // CrstFusionAppCtx + 9, // CrstGCCover + 14, // CrstGlobalStrLiteralMap 1, // CrstHandleTable 0, // CrstIbcProfile 8, // CrstIJWFixupData 0, // CrstIJWHash 7, // CrstILStubGen 3, // CrstInlineTrackingMap - 17, // CrstInstMethodHashTable - 20, // CrstInterop + 16, // CrstInstMethodHashTable + 19, // CrstInterop 5, // CrstInteropData 0, // CrstIsJMCMethod 7, // CrstISymUnmanagedReader - 11, // CrstJit + 10, // CrstJit 0, // CrstJitGenericHandleCache - 13, // CrstJitInlineTrackingMap + 12, // CrstJitInlineTrackingMap 4, // CrstJitPatchpoint -1, // CrstJitPerf 6, // CrstJumpStubCache 0, // CrstLeafLock -1, // CrstListLock - 15, // CrstLoaderAllocator - 16, // CrstLoaderAllocatorReferences + 14, // CrstLoaderAllocator + 15, // CrstLoaderAllocatorReferences 3, // CrstLoaderHeap 3, // CrstManagedObjectWrapperMap - 10, // CrstMethodDescBackpatchInfoTracker + 9, // CrstMethodDescBackpatchInfoTracker 5, // CrstModule - 16, // CrstModuleFixup + 15, // CrstModuleFixup 4, // CrstModuleLookupTable 0, // CrstMulticoreJitHash - 13, // CrstMulticoreJitManager + 12, // CrstMulticoreJitManager 3, // CrstNativeImageEagerFixups 0, // CrstNativeImageLoad 0, // CrstNls 0, // CrstNotifyGdb 2, // CrstObjectList 5, // CrstPEImage - 19, // CrstPendingTypeLoadEntry + 18, // CrstPendingTypeLoadEntry 4, // CrstPgoData 0, // CrstPinnedByrefValidation - 14, // CrstPinnedHeapHandleTable + 13, // CrstPinnedHeapHandleTable 0, // CrstProfilerGCRefDataFreeList - 13, // CrstProfilingAPIStatus + 12, // CrstProfilingAPIStatus 4, // CrstRCWCache 0, // CrstRCWCleanupList - 10, // CrstReadyToRunEntryPointToMethodDescMap + 9, // CrstReadyToRunEntryPointToMethodDescMap 8, // CrstReflection - 14, // CrstReJITGlobalRequest + 13, // CrstReJITGlobalRequest 4, // CrstRetThunkCache 3, // CrstSavedExceptionInfo 0, // CrstSaveModuleProfileData @@ -247,16 +244,13 @@ int g_rgCrstLevelMap[] = 3, // CrstSyncBlockCache 0, // CrstSyncHashLock 5, // CrstSystemBaseDomain - 13, // CrstSystemDomain + 12, // CrstSystemDomain 0, // CrstSystemDomainDelayedUnloadList 0, // CrstThreadIdDispenser - 7, // CrstThreadpoolTimerQueue - 7, // CrstThreadpoolWaitThreads - 13, // CrstThreadpoolWorker - 12, // CrstThreadStore - 8, // CrstTieredCompilation + 11, // CrstThreadStore + 0, // CrstTieredCompilation 4, // CrstTypeEquivalenceMap - 10, // CrstTypeIDMap + 9, // CrstTypeIDMap 4, // CrstUMEntryThunkCache 3, // CrstUMEntryThunkFreeListLock 4, // CrstUniqueStack @@ -374,9 +368,6 @@ LPCSTR g_rgCrstNameMap[] = "CrstSystemDomain", "CrstSystemDomainDelayedUnloadList", "CrstThreadIdDispenser", - "CrstThreadpoolTimerQueue", - "CrstThreadpoolWaitThreads", - "CrstThreadpoolWorker", "CrstThreadStore", "CrstTieredCompilation", "CrstTypeEquivalenceMap", diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 981b075eeb0a32..60a84ac8103a5d 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -60,12 +60,6 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, switch (configVariableIndex) { - case 0: - *configValueRef = 2; - *isBooleanRef = false; - *appContextConfigNameRef = NULL; - return 1; - case 1: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads, false, W("System.Threading.ThreadPool.MinThreads"))) { return 2; } FALLTHROUGH; case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return 3; } FALLTHROUGH; case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return 4; } FALLTHROUGH; diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index ed9713c3df3274..bf3c849f318642 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -396,9 +396,6 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetNextConfigUInt32Value", ThreadPoolNative::GetNextConfigUInt32Value) FCFuncEnd() -FCFuncStart(gRegisteredWaitHandleFuncs) -FCFuncEnd() - FCFuncStart(gWaitHandleFuncs) FCFuncElement("WaitOneCore", WaitHandleNative::CorWaitOneNative) FCFuncElement("WaitMultipleIgnoringSyncContext", WaitHandleNative::CorWaitMultipleNative) @@ -775,8 +772,6 @@ FCClassElement("ObjectMarshaler", "System.StubHelpers", gObjectMarshalerFuncs) #endif FCClassElement("OverlappedData", "System.Threading", gOverlappedFuncs) -FCClassElement("RegisteredWaitHandle", "System.Threading", gRegisteredWaitHandleFuncs) - FCClassElement("RuntimeAssembly", "System.Reflection", gRuntimeAssemblyFuncs) FCClassElement("RuntimeFieldHandle", "System", gCOMFieldHandleNewFuncs) FCClassElement("RuntimeHelpers", "System.Runtime.CompilerServices", gRuntimeHelpers) diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index b9ccde121583f6..e1bc975673db28 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -134,8 +134,6 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT) BOOL Thread::s_fCleanFinalizedThread = FALSE; -UINT64 Thread::s_workerThreadPoolCompletionCountOverflow = 0; -UINT64 Thread::s_ioThreadPoolCompletionCountOverflow = 0; UINT64 Thread::s_monitorLockContentionCountOverflow = 0; CrstStatic g_DeadlockAwareCrst; @@ -1596,8 +1594,6 @@ Thread::Thread() m_sfEstablisherOfActualHandlerFrame.Clear(); #endif // FEATURE_EH_FUNCLETS - m_workerThreadPoolCompletionCount = 0; - m_ioThreadPoolCompletionCount = 0; m_monitorLockContentionCount = 0; m_pDomain = SystemDomain::System()->DefaultDomain(); @@ -5345,12 +5341,6 @@ BOOL ThreadStore::RemoveThread(Thread *target) if (target->IsBackground()) s_pThreadStore->m_BackgroundThreadCount--; - InterlockedExchangeAdd64( - (LONGLONG *)&Thread::s_workerThreadPoolCompletionCountOverflow, - target->m_workerThreadPoolCompletionCount); - InterlockedExchangeAdd64( - (LONGLONG *)&Thread::s_ioThreadPoolCompletionCountOverflow, - target->m_ioThreadPoolCompletionCount); InterlockedExchangeAdd64( (LONGLONG *)&Thread::s_monitorLockContentionCountOverflow, target->m_monitorLockContentionCount); diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 5ab0b0464d927f..b51804a471705d 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -3432,10 +3432,6 @@ class Thread #endif // defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) private: - UINT32 m_workerThreadPoolCompletionCount; - static UINT64 s_workerThreadPoolCompletionCountOverflow; - UINT32 m_ioThreadPoolCompletionCount; - static UINT64 s_ioThreadPoolCompletionCountOverflow; UINT32 m_monitorLockContentionCount; static UINT64 s_monitorLockContentionCountOverflow; @@ -3489,36 +3485,6 @@ class Thread static UINT64 GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount); public: - static void IncrementWorkerThreadPoolCompletionCount(Thread *pThread) - { - WRAPPER_NO_CONTRACT; - IncrementCount(pThread, offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); - } - - static UINT64 GetWorkerThreadPoolCompletionCountOverflow() - { - WRAPPER_NO_CONTRACT; - return GetOverflowCount(&s_workerThreadPoolCompletionCountOverflow); - } - - static UINT64 GetTotalWorkerThreadPoolCompletionCount() - { - WRAPPER_NO_CONTRACT; - return GetTotalCount(offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); - } - - static void IncrementIOThreadPoolCompletionCount(Thread *pThread) - { - WRAPPER_NO_CONTRACT; - IncrementCount(pThread, offsetof(Thread, m_ioThreadPoolCompletionCount), &s_ioThreadPoolCompletionCountOverflow); - } - - static UINT64 GetIOThreadPoolCompletionCountOverflow() - { - WRAPPER_NO_CONTRACT; - return GetOverflowCount(&s_ioThreadPoolCompletionCountOverflow); - } - static void IncrementMonitorLockContentionCount(Thread *pThread) { WRAPPER_NO_CONTRACT; From e0ba959c0646b6b2af566096284d89d6b68dd668 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 20 Aug 2022 19:53:38 +0200 Subject: [PATCH 19/20] Fix crst ordering` --- src/coreclr/inc/CrstTypes.def | 2 +- src/coreclr/inc/crsttypes.h | 58 +++++++++++++++++------------------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index 987ed684e622fe..5ab977844a4deb 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -549,7 +549,7 @@ End Crst TieredCompilation AcquiredAfter CodeVersioning - AcquiredBefore + AcquiredBefore FuncPtrStubs End Crst COMCallWrapper diff --git a/src/coreclr/inc/crsttypes.h b/src/coreclr/inc/crsttypes.h index 5e01dd866bf5ec..c41a84b50073f6 100644 --- a/src/coreclr/inc/crsttypes.h +++ b/src/coreclr/inc/crsttypes.h @@ -142,19 +142,19 @@ enum CrstType // An array mapping CrstType to level. int g_rgCrstLevelMap[] = { - 9, // CrstAppDomainCache + 10, // CrstAppDomainCache 3, // CrstArgBasedStubCache 0, // CrstAssemblyList - 11, // CrstAssemblyLoader + 12, // CrstAssemblyLoader 4, // CrstAvailableClass 5, // CrstAvailableParamTypes 7, // CrstBaseDomain -1, // CrstCCompRC - 12, // CrstClassFactInfoHash - 10, // CrstClassInit + 13, // CrstClassFactInfoHash + 11, // CrstClassInit -1, // CrstClrNotification 6, // CrstCodeFragmentHeap - 8, // CrstCodeVersioning + 9, // CrstCodeVersioning 0, // CrstCOMCallWrapper 5, // CrstCOMWrapperCache 3, // CrstDataTest1 @@ -166,13 +166,13 @@ int g_rgCrstLevelMap[] = 0, // CrstDebuggerHeapExecMemLock 0, // CrstDebuggerHeapLock 4, // CrstDebuggerJitInfo - 9, // CrstDebuggerMutex + 10, // CrstDebuggerMutex 0, // CrstDelegateToFPtrHash - 15, // CrstDomainLocalBlock + 16, // CrstDomainLocalBlock 0, // CrstDynamicIL 3, // CrstDynamicMT 0, // CrstEtwTypeLogHash - 17, // CrstEventPipe + 18, // CrstEventPipe 0, // CrstEventStore 0, // CrstException 0, // CrstExecutableAllocatorLock @@ -180,55 +180,55 @@ int g_rgCrstLevelMap[] = 0, // CrstExternalObjectContextCache 4, // CrstFCall 7, // CrstFuncPtrStubs - 9, // CrstFusionAppCtx - 9, // CrstGCCover - 14, // CrstGlobalStrLiteralMap + 10, // CrstFusionAppCtx + 10, // CrstGCCover + 15, // CrstGlobalStrLiteralMap 1, // CrstHandleTable 0, // CrstIbcProfile 8, // CrstIJWFixupData 0, // CrstIJWHash 7, // CrstILStubGen 3, // CrstInlineTrackingMap - 16, // CrstInstMethodHashTable - 19, // CrstInterop + 17, // CrstInstMethodHashTable + 20, // CrstInterop 5, // CrstInteropData 0, // CrstIsJMCMethod 7, // CrstISymUnmanagedReader - 10, // CrstJit + 11, // CrstJit 0, // CrstJitGenericHandleCache - 12, // CrstJitInlineTrackingMap + 13, // CrstJitInlineTrackingMap 4, // CrstJitPatchpoint -1, // CrstJitPerf 6, // CrstJumpStubCache 0, // CrstLeafLock -1, // CrstListLock - 14, // CrstLoaderAllocator - 15, // CrstLoaderAllocatorReferences + 15, // CrstLoaderAllocator + 16, // CrstLoaderAllocatorReferences 3, // CrstLoaderHeap 3, // CrstManagedObjectWrapperMap - 9, // CrstMethodDescBackpatchInfoTracker + 10, // CrstMethodDescBackpatchInfoTracker 5, // CrstModule - 15, // CrstModuleFixup + 16, // CrstModuleFixup 4, // CrstModuleLookupTable 0, // CrstMulticoreJitHash - 12, // CrstMulticoreJitManager + 13, // CrstMulticoreJitManager 3, // CrstNativeImageEagerFixups 0, // CrstNativeImageLoad 0, // CrstNls 0, // CrstNotifyGdb 2, // CrstObjectList 5, // CrstPEImage - 18, // CrstPendingTypeLoadEntry + 19, // CrstPendingTypeLoadEntry 4, // CrstPgoData 0, // CrstPinnedByrefValidation - 13, // CrstPinnedHeapHandleTable + 14, // CrstPinnedHeapHandleTable 0, // CrstProfilerGCRefDataFreeList - 12, // CrstProfilingAPIStatus + 13, // CrstProfilingAPIStatus 4, // CrstRCWCache 0, // CrstRCWCleanupList - 9, // CrstReadyToRunEntryPointToMethodDescMap + 10, // CrstReadyToRunEntryPointToMethodDescMap 8, // CrstReflection - 13, // CrstReJITGlobalRequest + 14, // CrstReJITGlobalRequest 4, // CrstRetThunkCache 3, // CrstSavedExceptionInfo 0, // CrstSaveModuleProfileData @@ -244,13 +244,13 @@ int g_rgCrstLevelMap[] = 3, // CrstSyncBlockCache 0, // CrstSyncHashLock 5, // CrstSystemBaseDomain - 12, // CrstSystemDomain + 13, // CrstSystemDomain 0, // CrstSystemDomainDelayedUnloadList 0, // CrstThreadIdDispenser - 11, // CrstThreadStore - 0, // CrstTieredCompilation + 12, // CrstThreadStore + 8, // CrstTieredCompilation 4, // CrstTypeEquivalenceMap - 9, // CrstTypeIDMap + 10, // CrstTypeIDMap 4, // CrstUMEntryThunkCache 3, // CrstUMEntryThunkFreeListLock 4, // CrstUniqueStack From 11da1f52387d6a64b710c3311c7274dfd47e326c Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 20 Aug 2022 20:01:25 +0200 Subject: [PATCH 20/20] Cleanup --- src/coreclr/vm/comthreadpool.cpp | 23 +---------------------- src/coreclr/vm/comthreadpool.h | 13 ------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 60a84ac8103a5d..2d941b130991de 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -1,31 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Header: COMThreadPool.cpp -** -** Purpose: Native methods on System.ThreadPool -** and its inner classes -** -** -===========================================================*/ - -/********************************************************************************************************************/ #include "common.h" -#include "comdelegate.h" + #include "comthreadpool.h" -#include "class.h" -#include "object.h" -#include "field.h" -#include "excep.h" #include "eeconfig.h" -#include "corhost.h" -#include "nativeoverlapped.h" -#include "comsynchronizable.h" -#include "callhelpers.h" -#include "appdomain.inl" /*****************************************************************************************************/ // Enumerates some runtime config variables that are used by CoreLib for initialization. The config variable index should start diff --git a/src/coreclr/vm/comthreadpool.h b/src/coreclr/vm/comthreadpool.h index 5b21de54b6b5d1..e3ad28c01c0c17 100644 --- a/src/coreclr/vm/comthreadpool.h +++ b/src/coreclr/vm/comthreadpool.h @@ -1,22 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Header: COMThreadPool.h -** -** Purpose: Native methods on System.ThreadPool -** and its inner classes -** -** -===========================================================*/ - #ifndef _COMTHREADPOOL_H #define _COMTHREADPOOL_H -#include "nativeoverlapped.h" - class ThreadPoolNative { public: