You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1262 lines
30 KiB
1262 lines
30 KiB
/*++
|
|
|
|
Copyright (c) 2000-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
thread_manager.cxx
|
|
|
|
Abstract:
|
|
|
|
implementation for THREAD_MANAGER
|
|
|
|
Author:
|
|
|
|
Jeffrey Wall (jeffwall) 11-28-2000
|
|
|
|
Revision History:
|
|
|
|
|
|
|
|
--*/
|
|
|
|
#include <iis.h>
|
|
#include <dbgutil.h>
|
|
#include "thread_manager.h"
|
|
#include "thread_pool_private.h"
|
|
|
|
TCHAR g_szModuleName[MAX_PATH];
|
|
DWORD g_dwcCPU = 1;
|
|
|
|
HMODULE g_hmodW3TPDLL = NULL;
|
|
|
|
BOOL
|
|
TooMuchContextSwitchingLoad(ULONG ulFirstSample,
|
|
DWORD dwFirstSampleTime,
|
|
ULONG ulPerSecondSwitchRateMax,
|
|
DWORD dwNumProcs);
|
|
|
|
BOOL
|
|
TooMuchProcessorUsage(LARGE_INTEGER liOriginalBusy,
|
|
LARGE_INTEGER liOriginalTotal,
|
|
LONG lPercentageUseMax,
|
|
DWORD dwNumProcs);
|
|
|
|
BOOL GetContextSwitchCount(ULONG * pulSwitchCount);
|
|
|
|
HRESULT
|
|
GetCPUData(LARGE_INTEGER * pBusyTime,
|
|
LARGE_INTEGER * pTotalTime,
|
|
DWORD dwNumProcs);
|
|
|
|
//static
|
|
HRESULT
|
|
THREAD_MANAGER::CreateThreadManager(THREAD_MANAGER ** ppManager,
|
|
THREAD_POOL * pPool,
|
|
THREAD_POOL_DATA * pPoolData)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Allocate and initialize a THREAD_MANAGER
|
|
|
|
Arguments:
|
|
|
|
ppManager - where to store allocated manager pointer
|
|
pPool - pointer to THREAD_POOL associated with this manager
|
|
pPoolData - pointer to THREAD_POOL_DATA associated with this manager
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
--*/
|
|
{
|
|
DBG_ASSERT(NULL != ppManager);
|
|
DBG_ASSERT(NULL != pPool);
|
|
DBG_ASSERT(NULL != pPoolData);
|
|
|
|
*ppManager = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
THREAD_MANAGER * pManager = new THREAD_MANAGER(pPool, pPoolData);
|
|
if (NULL == pManager)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto done;
|
|
}
|
|
|
|
hr = pManager->Initialize();
|
|
if (FAILED(hr))
|
|
{
|
|
// don't call TerminateThreadManager - just delete it
|
|
delete pManager;
|
|
|
|
goto done;
|
|
}
|
|
|
|
*ppManager = pManager;
|
|
|
|
hr = S_OK;
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::TerminateThreadManager(LPTHREAD_STOP_ROUTINE lpStopAddress,
|
|
LPVOID lpParameter)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Shutdown ALL THREAD_MANAGER theads and destroy a THREAD_MANAGER
|
|
|
|
Arguments:
|
|
|
|
lpStopAddress - address of function that will stop threads
|
|
lpParameter - argument to pass to stop function
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
--*/
|
|
{
|
|
// block until threads are gone
|
|
DrainThreads(lpStopAddress, lpParameter);
|
|
|
|
// do some initializion succeeded only cleanup
|
|
if (m_hShutdownEvent)
|
|
{
|
|
CloseHandle(m_hShutdownEvent);
|
|
m_hShutdownEvent = NULL;
|
|
}
|
|
if (m_hParkEvent)
|
|
{
|
|
CloseHandle(m_hParkEvent);
|
|
m_hParkEvent = NULL;
|
|
}
|
|
|
|
DeleteCriticalSection(&m_CriticalSection);
|
|
|
|
// don't do anything else after deletion
|
|
delete this;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
THREAD_MANAGER::THREAD_MANAGER(THREAD_POOL * pPool,
|
|
THREAD_POOL_DATA * pPoolData) :
|
|
m_dwSignature(SIGNATURE_THREAD_MANAGER),
|
|
m_fShuttingDown(FALSE),
|
|
m_fWaitingForCreationCallback(FALSE),
|
|
m_hTimer(NULL),
|
|
m_pParam(NULL),
|
|
m_hParkEvent(NULL),
|
|
m_hShutdownEvent(NULL),
|
|
m_lParkedThreads(0),
|
|
m_pPool(pPool),
|
|
m_pPoolData(pPoolData),
|
|
m_lTotalThreads(0)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructs the ThreadManager
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT(m_pPool);
|
|
DBG_ASSERT(m_pPoolData);
|
|
ZeroMemory(&m_liOriginalBusy, sizeof(LARGE_INTEGER));
|
|
ZeroMemory(&m_liOriginalTotal, sizeof(LARGE_INTEGER));
|
|
|
|
return;
|
|
}
|
|
|
|
HRESULT
|
|
THREAD_MANAGER::Initialize()
|
|
/*++
|
|
|
|
Routine Description:
|
|
Do initialization for THREAD_MANAGER
|
|
|
|
Arguments:
|
|
|
|
VOID
|
|
Return Value:
|
|
|
|
HRESULT
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
DWORD dwRet;
|
|
HKEY hKey;
|
|
BOOL fRet;
|
|
|
|
// Must be set by the DLL main of the DLL loading w3tp
|
|
DBG_ASSERT( NULL != g_hmodW3TPDLL );
|
|
|
|
if ( GetModuleFileNameW(
|
|
g_hmodW3TPDLL,
|
|
g_szModuleName,
|
|
MAX_PATH
|
|
) == 0 )
|
|
{
|
|
DBG_ASSERT(FALSE && "Failed getting module name for w3tp.dll");
|
|
hr = E_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
m_hParkEvent = CreateEvent(NULL, // security descriptor
|
|
FALSE, // auto reset
|
|
FALSE, // not signaled at creation
|
|
NULL // event name
|
|
);
|
|
if (NULL == m_hParkEvent)
|
|
{
|
|
DBG_ASSERT(FALSE && "Could not create parking event");
|
|
hr = E_FAIL;
|
|
goto done;
|
|
}
|
|
m_hShutdownEvent = CreateEvent(NULL, // security descriptor
|
|
TRUE, // manual reset
|
|
FALSE, // not signaled at creation
|
|
NULL // event name
|
|
);
|
|
if (NULL == m_hShutdownEvent)
|
|
{
|
|
DBG_ASSERT(FALSE && "Could not create shutdown event");
|
|
hr = E_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
|
|
// keep this at the end of Initialize - if it fails, no need to clean it up ever
|
|
// If it succeededs, no need to clean it up in this function.
|
|
|
|
// By setting the high order bit for dwSpinCount, we preallocate the CriticalSection
|
|
fRet = InitializeCriticalSectionAndSpinCount(&m_CriticalSection,
|
|
0x80000000 );
|
|
if (FALSE == fRet)
|
|
{
|
|
DBG_ASSERT(FALSE && "Could not initialize critical section!");
|
|
hr = E_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
hr = S_OK;
|
|
done:
|
|
if (FAILED(hr))
|
|
{
|
|
if (m_hParkEvent)
|
|
{
|
|
CloseHandle(m_hParkEvent);
|
|
m_hParkEvent = NULL;
|
|
}
|
|
if (m_hShutdownEvent)
|
|
{
|
|
CloseHandle(m_hShutdownEvent);
|
|
m_hShutdownEvent = NULL;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
THREAD_MANAGER::~THREAD_MANAGER()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destructs the ThreadManager
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT(SIGNATURE_THREAD_MANAGER == m_dwSignature);
|
|
m_dwSignature = SIGNATURE_THREAD_MANAGER_FREE;
|
|
|
|
DBG_ASSERT(TRUE == m_fShuttingDown && "DrainThreads was not called!");
|
|
|
|
DBG_ASSERT(NULL == m_hTimer);
|
|
DBG_ASSERT(NULL == m_pParam);
|
|
DBG_ASSERT(0 == m_lParkedThreads);
|
|
DBG_ASSERT(0 == m_lTotalThreads);
|
|
|
|
m_pPool = NULL;
|
|
m_pPoolData = NULL;
|
|
}
|
|
|
|
//static
|
|
DWORD
|
|
THREAD_MANAGER::ThreadManagerThread(LPVOID ThreadParam)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Starter thread for THREAD_MANAGER created threads
|
|
Takes a reference against the current DLL
|
|
Notifies THREAD_MANAGER that it has started execution
|
|
Calls out to "real" thread procedure
|
|
Notifies THREAD_MANAGER that it is about to terminate
|
|
Releases reference to current DLL and terminates
|
|
|
|
Arguments:
|
|
|
|
ThreadParam - parameters for control of thread
|
|
|
|
Return Value:
|
|
|
|
win32 error or return value from "real" thread proc
|
|
--*/
|
|
{
|
|
HMODULE hModuleDll;
|
|
DWORD dwReturnCode;
|
|
THREAD_PARAM *pParam = NULL;
|
|
pParam = (THREAD_PARAM*)ThreadParam;
|
|
|
|
// grab a reference to this DLL
|
|
hModuleDll = LoadLibrary(g_szModuleName);
|
|
if (NULL == hModuleDll)
|
|
{
|
|
dwReturnCode = GetLastError();
|
|
goto done;
|
|
}
|
|
|
|
// verify the thread parameter passed was reasonable
|
|
DBG_ASSERT(NULL != pParam);
|
|
DBG_ASSERT(NULL != pParam->pThreadManager);
|
|
DBG_ASSERT(NULL != pParam->pThreadFunc);
|
|
|
|
if (pParam->fCallbackOnCreation)
|
|
{
|
|
// Inform thread manager that this thread has successfully gotten through the loader lock
|
|
pParam->pThreadManager->CreatedSuccessfully(pParam);
|
|
}
|
|
|
|
// actually do work thread is supposed to do
|
|
dwReturnCode = pParam->pThreadFunc(pParam->pvThreadArg);
|
|
|
|
done:
|
|
// Inform thread manager that this thread is going away
|
|
pParam->pThreadManager->RemoveThread(pParam);
|
|
|
|
// Thread owns[ed] memory passed
|
|
delete pParam;
|
|
|
|
// release reference to this DLL
|
|
FreeLibraryAndExitThread(hModuleDll, dwReturnCode);
|
|
|
|
// never executed
|
|
return dwReturnCode;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::RequestThread(LPTHREAD_START_ROUTINE lpStartAddress,
|
|
LPVOID lpStartParameter)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates a timer to determine the correct thread action to take.
|
|
|
|
May create a thread in a little while
|
|
May take away a thread in a little while
|
|
May not create the timer if there is another thread creation going on
|
|
|
|
Arguments:
|
|
|
|
lpStartAddress - address of function to begin thread execution
|
|
lpParameter - argument to pass to start function
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
BOOL fRet = FALSE;
|
|
HRESULT hr;
|
|
|
|
DBG_ASSERT(NULL != lpStartAddress);
|
|
|
|
if (TRUE == m_fShuttingDown ||
|
|
TRUE == m_fWaitingForCreationCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// only want to create one timer at a time
|
|
//
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
|
|
if (TRUE == m_fShuttingDown ||
|
|
TRUE == m_fWaitingForCreationCallback)
|
|
{
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
return;
|
|
}
|
|
|
|
// only want one thread at a time to be created
|
|
m_fWaitingForCreationCallback = TRUE;
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Thread Request received\n"));
|
|
|
|
DWORD dwCurrentTime = GetTickCount();
|
|
|
|
fRet = GetContextSwitchCount(&m_ulContextSwitchCount);
|
|
if (FALSE == fRet)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
if ( THREAD_POOL_MAX_CPU_USAGE_DEFAULT != m_pPoolData->m_poolConfig.dwMaxCPUUsage)
|
|
{
|
|
hr = GetCPUData(&m_liOriginalBusy,
|
|
&m_liOriginalTotal,
|
|
g_dwcCPU);
|
|
if (FAILED(hr))
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
DBG_ASSERT(NULL == m_pParam);
|
|
m_pParam = new THREAD_PARAM;
|
|
if (NULL == m_pParam)
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
m_pParam->pThreadFunc = lpStartAddress;
|
|
m_pParam->pvThreadArg = lpStartParameter;
|
|
m_pParam->pThreadManager = this;
|
|
m_pParam->dwRequestTime = dwCurrentTime;
|
|
m_pParam->fCallbackOnCreation = TRUE;
|
|
|
|
if (NULL != m_hTimer)
|
|
{
|
|
// if this isn't the first time we've requested a thread,
|
|
// we have a previous TimerQueueTimer. This timer was not
|
|
// removed during the callback, so we have to clean it up now.
|
|
// this is the blocking form of the delete operation, however
|
|
// since the timer has already fired it will not block.
|
|
fRet = DeleteTimerQueueTimer(NULL, // default timer queue
|
|
m_hTimer, // previous timer handle
|
|
INVALID_HANDLE_VALUE // wait until it is removed
|
|
);
|
|
m_hTimer = NULL;
|
|
}
|
|
|
|
DBG_ASSERT(NULL == m_hTimer);
|
|
fRet = CreateTimerQueueTimer(&m_hTimer, // storage for timer handle
|
|
NULL, // default timer queue
|
|
ControlTimerCallback, // callback function
|
|
this, // callback argument
|
|
m_pPoolData->m_poolConfig.dwTimerPeriod, // time til callback
|
|
0, // repeat time
|
|
WT_EXECUTEONLYONCE // no repition
|
|
);
|
|
if (FALSE == fRet)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
fRet = TRUE;
|
|
done:
|
|
if (FALSE == fRet)
|
|
{
|
|
delete m_pParam;
|
|
m_pParam = NULL;
|
|
|
|
m_fWaitingForCreationCallback = FALSE;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
return;
|
|
}
|
|
|
|
|
|
//static
|
|
VOID
|
|
THREAD_MANAGER::ControlTimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Callback for timer created in RequestThread
|
|
|
|
restores this pointer and forwards to DetermineThreadAction
|
|
|
|
Arguments:
|
|
lpParameter - THREAD_MANAGER this pointer
|
|
|
|
Return Value:
|
|
VOID
|
|
--*/
|
|
{
|
|
THREAD_MANAGER* pThreadManager = (THREAD_MANAGER*)lpParameter;
|
|
|
|
DBG_ASSERT(NULL != pThreadManager);
|
|
DBG_ASSERT(NULL != pThreadManager->m_pParam);
|
|
DBG_ASSERT(TRUE == pThreadManager->m_fWaitingForCreationCallback);
|
|
|
|
pThreadManager->DetermineThreadAction();
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::DetermineThreadAction()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Try to determine correct action, create or take away a thread
|
|
|
|
Will take away a thread if context switch rate is too high
|
|
|
|
m_pParam must be populated
|
|
Arguments:
|
|
|
|
VOID
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
BOOL fRet = FALSE;
|
|
DWORD dwElapsedTime = 0;
|
|
DWORD dwCurrentTime = 0;
|
|
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
|
|
DBG_ASSERT(NULL != m_pParam);
|
|
|
|
if (TRUE == m_fShuttingDown)
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
DBG_ASSERT(TRUE == m_fWaitingForCreationCallback);
|
|
|
|
if (THREAD_POOL_MAX_CPU_USAGE_DEFAULT != m_pPoolData->m_poolConfig.dwMaxCPUUsage)
|
|
{
|
|
if (TooMuchProcessorUsage(m_liOriginalBusy,
|
|
m_liOriginalTotal,
|
|
m_pPoolData->m_poolConfig.dwMaxCPUUsage,
|
|
g_dwcCPU))
|
|
{
|
|
// Too much processor usage to create a new thread
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (TooMuchContextSwitchingLoad(m_ulContextSwitchCount,
|
|
m_pParam->dwRequestTime,
|
|
m_pPoolData->m_poolConfig.dwPerSecondContextSwitchMax,
|
|
g_dwcCPU))
|
|
{
|
|
// Switching too much
|
|
|
|
DoThreadParking();
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
fRet = DoThreadCreation(m_pParam);
|
|
if (!fRet)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
fRet = TRUE;
|
|
done:
|
|
if (FALSE == fRet)
|
|
{
|
|
delete m_pParam;
|
|
m_pParam = NULL;
|
|
|
|
m_fWaitingForCreationCallback = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// thread now has responsibility for the memory
|
|
m_pParam = NULL;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
THREAD_MANAGER::DoThreadCreation(THREAD_PARAM *pParam)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If there are no threads in parked state
|
|
|
|
Creates a thread,
|
|
add the HANDLE to the internal list of handles
|
|
add the creation time to the internal list of times
|
|
|
|
Otherwise, bring a thread out of parked state
|
|
|
|
Arguments:
|
|
pParam - parameter to pass to created thread
|
|
|
|
Return Value:
|
|
|
|
TRUE if thread is created successfully
|
|
FALSE if thread is not created - either a thread was returned from
|
|
parked state, OR there was a problem with creation.
|
|
|
|
--*/
|
|
{
|
|
BOOL fRet = FALSE;
|
|
HANDLE hThread = NULL;
|
|
|
|
if (DoThreadUnParking())
|
|
{
|
|
// we have not created a new thread
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Signaled a thread to be unparked\n"));
|
|
|
|
// return false - signal that pParam needs to be freed by the caller
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
// bugbug: use _beginthreadex?
|
|
hThread = ::CreateThread( NULL, // default security descriptor
|
|
m_pPoolData->m_poolConfig.dwInitialStackSize, // Initial size as configured
|
|
ThreadManagerThread, // thread function
|
|
pParam, // thread argument
|
|
0, // create running
|
|
NULL // don't care for thread identifier
|
|
);
|
|
if( NULL == hThread )
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
// don't keep the handle around
|
|
CloseHandle(hThread);
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Created a new thread\n"));
|
|
|
|
InterlockedIncrement(&m_lTotalThreads);
|
|
|
|
// we've successfully created the thread!
|
|
fRet = TRUE;
|
|
done:
|
|
return fRet;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::DoThreadParking()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called when context switch rate has been determined to be too high
|
|
Removes a thread from participation in the thread pool, and parks it
|
|
|
|
Arguments:
|
|
none
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
--*/
|
|
{
|
|
BOOL fRet;
|
|
|
|
// make sure that we leave the starting number of threads in the pool
|
|
if (m_pPoolData &&
|
|
m_lTotalThreads - m_lParkedThreads <= (LONG) m_pPoolData->m_poolConfig.dwInitialThreadCount)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Posting to park a thread\n"));
|
|
|
|
fRet = m_pPool->PostCompletion(0,
|
|
ParkThread,
|
|
(LPOVERLAPPED)this);
|
|
DBG_ASSERT(TRUE == fRet);
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
THREAD_MANAGER::DoThreadUnParking()
|
|
/*++
|
|
|
|
Routine Description:
|
|
Release one thread from the parked state
|
|
|
|
Arguments:
|
|
|
|
VOID
|
|
Return Value:
|
|
|
|
BOOL - TRUE if thread was released
|
|
FALSE if no threads were available to release
|
|
--*/
|
|
{
|
|
if (0 == m_lParkedThreads)
|
|
{
|
|
return FALSE;
|
|
}
|
|
SetEvent(m_hParkEvent);
|
|
return TRUE;
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
THREAD_MANAGER::ParkThread(DWORD dwErrorCode,
|
|
DWORD dwNumberOfBytesTransferred,
|
|
LPOVERLAPPED lpo)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Put a THREAD_MANAGER thread in a parked state
|
|
|
|
Arguments:
|
|
|
|
dwErrorCode - not used
|
|
dwNumberOfBytesTransferred - not used
|
|
lpo - pointer to overlapped that is really a pointer to a THREAD_MANAGER
|
|
Return Value:
|
|
|
|
VOID
|
|
--*/
|
|
{
|
|
DWORD dwRet = 0;
|
|
THREAD_MANAGER * pThis= (THREAD_MANAGER*)lpo;
|
|
DBG_ASSERT(NULL != pThis);
|
|
|
|
HANDLE arrHandles[2];
|
|
arrHandles[0] = pThis->m_hParkEvent;
|
|
arrHandles[1] = pThis->m_hShutdownEvent;
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Thread parking\n"));
|
|
|
|
LONG lNewParkedThreads = InterlockedIncrement(&pThis->m_lParkedThreads);
|
|
|
|
if (pThis->m_pPoolData &&
|
|
pThis->m_lTotalThreads - lNewParkedThreads <= (LONG) pThis->m_pPoolData->m_poolConfig.dwInitialThreadCount)
|
|
{
|
|
InterlockedDecrement(&pThis->m_lParkedThreads);
|
|
return;
|
|
}
|
|
|
|
THREAD_POOL_DATA * pPoolData = pThis->m_pPoolData;
|
|
DWORD dwTimeout = pPoolData->m_poolConfig.dwThreadTimeout;
|
|
dwRet = WaitForMultipleObjects(2,
|
|
arrHandles,
|
|
FALSE,
|
|
dwTimeout);
|
|
|
|
InterlockedDecrement(&pThis->m_lParkedThreads);
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Thread unparked\n"));
|
|
|
|
if (WAIT_TIMEOUT == dwRet)
|
|
{
|
|
THREAD_POOL_DATA::ThreadPoolStop(pPoolData);
|
|
return;
|
|
}
|
|
|
|
DBG_ASSERT(WAIT_OBJECT_0 == dwRet ||
|
|
WAIT_OBJECT_0 + 1 == dwRet);
|
|
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
THREAD_MANAGER::CreateThread(LPTHREAD_START_ROUTINE lpStartAddress,
|
|
LPVOID lpParameter)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates a thread if no other thread is being created currently
|
|
|
|
Arguments:
|
|
|
|
lpStartAddress - address of function to begin thread execution
|
|
lpParameter - argument to pass to start function
|
|
|
|
Return Value:
|
|
|
|
TRUE if thread is created successfully
|
|
FALSE if thread is not created
|
|
|
|
--*/
|
|
{
|
|
BOOL fRet = FALSE;
|
|
THREAD_PARAM * pParam = NULL;
|
|
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
|
|
if (TRUE == m_fShuttingDown)
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
pParam = new THREAD_PARAM;
|
|
if (NULL == pParam)
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: CreateThread thread creation\n"));
|
|
|
|
pParam->pThreadFunc = lpStartAddress;
|
|
pParam->pvThreadArg = lpParameter;
|
|
pParam->pThreadManager = this;
|
|
pParam->dwRequestTime = GetTickCount();
|
|
pParam->fCallbackOnCreation = FALSE;
|
|
|
|
fRet = DoThreadCreation(pParam);
|
|
if (FALSE == fRet)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
// created thread has responsibility for this memory
|
|
pParam = NULL;
|
|
|
|
fRet = TRUE;
|
|
done:
|
|
if (FALSE == fRet)
|
|
{
|
|
delete pParam;
|
|
pParam = NULL;
|
|
|
|
m_fWaitingForCreationCallback = FALSE;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
return fRet;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::RemoveThread(THREAD_PARAM * pParam)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Removes given thread from list of active threads and closes handle
|
|
|
|
Arguments:
|
|
|
|
hThreadSelf - handle to current thread
|
|
|
|
Return Value:
|
|
|
|
void
|
|
--*/
|
|
{
|
|
InterlockedDecrement(&m_lTotalThreads);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::CreatedSuccessfully(THREAD_PARAM * pParam)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notification that given thread has successfully started
|
|
|
|
Arguments:
|
|
|
|
hThread - current thread handle
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT(pParam);
|
|
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
|
|
DBG_ASSERT(m_fWaitingForCreationCallback);
|
|
|
|
m_fWaitingForCreationCallback = FALSE;
|
|
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
THREAD_MANAGER::DrainThreads(LPTHREAD_STOP_ROUTINE lpStopAddress,
|
|
LPVOID lpParameter)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
stop all threads currently being managed.
|
|
Doesn't return until all threads are stopped.
|
|
|
|
Arguments:
|
|
|
|
lpStopAddress - address of function to call to signal one thread to stop
|
|
|
|
Return Value:
|
|
|
|
TRUE if all threads are stopped
|
|
FALSE if one or more threads could not be stopped
|
|
|
|
--*/
|
|
{
|
|
if (TRUE == m_fShuttingDown)
|
|
{
|
|
DBG_ASSERT(FALSE && "DrainThreads has been called previously!");
|
|
return;
|
|
}
|
|
|
|
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
// stop any additional thread creation
|
|
m_fShuttingDown = TRUE;
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
|
|
// release all parked threads
|
|
SetEvent(m_hShutdownEvent);
|
|
|
|
// push as many stops are there are threads running
|
|
for (INT i = m_lTotalThreads; i >= 0; i--)
|
|
//TODO: think about sync with interlocked and m_lTotalThreads - is there a race here?
|
|
{
|
|
lpStopAddress(lpParameter);
|
|
}
|
|
|
|
// stop the callback timer
|
|
if (NULL != m_hTimer)
|
|
{
|
|
// block until timer is deleted
|
|
DeleteTimerQueueTimer(NULL,
|
|
m_hTimer,
|
|
INVALID_HANDLE_VALUE);
|
|
m_hTimer = NULL;
|
|
if (m_pParam)
|
|
{
|
|
// the ownership for m_pParam moves from the creator
|
|
// to the timer to the thread.
|
|
// However, we just destroyed the timer - need to cleanup
|
|
// the memory
|
|
delete m_pParam;
|
|
m_pParam = NULL;
|
|
}
|
|
}
|
|
|
|
while(m_lTotalThreads > 0)
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Waiting for threads to drain, sleep 1000 \n"));
|
|
Sleep(1000);
|
|
}
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: All threads drained\n"));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOL
|
|
GetContextSwitchCount(ULONG * pulSwitchCount)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the current machine context switch count
|
|
|
|
Arguments:
|
|
|
|
pulSwitchCount - where to store switch count
|
|
|
|
Return Value:
|
|
|
|
TRUE if context switch count is read correctly
|
|
FALSE if context switch count could not be read
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT(NULL != pulSwitchCount);
|
|
|
|
SYSTEM_PERFORMANCE_INFORMATION spi;
|
|
ULONG ulReturnLength;
|
|
NTSTATUS status;
|
|
status = NtQuerySystemInformation(SystemPerformanceInformation,
|
|
&spi,
|
|
sizeof(spi),
|
|
&ulReturnLength);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*pulSwitchCount = spi.ContextSwitches;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
TooMuchContextSwitchingLoad(ULONG ulFirstSample,
|
|
DWORD dwFirstSampleTime,
|
|
ULONG ulPerSecondSwitchRateMax,
|
|
DWORD dwNumProcs)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if the system is under too much load in terms
|
|
of context switches / second
|
|
|
|
If dwNumProcs > 1 - the per second switch rate per processor is multiplied by two
|
|
|
|
Arguments:
|
|
|
|
ulFirstSample - first context switch count number
|
|
dwSampleTimeInMilliseconds - how much time between first sample and calling this function
|
|
ulPerSecondSwitchRateMax - Maximum switch rate per processor
|
|
dwNumProcs - number of processors on machine
|
|
|
|
Return Value:
|
|
|
|
TRUE if context switch rate per second per processor is > ulPerSecondSwitchRateMax
|
|
FALSE if context switch rate is below ulPerSecondSwitchRateMax
|
|
|
|
--*/
|
|
{
|
|
ULONG ulSecondSample = 0;
|
|
ULONG ulContextSwitchDifference = 0;
|
|
double dblPerSecondSwitchRate = 0;
|
|
double dblPerSecondSwitchRatePerProcessor = 0;
|
|
DWORD dwCurrentTime = 0;
|
|
DWORD dwElapsedTime = 0;
|
|
|
|
BOOL fRet = FALSE;
|
|
|
|
fRet = GetContextSwitchCount(&ulSecondSample);
|
|
if (FALSE == fRet)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
dwCurrentTime = GetTickCount();
|
|
if (dwCurrentTime <= dwFirstSampleTime)
|
|
{
|
|
// wrap around on time occurred - assume only one wrap around
|
|
const DWORD MAXDWORD = MAXULONG;
|
|
dwElapsedTime = MAXDWORD - dwFirstSampleTime + dwCurrentTime;
|
|
}
|
|
else
|
|
{
|
|
// no wrap around
|
|
dwElapsedTime = dwCurrentTime - dwFirstSampleTime;
|
|
}
|
|
DBG_ASSERT(dwElapsedTime > 0);
|
|
|
|
|
|
if (ulSecondSample <= ulFirstSample)
|
|
{
|
|
// wrap around on counter occurred - assume only one wrap around
|
|
ulContextSwitchDifference = (MAXULONG - ulFirstSample) + ulSecondSample;
|
|
}
|
|
else
|
|
{
|
|
// no wrap around
|
|
ulContextSwitchDifference = ulSecondSample - ulFirstSample;
|
|
}
|
|
DBG_ASSERT(ulContextSwitchDifference > 0);
|
|
|
|
dblPerSecondSwitchRate = ulContextSwitchDifference / ( dwElapsedTime / 1000.0);
|
|
|
|
dblPerSecondSwitchRatePerProcessor = dblPerSecondSwitchRate / dwNumProcs;
|
|
|
|
if (dwNumProcs > 1)
|
|
{
|
|
// on multiproc boxes, double the allowed context switch rate per processor
|
|
ulPerSecondSwitchRateMax *= 2;
|
|
}
|
|
|
|
if (dblPerSecondSwitchRatePerProcessor > ulPerSecondSwitchRateMax)
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: Not creating thread, ContextSwitch rate is: %g\n", dblPerSecondSwitchRate ));
|
|
fRet = TRUE;
|
|
goto done;
|
|
}
|
|
|
|
DBGPRINTF(( DBG_CONTEXT, "W3TP: OK to create thread, ContextSwitch rate is: %g\n", dblPerSecondSwitchRate ));
|
|
|
|
fRet = FALSE;
|
|
done:
|
|
return fRet;
|
|
}
|
|
|
|
HRESULT
|
|
GetCPUData(LARGE_INTEGER * pBusyTime,
|
|
LARGE_INTEGER * pTotalTime,
|
|
DWORD dwNumProcs)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Collects the percent CPU load for all machine processors.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Std HRESULT.
|
|
|
|
--*/
|
|
|
|
{
|
|
DBG_ASSERT(pBusyTime && pTotalTime);
|
|
DBG_ASSERT(dwNumProcs > 0);
|
|
|
|
HRESULT hr = S_OK;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
LARGE_INTEGER
|
|
cpuIdleTime = {0},
|
|
cpuUserTime = {0},
|
|
cpuKernelTime = {0},
|
|
cpuBusyTime = {0},
|
|
cpuTotalTime = {0},
|
|
sumBusyTime = {0},
|
|
sumTotalTime = {0};
|
|
|
|
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION * psppi = new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[dwNumProcs];
|
|
if (NULL == psppi)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
|
|
// get the new snapshot
|
|
status = NtQuerySystemInformation(
|
|
SystemProcessorPerformanceInformation,
|
|
psppi,
|
|
sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * dwNumProcs,
|
|
NULL
|
|
);
|
|
|
|
if(status != STATUS_SUCCESS){
|
|
|
|
hr = HRESULT_FROM_NT(status);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// calculate
|
|
for (DWORD i = 0; i < dwNumProcs; i++) {
|
|
|
|
cpuTotalTime = RtlLargeIntegerAdd(psppi[i].UserTime, psppi[i].KernelTime);
|
|
cpuBusyTime = RtlLargeIntegerSubtract(cpuTotalTime, psppi[i].IdleTime);
|
|
|
|
sumBusyTime = RtlLargeIntegerAdd(sumBusyTime, cpuBusyTime);
|
|
sumTotalTime = RtlLargeIntegerAdd(sumTotalTime, cpuTotalTime);
|
|
}
|
|
|
|
*pBusyTime = sumBusyTime;
|
|
*pTotalTime = sumTotalTime;
|
|
|
|
CLEANUP:
|
|
|
|
delete [] psppi;
|
|
psppi = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
LONG
|
|
GetPercentage(LARGE_INTEGER part, LARGE_INTEGER total)
|
|
{
|
|
if (0 == total.QuadPart)
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
LARGE_INTEGER li;
|
|
|
|
part.QuadPart *= 100;
|
|
|
|
li.QuadPart = part.QuadPart / total.QuadPart;
|
|
|
|
return li.LowPart;
|
|
}
|
|
|
|
BOOL
|
|
TooMuchProcessorUsage(LARGE_INTEGER liOriginalBusy,
|
|
LARGE_INTEGER liOriginalTotal,
|
|
LONG lPercentageUseMax,
|
|
DWORD dwNumProcs)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LARGE_INTEGER
|
|
liNewBusy = {0},
|
|
liNewTotal = {0},
|
|
liDiffBusy = {0},
|
|
liDiffTotal = {0};
|
|
|
|
hr = GetCPUData(&liNewBusy,
|
|
&liNewTotal,
|
|
dwNumProcs);
|
|
if (FAILED(hr))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
liDiffBusy = RtlLargeIntegerSubtract(liNewBusy, liOriginalBusy);
|
|
liDiffTotal = RtlLargeIntegerSubtract(liNewTotal, liOriginalTotal);
|
|
|
|
LONG lPercentageUse = GetPercentage(liDiffBusy, liDiffTotal);
|
|
if (lPercentageUse >= lPercentageUseMax)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|