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.
2692 lines
80 KiB
2692 lines
80 KiB
/******************************************************************************
|
|
*
|
|
* 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
|