|
|
/******************************************************************************
* * Copyright (C) 2001-2002 Microsoft Corporation. All Rights Reserved. * * File: work.cpp * * Content: DirectPlay Thread Pool work processing functions. * * History: * Date By Reason * ======== ======== ========= * 10/31/01 VanceO Created. * ******************************************************************************/
#include "dpnthreadpooli.h"
//=============================================================================
// Defines
//=============================================================================
#define MAX_SIMULTANEOUS_THREAD_START (MAXIMUM_WAIT_OBJECTS - 1) // WaitForMultipleObjects can only handle 64 items at a time, we need one slot for the start event
#undef DPF_MODNAME
#define DPF_MODNAME "InitializeWorkQueue"
//=============================================================================
// InitializeWorkQueue
//-----------------------------------------------------------------------------
//
// Description: Initializes the specified work queue.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to
// initialize.
// DWORD dwCPUNum - CPU number this queue is to
// represent.
// PFNDPNMESSAGEHANDLER pfnMsgHandler - User's message handler callback, or
// NULL if none.
// PVOID pvMsgHandlerContext - Context for user's message handler.
// DWORD dwWorkerThreadTlsIndex - TLS index to use for storing worker
// thread data.
//
// Returns: HRESULT
// DPN_OK - Successfully initialized the work queue object.
// DPNERR_OUTOFMEMORY - Failed to allocate memory while initializing.
//=============================================================================
#ifdef DPNBUILD_ONLYONETHREAD
#ifdef DPNBUILD_ONLYONEPROCESSOR
HRESULT InitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue) #else // ! DPNBUILD_ONLYONEPROCESSOR
HRESULT InitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue, const DWORD dwCPUNum) #endif // ! DPNBUILD_ONLYONEPROCESSOR
#else // ! DPNBUILD_ONLYONETHREAD
HRESULT InitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue, #ifndef DPNBUILD_ONLYONEPROCESSOR
const DWORD dwCPUNum, #endif // ! DPNBUILD_ONLYONEPROCESSOR
const PFNDPNMESSAGEHANDLER pfnMsgHandler, PVOID const pvMsgHandlerContext, const DWORD dwWorkerThreadTlsIndex) #endif // ! DPNBUILD_ONLYONETHREAD
{ HRESULT hr; BOOL fInittedWorkItemPool = FALSE; #if ((! defined(WINCE)) || (defined(DBG)))
BOOL fInittedListLock = FALSE; #endif // ! WINCE or DBG
BOOL fInittedTimerInfo = FALSE; #ifndef WINCE
BOOL fInittedIoInfo = FALSE; #endif // ! WINCE
#ifdef DBG
DWORD dwError; #endif // DBG
DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
pWorkQueue->Sig[0] = 'W'; pWorkQueue->Sig[1] = 'R'; pWorkQueue->Sig[2] = 'K'; pWorkQueue->Sig[3] = 'Q';
pWorkQueue->pWorkItemPool = (CFixedPool*) DNMalloc(sizeof(CFixedPool)); if (pWorkQueue->pWorkItemPool == NULL) { DPFX(DPFPREP, 0, "Couldn't allocate new work item pool!"); hr = DPNERR_OUTOFMEMORY; goto Failure; }
if (! pWorkQueue->pWorkItemPool->Initialize(sizeof(CWorkItem), CWorkItem::FPM_Alloc, CWorkItem::FPM_Get, CWorkItem::FPM_Release, //CWorkItem::FPM_Dealloc))
NULL)) { DPFX(DPFPREP, 0, "Couldn't initialize work item pool!"); hr = DPNERR_OUTOFMEMORY; goto Failure; } fInittedWorkItemPool = TRUE;
#if ((! defined(WINCE)) || (defined(DBG)))
if (! DNInitializeCriticalSection(&pWorkQueue->csListLock)) { DPFX(DPFPREP, 0, "Couldn't initialize list lock!"); hr = DPNERR_OUTOFMEMORY; goto Failure; } DebugSetCriticalSectionRecursionCount(&pWorkQueue->csListLock, 0); fInittedListLock = TRUE; #endif // ! WINCE or DBG
#ifndef DPNBUILD_USEIOCOMPLETIONPORTS
DNInitializeSListHead(&pWorkQueue->SlistFreeQueueNodes);
//
// Add an initial node entry as required by NB Queue implementation.
//
DNInterlockedPushEntrySList(&pWorkQueue->SlistFreeQueueNodes, (DNSLIST_ENTRY*) (&pWorkQueue->NBQueueBlockInitial));
//
// Initialize the actual non-blocking queue.
//
pWorkQueue->pvNBQueueWorkItems = DNInitializeNBQueueHead(&pWorkQueue->SlistFreeQueueNodes); if (pWorkQueue->pvNBQueueWorkItems == NULL) { DPFX(DPFPREP, 0, "Couldn't initialize non-blocking queue!"); hr = DPNERR_OUTOFMEMORY; goto Failure; } #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#ifndef DPNBUILD_ONLYONETHREAD
pWorkQueue->fTimerThreadNeeded = TRUE; pWorkQueue->dwNumThreadsExpected = 0; pWorkQueue->dwNumBusyThreads = 0; pWorkQueue->dwNumRunningThreads = 0; #endif // ! DPNBUILD_ONLYONETHREAD
#ifndef DPNBUILD_ONLYONEPROCESSOR
pWorkQueue->dwCPUNum = dwCPUNum; #endif // ! DPNBUILD_ONLYONEPROCESSOR
#ifdef DPNBUILD_USEIOCOMPLETIONPORTS
//
// Create an I/O completion port.
//
HANDLE hIoCompletionPort;
hIoCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (hIoCompletionPort == NULL) { #ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create I/O completion port (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; } pWorkQueue->hIoCompletionPort = MAKE_DNHANDLE(hIoCompletionPort); #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
//
// Create an event used to wake up idle worker threads.
//
pWorkQueue->hAlertEvent = DNCreateEvent(NULL, FALSE, FALSE, NULL); if (pWorkQueue->hAlertEvent == NULL) { #ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create alert event (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; } #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#ifndef DPNBUILD_ONLYONETHREAD
pWorkQueue->hExpectedThreadsEvent = NULL; pWorkQueue->pfnMsgHandler = pfnMsgHandler; pWorkQueue->pvMsgHandlerContext = pvMsgHandlerContext; pWorkQueue->dwWorkerThreadTlsIndex = dwWorkerThreadTlsIndex; #endif // ! DPNBUILD_ONLYONETHREAD
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Initialize our debugging/tuning statistics.
//
pWorkQueue->dwTotalNumWorkItems = 0; #ifndef WINCE
pWorkQueue->dwTotalTimeSpentUnsignalled = 0; pWorkQueue->dwTotalTimeSpentInWorkCallbacks = 0; #endif // ! WINCE
#ifndef DPNBUILD_ONLYONETHREAD
pWorkQueue->dwTotalNumTimerThreadAbdications = 0; #endif // ! DPNBUILD_ONLYONETHREAD
pWorkQueue->dwTotalNumWakesWithoutWork = 0; pWorkQueue->dwTotalNumContinuousWork = 0; pWorkQueue->dwTotalNumDoWorks = 0; pWorkQueue->dwTotalNumDoWorksTimeLimit = 0; pWorkQueue->dwTotalNumSimultaneousQueues = 0; memset(pWorkQueue->aCallbackStats, 0, sizeof(pWorkQueue->aCallbackStats)); #endif // DPNBUILD_THREADPOOLSTATISTICS
#ifdef DBG
#ifndef DPNBUILD_ONLYONETHREAD
//
// Initialize the structures helpful for debugging.
//
pWorkQueue->blThreadList.Initialize(); #endif // ! DPNBUILD_ONLYONETHREAD
#endif // DBG
//
// Initialize the timer aspects of the work queue
//
hr = InitializeWorkQueueTimerInfo(pWorkQueue); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't initialize timer info for work queue!"); goto Failure; } fInittedTimerInfo = TRUE;
#ifndef WINCE
//
// Initialize the I/O aspects of the work queue
//
hr = InitializeWorkQueueIoInfo(pWorkQueue); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't initialize I/O info for work queue!"); goto Failure; } fInittedIoInfo = TRUE; #endif // ! WINCE
Exit:
DPFX(DPFPREP, 6, "Returning: [0x%lx]", hr);
return hr;
Failure:
#ifndef WINCE
if (fInittedIoInfo) { DeinitializeWorkQueueIoInfo(pWorkQueue); fInittedIoInfo = FALSE; } #endif // ! WINCE
if (fInittedTimerInfo) { DeinitializeWorkQueueTimerInfo(pWorkQueue); fInittedTimerInfo = FALSE; }
#ifdef DPNBUILD_USEIOCOMPLETIONPORTS
if (pWorkQueue->hIoCompletionPort != NULL) { DNCloseHandle(pWorkQueue->hIoCompletionPort); pWorkQueue->hIoCompletionPort = NULL; } #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
if (pWorkQueue->hAlertEvent != NULL) { DNCloseHandle(pWorkQueue->hAlertEvent); pWorkQueue->hAlertEvent = NULL; }
if (pWorkQueue->pvNBQueueWorkItems != NULL) { DNDeinitializeNBQueueHead(pWorkQueue->pvNBQueueWorkItems); pWorkQueue->pvNBQueueWorkItems = NULL; } #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#if ((! defined(WINCE)) || (defined(DBG)))
if (fInittedListLock) { DNDeleteCriticalSection(&pWorkQueue->csListLock); fInittedListLock = FALSE; } #endif // ! WINCE or DBG
if (pWorkQueue->pWorkItemPool != NULL) { if (fInittedWorkItemPool) { pWorkQueue->pWorkItemPool->DeInitialize(); fInittedWorkItemPool = FALSE; }
DNFree(pWorkQueue->pWorkItemPool); pWorkQueue->pWorkItemPool = NULL; }
goto Exit; } // InitializeWorkQueue
#undef DPF_MODNAME
#define DPF_MODNAME "DeinitializeWorkQueue"
//=============================================================================
// DeinitializeWorkQueue
//-----------------------------------------------------------------------------
//
// Description: Cleans up the work queue.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to clean up.
//
// Returns: Nothing.
//=============================================================================
void DeinitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue) { #ifndef DPNBUILD_ONLYONETHREAD
HRESULT hr; #endif // ! DPNBUILD_ONLYONETHREAD
BOOL fResult; #ifdef DBG
DWORD dwMaxRecursionCount = 0; #ifdef WINNT
HANDLE hThread; FILETIME ftIgnoredCreation; FILETIME ftIgnoredExit; ULONGLONG ullKernel; ULONGLONG ullUser; #endif // WINNT
#endif // DBG
DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
#ifndef DPNBUILD_ONLYONETHREAD
#ifdef DBG
//
// Assert that there are truly threads running if there's supposed to be,
// and that we're not being called on one of those threads.
//
DNEnterCriticalSection(&pWorkQueue->csListLock); if (pWorkQueue->dwNumRunningThreads > 0) { CBilink * pBilink; DPTPWORKERTHREAD * pWorkerThread;
DNASSERT(! pWorkQueue->blThreadList.IsEmpty()); pBilink = pWorkQueue->blThreadList.GetNext(); while (pBilink != &pWorkQueue->blThreadList) { pWorkerThread = CONTAINING_OBJECT(pBilink, DPTPWORKERTHREAD, blList);
#ifdef WINNT
hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, pWorkerThread->dwThreadID); if (hThread != NULL) { if (GetThreadTimes(hThread, &ftIgnoredCreation, &ftIgnoredExit, (LPFILETIME) (&ullKernel), (LPFILETIME) (&ullUser))) { DPFX(DPFPREP, 6, "Found worker thread ID %u/0x%x, max recursion = %u, user time = %u, kernel time = %u.", pWorkerThread->dwThreadID, pWorkerThread->dwThreadID, pWorkerThread->dwMaxRecursionCount, (ULONG) (ullUser / ((ULONGLONG) 10000)), (ULONG) (ullKernel / ((ULONGLONG) 10000))); } else { DPFX(DPFPREP, 6, "Found worker thread ID %u/0x%x, max recursion = %u (get thread times failed).", pWorkerThread->dwThreadID, pWorkerThread->dwThreadID, pWorkerThread->dwMaxRecursionCount); }
CloseHandle(hThread); hThread = NULL; } else #endif // WINNT
{ DPFX(DPFPREP, 6, "Found worker thread ID %u/0x%x, max recursion = %u.", pWorkerThread->dwThreadID, pWorkerThread->dwThreadID, pWorkerThread->dwMaxRecursionCount); }
DNASSERT(pWorkerThread->dwThreadID != GetCurrentThreadId());
if (pWorkerThread->dwMaxRecursionCount > dwMaxRecursionCount) { dwMaxRecursionCount = pWorkerThread->dwMaxRecursionCount; }
pBilink = pBilink->GetNext(); } } else { DNASSERT(pWorkQueue->blThreadList.IsEmpty()); } DNLeaveCriticalSection(&pWorkQueue->csListLock); #endif // DBG
//
// Stop all threads, if there were any.
//
if (pWorkQueue->dwNumRunningThreads > 0) { hr = StopThreads(pWorkQueue, pWorkQueue->dwNumRunningThreads); DNASSERT(hr == DPN_OK); } else { //
// Make sure the count didn't go negative.
//
DNASSERT(pWorkQueue->dwNumRunningThreads == 0); }
//
// All threads should be gone by now. Technically, that isn't true because
// they still have some minor cleanup code to run after decrementing
// lNumRunningThreads. But for our purposes, the threads are gone.
// We also know that they pull themselves out of blThreadList prior to
// alerting us that they've left, so the list should be empty by now.
//
#ifdef DBG
DNASSERT(pWorkQueue->blThreadList.IsEmpty()); #endif // DBG
#endif // ! DPNBUILD_ONLYONETHREAD
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Print our debugging/tuning statistics.
//
#ifdef DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 7, "Work queue 0x%p work stats:", pWorkQueue); #else // ! DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 7, "Work queue 0x%p (CPU %u) work stats:", pWorkQueue, pWorkQueue->dwCPUNum); #endif // ! DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 7, " TotalNumWorkItems = %u", pWorkQueue->dwTotalNumWorkItems); #ifndef WINCE
DPFX(DPFPREP, 7, " TotalTimeSpentUnsignalled = %u", pWorkQueue->dwTotalTimeSpentUnsignalled); DPFX(DPFPREP, 7, " TotalTimeSpentInWorkCallbacks = %u", pWorkQueue->dwTotalTimeSpentInWorkCallbacks); #endif // ! WINCE
#ifndef DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 7, " TotalNumTimerThreadAbdications = %u", pWorkQueue->dwTotalNumTimerThreadAbdications); #endif // ! DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 7, " TotalNumWakesWithoutWork = %u", pWorkQueue->dwTotalNumWakesWithoutWork); DPFX(DPFPREP, 7, " TotalNumContinuousWork = %u", pWorkQueue->dwTotalNumContinuousWork); DPFX(DPFPREP, 7, " TotalNumDoWorks = %u", pWorkQueue->dwTotalNumDoWorks); DPFX(DPFPREP, 7, " TotalNumDoWorksTimeLimit = %u", pWorkQueue->dwTotalNumDoWorksTimeLimit); DPFX(DPFPREP, 7, " TotalNumSimultaneousQueues = %u", pWorkQueue->dwTotalNumSimultaneousQueues); DPFX(DPFPREP, 7, " MaxRecursionCount = %u", dwMaxRecursionCount); #endif // DPNBUILD_THREADPOOLSTATISTICS
#ifndef WINCE
DeinitializeWorkQueueIoInfo(pWorkQueue); #endif // ! WINCE
DeinitializeWorkQueueTimerInfo(pWorkQueue);
#ifdef DPNBUILD_USEIOCOMPLETIONPORTS
fResult = DNCloseHandle(pWorkQueue->hIoCompletionPort); DNASSERT(fResult); pWorkQueue->hIoCompletionPort = NULL; #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
fResult = DNCloseHandle(pWorkQueue->hAlertEvent); DNASSERT(fResult); pWorkQueue->hAlertEvent = NULL;
DNASSERT(DNIsNBQueueEmpty(pWorkQueue->pvNBQueueWorkItems)); DNDeinitializeNBQueueHead(pWorkQueue->pvNBQueueWorkItems); pWorkQueue->pvNBQueueWorkItems = NULL; #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#if ((! defined(WINCE)) || (defined(DBG)))
DNDeleteCriticalSection(&pWorkQueue->csListLock); #endif // ! WINCE or DBG
//
// All of the NB queue nodes should be back in the pool, but there's no way
// to tell if we have the correct amount.
//
DNASSERT(pWorkQueue->pWorkItemPool != NULL); pWorkQueue->pWorkItemPool->DeInitialize(); DNFree(pWorkQueue->pWorkItemPool); pWorkQueue->pWorkItemPool = NULL;
DPFX(DPFPREP, 6, "Leave"); } // DeinitializeWorkQueue
#undef DPF_MODNAME
#define DPF_MODNAME "QueueWorkItem"
//=============================================================================
// QueueWorkItem
//-----------------------------------------------------------------------------
//
// Description: Queues a new work item for processing.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
// PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute as soon as
// possible.
// PVOID pvCallbackContext - User specified context to pass to
// callback.
//
// Returns: BOOL
// TRUE - Successfully queued the item.
// FALSE - Failed to allocate memory for queueing the item.
//=============================================================================
BOOL QueueWorkItem(DPTPWORKQUEUE * const pWorkQueue, const PFNDPTNWORKCALLBACK pfnWorkCallback, PVOID const pvCallbackContext) { CWorkItem * pWorkItem; BOOL fResult;
pWorkItem = (CWorkItem*) pWorkQueue->pWorkItemPool->Get(pWorkQueue); if (pWorkItem == NULL) { DPFX(DPFPREP, 0, "Couldn't get new work item from pool!"); return FALSE; }
DPFX(DPFPREP, 5, "Creating and queuing work item 0x%p (fn = 0x%p, context = 0x%p, queue = 0x%p).", pWorkItem, pfnWorkCallback, pvCallbackContext, pWorkQueue);
pWorkItem->m_pfnWorkCallback = pfnWorkCallback; pWorkItem->m_pvCallbackContext = pvCallbackContext; #ifdef DPNBUILD_THREADPOOLSTATISTICS
pWorkItem->m_fCancelledOrCompleting = TRUE;
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWorkItems)); ThreadpoolStatsCreate(pWorkItem); ThreadpoolStatsQueue(pWorkItem); #endif // DPNBUILD_THREADPOOLSTATISTICS
#ifdef DPNBUILD_USEIOCOMPLETIONPORTS
fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), 0, 0, &pWorkItem->m_Overlapped); if (! fResult) { #ifdef DBG
DWORD dwError;
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't post queued completion status to port 0x%p (err = %u)!", pWorkQueue->hIoCompletionPort, dwError); #endif // DBG
//
// Careful, the item has been queued but it's possibly nobody knows...
//
} #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
DNInsertTailNBQueue(pWorkQueue->pvNBQueueWorkItems, (ULONG64) pWorkItem);
//
// Alert the threads that there's a new item to process.
//
fResult = DNSetEvent(pWorkQueue->hAlertEvent); if (! fResult) { #ifdef DBG
DWORD dwError;
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't set alert event 0x%p (err = %u)!", pWorkQueue->hAlertEvent, dwError); #endif // DBG
//
// Careful, the item has been queued but it's possibly nobody knows...
//
} #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
return TRUE; } // QueueWorkItem
#ifndef DPNBUILD_ONLYONETHREAD
#undef DPF_MODNAME
#define DPF_MODNAME "StartThreads"
//=============================================================================
// StartThreads
//-----------------------------------------------------------------------------
//
// Description: Increases the number of threads for the work queue.
//
// It is assumed that only one thread will call this function
// at a time, and no threads are currently being stopped.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object.
// DWORD dwNumThreads - Number of threads to start.
//
// Returns: HRESULT
// DPN_OK - Increasing the number of threads was successful.
// DPNERR_OUTOFMEMORY - Not enough memory to alter the number of threads.
//=============================================================================
HRESULT StartThreads(DPTPWORKQUEUE * const pWorkQueue, const DWORD dwNumThreads) { HRESULT hr = DPN_OK; DNHANDLE ahWaitObjects[MAX_SIMULTANEOUS_THREAD_START + 1]; DWORD adwThreadID[MAX_SIMULTANEOUS_THREAD_START]; DWORD dwNumThreadsExpected; DWORD dwTotalThreadsRemaining; DWORD dwTemp; DWORD dwResult; #ifdef DBG
DWORD dwError; #endif // DBG
DNASSERT(dwNumThreads > 0);
//
// Fill the entire array with NULL.
//
memset(ahWaitObjects, 0, sizeof(ahWaitObjects));
//
// Initialize the remaining count.
//
dwTotalThreadsRemaining = dwNumThreads;
//
// Create an event that will be set when an entire batch of threads has
// successfully started.
//
ahWaitObjects[0] = DNCreateEvent(NULL, FALSE, FALSE, NULL); if (ahWaitObjects[0] == NULL) { #ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create event (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; }
DNASSERT(pWorkQueue->hExpectedThreadsEvent == NULL); pWorkQueue->hExpectedThreadsEvent = ahWaitObjects[0];
//
// Keep adding batches of threads until we've started the requested amount.
//
while (dwTotalThreadsRemaining > 0) { //
// Set the counter of how many threads we're starting in this batch.
// WaitForSingleObjects can only handle a fixed amount of handles
// at a time, so if we can't fit any more, we'll have to pick them
// up again in the next loop.
//
//
dwNumThreadsExpected = dwTotalThreadsRemaining; if (dwNumThreadsExpected > MAX_SIMULTANEOUS_THREAD_START) { dwNumThreadsExpected = MAX_SIMULTANEOUS_THREAD_START; }
DNASSERT(pWorkQueue->dwNumThreadsExpected == 0); pWorkQueue->dwNumThreadsExpected = dwNumThreadsExpected;
for(dwTemp = 1; dwTemp <= dwNumThreadsExpected; dwTemp++) { ahWaitObjects[dwTemp] = DNCreateThread(NULL, 0, DPTPWorkerThreadProc, pWorkQueue, 0, &adwThreadID[dwTemp - 1]); if (ahWaitObjects[dwTemp] == NULL) { #ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create thread (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; }
dwTotalThreadsRemaining--; }
//
// Wait for either the successful start event or one of the threads to
// die prematurely.
//
DPFX(DPFPREP, 4, "Waiting for %u threads for queue 0x%p to start.", dwNumThreadsExpected, pWorkQueue); dwResult = DNWaitForMultipleObjects((dwNumThreadsExpected + 1), ahWaitObjects, FALSE, INFINITE); if (dwResult != WAIT_OBJECT_0) { if ((dwResult > WAIT_OBJECT_0) && (dwResult <= (WAIT_OBJECT_0 + (MAX_SIMULTANEOUS_THREAD_START - 1)))) { #ifdef DBG
dwResult -= WAIT_OBJECT_0; dwError = 0; GetExitCodeThread(ahWaitObjects[dwResult + 1], &dwError); DPFX(DPFPREP, 0, "Thread index %u (ID %u/0x%x shut down before starting successfully (err = %u)!", dwResult, adwThreadID[dwResult], adwThreadID[dwResult], dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; }
#ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Waiting for threads to start failed (err = %u)!", dwError); #endif // DBG
hr = DPNERR_GENERIC; goto Failure; }
//
// All the threads we were expecting to start, did.
//
DPFX(DPFPREP, 4, "Successfully started %u threads for queue 0x%p.", dwNumThreadsExpected, pWorkQueue);
//
// Close the handles for those threads since we will never use them
// again.
//
while (dwNumThreadsExpected > 0) { DNCloseHandle(ahWaitObjects[dwNumThreadsExpected]); ahWaitObjects[dwNumThreadsExpected] = NULL; dwNumThreadsExpected--; } } // end while (still more threads to add)
DNCloseHandle(ahWaitObjects[0]); ahWaitObjects[0] = NULL;
Exit:
pWorkQueue->hExpectedThreadsEvent = NULL;
return hr;
Failure:
for(dwTemp = 0; dwTemp <= MAX_SIMULTANEOUS_THREAD_START; dwTemp++) { if (ahWaitObjects[dwTemp] != NULL) { DNCloseHandle(ahWaitObjects[dwTemp]); ahWaitObjects[dwTemp] = NULL; } }
//
// Stop all threads that we successfully launched so that we end up back in
// the same state as when we started. Ignore error because we're already
// failing.
//
dwNumThreadsExpected = dwNumThreads - dwTotalThreadsRemaining; if (dwNumThreadsExpected > 0) { pWorkQueue->hExpectedThreadsEvent = NULL; StopThreads(pWorkQueue, dwNumThreadsExpected); }
goto Exit; } // StartThreads
#undef DPF_MODNAME
#define DPF_MODNAME "StopThreads"
//=============================================================================
// StopThreads
//-----------------------------------------------------------------------------
//
// Description: Decreases the number of threads for the work queue.
//
// It is assumed that only one thread will call this function
// at a time, and no threads are currently being started.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object.
// DWORD dwNumThreads - Number of threads to stop.
//
// Returns: HRESULT
// DPN_OK - Decreasing the number of threads was successful.
// DPNERR_OUTOFMEMORY - Not enough memory to alter the number of threads.
//=============================================================================
HRESULT StopThreads(DPTPWORKQUEUE * const pWorkQueue, const DWORD dwNumThreads) { HRESULT hr; DWORD dwTemp; DWORD dwResult;
DNASSERT(dwNumThreads > 0);
//
// Create an event that will be set when the desired number of threads has
// started shutting down.
//
DNASSERT(pWorkQueue->hExpectedThreadsEvent == NULL); pWorkQueue->hExpectedThreadsEvent = DNCreateEvent(NULL, FALSE, FALSE, NULL); if (pWorkQueue->hExpectedThreadsEvent == NULL) { #ifdef DBG
DWORD dwError;
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create event (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; }
DNASSERT(pWorkQueue->dwNumThreadsExpected == 0); pWorkQueue->dwNumThreadsExpected = dwNumThreads;
for(dwTemp = 0; dwTemp < dwNumThreads; dwTemp++) { //
// Queueing something with a NULL callback signifies "exit thread".
//
if (! QueueWorkItem(pWorkQueue, NULL, NULL)) { DPFX(DPFPREP, 0, "Couldn't queue exit thread work item!"); hr = DPNERR_OUTOFMEMORY; goto Failure; } } // end while (still more threads to remove)
//
// Wait for the last thread out to set the event.
//
DPFX(DPFPREP, 4, "Waiting for %u threads from queue 0x%p to stop.", dwNumThreads, pWorkQueue); dwResult = DNWaitForSingleObject(pWorkQueue->hExpectedThreadsEvent, INFINITE); DNASSERT(dwResult == WAIT_OBJECT_0);
//
// When the wait completes successfully, it means that the threads are on
// their way out. It does *not* mean the threads have stopped completely.
// We have to assume that the threads will have time to fully quit before
// anything major happens, such as unloading this module.
//
hr = DPN_OK;
Exit:
if (pWorkQueue->hExpectedThreadsEvent != NULL) { DNCloseHandle(pWorkQueue->hExpectedThreadsEvent); pWorkQueue->hExpectedThreadsEvent = NULL; }
return hr;
Failure:
goto Exit; } // StopThreads
#endif // ! DPNBUILD_ONLYONETHREAD
#undef DPF_MODNAME
#define DPF_MODNAME "DoWork"
//=============================================================================
// DoWork
//-----------------------------------------------------------------------------
//
// Description: Performs any work that is currently scheduled for the given
// queue. If dwMaxDoWorkTime is not INFINITE, work is started up
// until that time. At least one work item (if there is one) will
// always be executed.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
// DWORD dwMaxDoWorkTime - Maximum time at which a new job can be
// started, or INFINITE if all jobs should be
// processed before returning.
//
// Returns: Nothing.
//=============================================================================
void DoWork(DPTPWORKQUEUE * const pWorkQueue, const DWORD dwMaxDoWorkTime) #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
{ BOOL fNeedToServiceTimers; CWorkItem * pWorkItem; BOOL fResult; DWORD dwBytesTransferred; DWORD dwCompletionKey; OVERLAPPED * pOverlapped; UINT uiOriginalUniqueID;
DPFX(DPFPREP, 8, "Parameters: (0x%p, %i)", pWorkQueue, dwMaxDoWorkTime);
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorks)); #endif // DPNBUILD_THREADPOOLSTATISTICS
//
// See if no one is processing timers.
//
fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), FALSE);
//
// If need be, handle any expired or cancelled timer entries.
//
if (fNeedToServiceTimers) { ProcessTimers(pWorkQueue);
DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities because of DoWork."); #ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
pWorkQueue->dwTotalNumTimerThreadAbdications++; #endif // DPNBUILD_THREADPOOLSTATISTICS
DNASSERT(! pWorkQueue->fTimerThreadNeeded); #pragma TODO(vanceo, "Does this truly need to be interlocked?")
fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), (LONG) TRUE); DNASSERT(! fNeedToServiceTimers); // there had better not be more than one timer thread
}
//
// Keep looping until we run out of items to do. Note that this thread
// does not try to count itself as busy. Either we are in DoWork mode
// proper where the busy thread concept has no meaning, or we are a worker
// thread processing a job that called WaitWhileWorking where
// pWorkQueue->lNumBusyThreads would already have been incremented.
//
while ((GetQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), &dwBytesTransferred, &dwCompletionKey, &pOverlapped, 0)) || (pOverlapped != NULL)) { pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped); DNASSERT(pWorkItem->IsValid());
//
// Call the user's function or note that we may need to stop running.
//
if (pWorkItem->m_pfnWorkCallback != NULL) { //
// Save the uniqueness ID to determine if this item was a timer
// that got rescheduled.
//
uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p).", pWorkItem, pWorkItem->m_pfnWorkCallback, pWorkItem->m_pvCallbackContext, uiOriginalUniqueID, pWorkQueue);
ThreadpoolStatsBeginExecuting(pWorkItem); pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext, pWorkItem, uiOriginalUniqueID);
//
// Return the item to the pool unless it got rescheduled. This
// assumes that the actual pWorkItem memory remains valid even
// though it may have been rescheduled and then completed/cancelled
// by the time we perform this test. See CancelTimer.
//
if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID) { ThreadpoolStatsEndExecuting(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem); pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { ThreadpoolStatsEndExecutingRescheduled(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem); } } else { DPFX(DPFPREP, 3, "Requeuing exit thread work item 0x%p for other threads.", pWorkItem);
fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), 0, 0, &pWorkItem->m_Overlapped); DNASSERT(fResult);
#pragma BUGBUG(vanceo, "May not have processed everything we want")
break; }
//
// Make sure we haven't exceeded our time limit (if we have one).
//
if ((dwMaxDoWorkTime != INFINITE) && ((int) (dwMaxDoWorkTime - GETTIMESTAMP()) < 0)) { DPFX(DPFPREP, 5, "Exceeded time limit, not processing any more work.");
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorksTimeLimit)); #endif // DPNBUILD_THREADPOOLSTATISTICS
break; } }
DPFX(DPFPREP, 8, "Leave"); } // DoWork
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#else // ! DPNBUILD_USEIOCOMPLETIONPORTS
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{ DNSLIST_ENTRY * pSlistEntryHead = NULL; USHORT usCount = 0; DNSLIST_ENTRY * pSlistEntryTail; CWorkItem * pWorkItem; UINT uiOriginalUniqueID; #ifndef DPNBUILD_ONLYONETHREAD
BOOL fNeedToServiceTimers; #endif // ! DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 8, "Parameters: (0x%p, %i)", pWorkQueue, dwMaxDoWorkTime);
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorks)); #endif // DPNBUILD_THREADPOOLSTATISTICS
#ifndef WINCE
//
// Handle any I/O completions.
//
ProcessIo(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount); #endif // ! WINCE
#ifndef DPNBUILD_ONLYONETHREAD
//
// See if no one is processing timers.
//
fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), FALSE);
//
// If need be, handle any expired or cancelled timer entries.
//
if (fNeedToServiceTimers) #endif // ! DPNBUILD_ONLYONETHREAD
{ ProcessTimers(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount); }
//
// Queue any work items we accumulated in one fell swoop.
//
if (pSlistEntryHead != NULL) { #ifdef DPNBUILD_THREADPOOLSTATISTICS
if (usCount > 1) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumSimultaneousQueues)); } #ifdef WINCE
LONG lCount;
lCount = usCount; while (lCount > 0) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWorkItems)); lCount--; } #else // ! WINCE
DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalNumWorkItems), usCount); #endif // ! WINCE
#endif // DPNBUILD_THREADPOOLSTATISTICS
DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems, pSlistEntryHead, OFFSETOF(CWorkItem, m_SlistEntry)); }
#ifndef DPNBUILD_ONLYONETHREAD
//
// In case other threads are running, one of them should become a timer
// thread. We will need to kick them via the alert event so that they
// notice.
//
if (fNeedToServiceTimers) { DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities because of DoWork."); #ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
pWorkQueue->dwTotalNumTimerThreadAbdications++; #endif // DPNBUILD_THREADPOOLSTATISTICS
DNASSERT(! pWorkQueue->fTimerThreadNeeded); #pragma TODO(vanceo, "Does this truly need to be interlocked?")
fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), (LONG) TRUE); DNASSERT(! fNeedToServiceTimers); // there had better not be more than one timer thread
DNSetEvent(pWorkQueue->hAlertEvent); }
//
// Reset the tracking variables.
//
pSlistEntryHead = NULL; usCount = 0; #endif // ! DPNBUILD_ONLYONETHREAD
//
// Keep looping until we run out of items to do. Note that this thread
// does not try to count itself as busy. Either we are in DoWork mode
// proper where the busy thread concept has no meaning, or we are a worker
// thread processing a job that called WaitWhileWorking where
// pWorkQueue->lNumBusyThreads would already have been incremented.
//
pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); while (pWorkItem != NULL) { //
// Call the user's function or note that we need to stop running.
//
if (pWorkItem->m_pfnWorkCallback != NULL) { //
// Save the uniqueness ID to determine if this item was a timer
// that got rescheduled.
//
uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p).", pWorkItem, pWorkItem->m_pfnWorkCallback, pWorkItem->m_pvCallbackContext, uiOriginalUniqueID, pWorkQueue);
ThreadpoolStatsBeginExecuting(pWorkItem); pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext, pWorkItem, uiOriginalUniqueID);
//
// Return the item to the pool unless it got rescheduled. This
// assumes that the actual pWorkItem memory remains valid even
// though it may have been rescheduled and then completed/cancelled
// by the time we perform this test. See CancelTimer.
//
if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID) { ThreadpoolStatsEndExecuting(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem); pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { ThreadpoolStatsEndExecutingRescheduled(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem); } } else { DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p.", pWorkItem);
//
// This thread shouldn't get told to exit when either in DoWork
// mode proper, or while performing work while waiting. However,
// it is possible in the latter case to pick up a quit event
// "intended" (in a sense) for another thread. We need to resubmit
// the exit thread request so that another thread that can process
// it, will.
//
// If we just put it back on the queue now, our while loop would
// probably just pull it off again getting us nowhere. Instead, we
// will save up all of the exit requests we mistakenly receive and
// dump them all back on the queue once we've run out of things to
// do.
//
#ifdef DPNBUILD_ONLYONETHREAD
DNASSERT(FALSE); #else // ! DPNBUILD_ONLYONETHREAD
DNASSERT(pWorkQueue->dwNumRunningThreads > 1); if (pSlistEntryHead == NULL) { pSlistEntryTail = pSlistEntryHead; } pWorkItem->m_SlistEntry.Next = pSlistEntryHead; pSlistEntryHead = &pWorkItem->m_SlistEntry; usCount++; #endif // ! DPNBUILD_ONLYONETHREAD
}
//
// Make sure we haven't exceeded our time limit (if we have one).
//
if ((dwMaxDoWorkTime != INFINITE) && ((int) (dwMaxDoWorkTime - GETTIMESTAMP()) < 0)) { DPFX(DPFPREP, 5, "Exceeded time limit, not processing any more work.");
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorksTimeLimit)); #endif // DPNBUILD_THREADPOOLSTATISTICS
break; }
//
// Try to get the next work item.
//
pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); #ifdef DPNBUILD_THREADPOOLSTATISTICS
if (pWorkItem != NULL) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumContinuousWork)); } #endif // DPNBUILD_THREADPOOLSTATISTICS
} // end while (more items)
#ifndef DPNBUILD_ONLYONETHREAD
//
// Re-queue any exit thread work items we accumulated.
//
if (pSlistEntryHead != NULL) { DPFX(DPFPREP, 1, "Re-queuing %u exit thread work items for queue 0x%p.", usCount, pWorkQueue);
DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems, pSlistEntryHead, OFFSETOF(CWorkItem, m_SlistEntry)); } else { DNASSERT(usCount == 0); } #endif // ! DPNBUILD_ONLYONETHREAD
DPFX(DPFPREP, 8, "Leave"); } // DoWork
#endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#ifndef DPNBUILD_ONLYONETHREAD
#undef DPF_MODNAME
#define DPF_MODNAME "DPTPWorkerThreadProc"
//=============================================================================
// DPTPWorkerThreadProc
//-----------------------------------------------------------------------------
//
// Description: The standard worker thread function for executing work
// items.
//
// Arguments:
// PVOID pvParameter - Pointer to thread parameter data.
//
// Returns: DWORD
//=============================================================================
DWORD WINAPI DPTPWorkerThreadProc(PVOID pvParameter) { DPTPWORKQUEUE * pWorkQueue = (DPTPWORKQUEUE*) pvParameter; DPTPWORKERTHREAD WorkerThread; BOOL fUninitializeCOM = TRUE; PFNDPNMESSAGEHANDLER pfnMsgHandler; PVOID pvMsgHandlerContext; PVOID pvUserThreadContext; HRESULT hr; DWORD dwResult; #ifndef DPNBUILD_ONLYONEPROCESSOR
#if ((! defined(DPNBUILD_SOFTTHREADAFFINITY)) && (! defined(DPNBUILD_USEIOCOMPLETIONPORTS)))
DWORD_PTR dwpAffinityMask; #endif // ! DPNBUILD_SOFTTHREADAFFINITY and ! DPNBUILD_USEIOCOMPLETIONPORTS
#ifdef DBG
SYSTEM_INFO SystemInfo; #endif // DBG
#endif // ! DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 6, "Parameters: (0x%p)", pvParameter);
#ifndef DPNBUILD_ONLYONEPROCESSOR
//
// Bind to a specific CPU. First, assert that the CPU number is valid in
// debug builds.
//
#ifdef DBG
GetSystemInfo(&SystemInfo); DNASSERT(pWorkQueue->dwCPUNum < SystemInfo.dwNumberOfProcessors); #endif // DBG
#ifndef DPNBUILD_USEIOCOMPLETIONPORTS
SetThreadIdealProcessor(GetCurrentThread(), pWorkQueue->dwCPUNum); #ifndef DPNBUILD_SOFTTHREADAFFINITY
DNASSERT(pWorkQueue->dwCPUNum < (sizeof(dwpAffinityMask) * 8)); dwpAffinityMask = 1 << pWorkQueue->dwCPUNum; SetThreadAffinityMask(GetCurrentThread(), dwpAffinityMask); #endif // ! DPNBUILD_SOFTTHREADAFFINITY
#endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#endif // ! DPNBUILD_ONLYONEPROCESSOR
//
// Boost the thread priority.
//
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
//
// Init COM.
//
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { DPFX(DPFPREP, 0, "Failed to initialize COM (err = 0x%lx)! Continuing.", hr); fUninitializeCOM = FALSE;
//
// Continue...
//
}
//
// Initialize the worker thread data.
//
memset(&WorkerThread, 0, sizeof(WorkerThread));
WorkerThread.Sig[0] = 'W'; WorkerThread.Sig[1] = 'K'; WorkerThread.Sig[2] = 'T'; WorkerThread.Sig[3] = 'D';
WorkerThread.pWorkQueue = pWorkQueue;
#ifdef DBG
WorkerThread.dwThreadID = GetCurrentThreadId();
WorkerThread.blList.Initialize();
DNEnterCriticalSection(&pWorkQueue->csListLock); WorkerThread.blList.InsertBefore(&pWorkQueue->blThreadList); DNLeaveCriticalSection(&pWorkQueue->csListLock); #endif // DBG
//
// Save the worker thread data.
//
TlsSetValue(pWorkQueue->dwWorkerThreadTlsIndex, &WorkerThread);
dwResult = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumRunningThreads)); DPFX(DPFPREP, 7, "Thread %u/0x%x from queue 0x%p started, num running threads is now %u.", GetCurrentThreadId(), GetCurrentThreadId(), pWorkQueue, dwResult);
//
// Save the current user message handler. If there is one, call it now
// with thread initialization information.
//
pfnMsgHandler = pWorkQueue->pfnMsgHandler; if (pfnMsgHandler != NULL) { DPNMSG_CREATE_THREAD MsgCreateThread;
//
// Save the message handler context.
//
pvMsgHandlerContext = pWorkQueue->pvMsgHandlerContext;
//
// Call the user's message handler with a CREATE_THREAD message.
//
MsgCreateThread.dwSize = sizeof(MsgCreateThread); MsgCreateThread.dwFlags = 0; #ifdef DPNBUILD_ONLYONEPROCESSOR
MsgCreateThread.dwProcessorNum = 0; #else // ! DPNBUILD_ONLYONEPROCESSOR
MsgCreateThread.dwProcessorNum = pWorkQueue->dwCPUNum; #endif // ! DPNBUILD_ONLYONEPROCESSOR
MsgCreateThread.pvUserContext = NULL;
hr = pfnMsgHandler(pvMsgHandlerContext, DPN_MSGID_CREATE_THREAD, &MsgCreateThread); #ifdef DBG
if (hr != DPN_OK) { DPFX(DPFPREP, 0, "User returned error 0x%08x from CREATE_THREAD indication!", 1, hr); } #endif // DBG
//
// Save what the user returned for a thread context.
//
pvUserThreadContext = MsgCreateThread.pvUserContext; }
//
// The user (if any) now knows about the thread.
//
WorkerThread.fThreadIndicated = TRUE;
DNASSERT(pWorkQueue->dwNumThreadsExpected > 0); dwResult = DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumThreadsExpected)); if (dwResult == 0) { DPFX(DPFPREP, 9, "All threads expected to start have, setting event."); DNASSERT(pWorkQueue->hExpectedThreadsEvent != NULL); DNSetEvent(pWorkQueue->hExpectedThreadsEvent); // ignore error
} else { DPFX(DPFPREP, 9, "Number of threads expected to start is now %u.", dwResult); }
//
// Perform the work loop.
//
DPTPWorkerLoop(pWorkQueue);
//
// The user (if any) is about to be told about the thread's destruction.
//
WorkerThread.fThreadIndicated = FALSE;
//
// If there was a user message handler, call it now with thread shutdown
// information.
//
if (pfnMsgHandler != NULL) { DPNMSG_DESTROY_THREAD MsgDestroyThread;
//
// Call the user's message handler with a DESTROY_THREAD message.
//
MsgDestroyThread.dwSize = sizeof(MsgDestroyThread); #ifdef DPNBUILD_ONLYONEPROCESSOR
MsgDestroyThread.dwProcessorNum = 0; #else // ! DPNBUILD_ONLYONEPROCESSOR
MsgDestroyThread.dwProcessorNum = pWorkQueue->dwCPUNum; #endif // ! DPNBUILD_ONLYONEPROCESSOR
MsgDestroyThread.pvUserContext = pvUserThreadContext;
hr = pfnMsgHandler(pvMsgHandlerContext, DPN_MSGID_DESTROY_THREAD, &MsgDestroyThread); #ifdef DBG
if (hr != DPN_OK) { DPFX(DPFPREP, 0, "User returned error 0x%08x from DESTROY_THREAD indication!", 1, hr); } #endif // DBG
pfnMsgHandler = NULL; pvMsgHandlerContext = NULL; pvUserThreadContext = NULL; }
#ifdef DBG
DNEnterCriticalSection(&pWorkQueue->csListLock); WorkerThread.blList.RemoveFromList(); DNLeaveCriticalSection(&pWorkQueue->csListLock);
DNASSERT(WorkerThread.dwRecursionCount == 0); DNASSERT(TlsGetValue(pWorkQueue->dwWorkerThreadTlsIndex) == &WorkerThread); #endif // DBG
DNASSERT(pWorkQueue->dwNumRunningThreads > 0); dwResult = DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumRunningThreads)); DPFX(DPFPREP, 7, "Thread %u/0x%x from queue 0x%p done, num running threads is now %u.", GetCurrentThreadId(), GetCurrentThreadId(), pWorkQueue, dwResult);
#ifndef WINCE
CancelIoForThisThread(pWorkQueue); #endif // ! WINCE
if (fUninitializeCOM) { CoUninitialize(); fUninitializeCOM = FALSE; }
DNASSERT(pWorkQueue->dwNumThreadsExpected > 0); dwResult = DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumThreadsExpected)); if (dwResult == 0) { DPFX(DPFPREP, 9, "All threads expected to stop have, setting event."); DNASSERT(pWorkQueue->hExpectedThreadsEvent != NULL); DNSetEvent(pWorkQueue->hExpectedThreadsEvent); // ignore error
} else { DPFX(DPFPREP, 9, "Number of threads expected to stop is now %u.", dwResult); }
//
// Since we have decremented dwNumRunningThreads and possibly set the
// event, any other threads waiting on that will think we are gone.
// Therefore, we cannot use pWorkQueue after this as it may have been
// deallocated. We must also endeavor to do as little work as possible
// because there is a race condition where the module in which this code
// resides could be unloaded.
//
DPFX(DPFPREP, 6, "Leave");
return 0; } // DPTPWorkerThreadProc
#ifdef DPNBUILD_MANDATORYTHREADS
#undef DPF_MODNAME
#define DPF_MODNAME "DPTPMandatoryThreadProc"
//=============================================================================
// DPTPMandatoryThreadProc
//-----------------------------------------------------------------------------
//
// Description: The standard worker thread function for executing work
// items.
//
// Arguments:
// PVOID pvParameter - Pointer to thread parameter data.
//
// Returns: DWORD
//=============================================================================
DWORD WINAPI DPTPMandatoryThreadProc(PVOID pvParameter) { DPTPMANDATORYTHREAD * pMandatoryThread = (DPTPMANDATORYTHREAD*) pvParameter; DPTHREADPOOLOBJECT * pDPTPObject; PFNDPNMESSAGEHANDLER pfnMsgHandler; PVOID pvMsgHandlerContext; PVOID pvUserThreadContext; HRESULT hr; DWORD dwResult;
DPFX(DPFPREP, 6, "Parameters: (0x%p)", pvParameter);
//
// Save a copy of the local object.
//
pDPTPObject = pMandatoryThread->pDPTPObject;
#ifdef DBG
//
// Store the thread ID.
//
pMandatoryThread->dwThreadID = GetCurrentThreadId(); #endif // DBG
//
// Take the lock, and then ensure we aren't in DoWork mode.
//
DNEnterCriticalSection(&pDPTPObject->csLock);
if (pDPTPObject->dwTotalUserThreadCount == 0) { //
// Bail out of this function. The thread starting us still owns the
// pMandatoryThread memory and will handle this failure correctly.
//
DNLeaveCriticalSection(&pDPTPObject->csLock); return DPNERR_NOTALLOWED; }
//
// Increment the thread count. We use interlocked increment because we can
// touch the counter outside of the lock below.
//
DNASSERT(pDPTPObject->dwMandatoryThreadCount != -1); DNInterlockedIncrement((LPLONG) (&pDPTPObject->dwMandatoryThreadCount));
#ifdef DBG
//
// Add this thread to the list of tracked threads.
//
pMandatoryThread->blList.InsertBefore(&pDPTPObject->blMandatoryThreads); #endif // DBG
DNLeaveCriticalSection(&pDPTPObject->csLock);
//
// Save the current user message handler. If there is one, call it now
// with thread initialization information.
//
pfnMsgHandler = pMandatoryThread->pfnMsgHandler; if (pfnMsgHandler != NULL) { DPNMSG_CREATE_THREAD MsgCreateThread;
//
// Save the message handler context.
//
pvMsgHandlerContext = pMandatoryThread->pvMsgHandlerContext;
//
// Call the user's message handler with a CREATE_THREAD message.
//
MsgCreateThread.dwSize = sizeof(MsgCreateThread); MsgCreateThread.dwFlags = DPNTHREAD_MANDATORY; MsgCreateThread.dwProcessorNum = -1; MsgCreateThread.pvUserContext = NULL;
hr = pfnMsgHandler(pvMsgHandlerContext, DPN_MSGID_CREATE_THREAD, &MsgCreateThread); #ifdef DBG
if (hr != DPN_OK) { DPFX(DPFPREP, 0, "User returned error 0x%08x from CREATE_THREAD indication!", 1, hr); } #endif // DBG
//
// Save what the user returned for a thread context.
//
pvUserThreadContext = MsgCreateThread.pvUserContext; }
//
// Alert the creator thread. After this call, we own the pMandatoryThread
// memory.
//
DPFX(DPFPREP, 9, "Thread created successfully, setting event.");
DNASSERT(pMandatoryThread->hStartedEvent != NULL); DNSetEvent(pMandatoryThread->hStartedEvent); // ignore error
//
// Call the user's thread proc function.
//
DPFX(DPFPREP, 2, "Calling user thread function 0x%p with parameter 0x%p.", pMandatoryThread->lpStartAddress, pMandatoryThread->lpParameter);
dwResult = pMandatoryThread->lpStartAddress(pMandatoryThread->lpParameter);
DPFX(DPFPREP, 2, "Returning from user thread with result %u/0x%x.", dwResult, dwResult);
//
// If there was a user message handler, call it now with thread shutdown
// information.
//
if (pfnMsgHandler != NULL) { DPNMSG_DESTROY_THREAD MsgDestroyThread;
//
// Call the user's message handler with a DESTROY_THREAD message.
//
MsgDestroyThread.dwSize = sizeof(MsgDestroyThread); MsgDestroyThread.dwProcessorNum = -1; MsgDestroyThread.pvUserContext = pvUserThreadContext;
hr = pfnMsgHandler(pvMsgHandlerContext, DPN_MSGID_DESTROY_THREAD, &MsgDestroyThread); #ifdef DBG
if (hr != DPN_OK) { DPFX(DPFPREP, 0, "User returned error 0x%08x from DESTROY_THREAD indication!", 1, hr); } #endif // DBG
pfnMsgHandler = NULL; pvMsgHandlerContext = NULL; pvUserThreadContext = NULL; }
//
// Assert that the thread pool object is still valid.
//
DNASSERT((pDPTPObject->Sig[0] == 'D') && (pDPTPObject->Sig[1] == 'P') && (pDPTPObject->Sig[2] == 'T') && (pDPTPObject->Sig[3] == 'P'));
#ifdef DBG
//
// Remove this thread from the list of tracked threads.
//
DNEnterCriticalSection(&pDPTPObject->csLock); pMandatoryThread->blList.RemoveFromList(); DNLeaveCriticalSection(&pDPTPObject->csLock); #endif // DBG
//
// Release the object resources.
//
DNFree(pMandatoryThread); pMandatoryThread = NULL;
DPFX(DPFPREP, 6, "Leave (mandatory thread count was approximately %u)", pDPTPObject->dwMandatoryThreadCount);
//
// Signal to the object that this thread is gone. There is a race
// condition because we've set the counter, but there are a non-zero number
// of instructions we still have to execute before this thread is truly
// gone. COM has a similar race condition, and if the live with it, so can
// we. Because we want to minimize the number of instructions afterward,
// note that we use interlocked decrement outside of the critical section.
//
DNASSERT(pDPTPObject->dwMandatoryThreadCount > 0); DNInterlockedDecrement((LPLONG) (&pDPTPObject->dwMandatoryThreadCount));
return dwResult; } // DPTPMandatoryThreadProc
#endif // DPNBUILD_MANDATORYTHREADS
#undef DPF_MODNAME
#define DPF_MODNAME "DPTPWorkerLoop"
//=============================================================================
// DPTPWorkerLoop
//-----------------------------------------------------------------------------
//
// Description: The worker thread looping function for executing work items.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
//
// Returns: None.
//=============================================================================
void DPTPWorkerLoop(DPTPWORKQUEUE * const pWorkQueue) #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
{ BOOL fShouldRun = TRUE; BOOL fRunningAsTimerThread = FALSE; DWORD dwBytesTransferred; DWORD dwCompletionKey; OVERLAPPED * pOverlapped; CWorkItem * pWorkItem; DWORD dwNumBusyThreads; DWORD dwNumRunningThreads; BOOL fResult; UINT uiOriginalUniqueID; #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
DWORD dwStartTime; #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
//
// Keep looping until we're told to exit.
//
while (fShouldRun) { //
// If we're not already running as a timer thread, atomically see if
// there is currently a timer thread, and become it if not.
// There can be only one.
//
if (! fRunningAsTimerThread) { fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), (LONG) FALSE); #ifdef DBG
if (fRunningAsTimerThread) { DPFX(DPFPREP, 9, "Becoming timer thread."); } #endif // DBG
}
//
// If we're the timer thread, wake up periodically to service timers by
// waiting on a waitable timer object. Otherwise, wait for a queue
// item.
//
if (fRunningAsTimerThread) { DWORD dwResult;
//DPFX(DPFPREP, 9, "Waiting on timer handle 0x%p.", pWorkQueue->hTimer);
dwResult = DNWaitForSingleObject(pWorkQueue->hTimer, INFINITE); DNASSERT(dwResult == WAIT_OBJECT_0);
//
// Handle any expired or cancelled timer entries.
//
ProcessTimers(pWorkQueue);
//
// Increase the number of busy threads.
//
dwNumBusyThreads = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
//
// Grab the current total thread count.
//
dwNumRunningThreads = *((volatile DWORD *) (&pWorkQueue->dwNumRunningThreads));
//
// If there are other threads, we won't attempt to process work
// unless they're all busy.
//
if ((dwNumRunningThreads == 1) || (dwNumBusyThreads == dwNumRunningThreads)) { #ifdef DBG
if (dwNumRunningThreads > 1) // reduce spew when only running 1 thread
{ DPFX(DPFPREP, 9, "No idle threads (busy = %u), may become worker thread.", dwNumBusyThreads); } #endif // DBG
#pragma TODO(vanceo, "Optimize single thread case (right now we only process one item per timer bucket)")
//
// See if there's any work to do at the moment.
//
fResult = GetQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), &dwBytesTransferred, &dwCompletionKey, &pOverlapped, 0); if ((fResult) || (pOverlapped != NULL)) { pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped); DNASSERT(pWorkItem->IsValid());
DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities.");
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
pWorkQueue->dwTotalNumTimerThreadAbdications++; #endif // DPNBUILD_THREADPOOLSTATISTICS
DNASSERT(! pWorkQueue->fTimerThreadNeeded); #pragma TODO(vanceo, "Does this truly need to be interlocked?")
fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), (LONG) TRUE); DNASSERT(! fRunningAsTimerThread); // there had better not be more than one timer thread
//
// Call the user's function or note that we may need to
// stop running.
//
if (pWorkItem->m_pfnWorkCallback != NULL) { //
// Save the uniqueness ID to determine if this item was
// a timer that got rescheduled.
//
uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p) as previous timer thread.", pWorkItem, pWorkItem->m_pfnWorkCallback, pWorkItem->m_pvCallbackContext, uiOriginalUniqueID, pWorkQueue);
ThreadpoolStatsBeginExecuting(pWorkItem); pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext, pWorkItem, uiOriginalUniqueID);
//
// Return the item to the pool unless it got
// rescheduled. This assumes that the actual pWorkItem
// memory remains valid even though it may have been
// rescheduled and then completed/cancelled by the time
// we perform this test. See CancelTimer.
//
if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID) { ThreadpoolStatsEndExecuting(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem); pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { ThreadpoolStatsEndExecutingRescheduled(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem); } } else { //
// If there are any other threads, requeue the kill
// thread work item, otherwise, quit.
//
if (dwNumRunningThreads > 1) { DPFX(DPFPREP, 3, "Requeuing exit thread work item 0x%p for other threads.", pWorkItem);
fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), 0, 0, &pWorkItem->m_Overlapped); DNASSERT(fResult); } else { DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p as previous timer thread.", pWorkItem);
//
// Return the item to the pool.
//
pWorkQueue->pWorkItemPool->Release(pWorkItem);
//
// Bail out of the 'while' loop.
//
fShouldRun = FALSE; } } } else { //
// No queued work at the moment.
//
} } else { //DPFX(DPFPREP, 9, "Other non-busy threads exist, staying as timer thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads);
} } else { #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
dwStartTime = GETTIMESTAMP(); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
//DPFX(DPFPREP, 9, "Getting queued packet on completion port 0x%p.", pWorkQueue->hIoCompletionPort);
fResult = GetQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), &dwBytesTransferred, &dwCompletionKey, &pOverlapped, INFINITE);
#if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalTimeSpentUnsignalled), (GETTIMESTAMP() - dwStartTime)); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
DNASSERT(pOverlapped != NULL);
pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped); DNASSERT(pWorkItem->IsValid());
//
// Increase the number of busy threads.
//
dwNumBusyThreads = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
//
// Call the user's function or note that we need to stop running.
//
if (pWorkItem->m_pfnWorkCallback != NULL) { //
// Save the uniqueness ID to determine if this item was a timer
// that got rescheduled.
//
uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p) as worker thread.", pWorkItem, pWorkItem->m_pfnWorkCallback, pWorkItem->m_pvCallbackContext, uiOriginalUniqueID, pWorkQueue);
ThreadpoolStatsBeginExecuting(pWorkItem); pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext, pWorkItem, uiOriginalUniqueID);
//
// Return the item to the pool unless it got rescheduled. This
// assumes that the actual pWorkItem memory remains valid even
// though it may have been rescheduled and then completed/
// cancelled by the time we perform this test. See
// CancelTimer.
//
if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID) { ThreadpoolStatsEndExecuting(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem); pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { ThreadpoolStatsEndExecutingRescheduled(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem); } } else { DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p as worker thread.", pWorkItem);
//
// Return the item to the pool.
//
pWorkQueue->pWorkItemPool->Release(pWorkItem);
//
// Bail out of the 'while' loop.
//
fShouldRun = FALSE; } }
//
// Revert the busy count.
//
DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumBusyThreads)); } // end while (should keep running)
DPFX(DPFPREP, 6, "Leave"); } // DPTPWorkerLoop
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#else // ! DPNBUILD_USEIOCOMPLETIONPORTS
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{ BOOL fShouldRun = TRUE; BOOL fRunningAsTimerThread = FALSE; BOOL fSetAndWait = FALSE; DNSLIST_ENTRY * pSlistEntryHead; DNSLIST_ENTRY * pSlistEntryTail; USHORT usCount; DWORD dwNumBusyThreads; DWORD dwNumRunningThreads; CWorkItem * pWorkItem; DWORD dwResult; DWORD dwWaitTimeout; DNHANDLE hWaitObject; UINT uiOriginalUniqueID; #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
DWORD dwStartTime; #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
//
// When we can't use waitable timers, we always wait on the same object,
// but the timeout can differ. If we can use waitable timers, the timeout
// is always INFINITE.
//
#if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
dwWaitTimeout = INFINITE; #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
hWaitObject = pWorkQueue->hAlertEvent; #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
//
// Keep looping until we're told to exit.
//
while (fShouldRun) { //
// We also have an additional optimization technique that attempts to
// minimize the amount that the timer responsibility passes from thread
// to thread. It only truly works on NT, because of the atomic
// SignalObjectAndWait function. Having separate SetEvent and Wait
// operations could mean an extra context switch: SetEvent causes a
// waiting thread to take over, then when the timer thread is activated
// again, it just goes back to waiting.
//
// But we'll still execute the code on 9x (when we can use waitable
// timers). The real problem is on CE, where the event to set and the
// event on which the timer thread waits are the same, so it would
// almost certainly just be waking itself up. So if we're not using
// waitable timers, we set the event at a different location in an
// attempt to reduce this possibility.
//
// You'll probably need to understand the rest of this function for
// this "optimization" to make much sense.
//
#if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
if (fSetAndWait) { DNASSERT(fRunningAsTimerThread);
DPFX(DPFPREP, 8, "Signalling event 0x%p and waiting on timer 0x%p.", pWorkQueue->hAlertEvent, pWorkQueue->hTimer);
#if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
dwStartTime = GETTIMESTAMP(); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
dwResult = DNSignalObjectAndWait(pWorkQueue->hAlertEvent, pWorkQueue->hTimer, INFINITE, FALSE);
#if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalTimeSpentUnsignalled), (GETTIMESTAMP() - dwStartTime)); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
//
// Clear the flag.
//
fSetAndWait = FALSE; } else #endif // WINNT or (WIN95 and ! DPNBUILD_NOWAITABLETIMERSON9X)
{ //
// If we're not already running as a timer thread, atomically see
// if there is currently a timer thread, and become it if not.
// There can be only one.
//
if (! fRunningAsTimerThread) { fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), (LONG) FALSE); #ifdef DBG
if (fRunningAsTimerThread) { DPFX(DPFPREP, 9, "Becoming timer thread."); } #endif // DBG
}
//
// Normally we wait for a work item. If we're the timer thread, we
// need to wake up at regular intervals to service any timer
// entries. On CE, that is done by using a timeout value for our
// Wait operation. On desktop, we use a waitable timer object.
//
if (fRunningAsTimerThread) { #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
hWaitObject = pWorkQueue->hTimer; #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
dwWaitTimeout = TIMER_BUCKET_GRANULARITY(pWorkQueue); #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
} else { #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
hWaitObject = pWorkQueue->hAlertEvent; #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
dwWaitTimeout = INFINITE; #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
}
//DPFX(DPFPREP, 9, "Waiting on handle 0x%p for %i ms.", hWaitObject, (int) dwWaitTimeout);
#if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
dwStartTime = GETTIMESTAMP(); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
dwResult = DNWaitForSingleObject(hWaitObject, dwWaitTimeout);
#if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalTimeSpentUnsignalled), (GETTIMESTAMP() - dwStartTime)); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
} DNASSERT((dwResult == WAIT_OBJECT_0) || (dwResult == WAIT_TIMEOUT));
//
// Prepare to collect some work items that need to be queued.
//
pSlistEntryHead = NULL; usCount = 0;
#ifndef WINCE
//
// Handle any I/O completions.
//
ProcessIo(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount); #endif // ! WINCE
//
// If we're acting as the timer thread, retrieve any expired or
// cancelled timer entries.
//
if (fRunningAsTimerThread) { ProcessTimers(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount); }
//
// Queue any work items we accumulated in one fell swoop.
//
if (pSlistEntryHead != NULL) { #ifdef DPNBUILD_THREADPOOLSTATISTICS
if (usCount > 1) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumSimultaneousQueues)); } #ifdef WINCE
LONG lCount;
lCount = usCount; while (lCount > 0) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWorkItems)); lCount--; } #else // ! WINCE
DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalNumWorkItems), usCount); #endif // ! WINCE
#endif // DPNBUILD_THREADPOOLSTATISTICS
DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems, pSlistEntryHead, OFFSETOF(CWorkItem, m_SlistEntry)); }
//
// Increase the number of busy threads.
//
dwNumBusyThreads = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
//
// We may be able to optimize context switching for the timer thread,
// so treat it differently. Worker threads should just go straight to
// running through the queue (if possible).
//
if (fRunningAsTimerThread) { //
// Grab the current total thread count.
//
dwNumRunningThreads = *((volatile DWORD *) (&pWorkQueue->dwNumRunningThreads));
//
// If we didn't add any work items, there's no point in this timer
// thread looking for jobs to execute. If we found one, we'd have
// to wake another thread so it could become the timer thread while
// we processed the item, and thus incur a context switch.
// Besides, if someone other than us added any work, they would
// have set the alert event already.
//
// There are two exceptions to that rule. One is if there is only
// this one thread, in which case we must process any work items
// because there's no one else who can. The other is on platforms
// without waitable timers. In that case, we may have dropped
// through not because the time elapsed, but rather because the
// event was set. So to prevent us from eating these alerts, we
// will check for any work to do.
//
if ((usCount == 0) && (dwNumRunningThreads > 1)) { #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
//DPFX(DPFPREP, 9, "No work items added, staying as timer thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads);
//
// Don't execute any work.
//
pWorkItem = NULL; #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
//
// See the comments above and below. If we find something to
// do, we need to try to make another thread the timer thread
// while we work on this item.
//
pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); if (pWorkItem != NULL) { DPFX(DPFPREP, 9, "No work items added, but there's some in the queue, becoming worker thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads); fSetAndWait = TRUE; } else { //DPFX(DPFPREP, 9, "No work items, staying as timer thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads);
} #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
} else { //
// We added work, so we may need to notify other threads.
//
// If it appears that there are enough idle threads to handle
// the work that we added, don't bother executing any work in
// this timer thread. We will need to alert other threads
// about the work, but as stated above, we would need to wake
// another thread anyway even if we did execute it in this
// thread. On NT, we get a nice optimization that allows us to
// alert a thread and go to sleep atomically (see above), so
// it's not too painful.
//
// Otherwise, all other threads are busy, so we should just
// execute the work in this thread. The first thread to
// complete the work will become the new timer thread.
//
if (dwNumBusyThreads < dwNumRunningThreads) { #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
DPFX(DPFPREP, 9, "Other non-busy threads exist, staying as timer thread (busy = %u, total = %u, added %u items).", dwNumBusyThreads, dwNumRunningThreads, usCount);
//
// Don't execute any work, and set the event up top.
//
pWorkItem = NULL; fSetAndWait = TRUE; #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
//
// Because CE sets and waits on the same event, we opt to
// make this timer thread a worker thread anyway, and set
// the alert event sooner. See comments at the top and
// down below for more details.
//
// So attempt to pop the next work item off the stack.
// Note that theoretically all of the work we added could
// have been processed already, so we might still end up
// with nothing to do. Don't enable fSetAndWait if that's
// the case.
//
DPFX(DPFPREP, 9, "Trying to become worker thread (busy = %u, total = %u, added %u items).", dwNumBusyThreads, dwNumRunningThreads, usCount); pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); if (pWorkItem != NULL) { fSetAndWait = TRUE; } #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
} else { #ifdef DBG
if (dwNumRunningThreads > 1) // reduce spew when only running 1 thread
{ DPFX(DPFPREP, 9, "No idle threads (busy = %u) after adding %u items, may become worker thread.", dwNumBusyThreads, usCount); } #endif // DBG
//
// Attempt to pop the next work item off the stack.
// Note that theoretically all of the work we added could
// have been processed already, so we could end up with
// nothing to do.
//
pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); } } // end else (added work from timers or I/O)
} else { //
// Not a timer thread. Attempt to pop the next work item off the
// stack, if any.
//
pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); #ifdef DPNBUILD_THREADPOOLSTATISTICS
if (pWorkItem == NULL) { DPFX(DPFPREP, 7, "No items for worker thread (busy = %u, had added %u items to queue 0x%p).", dwNumBusyThreads, usCount, pWorkQueue); DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWakesWithoutWork)); } #endif // DPNBUILD_THREADPOOLSTATISTICS
}
//
// Execute as many work items in a row as possible.
//
while (pWorkItem != NULL) { //
// If we were acting as the timer thread, we need another thread to
// take over that responsibility while we're busy processing what
// could be a long work item.
//
if (fRunningAsTimerThread) { DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities."); #ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the debugging/tuning statistics.
//
pWorkQueue->dwTotalNumTimerThreadAbdications++; #endif // DPNBUILD_THREADPOOLSTATISTICS
DNASSERT(! pWorkQueue->fTimerThreadNeeded); #pragma TODO(vanceo, "Does this truly need to be interlocked?")
fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded), (LONG) TRUE); DNASSERT(! fRunningAsTimerThread); // there had better not be more than one timer thread
//
// On CE (or 9x, if we're not using waitable timers), we may
// also need to kick other threads so that they notice there's
// no timer thread any more. We endure SetEvent's unfortunate
// context switch because if this turned out to be a really
// long job, and no I/O or newly queued work items jolt the
// other threads out of their Waits, timers could go unserviced
// until this thread finishes. We choose to have more accurate
// timers.
//
// We do this now because it's more efficient to switch to
// another thread now than run the risk of just alerting
// ourselves (see the comments at the top of this function).
//
// On desktop, we should only get in here if we couldn't alert
// any other threads, so it wouldn't make sense to SetEvent.
//
#pragma TODO(vanceo, "We may be able to live with some possible loss of timer response, if we could assume we'll get I/O or queued work items")
#if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
DNASSERT(! fSetAndWait); #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
if (fSetAndWait) { DNSetEvent(pWorkQueue->hAlertEvent); // ignore potential failure, there's nothing we could do
fSetAndWait = FALSE; } #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
}
//
// Call the user's function or note that we need to stop running.
//
if (pWorkItem->m_pfnWorkCallback != NULL) { //
// Save the uniqueness ID to determine if this item was a timer
// that got rescheduled.
//
uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p).", pWorkItem, pWorkItem->m_pfnWorkCallback, pWorkItem->m_pvCallbackContext, uiOriginalUniqueID, pWorkQueue);
ThreadpoolStatsBeginExecuting(pWorkItem); pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext, pWorkItem, uiOriginalUniqueID);
//
// Return the item to the pool unless it got rescheduled. This
// assumes that the actual pWorkItem memory remains valid even
// though it may have been rescheduled and then completed/
// cancelled by the time we perform this test. See
// CancelTimer.
//
if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID) { ThreadpoolStatsEndExecuting(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem); pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { ThreadpoolStatsEndExecutingRescheduled(pWorkItem); DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem); } } else { DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p.", pWorkItem);
//
// Return the item to the pool.
//
pWorkQueue->pWorkItemPool->Release(pWorkItem);
//
// We're about to bail out of the processing loop, so we need
// other threads to notice. It's possible that all threads
// were busy so that the alert event was set twice before any
// thread noticed it, and only this thread was released. In
// that case, we need the other threads to start processing.
//
if (! DNIsNBQueueEmpty(pWorkQueue->pvNBQueueWorkItems)) { DNSetEvent(pWorkQueue->hAlertEvent); // ignore potential failure, there's nothing we could do
}
//
// Bail out of both 'while' loops.
//
fShouldRun = FALSE; break; }
#ifndef WINCE
//
// Handle any I/O completions that came in while we were working.
//
pSlistEntryHead = NULL; usCount = 0; ProcessIo(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount);
//
// Queue any I/O operations that completed.
//
if (pSlistEntryHead != NULL) { #ifdef DPNBUILD_THREADPOOLSTATISTICS
if (usCount > 1) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumSimultaneousQueues)); } DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalNumWorkItems), usCount); #endif // DPNBUILD_THREADPOOLSTATISTICS
DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems, pSlistEntryHead, OFFSETOF(CWorkItem, m_SlistEntry)); } #endif // ! WINCE
//
// Look for another work item.
//
pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems); #ifdef DPNBUILD_THREADPOOLSTATISTICS
if (pWorkItem != NULL) { DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumContinuousWork)); } #endif // DPNBUILD_THREADPOOLSTATISTICS
} // end while (more work items)
//
// Revert the busy count.
//
DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumBusyThreads)); } // end while (should keep running)
DPFX(DPFPREP, 6, "Leave"); } // DPTPWorkerLoop
#endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
#endif // ! DPNBUILD_ONLYONETHREAD
|