668 lines
16 KiB
C++
668 lines
16 KiB
C++
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
tpswork.h
|
|
|
|
Abstract:
|
|
|
|
Worker thread classes. Moved out of tpsclass.h
|
|
|
|
Contents:
|
|
CIoWorkerThreadInfo
|
|
CIoWorkerRequest
|
|
CThreadPool
|
|
|
|
Author:
|
|
|
|
Richard L Firth (rfirth) 08-Aug-1998
|
|
|
|
Revision History:
|
|
|
|
08-Aug-1998 rfirth
|
|
Created
|
|
|
|
--*/
|
|
|
|
//
|
|
// manifests
|
|
//
|
|
|
|
#define THREAD_CREATION_DAMPING_TIME 5000
|
|
#define NEW_THREAD_THRESHOLD 10
|
|
#define MIN_WORKER_THREADS 1
|
|
#define MAX_WORKER_THREADS 128
|
|
#define MAX_IO_WORKER_THREADS 256
|
|
#define MAX_QUEUE_DEPTH 0
|
|
#define THREAD_IDLE_TIMEOUT 60000
|
|
|
|
#define TPS_ID 0x80000000
|
|
|
|
//
|
|
// external data
|
|
//
|
|
|
|
extern DWORD g_dwWorkItemId;
|
|
|
|
//
|
|
// classes
|
|
//
|
|
|
|
//
|
|
// CIoWorkerThreadInfo
|
|
//
|
|
|
|
class CIoWorkerThreadInfo : public CDoubleLinkedListEntry {
|
|
|
|
private:
|
|
|
|
HANDLE m_hThread;
|
|
|
|
public:
|
|
|
|
CIoWorkerThreadInfo(CDoubleLinkedList * pList) {
|
|
m_hThread = (HANDLE)-1;
|
|
InsertHead(pList);
|
|
}
|
|
|
|
~CIoWorkerThreadInfo() {
|
|
|
|
ASSERT(m_hThread == NULL);
|
|
|
|
}
|
|
|
|
VOID SetHandle(HANDLE hThread) {
|
|
m_hThread = hThread;
|
|
}
|
|
|
|
HANDLE GetHandle(VOID) const {
|
|
return m_hThread;
|
|
}
|
|
};
|
|
|
|
//
|
|
// CIoWorkerRequest
|
|
//
|
|
|
|
class CIoWorkerRequest {
|
|
|
|
private:
|
|
|
|
LPTHREAD_START_ROUTINE m_pfnCallback;
|
|
LPVOID m_pContext;
|
|
|
|
public:
|
|
|
|
CIoWorkerRequest(LPTHREAD_START_ROUTINE pfnCallback, LPVOID pContext) {
|
|
m_pfnCallback = pfnCallback;
|
|
m_pContext = pContext;
|
|
}
|
|
|
|
LPTHREAD_START_ROUTINE GetCallback(VOID) const {
|
|
return m_pfnCallback;
|
|
}
|
|
|
|
LPVOID GetContext(VOID) const {
|
|
return m_pContext;
|
|
}
|
|
};
|
|
|
|
//
|
|
// CThreadPool - maintains lists of work items, non-IO worker threads and
|
|
// IO worker threads
|
|
//
|
|
|
|
class CThreadPool {
|
|
|
|
private:
|
|
|
|
//
|
|
// private classes
|
|
//
|
|
|
|
//
|
|
// CWorkItem - queued app-supplied functions, ordered by priority
|
|
//
|
|
|
|
class CWorkItem : public CPrioritizedListEntry {
|
|
|
|
public:
|
|
|
|
FARPROC m_function;
|
|
ULONG_PTR m_context;
|
|
DWORD_PTR m_tag;
|
|
DWORD_PTR m_id;
|
|
DWORD m_flags;
|
|
HINSTANCE m_hInstModule;
|
|
|
|
CWorkItem(FARPROC lpfn,
|
|
ULONG_PTR context,
|
|
LONG priority,
|
|
DWORD_PTR tag,
|
|
DWORD_PTR * pid,
|
|
LPCSTR pszModule,
|
|
DWORD flags
|
|
) : CPrioritizedListEntry(priority)
|
|
{
|
|
m_function = lpfn;
|
|
m_context = context;
|
|
m_tag = tag;
|
|
m_id = (DWORD_PTR)0;
|
|
m_flags = flags;
|
|
|
|
if (pszModule && *pszModule)
|
|
{
|
|
m_hInstModule = LoadLibrary(pszModule);
|
|
|
|
if (!m_hInstModule)
|
|
{
|
|
TraceMsg(TF_WARNING, TEXT("CWorkItem::CWorkItem - faild to load %hs (error = %d), worker thread could be abanonded!!"), pszModule, GetLastError());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_hInstModule = NULL;
|
|
}
|
|
|
|
if (pid) {
|
|
m_id = (DWORD_PTR)++g_dwWorkItemId;
|
|
*pid = m_id;
|
|
m_flags |= TPS_ID;
|
|
}
|
|
}
|
|
|
|
~CWorkItem()
|
|
{
|
|
// we used to call FreeLibrary(m_hInstModule) here but we delete the workitem
|
|
// when we grab it off of the queue (in RemoveWorkItem). so we have to wait until
|
|
// we are actually done running the task before we call FreeLibaray()
|
|
}
|
|
|
|
BOOL Match(DWORD_PTR Tag, BOOL IsTag) {
|
|
return IsTag
|
|
? ((m_flags & TPS_TAGGEDITEM) && (m_tag == Tag))
|
|
: ((m_flags & TPS_ID) && (m_id == Tag));
|
|
}
|
|
|
|
BOOL IsLongExec(VOID) {
|
|
return (m_flags & TPS_LONGEXECTIME) ? TRUE : FALSE;
|
|
}
|
|
};
|
|
|
|
//
|
|
// work item queue variables
|
|
//
|
|
|
|
CPrioritizedList m_queue;
|
|
CCriticalSection_NoCtor m_qlock;
|
|
HANDLE m_event;
|
|
DWORD m_error;
|
|
DWORD m_queueSize;
|
|
DWORD m_qFactor;
|
|
DWORD m_minWorkerThreads;
|
|
DWORD m_maxWorkerThreads;
|
|
DWORD m_maxQueueDepth;
|
|
DWORD m_workerIdleTimeout;
|
|
DWORD m_creationDelta;
|
|
DWORD m_totalWorkerThreads;
|
|
DWORD m_availableWorkerThreads;
|
|
|
|
#if DBG
|
|
DWORD m_queueSizeMax;
|
|
DWORD m_qFactorMax;
|
|
DWORD m_maxWorkerThreadsCreated;
|
|
#endif
|
|
|
|
//
|
|
// private member functions
|
|
//
|
|
|
|
CWorkItem * DequeueWorkItem(VOID) {
|
|
|
|
CWorkItem * pItem = NULL;
|
|
|
|
if (!m_queue.IsEmpty()) {
|
|
pItem = (CWorkItem *)m_queue.RemoveHead();
|
|
--m_queueSize;
|
|
}
|
|
return pItem;
|
|
}
|
|
|
|
VOID
|
|
Worker(
|
|
VOID
|
|
);
|
|
|
|
public:
|
|
|
|
static
|
|
VOID
|
|
WorkerThread(
|
|
VOID
|
|
);
|
|
|
|
BOOL Init(VOID) {
|
|
m_queue.Init();
|
|
m_qlock.Init();
|
|
|
|
//
|
|
// create auto-reset, initially unsignalled event
|
|
//
|
|
|
|
m_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
m_error = (m_event != NULL) ? ERROR_SUCCESS : GetLastError();
|
|
m_queueSize = 0;
|
|
m_qFactor = 0;
|
|
m_minWorkerThreads = MIN_WORKER_THREADS;
|
|
m_maxWorkerThreads = MAX_WORKER_THREADS;
|
|
m_maxQueueDepth = MAX_QUEUE_DEPTH;
|
|
m_workerIdleTimeout = THREAD_IDLE_TIMEOUT;
|
|
m_creationDelta = THREAD_CREATION_DAMPING_TIME;
|
|
m_totalWorkerThreads = 0;
|
|
m_availableWorkerThreads = 0;
|
|
|
|
#if DBG
|
|
m_queueSizeMax = 0;
|
|
m_qFactorMax = 0;
|
|
m_maxWorkerThreadsCreated = 0;
|
|
#endif
|
|
|
|
return m_error == ERROR_SUCCESS;
|
|
}
|
|
|
|
VOID Terminate(DWORD Limit) {
|
|
PurgeWorkItems();
|
|
TerminateThreads(Limit);
|
|
if (m_event != NULL) {
|
|
|
|
BOOL bOk = CloseHandle(m_event);
|
|
|
|
ASSERT(bOk);
|
|
|
|
m_event = NULL;
|
|
}
|
|
m_qlock.Terminate();
|
|
|
|
ASSERT(m_queue.IsEmpty());
|
|
|
|
//#if DBG
|
|
//char buf[256];
|
|
//wsprintf(buf,
|
|
// "CThreadPool::Terminate(): m_queueSizeMax = %d, m_maxWorkerThreadsCreated = %d, m_qFactorMax = %d\n",
|
|
// m_queueSizeMax,
|
|
// m_maxWorkerThreadsCreated,
|
|
// m_qFactorMax
|
|
// );
|
|
//OutputDebugString(buf);
|
|
//#endif
|
|
}
|
|
|
|
DWORD GetError() const {
|
|
return m_error;
|
|
}
|
|
|
|
VOID
|
|
SetLimits(
|
|
IN DWORD dwMinimumThreads,
|
|
IN DWORD dwMaximumThreads,
|
|
IN DWORD dwMaximumQueueDepth,
|
|
IN DWORD dwThreadIdleTimeout,
|
|
IN DWORD dwThreadCreationDelta
|
|
)
|
|
{
|
|
m_minWorkerThreads = dwMinimumThreads;
|
|
m_maxWorkerThreads = dwMaximumThreads;
|
|
m_maxQueueDepth = dwMaximumQueueDepth;
|
|
m_workerIdleTimeout = dwThreadIdleTimeout;
|
|
m_creationDelta = dwThreadCreationDelta;
|
|
}
|
|
|
|
VOID MakeAvailable(VOID) {
|
|
InterlockedIncrement((LPLONG)&m_availableWorkerThreads);
|
|
if (m_qFactor == 0) {
|
|
m_qFactor = 1;
|
|
} else {
|
|
m_qFactor <<= 1;
|
|
}
|
|
#if DBG
|
|
if (m_qFactor > m_qFactorMax) {
|
|
m_qFactorMax = m_qFactor;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
VOID MakeUnavailable(VOID) {
|
|
InterlockedDecrement((LPLONG)&m_availableWorkerThreads);
|
|
m_qFactor >>= 1;
|
|
if ((m_qFactor == 0) && (m_availableWorkerThreads != 0)) {
|
|
m_qFactor = 1;
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
QueueWorkItem(
|
|
FARPROC pfnFunction,
|
|
ULONG_PTR pContext,
|
|
LONG lPriority,
|
|
DWORD_PTR dwTag,
|
|
DWORD_PTR * pdwId,
|
|
LPCSTR pszModule,
|
|
DWORD dwFlags
|
|
)
|
|
{
|
|
//
|
|
// add a work item to the queue at the appropriate place and create a
|
|
// thread to handle it if necessary
|
|
//
|
|
|
|
CWorkItem * pItem = new CWorkItem(pfnFunction,
|
|
pContext,
|
|
lPriority,
|
|
dwTag,
|
|
pdwId,
|
|
pszModule,
|
|
dwFlags
|
|
);
|
|
|
|
if (pItem == NULL) {
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
m_qlock.Acquire();
|
|
|
|
//
|
|
// demand-thread work-items have the highest priority. Put at head of
|
|
// queue, else insert based on priority
|
|
//
|
|
|
|
if (dwFlags & TPS_DEMANDTHREAD) {
|
|
pItem->InsertHead(&m_queue);
|
|
} else {
|
|
m_queue.insert(pItem);
|
|
}
|
|
++m_queueSize;
|
|
#if DBG
|
|
if (m_queueSize > m_queueSizeMax) {
|
|
m_queueSizeMax = m_queueSize;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// determine whether we need to create a new thread:
|
|
//
|
|
// * no available threads
|
|
// * work queue growing too fast
|
|
// * all available threads about to be taken by long-exec work items
|
|
//
|
|
|
|
BOOL bCreate = FALSE;
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
if (m_queueSize > (m_availableWorkerThreads * m_qFactor)) {
|
|
bCreate = TRUE;
|
|
} else {
|
|
|
|
DWORD i = 0;
|
|
DWORD n = 0;
|
|
CWorkItem * pItem = (CWorkItem *)m_queue.Next();
|
|
|
|
while ((pItem != m_queue.Head()) && (i < m_availableWorkerThreads)) {
|
|
if (pItem->IsLongExec()) {
|
|
++n;
|
|
}
|
|
pItem = (CWorkItem *)pItem->Next();
|
|
++i;
|
|
}
|
|
if (n == m_availableWorkerThreads) {
|
|
bCreate = TRUE;
|
|
}
|
|
}
|
|
m_qlock.Release();
|
|
if (bCreate) {
|
|
// if the CreateWorkerThread fails, do NOT pass back an error code to the caller
|
|
// since we've already added the workitem to the queue. An error code will
|
|
// likely result in the caller freeing the data for the work item. (saml 081799)
|
|
CreateWorkerThread();
|
|
}
|
|
SetEvent(m_event);
|
|
return error;
|
|
}
|
|
|
|
DWORD
|
|
RemoveWorkItem(
|
|
FARPROC * ppfnFunction,
|
|
ULONG_PTR * pContext,
|
|
HMODULE* hModuleToFree,
|
|
DWORD * pdwFlags,
|
|
DWORD dwTimeout
|
|
)
|
|
{
|
|
BOOL bFirstTime = TRUE;
|
|
DWORD dwWaitTime = dwTimeout;
|
|
|
|
while (TRUE) {
|
|
|
|
CWorkItem * pItem;
|
|
|
|
//
|
|
// first test the FIFO state without waiting for the event
|
|
//
|
|
|
|
if (!m_queue.IsEmpty())
|
|
{
|
|
m_qlock.Acquire();
|
|
pItem = DequeueWorkItem();
|
|
|
|
if (pItem != NULL)
|
|
{
|
|
if (pItem->m_flags & TPS_LONGEXECTIME)
|
|
{
|
|
MakeUnavailable();
|
|
}
|
|
|
|
m_qlock.Release();
|
|
*ppfnFunction = pItem->m_function;
|
|
*pContext = pItem->m_context;
|
|
*pdwFlags = pItem->m_flags & ~TPS_RESERVED_FLAGS;
|
|
*hModuleToFree = pItem->m_hInstModule;
|
|
delete pItem;
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
m_qlock.Release();
|
|
}
|
|
|
|
DWORD dwStartTime;
|
|
|
|
if ((dwTimeout != INFINITE) && bFirstTime) {
|
|
dwStartTime = GetTickCount();
|
|
}
|
|
|
|
//
|
|
// if dwTimeout is 0 (poll) and we've already waited unsuccessfully
|
|
// then we're done: we timed out
|
|
//
|
|
|
|
if ((dwTimeout == 0) && !bFirstTime) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// wait alertably: process I/O completions while we wait
|
|
//
|
|
// FEATURE - we want MsgWaitForMultipleObjectsEx() here, but Win95
|
|
// doesn't support it
|
|
//
|
|
|
|
DWORD status = MsgWaitForMultipleObjects(1,
|
|
&m_event,
|
|
FALSE,
|
|
dwWaitTime,
|
|
//QS_ALLINPUT
|
|
QS_SENDMESSAGE | QS_KEY
|
|
);
|
|
|
|
//
|
|
// quit now if thread pool is terminating
|
|
//
|
|
|
|
if (g_bTpsTerminating) {
|
|
break;
|
|
}
|
|
bFirstTime = FALSE;
|
|
if ((status == WAIT_OBJECT_0) || (status == WAIT_IO_COMPLETION)) {
|
|
|
|
//
|
|
// we think there is something to remove from the FIFO or I/O
|
|
// completed. If we're not waiting forever, update the time to
|
|
// wait on the next iteration based on the time we started
|
|
//
|
|
|
|
if (dwTimeout != INFINITE) {
|
|
|
|
DWORD dwElapsedTime = GetTickCount() - dwStartTime;
|
|
|
|
if (dwElapsedTime > dwTimeout) {
|
|
|
|
//
|
|
// waited longer than requested. Don't wait again if
|
|
// we find there's nothing in the FIFO
|
|
//
|
|
|
|
dwWaitTime = 0;
|
|
} else {
|
|
|
|
//
|
|
// amount of time to wait next iteration is amount of
|
|
// time until expiration of originally specified period
|
|
//
|
|
|
|
dwWaitTime = dwTimeout - dwElapsedTime;
|
|
}
|
|
}
|
|
continue;
|
|
} else if (status == WAIT_OBJECT_0 + 1) {
|
|
|
|
MSG msg;
|
|
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
if (msg.message == WM_QUIT) {
|
|
return WAIT_ABANDONED;
|
|
} else {
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// WAIT_TIMEOUT (or WAIT_ABANDONED (?))
|
|
//
|
|
|
|
break;
|
|
}
|
|
return WAIT_TIMEOUT;
|
|
}
|
|
|
|
DWORD RemoveTagged(DWORD_PTR Tag, BOOL IsTag) {
|
|
|
|
DWORD count = 0;
|
|
|
|
m_qlock.Acquire();
|
|
|
|
CPrioritizedListEntry * pEntry = (CPrioritizedListEntry *)m_queue.Next();
|
|
CPrioritizedListEntry * pPrev = (CPrioritizedListEntry *)m_queue.Head();
|
|
|
|
while (pEntry != m_queue.Head()) {
|
|
|
|
CWorkItem * pItem = (CWorkItem *)pEntry;
|
|
|
|
if (pItem->Match(Tag, IsTag)) {
|
|
pItem->Remove();
|
|
--m_queueSize;
|
|
delete pItem;
|
|
++count;
|
|
if (!IsTag) {
|
|
break;
|
|
}
|
|
} else {
|
|
pPrev = pEntry;
|
|
}
|
|
pEntry = (CPrioritizedListEntry *)pPrev->Next();
|
|
}
|
|
m_qlock.Release();
|
|
return count;
|
|
}
|
|
|
|
DWORD GetQueueSize(VOID) const {
|
|
return m_queueSize;
|
|
}
|
|
|
|
VOID PurgeWorkItems(VOID) {
|
|
m_qlock.Acquire();
|
|
|
|
CWorkItem * pItem;
|
|
|
|
while ((pItem = DequeueWorkItem()) != NULL) {
|
|
delete pItem;
|
|
}
|
|
m_qlock.Release();
|
|
}
|
|
|
|
VOID Signal(VOID) {
|
|
if (m_event != NULL) {
|
|
SetEvent(m_event);
|
|
}
|
|
}
|
|
|
|
DWORD CreateWorkerThread(VOID) {
|
|
|
|
HANDLE hThread;
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
error = StartThread((LPTHREAD_START_ROUTINE)WorkerThread,
|
|
&hThread,
|
|
FALSE
|
|
);
|
|
if (error == ERROR_SUCCESS) {
|
|
AddWorker();
|
|
#if DBG
|
|
if (m_totalWorkerThreads > m_maxWorkerThreadsCreated) {
|
|
m_maxWorkerThreadsCreated = m_totalWorkerThreads;
|
|
}
|
|
//char buf[256];
|
|
//wsprintf(buf, ">>>> started worker thread. Total = %d/%d. Avail = %d. Factor = %d/%d\n",
|
|
// m_totalWorkerThreads,
|
|
// m_maxWorkerThreadsCreated,
|
|
// m_availableWorkerThreads,
|
|
// m_qFactor,
|
|
// m_qFactorMax
|
|
// );
|
|
//OutputDebugString(buf);
|
|
#endif
|
|
CloseHandle(hThread); // thread handle not required
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
VOID TerminateThreads(DWORD Limit) {
|
|
while (m_totalWorkerThreads > Limit) {
|
|
Signal();
|
|
SleepEx(0, FALSE);
|
|
}
|
|
}
|
|
|
|
VOID AddWorker(VOID) {
|
|
InterlockedIncrement((LPLONG)&m_totalWorkerThreads);
|
|
MakeAvailable();
|
|
}
|
|
|
|
VOID RemoveWorker(VOID) {
|
|
MakeUnavailable();
|
|
InterlockedDecrement((LPLONG)&m_totalWorkerThreads);
|
|
}
|
|
};
|