/****************************************************************************** * * 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