/****************************************************************************** * * Copyright (C) 2001-2002 Microsoft Corporation. All Rights Reserved. * * File: threadpoolapi.cpp * * Content: DirectPlay Thread Pool API implementation functions. * * History: * Date By Reason * ======== ======== ========= * 10/31/01 VanceO Created. * ******************************************************************************/ #include "dpnthreadpooli.h" //============================================================================= // Macros //============================================================================= #ifdef DPNBUILD_ONLYONEPROCESSOR #define GET_OR_CHOOSE_WORKQUEUE(pDPTPObject, dwCPU) (&pDPTPObject->WorkQueue) #else // ! DPNBUILD_ONLYONEPROCESSOR #define GET_OR_CHOOSE_WORKQUEUE(pDPTPObject, dwCPU) ((dwCPU == -1) ? ChooseWorkQueue(pDPTPObject) : WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU)) #endif // ! DPNBUILD_ONLYONEPROCESSOR //============================================================================= // Local function prototypes //============================================================================= #ifndef DPNBUILD_ONLYONEPROCESSOR DPTPWORKQUEUE * ChooseWorkQueue(DPTHREADPOOLOBJECT * const pDPTPObject); #endif // ! DPNBUILD_ONLYONEPROCESSOR #ifndef DPNBUILD_ONLYONETHREAD HRESULT SetTotalNumberOfThreads(DPTHREADPOOLOBJECT * const pDPTPObject, const DWORD dwNumThreads); #endif // ! DPNBUILD_ONLYONETHREAD #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_LIBINTERFACE))) #undef DPF_MODNAME #define DPF_MODNAME "DPTP_Initialize" //============================================================================= // DPTP_Initialize //----------------------------------------------------------------------------- // // Description: Initializes the thread pool interface for the process. Only // one thread pool object per process is used. If another // IDirectPlay8ThreadPool interface was created and initialized, // this interface will return DPNERR_ALREADYINITIALIZED. // // The interface cannot be initialized if a DirectPlay object // has already created threads. DPNERR_NOTALLOWED will be // returned in that case. // // Arguments: // xxx pInterface - Pointer to interface. // PVOID pvUserContext - User context for all message callbacks. // PFNDPNMESSAGEHANDLER pfn - Pointer to function called to handle // thread pool messages. // DWORD dwFlags - Flags to use when initializing. // // Returns: HRESULT // DPN_OK - Initializing was successful. // DPNERR_ALREADYINITIALIZED - The interface has already been initialized. // DPNERR_INVALIDFLAGS - Invalid flags were specified. // DPNERR_INVALIDPARAM - An invalid parameter was specified. // DPNERR_NOTALLOWED - Threads have already been started. //============================================================================= STDMETHODIMP DPTP_Initialize(IDirectPlay8ThreadPool * pInterface, PVOID const pvUserContext, const PFNDPNMESSAGEHANDLER pfn, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; #ifndef DPNBUILD_ONLYONETHREAD DWORD dwTemp; DPTPWORKQUEUE * pWorkQueue; #endif // ! DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, 0x%p, 0x%x)", pInterface, pvUserContext, pfn, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #ifndef DPNBUILD_NOPARAMVAL //if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_PARAMVALIDATION) { // // Validate parameters. // hr = DPTPValidateInitialize(pInterface, pvUserContext, pfn, dwFlags); if (hr != DPN_OK) { DPF_RETURN(hr); } } #endif // ! DPNBUILD_NOPARAMVAL // // Lock the object to prevent multiple threads from trying to change the // flags or thread count simultaneously. // DNEnterCriticalSection(&pDPTPObject->csLock); if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_INITIALIZED) { DPFX(DPFPREP, 0, "Thread pool object already initialized!"); hr = DPNERR_ALREADYINITIALIZED; goto Failure; } #ifndef DPNBUILD_ONLYONETHREAD DNASSERT(pDPTPObject->dwTotalUserThreadCount == -1); // // If a Work interface has already spun up some threads, we must fail. // if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { DPFX(DPFPREP, 0, "Threads already exist, can't initialize!"); hr = DPNERR_NOTALLOWED; goto Failure; } #ifdef DPNBUILD_MANDATORYTHREADS if (pDPTPObject->dwMandatoryThreadCount > 0) { DPFX(DPFPREP, 0, "Mandatory threads already exist, can't initialize!"); hr = DPNERR_NOTALLOWED; goto Failure; } #endif // DPNBUILD_MANDATORYTHREADS // // Update all the work queues with the new message handler and context. // for(dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); DNASSERT(pWorkQueue->pfnMsgHandler == NULL); pWorkQueue->pfnMsgHandler = pfn; pWorkQueue->pvMsgHandlerContext = pvUserContext; } #endif // ! DPNBUILD_ONLYONETHREAD // // Mark the user's interface as ready. // pDPTPObject->dwFlags |= DPTPOBJECTFLAG_USER_INITIALIZED; #ifndef DPNBUILD_NOPARAMVAL // // If user doesn't want validation, turn it off. // if (dwFlags & DPNINITIALIZE_DISABLEPARAMVAL) { pDPTPObject->dwFlags &= ~DPTPOBJECTFLAG_USER_PARAMVALIDATION; } #endif // ! DPNBUILD_NOPARAMVAL DNLeaveCriticalSection(&pDPTPObject->csLock); hr = DPN_OK; Exit: DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; Failure: DNLeaveCriticalSection(&pDPTPObject->csLock); goto Exit; } // DPTP_Initialize #undef DPF_MODNAME #define DPF_MODNAME "DPTP_Close" //============================================================================= // DPTP_Close //----------------------------------------------------------------------------- // // Description: Closes the thread pool interface. Any threads that exist // will call the message handler with DPN_MSGID_DESTROY_THREAD // before this method returns. // // This method cannot be called while a call to DoWork has not // returned, or from a thread pool thread. DPNERR_NOTALLOWED is // returned in these cases. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwFlags - Flags to use when closing. // // Returns: HRESULT // DPN_OK - Closing was successful. // DPNERR_INVALIDFLAGS - Invalid flags were specified. // DPNERR_NOTALLOWED - A thread is in a call to DoWork or this is a // thread pool thread. // DPNERR_UNINITIALIZED - The interface has not yet been initialized. //============================================================================= STDMETHODIMP DPTP_Close(IDirectPlay8ThreadPool * pInterface, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; #ifndef DPNBUILD_ONLYONETHREAD DPTPWORKERTHREAD * pWorkerThread; DWORD dwTemp; DPTPWORKQUEUE * pWorkQueue; #endif // ! DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%x)", pInterface, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #ifndef DPNBUILD_NOPARAMVAL if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_PARAMVALIDATION) { // // Validate parameters. // hr = DPTPValidateClose(pInterface, dwFlags); if (hr != DPN_OK) { DPF_RETURN(hr); } } #endif // ! DPNBUILD_NOPARAMVAL // // Lock the object to prevent multiple threads from trying to change the // flags or thread count simultaneously. // DNEnterCriticalSection(&pDPTPObject->csLock); if (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_INITIALIZED)) { DPFX(DPFPREP, 0, "Thread pool object not initialized!"); hr = DPNERR_UNINITIALIZED; goto Failure; } if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK) { DPFX(DPFPREP, 0, "Another thread is in a call to DoWork!"); hr = DPNERR_NOTALLOWED; goto Failure; } #ifndef DPNBUILD_ONLYONETHREAD // // If this is a thread pool thread, fail. // pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); if (pWorkerThread != NULL) { DPFX(DPFPREP, 0, "Cannot call Close from a thread pool thread!"); hr = DPNERR_NOTALLOWED; goto Failure; } // // If a thread is currently changing the thread count (or trying // to but we got the lock first), bail. // if ((pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING) || (pDPTPObject->lNumThreadCountChangeWaiters > 0)) { DPFX(DPFPREP, 0, "Cannot call Close with other threads still using other methods!"); hr = DPNERR_NOTALLOWED; goto Failure; } #ifdef DPNBUILD_MANDATORYTHREADS // // If there are mandatory threads still running, we can't close yet. // There is no way to have them issue DESTROY_THREAD callbacks. // if (pDPTPObject->dwMandatoryThreadCount > 0) { DPFX(DPFPREP, 0, "Mandatory threads still exist, can't close!"); hr = DPNERR_NOTALLOWED; goto Failure; } #endif // DPNBUILD_MANDATORYTHREADS // // Clear the message handler information. // for(dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); DNASSERT(pWorkQueue->pfnMsgHandler != NULL); pWorkQueue->pfnMsgHandler = NULL; pWorkQueue->pvMsgHandlerContext = NULL; } // // If there were any threads, we must shut them down so they stop using the // user's callback. // #pragma TODO(vanceo, "Is there no efficient way to ensure all threads process a 'RemoveCallback' job?") if (((pDPTPObject->dwTotalUserThreadCount != -1) && (pDPTPObject->dwTotalUserThreadCount != 0)) || (pDPTPObject->dwTotalDesiredWorkThreadCount != -1)) { hr = SetTotalNumberOfThreads(pDPTPObject, 0); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't shut down existing threads!"); goto Failure; } // // If some Work interface wanted threads, we need to spin them back up // because we don't know if the user is closing his/her interface // before all work is truly done. // if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { hr = SetTotalNumberOfThreads(pDPTPObject, pDPTPObject->dwTotalDesiredWorkThreadCount); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't restart Work interface requested number of threads!"); goto Failure; } } } // // In case the user had set the thread count, restore it to the "unknown" // value. // pDPTPObject->dwTotalUserThreadCount = -1; #endif // ! DPNBUILD_ONLYONETHREAD // // Mark the user's interface as no longer available. // pDPTPObject->dwFlags &= ~DPTPOBJECTFLAG_USER_INITIALIZED; #ifndef DPNBUILD_NOPARAMVAL // // Re-enable validation, in case it was off. // pDPTPObject->dwFlags |= DPTPOBJECTFLAG_USER_PARAMVALIDATION; #endif // ! DPNBUILD_NOPARAMVAL DNLeaveCriticalSection(&pDPTPObject->csLock); hr = DPN_OK; Exit: DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; Failure: DNLeaveCriticalSection(&pDPTPObject->csLock); goto Exit; } // DPTP_Close #undef DPF_MODNAME #define DPF_MODNAME "DPTP_GetThreadCount" //============================================================================= // DPTP_GetThreadCount //----------------------------------------------------------------------------- // // Description: Retrieves the current number of threads for the given // processor, of if dwProcessorNum is -1, the total number of // threads for all processors. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwProcessorNum - Processor whose thread count should be retrieved, // or -1 to retrieve the total number of threads. // DWORD * pdwNumThreads - Pointer to DWORD in which to store the current // number of threads. // DWORD dwFlags - Flags to use when retrieving thread count. // // Returns: HRESULT // DPN_OK - Retrieving the number of threads was successful. // DPNERR_INVALIDFLAGS - Invalid flags were specified. // DPNERR_INVALIDPARAM - An invalid parameter was specified. // DPNERR_UNINITIALIZED - The interface has not yet been initialized. //============================================================================= STDMETHODIMP DPTP_GetThreadCount(IDirectPlay8ThreadPool * pInterface, const DWORD dwProcessorNum, DWORD * const pdwNumThreads, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, 0x%p, 0x%x)", pInterface, dwProcessorNum, pdwNumThreads, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #ifndef DPNBUILD_NOPARAMVAL if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_PARAMVALIDATION) { // // Validate parameters. // hr = DPTPValidateGetThreadCount(pInterface, dwProcessorNum, pdwNumThreads, dwFlags); if (hr != DPN_OK) { DPF_RETURN(hr); } } #endif // ! DPNBUILD_NOPARAMVAL // // Check object state (note: done without object lock). // if (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_INITIALIZED)) { DPFX(DPFPREP, 0, "Thread pool object not initialized!"); DPF_RETURN(DPNERR_UNINITIALIZED); } #ifdef DPNBUILD_ONLYONETHREAD *pdwNumThreads = 0; #else // ! DPNBUILD_ONLYONETHREAD if (dwProcessorNum == -1) { if (pDPTPObject->dwTotalUserThreadCount != -1) { *pdwNumThreads = pDPTPObject->dwTotalUserThreadCount; } else if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { *pdwNumThreads = pDPTPObject->dwTotalDesiredWorkThreadCount; } else { *pdwNumThreads = 0; } } else { *pdwNumThreads = (WORKQUEUE_FOR_CPU(pDPTPObject, dwProcessorNum))->dwNumRunningThreads; } #endif // ! DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, 7, "Number of threads = %u.", (*pdwNumThreads)); hr = DPN_OK; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTP_GetThreadCount #undef DPF_MODNAME #define DPF_MODNAME "DPTP_SetThreadCount" //============================================================================= // DPTP_SetThreadCount //----------------------------------------------------------------------------- // // Description: Alters the current number of threads for the given processor // number, or if dwProcessorNum is -1, the total number of threads // for all processors. // // If the new thread count is higher than the previous count, // the correct number of threads will be started (generating // DPN_MSGID_CREATE_THREAD messages) before this method returns. // // If the new thread count is lower than the previous count, // the correct number of threads will be shutdown (generating // DPN_MSGID_DESTROY_THREAD messages) before this method returns. // // This method cannot be used while another thread is // performing work. If a thread is in a call to DoWork, then // DPNERR_NOTALLOWED is returned and the thread count remains // unchanged. // // Thread pool threads cannot reduce the thread count. If this // thread is owned by the thread pool and dwNumThreads is less // than the current number of threads for the processor, // DPNERR_NOTALLOWED is returned and the thread count remains // unchanged. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwProcessorNum - Processor number, or -1 for all processors. // DWORD dwNumThreads - Desired number of threads per processor. // DWORD dwFlags - Flags to use when setting the thread count. // // Returns: HRESULT // DPN_OK - Setting the number of threads was successful. // DPNERR_INVALIDFLAGS - Invalid flags were specified. // DPNERR_INVALIDPARAM - An invalid parameter was specified. // DPNERR_NOTALLOWED - A thread is currently calling DoWork, or this // thread pool thread is trying to reduce the // thread count. // DPNERR_UNINITIALIZED - The interface has not yet been initialized. //============================================================================= STDMETHODIMP DPTP_SetThreadCount(IDirectPlay8ThreadPool * pInterface, const DWORD dwProcessorNum, const DWORD dwNumThreads, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; #ifndef DPNBUILD_ONLYONETHREAD BOOL fSetThreadCountChanging = FALSE; DPTPWORKQUEUE * pWorkQueue; DPTPWORKERTHREAD * pWorkerThread; DWORD dwDelta; #endif // ! DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, %u, 0x%x)", pInterface, dwProcessorNum, dwNumThreads, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #ifndef DPNBUILD_NOPARAMVAL if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_PARAMVALIDATION) { // // Validate parameters. // hr = DPTPValidateSetThreadCount(pInterface, dwProcessorNum, dwNumThreads, dwFlags); if (hr != DPN_OK) { DPF_RETURN(hr); } } #endif // ! DPNBUILD_NOPARAMVAL // // Check object state (note: done without object lock). // if (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_INITIALIZED)) { DPFX(DPFPREP, 0, "Thread pool object not initialized!"); DPF_RETURN(DPNERR_UNINITIALIZED); } // // Lock the object to prevent multiple threads from trying to change the // thread count simultaneously. // DNEnterCriticalSection(&pDPTPObject->csLock); // // Make sure no one is trying to perform work at the moment. // if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK) { DPFX(DPFPREP, 0, "Cannot change thread count while a thread is in a call to DoWork!"); hr = DPNERR_NOTALLOWED; goto Exit; } #ifdef DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, 0, "Not changing thread count to %u!", dwNumThreads); hr = DPNERR_UNSUPPORTED; #else // ! DPNBUILD_ONLYONETHREAD // // See if another thread is already changing the thread count. If so, wait // until they're done, unless this is a thread pool thread in the middle of // a CREATE_THREAD or DESTROY_THREAD indication. // if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING) { pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); if ((pWorkerThread != NULL) && (! pWorkerThread->fThreadIndicated)) { // // This is a thread pool thread that isn't marked as indicated to // the user, i.e. it's before CREATE_THREAD returned or it's after // the DESTROY_THREAD has started to be indicated. // DPFX(DPFPREP, 0, "Cannot change thread count from a thread pool thread in CREATE_THREAD or DESTROY_THREAD callback!"); hr = DPNERR_NOTALLOWED; goto Exit; } // // Otherwise, wait for the previous thread to finish. // do { DNASSERT(pDPTPObject->lNumThreadCountChangeWaiters >= 0); pDPTPObject->lNumThreadCountChangeWaiters++; DPFX(DPFPREP, 1, "Waiting for thread count change to complete (waiters = %i).", pDPTPObject->lNumThreadCountChangeWaiters); // // Drop the lock while we wait. // DNLeaveCriticalSection(&pDPTPObject->csLock); DNWaitForSingleObject(pDPTPObject->hThreadCountChangeComplete, INFINITE); // // Retake the lock and see if we can move on. // DNEnterCriticalSection(&pDPTPObject->csLock); DNASSERT(pDPTPObject->lNumThreadCountChangeWaiters > 0); pDPTPObject->lNumThreadCountChangeWaiters--; } while (pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING); // // It's safe to proceed now. // DPFX(DPFPREP, 1, "Thread count change completed, continuing."); // // The user would need to be doing something spectacularly silly if // we're no longer initialized, or another thread is now calling // DoWork. We'll crash in retail, assert in debug. // DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_INITIALIZED); DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK); } #ifdef DPNBUILD_MANDATORYTHREADS // // Make sure we're not stopping all threads if there are any mandatory // threads. // if ((dwNumThreads == 0) && (pDPTPObject->dwMandatoryThreadCount > 0)) { DPFX(DPFPREP, 0, "Cannot set number of threads to 0 because there is already at least one mandatory thread!"); hr = DPNERR_NOTALLOWED; goto Exit; } #endif // DPNBUILD_MANDATORYTHREADS // // If the thread count really did change, start or stop the right number of // threads for all processors or the specific processor. // if (dwProcessorNum == -1) { if (dwNumThreads != pDPTPObject->dwTotalUserThreadCount) { if (dwNumThreads != pDPTPObject->dwTotalDesiredWorkThreadCount) { if ((dwNumThreads != 0) || (pDPTPObject->dwTotalUserThreadCount != -1) || (pDPTPObject->dwTotalDesiredWorkThreadCount != -1)) { // // Prevent the user from trying to reduce the total thread // count from a worker thread. // pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); if (pWorkerThread != NULL) { DWORD dwNumThreadsPerProcessor; DWORD dwExtraThreads; DWORD dwTemp; // // Make sure the thread count for any individual // processor isn't shrinking. // #ifdef DPNBUILD_USEIOCOMPLETIONPORTS dwNumThreadsPerProcessor = dwNumThreads; dwExtraThreads = 0; #else // ! DPNBUILD_USEIOCOMPLETIONPORTS dwNumThreadsPerProcessor = dwNumThreads / NUM_CPUS(pDPTPObject); dwExtraThreads = dwNumThreads % NUM_CPUS(pDPTPObject); #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS for (dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) { dwDelta = dwNumThreadsPerProcessor - (WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp))->dwNumRunningThreads; if (dwTemp < dwExtraThreads) { dwDelta++; } if ((int) dwDelta < 0) { DPFX(DPFPREP, 0, "Cannot reduce thread count from a thread pool thread (processor %u)!", dwTemp); hr = DPNERR_NOTALLOWED; goto Exit; } } } // // Drop the lock while changing the thread count to prevent // deadlocks. Set the flag to alert other threads while // we're doing this. // DNASSERT(! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING)); pDPTPObject->dwFlags |= DPTPOBJECTFLAG_THREADCOUNTCHANGING; fSetThreadCountChanging = TRUE; DNLeaveCriticalSection(&pDPTPObject->csLock); // // Actually set the total number of threads. // hr = SetTotalNumberOfThreads(pDPTPObject, dwNumThreads); // // Retake the lock. We'll clear the alert flag and release // any waiting threads at the bottom. // DNEnterCriticalSection(&pDPTPObject->csLock); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't set total number of threads!"); goto Exit; } pDPTPObject->dwTotalUserThreadCount = dwNumThreads; } else { DPFX(DPFPREP, 1, "No threads running, no change necessary."); pDPTPObject->dwTotalUserThreadCount = 0; } } else { DPFX(DPFPREP, 1, "Correct total number of threads (%u) already running.", dwNumThreads); pDPTPObject->dwTotalUserThreadCount = dwNumThreads; } } else { DPFX(DPFPREP, 1, "Total thread count unchanged (%u).", dwNumThreads); } } else { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwProcessorNum); dwDelta = dwNumThreads - pWorkQueue->dwNumRunningThreads; if (dwDelta == 0) { if (pDPTPObject->dwTotalUserThreadCount == -1) { if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { DPFX(DPFPREP, 1, "Correct number of threads (%u) already running on processor.", dwNumThreads); pDPTPObject->dwTotalUserThreadCount = pDPTPObject->dwTotalDesiredWorkThreadCount; } else { DNASSERT(dwNumThreads == 0); DPFX(DPFPREP, 1, "No threads are running on processor, no change necessary."); pDPTPObject->dwTotalUserThreadCount = 0; } } else { DPFX(DPFPREP, 1, "Correct number of threads (%u) already set for processor.", dwNumThreads); } } else { // // Drop the lock while changing the thread count to prevent // deadlocks. Set the flag to alert other threads while we're // doing this. // DNASSERT(! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING)); pDPTPObject->dwFlags |= DPTPOBJECTFLAG_THREADCOUNTCHANGING; fSetThreadCountChanging = TRUE; DNLeaveCriticalSection(&pDPTPObject->csLock); if ((int) dwDelta > 0) { // // We need to add threads. // hr = StartThreads(pWorkQueue, dwDelta); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't start %u threads for processor!", dwDelta); // // Retake the lock before bailing. // DNEnterCriticalSection(&pDPTPObject->csLock); goto Exit; } } else { // // Prevent the user from trying to reduce the processor's // thread count from a worker thread (for any processor). // pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); if (pWorkerThread != NULL) { DPFX(DPFPREP, 0, "Cannot reduce thread count from a thread pool thread!"); // // Retake the lock before bailing. // DNEnterCriticalSection(&pDPTPObject->csLock); hr = DPNERR_NOTALLOWED; goto Exit; } // // We need to remove {absolute value of delta} threads. // hr = StopThreads(pWorkQueue, ((int) dwDelta * -1)); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't stop %u threads for processor!", ((int) dwDelta * -1)); // // Retake the lock before bailing. // DNEnterCriticalSection(&pDPTPObject->csLock); goto Exit; } } DNASSERT(pWorkQueue->dwNumRunningThreads == dwNumThreads); // // Retake the lock. We'll clear the alert flag and release any // waiting threads at the bottom. // DNEnterCriticalSection(&pDPTPObject->csLock); if (pDPTPObject->dwTotalUserThreadCount == -1) { pDPTPObject->dwTotalUserThreadCount = dwDelta; if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { pDPTPObject->dwTotalUserThreadCount += pDPTPObject->dwTotalDesiredWorkThreadCount; } } else { pDPTPObject->dwTotalUserThreadCount += dwDelta; } DNASSERT(pDPTPObject->dwTotalUserThreadCount != -1); } } hr = DPN_OK; #endif // ! DPNBUILD_ONLYONETHREAD Exit: // // If we start changing the thread count, clear the flag, and release any // threads waiting on us (they'll block until we drop the lock again // shortly). // if (fSetThreadCountChanging) { DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING); pDPTPObject->dwFlags &= ~DPTPOBJECTFLAG_THREADCOUNTCHANGING; fSetThreadCountChanging = FALSE; if (pDPTPObject->lNumThreadCountChangeWaiters > 0) { DPFX(DPFPREP, 1, "Releasing %i waiters.", pDPTPObject->lNumThreadCountChangeWaiters); DNReleaseSemaphore(pDPTPObject->hThreadCountChangeComplete, pDPTPObject->lNumThreadCountChangeWaiters, NULL); } } DNLeaveCriticalSection(&pDPTPObject->csLock); DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTP_SetThreadCount #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_LIBINTERFACE #undef DPF_MODNAME #define DPF_MODNAME "DPTP_DoWork" //============================================================================= // DPTP_DoWork //----------------------------------------------------------------------------- // // Description: Performs any work that is currently scheduled. This allows // DirectPlay to operate without any threads of its own. It is // expected that this will be called frequently and at regular // intervals so that time critical operations can be performed // with reasonable accuracy. // // This method will return DPN_OK when no additional work is // immediately available. If the allowed time slice is not // INFINITE, this method will return DPNSUCCESS_PENDING if the // time limit is exceeded but there is still work remaining. If // the allowed time slice is 0, only the first work item (if any) // will be performed. The allowed time slice must be less than // 60,000 milliseconds (1 minute) if it is not INFINITE. // // This method cannot be called unless the thread count has // been set to 0. It will return DPNERR_NOTREADY if there are // threads currently active. // // If an attempt is made to call this method by more than one // thread simultaneously, recursively, or within a DirectPlay // callback, DPNERR_NOTALLOWED is returned. // // // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwAllowedTimeSlice - The maximum number of milliseconds to perform // work, or INFINITE to allow all immediately // available items to be executed. // DWORD dwFlags - Flags to use when performing work. // // Returns: HRESULT // DPN_OK - Performing the work was successful. // DPNSUCCESS_PENDING - No errors occurred, but there is work that could // not be accomplished due to the time limit. // DPNERR_INVALIDFLAGS - Invalid flags were specified. // DPNERR_NOTALLOWED - This method is already being called by some // thread. // DPNERR_NOTREADY - The thread count has not been set to 0. // DPNERR_UNINITIALIZED - The interface has not yet been initialized. //============================================================================= #ifdef DPNBUILD_LIBINTERFACE STDMETHODIMP DPTP_DoWork(const DWORD dwAllowedTimeSlice, const DWORD dwFlags) #else // ! DPNBUILD_LIBINTERFACE STDMETHODIMP DPTP_DoWork(IDirectPlay8ThreadPool * pInterface, const DWORD dwAllowedTimeSlice, const DWORD dwFlags) #endif // ! DPNBUILD_LIBINTERFACE { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DWORD dwMaxDoWorkTime; #ifndef DPNBUILD_ONLYONEPROCESSOR DWORD dwCPU; #endif // ! DPNBUILD_ONLYONEPROCESSOR BOOL fRemainingItems; #ifdef DPNBUILD_LIBINTERFACE DPFX(DPFPREP, 8, "Parameters: (%i, 0x%x)", dwAllowedTimeSlice, dwFlags); #else // ! DPNBUILD_LIBINTERFACE DPFX(DPFPREP, 8, "Parameters: (0x%p, %i, 0x%x)", pInterface, dwAllowedTimeSlice, dwFlags); #endif // ! DPNBUILD_LIBINTERFACE #ifdef DPNBUILD_LIBINTERFACE #ifdef DPNBUILD_MULTIPLETHREADPOOLS #pragma error("Multiple thread pools support under DPNBUILD_LIBINTERFACE requires more work") #else // ! DPNBUILD_MULTIPLETHREADPOOLS pDPTPObject = g_pDPTPObject; #endif // ! DPNBUILD_MULTIPLETHREADPOOLS #else // ! DPNBUILD_LIBINTERFACE pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); #endif // ! DPNBUILD_LIBINTERFACE DNASSERT(pDPTPObject != NULL); #ifndef DPNBUILD_NOPARAMVAL if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_PARAMVALIDATION) { // // Validate parameters. // #ifdef DPNBUILD_LIBINTERFACE hr = DPTPValidateDoWork(dwAllowedTimeSlice, dwFlags); #else // ! DPNBUILD_LIBINTERFACE hr = DPTPValidateDoWork(pInterface, dwAllowedTimeSlice, dwFlags); #endif // ! DPNBUILD_LIBINTERFACE if (hr != DPN_OK) { DPF_RETURN(hr); } } #endif // ! DPNBUILD_NOPARAMVAL #ifndef DPNBUILD_LIBINTERFACE // // Check object state (note: done without object lock). // if (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_INITIALIZED)) { DPFX(DPFPREP, 0, "Thread pool object not initialized!"); DPF_RETURN(DPNERR_UNINITIALIZED); } #endif // ! DPNBUILD_LIBINTERFACE // // Save the time limit we need to use. // if (dwAllowedTimeSlice != INFINITE) { dwMaxDoWorkTime = GETTIMESTAMP() + dwAllowedTimeSlice; // // Make sure the timer never lands exactly on INFINITE, that value has // special meaning. // if (dwMaxDoWorkTime == INFINITE) { dwMaxDoWorkTime--; } } else { dwMaxDoWorkTime = INFINITE; } DNEnterCriticalSection(&pDPTPObject->csLock); #ifndef DPNBUILD_ONLYONETHREAD if (pDPTPObject->dwTotalUserThreadCount != 0) { DPFX(DPFPREP, 0, "Thread count must be set to 0 prior to using DoWork!"); hr = DPNERR_NOTREADY; goto Failure; } #endif // ! DPNBUILD_ONLYONETHREAD // // Make sure only one person is trying to call us at a time. // if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK) { DPFX(DPFPREP, 0, "DoWork cannot be performed recursively, or by multiple threads simultaneously!"); hr = DPNERR_NOTALLOWED; goto Failure; } pDPTPObject->dwFlags |= DPTPOBJECTFLAG_USER_DOINGWORK; #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) pDPTPObject->dwCurrentDoWorkThreadID = GetCurrentThreadId(); #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL DNLeaveCriticalSection(&pDPTPObject->csLock); // // Set the recursion depth. // #ifdef DPNBUILD_ONLYONETHREAD DNASSERT(pDPTPObject->dwWorkRecursionCount == 0); pDPTPObject->dwWorkRecursionCount = 1; #else // ! DPNBUILD_ONLYONETHREAD DNASSERT((DWORD) ((DWORD_PTR) (TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex))) == 0); TlsSetValue(pDPTPObject->dwWorkRecursionCountTlsIndex, (PVOID) ((DWORD_PTR) 1)); #endif // ! DPNBUILD_ONLYONETHREAD // // Actually perform the work. // #ifdef DPNBUILD_ONLYONEPROCESSOR DoWork(&pDPTPObject->WorkQueue, dwMaxDoWorkTime); fRemainingItems = ! DNIsNBQueueEmpty(pDPTPObject->WorkQueue.pvNBQueueWorkItems); #else // ! DPNBUILD_ONLYONEPROCESSOR // // Since we're in DoWork mode, technically only one CPU work queue needs to // be used, but it's possible that work got scheduled to one of the other // CPUs. Rather than trying to figure out the logic of when and how to // move everything from that queue to the first CPU's queue, we will just // process all of them every time. // fRemainingItems = FALSE; for(dwCPU = 0; dwCPU < NUM_CPUS(pDPTPObject); dwCPU++) { DoWork(WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU), dwMaxDoWorkTime); #ifdef DPNBUILD_USEIOCOMPLETIONPORTS #pragma BUGBUG(vanceo, "Find equivalent for I/O completion ports") #else // ! DPNBUILD_USEIOCOMPLETIONPORTS fRemainingItems |= ! DNIsNBQueueEmpty((WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU))->pvNBQueueWorkItems); #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS // // Even if the time has expired on this CPU, we will continue to the // rest. That way, we execute at least one item for every CPU queue // each time through (to prevent total starvation). This may make us // go even farther over the time limit, but hopefully not by much. Of // course, it's a bit silly to be using DoWork mode on a multiprocessor // machine in the first place. // } #endif // ! DPNBUILD_ONLYONEPROCESSOR // // Decrement the recursion count and allow other callers again. // #ifdef DPNBUILD_ONLYONETHREAD DNASSERT(pDPTPObject->dwWorkRecursionCount == 1); pDPTPObject->dwWorkRecursionCount = 0; #else // ! DPNBUILD_ONLYONETHREAD DNASSERT((DWORD) ((DWORD_PTR) (TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex))) == 1); TlsSetValue(pDPTPObject->dwWorkRecursionCountTlsIndex, (PVOID) ((DWORD_PTR) 0)); #endif // ! DPNBUILD_ONLYONETHREAD DNEnterCriticalSection(&pDPTPObject->csLock); DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK); pDPTPObject->dwFlags &= ~DPTPOBJECTFLAG_USER_DOINGWORK; #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == GetCurrentThreadId()); pDPTPObject->dwCurrentDoWorkThreadID = 0; #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL DNLeaveCriticalSection(&pDPTPObject->csLock); // // Return the appropriate error code. // if (fRemainingItems) { DPFX(DPFPREP, 7, "Some items remain unprocessed."); hr = DPNSUCCESS_PENDING; } else { hr = DPN_OK; } Exit: DPFX(DPFPREP, 8, "Returning: [0x%lx]", hr); return hr; Failure: DNLeaveCriticalSection(&pDPTPObject->csLock); goto Exit; } // DPTP_DoWork #pragma TODO(vanceo, "Make validation for private interface a build flag (off by default)") #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_QueueWorkItem" //============================================================================= // DPTPW_QueueWorkItem //----------------------------------------------------------------------------- // // Description: Queues a new work item for processing. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwCPU - CPU queue on which item is to be // placed, or -1 for any. // PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute as soon as // possible. // PVOID pvCallbackContext - User specified context to pass to // callback. // DWORD dwFlags - Flags to use when queueing. // // Returns: HRESULT // DPN_OK - Queuing the work item was successful. // DPNERR_OUTOFMEMORY - Not enough memory to queue the work item. //============================================================================= STDMETHODIMP DPTPW_QueueWorkItem(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwCPU, const PFNDPTNWORKCALLBACK pfnWorkCallback, PVOID const pvCallbackContext, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPTPWORKQUEUE * pWorkQueue; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, 0x%p, 0x%p, 0x%x)", pInterface, dwCPU, pfnWorkCallback, pvCallbackContext, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Figure out which CPU queue to use. // DNASSERT((dwCPU == -1) || (dwCPU < NUM_CPUS(pDPTPObject))); pWorkQueue = GET_OR_CHOOSE_WORKQUEUE(pDPTPObject, dwCPU); // // Call the implementation function. // if (! QueueWorkItem(pWorkQueue, pfnWorkCallback, pvCallbackContext)) { hr = DPNERR_OUTOFMEMORY; } else { hr = DPN_OK; } DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTPW_QueueWorkItem #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_ScheduleTimer" //============================================================================= // DPTPW_ScheduleTimer //----------------------------------------------------------------------------- // // Description: Schedules a new work item for some point in the future. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwCPU - CPU on which item is to be scheduled, // or -1 for any. // DWORD dwDelay - How much time should elapsed before // executing the work item, in ms. // PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute when timer // elapses. // PVOID pvCallbackContext - User specified context to pass to // callback. // void ** ppvTimerData - Place to store pointer to data for // timer so that it can be cancelled. // UINT * puiTimerUnique - Place to store uniqueness value for // timer so that it can be cancelled. // // Returns: HRESULT // DPN_OK - Scheduling the timer was successful. // DPNERR_OUTOFMEMORY - Not enough memory to schedule the timer. //============================================================================= STDMETHODIMP DPTPW_ScheduleTimer(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwCPU, const DWORD dwDelay, const PFNDPTNWORKCALLBACK pfnWorkCallback, PVOID const pvCallbackContext, void ** const ppvTimerData, UINT * const puiTimerUnique, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPTPWORKQUEUE * pWorkQueue; DPFX(DPFPREP, 8, "Parameters: (0x%p, %i, %u, 0x%p, 0x%p, 0x%p, 0x%p, 0x%x)", pInterface, dwCPU, dwDelay, pfnWorkCallback, pvCallbackContext, ppvTimerData, puiTimerUnique, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Figure out which CPU queue to use. // DNASSERT((dwCPU == -1) || (dwCPU < NUM_CPUS(pDPTPObject))); pWorkQueue = GET_OR_CHOOSE_WORKQUEUE(pDPTPObject, dwCPU); // // Call the implementation function. // if (! ScheduleTimer(pWorkQueue, dwDelay, pfnWorkCallback, pvCallbackContext, ppvTimerData, puiTimerUnique)) { hr = DPNERR_OUTOFMEMORY; } else { hr = DPN_OK; } DPFX(DPFPREP, 8, "Returning: [0x%lx]", hr); return hr; } // DPTPW_ScheduleTimer #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_StartTrackingFileIo" //============================================================================= // DPTPW_StartTrackingFileIo //----------------------------------------------------------------------------- // // Description: Starts tracking overlapped I/O for a given file handle on // the specified CPU (or all CPUs). The handle is not duplicated // and it should remain valid until // IDirectPlay8ThreadPoolWork::StopTrackingFileIo is called. // // This method is not available on Windows CE because it does // not support overlapped I/O. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwCPU - CPU with which I/O is to be tracked, or -1 for all. // HANDLE hFile - Handle of file to track. // DWORD dwFlags - Flags to use when starting to track file I/O. // // Returns: HRESULT // DPN_OK - Starting tracking for the file was successful. // DPNERR_ALREADYREGISTERED - The specified file handle is already being // tracked. // DPNERR_OUTOFMEMORY - Not enough memory to track the file. //============================================================================= STDMETHODIMP DPTPW_StartTrackingFileIo(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwCPU, const HANDLE hFile, const DWORD dwFlags) { #ifdef WINCE DPFX(DPFPREP, 0, "Overlapped I/O not supported on Windows CE!", 0); return DPNERR_UNSUPPORTED; #else // ! WINCE HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DWORD dwTemp; DPTPWORKQUEUE * pWorkQueue; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, 0x%p, 0x%x)", pInterface, dwCPU, hFile, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Call the implementation function for all relevant CPUs. // if (dwCPU == -1) { for(dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); hr = StartTrackingFileIo(pWorkQueue, hFile); if (hr != DPN_OK) { // // Stop tracking the file on all CPUs where we had already // succeeded. Ignore any error the function might return. // while (dwTemp > 0) { dwTemp--; pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); StopTrackingFileIo(pWorkQueue, hFile); } break; } } } else { DNASSERT(dwCPU < NUM_CPUS(pDPTPObject)); pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU); hr = StartTrackingFileIo(pWorkQueue, hFile); } DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #endif // ! WINCE } // DPTPW_StartTrackingFileIo #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_StopTrackingFileIo" //============================================================================= // DPTPW_StopTrackingFileIo //----------------------------------------------------------------------------- // // Description: Stops tracking overlapped I/O for a given file handle on // the specified CPU (or all CPUs). // // This method is not available on Windows CE because it does // not support overlapped I/O. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwCPU - CPU with which I/O was tracked, or -1 for all. // HANDLE hFile - Handle of file to stop tracking. // DWORD dwFlags - Flags to use when no turning off file I/O tracking. // // Returns: HRESULT // DPN_OK - Stopping tracking for the file was successful. // DPNERR_INVALIDHANDLE - File handle was not being tracked. //============================================================================= STDMETHODIMP DPTPW_StopTrackingFileIo(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwCPU, const HANDLE hFile, const DWORD dwFlags) { #ifdef WINCE DPFX(DPFPREP, 0, "Overlapped I/O not supported on Windows CE!", 0); return DPNERR_UNSUPPORTED; #else // ! WINCE HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DWORD dwTemp; DPTPWORKQUEUE * pWorkQueue; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, 0x%p, 0x%x)", pInterface, dwCPU, hFile, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Call the implementation function for all relevant CPUs. // if (dwCPU == -1) { for(dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); hr = StopTrackingFileIo(pWorkQueue, hFile); if (hr != DPN_OK) { break; } } } else { DNASSERT(dwCPU < NUM_CPUS(pDPTPObject)); pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU); hr = StopTrackingFileIo(pWorkQueue, hFile); } DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #endif // ! WINCE } // DPTPW_StopTrackingFileIo #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_CreateOverlapped" //============================================================================= // DPTPW_CreateOverlapped //----------------------------------------------------------------------------- // // Description: Creates an overlapped structure for an asynchronous I/O // operation so it can be monitored for completion. // // If this implementation is using I/O completion ports, the // caller should be prepared for the work callback function to be // invoked as soon as he or she calls the intended asynchronous // file function. Otherwise, he or she must call // IDirectPlay8ThreadPoolWork::SubmitIoOperation. // // If the intended asynchronous file function fails immediately // and the overlapped structure will never be completed // asynchronously, the caller must return the unused overlapped // structure with IDirectPlay8ThreadPoolWork::ReleaseOverlapped. // // This method is not available on Windows CE because it does // not support overlapped I/O. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwCPU - CPU with which I/O is to be // monitored, or -1 for any. // PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute when operation // completes. // PVOID pvCallbackContext - User specified context to pass to // callback. // OVERLAPPED * pOverlapped - Pointer to overlapped structure used // by OS. // DWORD dwFlags - Flags to use when submitting I/O. // // Returns: HRESULT // DPN_OK - Creating the structure was successful. // DPNERR_OUTOFMEMORY - Not enough memory to create the structure. //============================================================================= STDMETHODIMP DPTPW_CreateOverlapped(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwCPU, const PFNDPTNWORKCALLBACK pfnWorkCallback, PVOID const pvCallbackContext, OVERLAPPED ** const ppOverlapped, const DWORD dwFlags) { #ifdef WINCE DPFX(DPFPREP, 0, "Overlapped I/O not supported on Windows CE!", 0); return DPNERR_UNSUPPORTED; #else // ! WINCE HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPTPWORKQUEUE * pWorkQueue; CWorkItem * pWorkItem; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, 0x%p, 0x%p, 0x%p, 0x%x)", pInterface, dwCPU, pfnWorkCallback, pvCallbackContext, ppOverlapped, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Figure out which CPU queue to use. // DNASSERT((dwCPU == -1) || (dwCPU < NUM_CPUS(pDPTPObject))); pWorkQueue = GET_OR_CHOOSE_WORKQUEUE(pDPTPObject, dwCPU); // // Call the implementation function. // pWorkItem = CreateOverlappedIoWorkItem(pWorkQueue, pfnWorkCallback, pvCallbackContext); if (pWorkItem == NULL) { hr = DPNERR_OUTOFMEMORY; } else { DNASSERT(ppOverlapped != NULL); *ppOverlapped = &pWorkItem->m_Overlapped; hr = DPN_OK; } DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #endif // ! WINCE } // DPTPW_CreateOverlapped #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_SubmitIoOperation" //============================================================================= // DPTPW_SubmitIoOperation //----------------------------------------------------------------------------- // // Description: Submits an overlapped structure for an asynchronous I/O // operation so it can be monitored for completion. // // If this implementation is using I/O completion ports, this // method does not need to be used. Otherwise, the caller should // be prepared for the work callback function to be invoked even // before this method returns. // // The caller must pass a valid OVERLAPPED structure that was // allocated using IDirectPlay8ThreadPoolWork::CreateOverlapped. // // This method is not available on Windows CE because it does // not support overlapped I/O. // // Arguments: // xxx pInterface - Pointer to interface. // OVERLAPPED * pOverlapped - Pointer to overlapped structure to monitor. // DWORD dwFlags - Flags to use when submitting I/O. // // Returns: HRESULT // DPN_OK - Submitting the I/O operation was successful. //============================================================================= STDMETHODIMP DPTPW_SubmitIoOperation(IDirectPlay8ThreadPoolWork * pInterface, OVERLAPPED * const pOverlapped, const DWORD dwFlags) { #ifdef WINCE DPFX(DPFPREP, 0, "Overlapped I/O not supported on Windows CE!", 0); return DPNERR_UNSUPPORTED; #else // ! WINCE #ifdef DPNBUILD_USEIOCOMPLETIONPORTS DPFX(DPFPREP, 0, "Implementation using I/O completion ports, SubmitIoOperation should not be used!", 0); return DPNERR_INVALIDVERSION; #else // ! DPNBUILD_USEIOCOMPLETIONPORTS DPTHREADPOOLOBJECT * pDPTPObject; CWorkItem * pWorkItem; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, 0x%x)", pInterface, pOverlapped, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped); DNASSERT(pWorkItem->IsValid()); // // Call the implementation function. // SubmitIoOperation(pWorkItem->m_pWorkQueue, pWorkItem); DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [DPN_OK]"); return DPN_OK; #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS #endif // ! WINCE } // DPTPW_SubmitIoOperation #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_ReleaseOverlapped" //============================================================================= // DPTPW_ReleaseOverlapped //----------------------------------------------------------------------------- // // Description: Returns an unused overlapped structure previously created by // IDirectPlay8ThreadPoolWork::CreateOverlapped. This should only // be called if the overlapped I/O will never complete // asynchronously. // // This method is not available on Windows CE because it does // not support overlapped I/O. // // Arguments: // xxx pInterface - Pointer to interface. // OVERLAPPED * pOverlapped - Pointer to overlapped structure to release. // DWORD dwFlags - Flags to use when releasing structure. // // Returns: HRESULT // DPN_OK - Releasing the I/O operation was successful. //============================================================================= STDMETHODIMP DPTPW_ReleaseOverlapped(IDirectPlay8ThreadPoolWork * pInterface, OVERLAPPED * const pOverlapped, const DWORD dwFlags) { #ifdef WINCE DPFX(DPFPREP, 0, "Overlapped I/O not supported on Windows CE!", 0); return DPNERR_UNSUPPORTED; #else // ! WINCE HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; CWorkItem * pWorkItem; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, 0x%x)", pInterface, pOverlapped, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped); DNASSERT(pWorkItem->IsValid()); // // Call the implementation function. // ReleaseOverlappedIoWorkItem(pWorkItem->m_pWorkQueue, pWorkItem); hr = DPN_OK; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #endif // ! WINCE } // DPTPW_ReleaseOverlapped #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_CancelTimer" //============================================================================= // DPTPW_CancelTimer //----------------------------------------------------------------------------- // // Description: Attempts to cancel a timed work item. If the item is // already in the process of completing, DPNERR_CANNOTCANCEL is // returned, and the callback will still be (or is being) called. // If the item could be cancelled, DPN_OK is returned and the // callback will not be executed. // // Arguments: // xxx pInterface - Pointer to interface. // void * pvTimerData - Pointer to data for timer being cancelled. // UINT uiTimerUnique - Uniqueness value for timer being cancelled. // DWORD dwFlags - Flags to use when cancelling timer. // // Returns: HRESULT // DPN_OK - Cancelling the timer was successful. // DPNERR_CANNOTCANCEL - The timer could not be cancelled. //============================================================================= STDMETHODIMP DPTPW_CancelTimer(IDirectPlay8ThreadPoolWork * pInterface, void * const pvTimerData, const UINT uiTimerUnique, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, %u, 0x%x)", pInterface, pvTimerData, uiTimerUnique, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Call the implementation function. // hr = CancelTimer(pvTimerData, uiTimerUnique); DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTPW_CancelTimer #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_ResetCompletingTimer" //============================================================================= // DPTPW_ResetCompletingTimer //----------------------------------------------------------------------------- // // Description: Reschedules a timed work item whose callback is currently // being called. Resetting timers that have not expired yet, // timers that have been cancelled, or timers whose callback has // already returned is not allowed. // // Using this method will never fail, since no new memory is // allocated. // // Arguments: // xxx pInterface - Pointer to interface. // void * pvTimerData - Pointer to data for timer being // reset. // DWORD dwNewDelay - How much time should elapsed // before executing the work item // again, in ms. // PFNDPTNWORKCALLBACK pfnNewWorkCallback - Callback to execute when timer // elapses. // PVOID pvNewCallbackContext - User specified context to pass to // callback. // UINT * puiNewTimerUnique - Place to store new uniqueness // value for timer so that it can // be cancelled. // DWORD dwFlags - Flags to use when resetting // timer. // // Returns: HRESULT // DPN_OK - Resetting the timer was successful. //============================================================================= STDMETHODIMP DPTPW_ResetCompletingTimer(IDirectPlay8ThreadPoolWork * pInterface, void * const pvTimerData, const DWORD dwNewDelay, const PFNDPTNWORKCALLBACK pfnNewWorkCallback, PVOID const pvNewCallbackContext, UINT * const puiNewTimerUnique, const DWORD dwFlags) { DPTHREADPOOLOBJECT * pDPTPObject; DPFX(DPFPREP, 8, "Parameters: (0x%p, 0x%p, %u, 0x%p, 0x%p, 0x%p, 0x%x)", pInterface, pvTimerData, dwNewDelay, pfnNewWorkCallback, pvNewCallbackContext, puiNewTimerUnique, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #ifdef DBG // // We should be in a timer callback, and therefore at least in a threadpool // thread or doing work. // #ifndef DPNBUILD_ONLYONETHREAD if (TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex) == NULL) #endif // ! DPNBUILD_ONLYONETHREAD { DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK); } #endif // DBG // // Call the implementation function. // ResetCompletingTimer(pvTimerData, dwNewDelay, pfnNewWorkCallback, pvNewCallbackContext, puiNewTimerUnique); DPFX(DPFPREP, 8, "Returning: [DPN_OK]"); return DPN_OK; } // DPTPW_ResetCompletingTimer #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_WaitWhileWorking" //============================================================================= // DPTPW_WaitWhileWorking //----------------------------------------------------------------------------- // // Description: Waits for the specified kernel object to become signalled, // but allows thread pool work to be performed while waiting. No // timeout can be requested, this method will wait on the handle // forever. // // If this thread does not belong to the thread pool or is not // currently within a DoWork call, no work is performed. In this // case it behaves exactly the same as WaitForSingleObject with a // timeout of INFINITE. // // Arguments: // xxx pInterface - Pointer to interface. // HANDLE hWaitObject - Handle on which to wait. // DWORD dwFlags - Flags to use when waiting. // // Returns: HRESULT // DPN_OK - The object became signalled. //============================================================================= STDMETHODIMP DPTPW_WaitWhileWorking(IDirectPlay8ThreadPoolWork * pInterface, const HANDLE hWaitObject, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; #ifndef DPNBUILD_ONLYONEPROCESSOR DWORD dwCPU = 0; #endif // ! DPNBUILD_ONLYONEPROCESSOR #ifndef DPNBUILD_ONLYONETHREAD DPTPWORKERTHREAD * pWorkerThread; #ifndef DPNBUILD_ONLYONEPROCESSOR DPTPWORKQUEUE * pWorkQueue; #endif // ! DPNBUILD_ONLYONEPROCESSOR #endif // ! DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, 0x%x)", pInterface, hWaitObject, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Don't call this method while holding locks! // AssertNoCriticalSectionsTakenByThisThread(); // // Determine if this is a thread pool owned thread or one that is inside a // DoWork call. If it is either, go ahead and start waiting & working. // Otherwise, just perform a normal WaitForSingleObject. // Since all CPU queues share the same TLS index, just use the one from CPU // 0 as representative of all of them. // #ifndef DPNBUILD_ONLYONETHREAD pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); DPFX(DPFPREP, 7, "Worker thread = 0x%p, doing work = 0x%x.", pWorkerThread, (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK)); if (pWorkerThread != NULL) { pWorkerThread->dwRecursionCount++; #ifdef DBG if (pWorkerThread->dwRecursionCount > pWorkerThread->dwMaxRecursionCount) { pWorkerThread->dwMaxRecursionCount = pWorkerThread->dwRecursionCount; } #endif // DBG // // Keep looping until the object is ready. // while (WaitForSingleObject(hWaitObject, TIMER_BUCKET_GRANULARITY(pWorkerThread->pWorkQueue)) == WAIT_TIMEOUT) { // // The object is not ready, so process some work. // DoWork(pWorkerThread->pWorkQueue, INFINITE); #ifndef DPNBUILD_ONLYONEPROCESSOR // // It's possible to have 0 threads on a subset of processors. To // prevent deadlocks caused by items getting scheduled to a CPU // which then has all its threads removed, we need to make an // attempt at servicing its items. // We won't service every CPU each timeout, just one per loop. We // also won't take the lock while check the thread count, we can // stand a little error. The worst that could happen is that we // check the queue unnecessarily or a little late. Better than // hanging... // pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU); if (pWorkQueue->dwNumRunningThreads == 0) { DNASSERT(pWorkQueue != pWorkerThread->pWorkQueue); DoWork(pWorkQueue, INFINITE); } dwCPU++; if (dwCPU >= NUM_CPUS(pDPTPObject)) { dwCPU = 0; } #endif // ! DPNBUILD_ONLYONEPROCESSOR } DNASSERT(pWorkerThread->dwRecursionCount > 0); pWorkerThread->dwRecursionCount--; } else #endif // ! DPNBUILD_ONLYONETHREAD { BOOL fPseudoDoWork; // // Lock the object to prevent multiple threads from trying to change // the settings while we check and change them. // DNEnterCriticalSection(&pDPTPObject->csLock); // // If we're in no-threaded DoWork mode, but we're not in a DoWork call // at this moment, pretend that we are. // #ifdef DPNBUILD_ONLYONETHREAD if (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK)) #else // ! DPNBUILD_ONLYONETHREAD if ((pDPTPObject->dwTotalUserThreadCount == 0) && (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK))) #endif // ! DPNBUILD_ONLYONETHREAD { pDPTPObject->dwFlags |= DPTPOBJECTFLAG_USER_DOINGWORK; #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) pDPTPObject->dwCurrentDoWorkThreadID = GetCurrentThreadId(); #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL fPseudoDoWork = TRUE; } else { fPseudoDoWork = FALSE; } if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK) { #ifndef DPNBUILD_ONLYONETHREAD DWORD dwRecursionDepth; #endif // ! DPNBUILD_ONLYONETHREAD #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == GetCurrentThreadId()); #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL // // We can leave the lock because nobody else should be touching the // work queue while we're doing work. // DNLeaveCriticalSection(&pDPTPObject->csLock); // // Increment the recursion depth. // #ifdef DPNBUILD_ONLYONETHREAD pDPTPObject->dwWorkRecursionCount++; #else // ! DPNBUILD_ONLYONETHREAD dwRecursionDepth = (DWORD) ((DWORD_PTR) TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex)); dwRecursionDepth++; TlsSetValue(pDPTPObject->dwWorkRecursionCountTlsIndex, (PVOID) ((DWORD_PTR) dwRecursionDepth)); #endif // ! DPNBUILD_ONLYONETHREAD // // Keep looping until the object is ready. // while (WaitForSingleObject(hWaitObject, TIMER_BUCKET_GRANULARITY(WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU))) == WAIT_TIMEOUT) { // // The object is not ready, so process some work. Note that // timers can be missed by an amount proportional to the number // of CPUs since we only check a single queue each interval. // DoWork(WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU), INFINITE); #ifndef DPNBUILD_ONLYONEPROCESSOR // // Try the next CPU queue (wrapping appropriately). // dwCPU++; if (dwCPU == NUM_CPUS(pDPTPObject)) { dwCPU = 0; } #endif // ! DPNBUILD_ONLYONEPROCESSOR } // // Decrement the recursion depth. // #ifdef DPNBUILD_ONLYONETHREAD pDPTPObject->dwWorkRecursionCount--; #else // ! DPNBUILD_ONLYONETHREAD DNASSERT((DWORD) ((DWORD_PTR) TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex)) == dwRecursionDepth); dwRecursionDepth--; TlsSetValue(pDPTPObject->dwWorkRecursionCountTlsIndex, (PVOID) ((DWORD_PTR) dwRecursionDepth)); #endif // ! DPNBUILD_ONLYONETHREAD // // Clear the pseudo-DoWork mode flag if necessary. // if (fPseudoDoWork) { DNEnterCriticalSection(&pDPTPObject->csLock); DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK); pDPTPObject->dwFlags &= ~DPTPOBJECTFLAG_USER_DOINGWORK; #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == GetCurrentThreadId()); pDPTPObject->dwCurrentDoWorkThreadID = 0; #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL #ifdef DPNBUILD_ONLYONETHREAD DNASSERT(pDPTPObject->dwWorkRecursionCount == 0); #else // ! DPNBUILD_ONLYONETHREAD DNASSERT(dwRecursionDepth == 0); #endif // ! DPNBUILD_ONLYONETHREAD DNLeaveCriticalSection(&pDPTPObject->csLock); } } else { DNLeaveCriticalSection(&pDPTPObject->csLock); DNASSERT(! fPseudoDoWork); WaitForSingleObject(hWaitObject, INFINITE); } } hr = DPN_OK; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTPW_WaitWhileWorking #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_SleepWhileWorking" //============================================================================= // DPTPW_SleepWhileWorking //----------------------------------------------------------------------------- // // Description: Does not return for a specified number of milliseconds, but // allows thread pool work to be performed during that time. // // If this thread does not belong to the thread pool or is not // currently within a DoWork call, no work is performed. In this // case it behaves exactly the same as Sleep with the specified // timeout. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwTimeout - Timeout for the sleep operation. // DWORD dwFlags - Flags to use when sleeping. // // Returns: HRESULT // DPN_OK - The sleep occurred successfully. //============================================================================= STDMETHODIMP DPTPW_SleepWhileWorking(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwTimeout, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DWORD dwCPU = 0; #ifndef DPNBUILD_ONLYONETHREAD DPTPWORKERTHREAD * pWorkerThread; #ifndef DPNBUILD_ONLYONEPROCESSOR DPTPWORKQUEUE * pWorkQueue; #endif // ! DPNBUILD_ONLYONEPROCESSOR #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE))) DWORD dwStartTime; #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE #endif // ! DPNBUILD_ONLYONETHREAD DWORD dwStopTime; DWORD dwInterval; DWORD dwTimeLeft; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %u, 0x%x)", pInterface, dwTimeout, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Don't call this method while holding locks! // AssertNoCriticalSectionsTakenByThisThread(); // // We shouldn't sleep for a really long time, it causes bad results in our // calculations, and it doesn't make much sense in our case anyway (having // a thread be unusable for 24 days?) // DNASSERT(dwTimeout < 0x80000000); // // Determine if this is a thread pool owned thread or one that is inside a // DoWork call. If it is either, go ahead and start waiting & working. // Otherwise, just perform a normal WaitForSingleObject. // Since all CPU queues share the same TLS index, just use the one from CPU // 0 as representative of all of them. // #ifndef DPNBUILD_ONLYONETHREAD pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); DPFX(DPFPREP, 7, "Worker thread = 0x%p, doing work = 0x%x.", pWorkerThread, (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK)); if (pWorkerThread != NULL) { pWorkerThread->dwRecursionCount++; #ifdef DBG if (pWorkerThread->dwRecursionCount > pWorkerThread->dwMaxRecursionCount) { pWorkerThread->dwMaxRecursionCount = pWorkerThread->dwRecursionCount; } #endif // DBG // // Keep looping until the timeout expires. We can be jolted awake // earlier if the alert event gets set. // dwStopTime = GETTIMESTAMP() + dwTimeout; dwInterval = TIMER_BUCKET_GRANULARITY(pWorkerThread->pWorkQueue); // // Give up at least one time slice. // Sleep(0); do { // // Process some work. // DoWork(pWorkerThread->pWorkQueue, dwStopTime); #ifndef DPNBUILD_ONLYONEPROCESSOR // // It's possible to have 0 threads on a subset of processors. To // prevent deadlocks caused by items getting scheduled to a CPU // which then has all its threads removed, we need to make an // attempt at servicing its items. // We won't service every CPU each timeout, just one per loop. We // also won't take the lock while check the thread count, we can // stand a little error. The worst that could happen is that we // check the queue unnecessarily or a little late. Better than // hanging... // pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU); if (pWorkQueue->dwNumRunningThreads == 0) { DNASSERT(pWorkQueue != pWorkerThread->pWorkQueue); DoWork(pWorkQueue, dwStopTime); } dwCPU++; if (dwCPU >= NUM_CPUS(pDPTPObject)) { dwCPU = 0; } #endif // ! DPNBUILD_ONLYONEPROCESSOR // // If it's past time to stop sleeping, bail. // dwTimeLeft = dwStopTime - GETTIMESTAMP(); if ((int) dwTimeLeft <= 0) { break; } // // If the time left is less than the current interval, use that // instead for more accurate results. // if (dwTimeLeft < dwInterval) { dwInterval = dwTimeLeft; } #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE))) dwStartTime = GETTIMESTAMP(); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE #ifdef DPNBUILD_USEIOCOMPLETIONPORTS #pragma BUGBUG(vanceo, "Sleep alertably") Sleep(dwInterval); #else // ! DPNBUILD_USEIOCOMPLETIONPORTS // // Ignore the return code, we want to start working on the queue // regardless of timeout or alert event. // DNWaitForSingleObject(pWorkerThread->pWorkQueue->hAlertEvent, dwInterval); #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE))) DNInterlockedExchangeAdd((LPLONG) (&pWorkerThread->pWorkQueue->dwTotalTimeSpentUnsignalled), (GETTIMESTAMP() - dwStartTime)); #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE } while (TRUE); DNASSERT(pWorkerThread->dwRecursionCount > 0); pWorkerThread->dwRecursionCount--; } else #endif // ! DPNBUILD_ONLYONETHREAD { BOOL fPseudoDoWork; // // Lock the object to prevent multiple threads from trying to change // the settings while we check and change them. // DNEnterCriticalSection(&pDPTPObject->csLock); // // If we're in no-threaded DoWork mode, but we're not in a DoWork call // at this moment, pretend that we are. // #ifdef DPNBUILD_ONLYONETHREAD if (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK)) #else // ! DPNBUILD_ONLYONETHREAD if ((pDPTPObject->dwTotalUserThreadCount == 0) && (! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK))) #endif // ! DPNBUILD_ONLYONETHREAD { pDPTPObject->dwFlags |= DPTPOBJECTFLAG_USER_DOINGWORK; #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) pDPTPObject->dwCurrentDoWorkThreadID = GetCurrentThreadId(); #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL fPseudoDoWork = TRUE; } else { fPseudoDoWork = FALSE; } if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK) { #ifndef DPNBUILD_USEIOCOMPLETIONPORTS DNHANDLE ahWaitObjects[64]; DWORD dwNumWaitObjects; #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS #ifndef DPNBUILD_ONLYONETHREAD DWORD dwRecursionDepth; #endif // ! DPNBUILD_ONLYONETHREAD #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == GetCurrentThreadId()); #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL // // We can leave the lock because nobody else should be touching the // work queue while we're doing work. // DNLeaveCriticalSection(&pDPTPObject->csLock); // // Increment the recursion depth. // #ifdef DPNBUILD_ONLYONETHREAD pDPTPObject->dwWorkRecursionCount++; #else // ! DPNBUILD_ONLYONETHREAD dwRecursionDepth = (DWORD) ((DWORD_PTR) TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex)); dwRecursionDepth++; TlsSetValue(pDPTPObject->dwWorkRecursionCountTlsIndex, (PVOID) ((DWORD_PTR) dwRecursionDepth)); #endif // ! DPNBUILD_ONLYONETHREAD #ifndef DPNBUILD_USEIOCOMPLETIONPORTS // // Keep looping until the timeout expires. We can be jolted awake // earlier if one of the alert events gets set. We can only wait // on 64 objects, so we must cap the number of alert events to 64. // dwNumWaitObjects = NUM_CPUS(pDPTPObject); if (dwNumWaitObjects > 64) { DPFX(DPFPREP, 3, "Capping number of alert events to 64 (num CPUs = %u).", dwNumWaitObjects); dwNumWaitObjects = 64; } for(dwCPU = 0; dwCPU < dwNumWaitObjects; dwCPU++) { ahWaitObjects[dwCPU] = (WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU))->hAlertEvent; } #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS dwStopTime = GETTIMESTAMP() + dwTimeout; dwInterval = TIMER_BUCKET_GRANULARITY(WORKQUEUE_FOR_CPU(pDPTPObject, 0)); // // Give up at least one time slice. // Sleep(0); do { // // Process all CPU queues. // for(dwCPU = 0; dwCPU < NUM_CPUS(pDPTPObject); dwCPU++) { DoWork(WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU), dwStopTime); } // // If it's past time to stop sleeping, bail. // dwTimeLeft = dwStopTime - GETTIMESTAMP(); if ((int) dwTimeLeft <= 0) { break; } // // If the time left is less than the current interval, use that // instead for more accurate results. // if (dwTimeLeft < dwInterval) { dwInterval = dwTimeLeft; } #ifdef DPNBUILD_USEIOCOMPLETIONPORTS #pragma BUGBUG(vanceo, "Sleep alertably") Sleep(dwInterval); #else // ! DPNBUILD_USEIOCOMPLETIONPORTS // // Ignore return code, we want to start working on all CPUs // regardless of timeout or alert event. // DNWaitForMultipleObjects(dwNumWaitObjects, ahWaitObjects, FALSE, dwInterval); #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS } while (TRUE); // // Decrement the recursion depth. // #ifdef DPNBUILD_ONLYONETHREAD pDPTPObject->dwWorkRecursionCount--; #else // ! DPNBUILD_ONLYONETHREAD DNASSERT((DWORD) ((DWORD_PTR) TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex)) == dwRecursionDepth); dwRecursionDepth--; TlsSetValue(pDPTPObject->dwWorkRecursionCountTlsIndex, (PVOID) ((DWORD_PTR) dwRecursionDepth)); #endif // ! DPNBUILD_ONLYONETHREAD // // Clear the pseudo-DoWork mode flag if necessary. // if (fPseudoDoWork) { DNEnterCriticalSection(&pDPTPObject->csLock); DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK); pDPTPObject->dwFlags &= ~DPTPOBJECTFLAG_USER_DOINGWORK; #if ((! defined(DPNBUILD_ONLYONETHREAD)) || (! defined(DPNBUILD_NOPARAMVAL))) DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == GetCurrentThreadId()); pDPTPObject->dwCurrentDoWorkThreadID = 0; #endif // ! DPNBUILD_ONLYONETHREAD or ! DPNBUILD_NOPARAMVAL #ifdef DPNBUILD_ONLYONETHREAD DNASSERT(pDPTPObject->dwWorkRecursionCount == 0); #else // ! DPNBUILD_ONLYONETHREAD DNASSERT(dwRecursionDepth == 0); #endif // ! DPNBUILD_ONLYONETHREAD DNLeaveCriticalSection(&pDPTPObject->csLock); } } else { DNLeaveCriticalSection(&pDPTPObject->csLock); DNASSERT(! fPseudoDoWork); Sleep(dwTimeout); } } hr = DPN_OK; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTPW_SleepWhileWorking #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_RequestTotalThreadCount" //============================================================================= // DPTPW_RequestTotalThreadCount //----------------------------------------------------------------------------- // // Description: Requests a minimum number of threads for all processors. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwNumThreads - Desired number of threads. // DWORD dwFlags - Flags to use when setting the thread count. // // Returns: HRESULT // DPN_OK - Requesting the number of threads was // successful. // DPNERR_ALREADYINITIALIZED - The user has already set an incompatible // number of threads. //============================================================================= STDMETHODIMP DPTPW_RequestTotalThreadCount(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwNumThreads, const DWORD dwFlags) { #ifdef DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, 0, "Requesting threads is unsupported!"); DNASSERT(!"Requesting threads is unsupported!"); return DPNERR_UNSUPPORTED; #else // ! DPNBUILD_ONLYONETHREAD HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %u, 0x%x)", pInterface, dwNumThreads, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #pragma TODO(vanceo, "Possibly prevent calling on last thread pool thread or while DoWork in progress") // // Lock the object to prevent multiple threads from trying to change the // thread count simultaneously. // DNEnterCriticalSection(&pDPTPObject->csLock); // // This is a minimum request, so if a Work interface has already requested // more threads, we're fine. But if the user has already set a specific // number of threads then this Work interface can't override that. // if (pDPTPObject->dwTotalUserThreadCount == -1) { if ((pDPTPObject->dwTotalDesiredWorkThreadCount == -1) || (pDPTPObject->dwTotalDesiredWorkThreadCount < dwNumThreads)) { hr = SetTotalNumberOfThreads(pDPTPObject, dwNumThreads); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't set new minimum number of threads!"); // // Drop through... // } else { pDPTPObject->dwTotalDesiredWorkThreadCount = dwNumThreads; } } else { DPFX(DPFPREP, 1, "Work interface has already requested %u threads, succeeding.", pDPTPObject->dwTotalDesiredWorkThreadCount); hr = DPN_OK; } } else { if (pDPTPObject->dwTotalUserThreadCount < dwNumThreads) { DPFX(DPFPREP, 1, "User has already requested a lower number of threads (%u).", pDPTPObject->dwTotalUserThreadCount); hr = DPNERR_ALREADYINITIALIZED; // // Drop through... // } else { DPFX(DPFPREP, 1, "User has already requested %u threads, succeeding.", pDPTPObject->dwTotalUserThreadCount); hr = DPN_OK; } } DNLeaveCriticalSection(&pDPTPObject->csLock); DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #endif // ! DPNBUILD_ONLYONETHREAD } // DPTPW_RequestTotalThreadCount #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_GetTotalThreadCount" //============================================================================= // DPTPW_GetTotalThreadCount //----------------------------------------------------------------------------- // // Description: Retrieves the current number of threads on the specified // processor(s) requested by the main user interface. If the user // interface has not specified a thread count, but a work // interface has, then pdwNumThreads is set to the requested // thread count and DPNSUCCESS_PENDING is returned. If neither // the main user interface nor a work interface have set the // number of threads, then pdwNumThreads is set to 0 and // DPNERR_NOTREADY is returned. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwCPU - CPU whose thread count is to be retrieved, or -1 // for total thread count. // DWORD * pdwNumThreads - Pointer to DWORD in which to store the current // number of threads per processor. // DWORD dwFlags - Flags to use when retrieving thread count. // // Returns: HRESULT // DPN_OK - Retrieving the number of threads specified by user // was successful. // DPNSUCCESS_PENDING - The user hasn't specified a thread count, but the // number requested by work interfaces is available. // DPNERR_NOTREADY - No thread count has been specified yet. //============================================================================= STDMETHODIMP DPTPW_GetThreadCount(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwCPU, DWORD * const pdwNumThreads, const DWORD dwFlags) { #ifdef DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, 0, "Retrieving thread count is unsupported!"); DNASSERT(!"Retrieving thread count is unsupported!"); return DPNERR_UNSUPPORTED; #else // ! DPNBUILD_ONLYONETHREAD HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DPTPWORKQUEUE * pWorkQueue; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %i, 0x%p, 0x%x)", pInterface, dwCPU, pdwNumThreads, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); DNASSERT((dwCPU == -1) || (dwCPU < NUM_CPUS(pDPTPObject))); // // Lock the object while we retrieve the thread counts. // DNEnterCriticalSection(&pDPTPObject->csLock); if (dwCPU == -1) { // // Get the total thread count. // if (pDPTPObject->dwTotalUserThreadCount != -1) { *pdwNumThreads = pDPTPObject->dwTotalUserThreadCount; hr = DPN_OK; } else if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { *pdwNumThreads = pDPTPObject->dwTotalDesiredWorkThreadCount; hr = DPNSUCCESS_PENDING; } else { *pdwNumThreads = 0; hr = DPNERR_NOTREADY; } } else { // // Get the thread count for the specific CPU. // pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwCPU); *pdwNumThreads = pWorkQueue->dwNumRunningThreads; if (pDPTPObject->dwTotalUserThreadCount != -1) { hr = DPN_OK; } else if (pDPTPObject->dwTotalDesiredWorkThreadCount != -1) { hr = DPNSUCCESS_PENDING; } else { hr = DPNERR_NOTREADY; } } DNLeaveCriticalSection(&pDPTPObject->csLock); DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #endif // ! DPNBUILD_ONLYONETHREAD } // DPTPW_GetTotalThreadCount #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_GetWorkRecursionDepth" //============================================================================= // DPTPW_GetWorkRecursionDepth //----------------------------------------------------------------------------- // // Description: Stores the Work recursion depth of the current thread in the // value pointed to by pdwDepth. The recursion depth is the // number of times the thread has called DoWork, WaitWhileWorking, // or SleepWhileWorking. If the thread is not currently in any // of those functions, then the depth returned is 0. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD * pdwDepth - Place to store recursion depth of current thread. // DWORD dwFlags - Flags to use when retrieving recursion depth. // // Returns: HRESULT // DPN_OK - The recursion depth was retrieved successfully. //============================================================================= STDMETHODIMP DPTPW_GetWorkRecursionDepth(IDirectPlay8ThreadPoolWork * pInterface, DWORD * const pdwDepth, const DWORD dwFlags) { HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; #ifndef DPNBUILD_ONLYONETHREAD DPTPWORKERTHREAD * pWorkerThread; #endif // ! DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, 0x%x)", pInterface, pdwDepth, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); #ifdef DPNBUILD_ONLYONETHREAD // // Retrieve the recursion count for the only thread.. // DNEnterCriticalSection(&pDPTPObject->csLock); #ifdef DBG if (pDPTPObject->dwWorkRecursionCount > 0) { DPFX(DPFPREP, 5, "Thread is in a DoWork call with recursion depth %u.", pDPTPObject->dwWorkRecursionCount); DNASSERT(pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK); #ifndef DPNBUILD_NOPARAMVAL DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == GetCurrentThreadId()); #endif // ! DPNBUILD_NOPARAMVAL } else { DPFX(DPFPREP, 5, "Thread is not in a DoWork call."); DNASSERT(! (pDPTPObject->dwFlags & DPTPOBJECTFLAG_USER_DOINGWORK)); #ifndef DPNBUILD_NOPARAMVAL DNASSERT(pDPTPObject->dwCurrentDoWorkThreadID == 0); #endif // ! DPNBUILD_NOPARAMVAL } #endif // DBG *pdwDepth = pDPTPObject->dwWorkRecursionCount; DNLeaveCriticalSection(&pDPTPObject->csLock); #else // ! DPNBUILD_ONLYONETHREAD // // Retrieve the worker thread state. Since all CPU queues share the same // TLS index, just use the one from CPU 0 as representative of all of them. // pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); if (pWorkerThread != NULL) { DPFX(DPFPREP, 5, "Worker thread 0x%p has recursion count of %u.", pWorkerThread, pWorkerThread->dwRecursionCount); *pdwDepth = pWorkerThread->dwRecursionCount; } else { // // It's an app thread. Retrieve the recursion count from the TLS slot // dedicated to that purpose. // *pdwDepth = (DWORD) ((DWORD_PTR) TlsGetValue(pDPTPObject->dwWorkRecursionCountTlsIndex)); DPFX(DPFPREP, 5, "App thread has recursion count of %u.", *pdwDepth); } #endif // ! DPNBUILD_ONLYONETHREAD hr = DPN_OK; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; } // DPTPW_GetWorkRecursionDepth #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_Preallocate" //============================================================================= // DPTPW_Preallocate //----------------------------------------------------------------------------- // // Description: Pre-allocates per-CPU pooled resources for the given object. // // Arguments: // xxx pInterface - Pointer to interface. // DWORD dwNumWorkItems - Number of work items to pre-allocate per CPU. // DWORD dwNumTimers - Number of timers to pre-allocate per CPU. // DWORD dwNumIoOperations - Number of I/O operations to pre-allocate per // CPU. // DWORD dwFlags - Flags to use when pre-allocating. // // Returns: HRESULT // DPN_OK - The recursion depth was retrieved successfully. //============================================================================= STDMETHODIMP DPTPW_Preallocate(IDirectPlay8ThreadPoolWork * pInterface, const DWORD dwNumWorkItems, const DWORD dwNumTimers, const DWORD dwNumIoOperations, const DWORD dwFlags) { #ifdef DPNBUILD_PREALLOCATEDMEMORYMODEL HRESULT hr = DPN_OK; DPTHREADPOOLOBJECT * pDPTPObject; DWORD dwNumToAllocate; DWORD dwTemp; DPTPWORKQUEUE * pWorkQueue; DWORD dwAllocated; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, %u, %u, %u, 0x%x)", pInterface, dwNumWorkItems, dwNumTimers, dwNumIoOperations, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); // // Work items, timers, and I/O operations all come from the same pool. // dwNumToAllocate = dwNumWorkItems + dwNumTimers + dwNumIoOperations; // // Populate the pools for each CPU. // for(dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); dwAllocated = pWorkQueue->pWorkItemPool->Preallocate(dwNumToAllocate, pWorkQueue; if (dwAllocated < dwNumToAllocate) { DPFX(DPFPREP, 0, "Only preallocated %u of %u address elements!", dwAllocated, dwNumToAllocate); hr = DPNERR_OUTOFMEMORY; break; } } DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; #else // ! DPNBUILD_PREALLOCATEDMEMORYMODEL DPFX(DPFPREP, 0, "Preallocation is unsupported!"); DNASSERT(!"Preallocation is unsupported!"); return DPNERR_UNSUPPORTED; #endif // ! DPNBUILD_PREALLOCATEDMEMORYMODEL } // DPTPW_PreallocateItems #ifdef DPNBUILD_MANDATORYTHREADS #undef DPF_MODNAME #define DPF_MODNAME "DPTPW_CreateMandatoryThread" //============================================================================= // DPTPW_CreateMandatoryThread //----------------------------------------------------------------------------- // // Description: Creates a mandatory thread that is aware of the thread pool // but not directly controllable through the thread pool. // // This is largely a wrapper for the OS' CreateThread function. // The lpThreadAttributes, dwStackSize, lpStartAddress, // lpParameter and lpThreadId parameters are described in more // detail in the documentation for that function. The dwFlags // parameter is also passed straight through to the OS. However // the CREATE_SUSPENDED flag is not supported. // // The thread routine must simply return when finished. It // must not call ExitThread, endthread, or TerminateThread. // // Threads cannot be created when the user has put the // threadpool in "DoWork" mode. Similarly, "DoWork" mode cannot // be enabled when mandatory threads exist (see // IDirectPlay8ThreadPool::SetThreadCount). // // Arguments: // xxx pInterface - Pointer to interface. // LPSECURITY_ATTRIBUTES lpThreadAttributes - Attributes for thread. // SIZE_T dwStackSize - Stack size for thread. // LPTHREAD_START_ROUTINE lpStartAddress - Entry point for thread. // LPVOID lpParameter - Entry parameter for thread. // LPDWORD lpThreadId - Place to store ID of new // thread. // HANDLE * phThread - Place to store handle of // new thread. // DWORD dwFlags - Flags to use when creating // thread. // // Returns: HRESULT // DPN_OK - Creating the thread was successful. // DPNERR_OUTOFMEMORY - Not enough memory to create the thread. // DPNERR_NOTALLOWED - The user is in DoWork mode, threads cannot be // created. //============================================================================= STDMETHODIMP DPTPW_CreateMandatoryThread(IDirectPlay8ThreadPoolWork * pInterface, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, LPDWORD lpThreadId, HANDLE *const phThread, const DWORD dwFlags) { #ifdef DPNBUILD_ONLYONETHREAD DPFX(DPFPREP, 0, "Thread creation is not supported!"); DNASSERT(!"Thread creation is not supported!"); return DPNERR_UNSUPPORTED; #else // ! DPNBUILD_ONLYONETHREAD HRESULT hr; DPTHREADPOOLOBJECT * pDPTPObject; DNHANDLE hThread = NULL; DNHANDLE hStartedEvent = NULL; DPTPMANDATORYTHREAD * pMandatoryThread = NULL; DWORD dwThreadID; DNHANDLE ahWaitObjects[2]; DWORD dwResult; DPFX(DPFPREP, DPF_ENTRYLEVEL, "Parameters: (0x%p, 0x%p, %u, 0x%p, 0x%p, 0x%p, 0x%p, 0x%x)", pInterface, lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, lpThreadId, phThread, dwFlags); pDPTPObject = (DPTHREADPOOLOBJECT*) GET_OBJECT_FROM_INTERFACE(pInterface); DNASSERT(pDPTPObject != NULL); DNASSERT(lpStartAddress != NULL); DNASSERT(lpThreadId != NULL); DNASSERT(phThread != NULL); DNASSERT(! (dwFlags & CREATE_SUSPENDED)); // // We could check to see if we're in DoWork mode, but we don't want to hold // the lock while creating the thread. We would end up checking twice, // once here, and again in the thread when it was about to increment the // mandatory thread count, so we'll just use the one in the thread. See // DPTPMandatoryThreadProc // // // Create event so we can be notified when the thread starts. // hStartedEvent = DNCreateEvent(NULL, FALSE, FALSE, NULL); if (hStartedEvent == NULL) { #ifdef DBG dwResult = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create start event (err = %u)!", dwResult); #endif // DBG hr = DPNERR_GENERIC; goto Failure; } // // Allocate a tracking structure for the thread. // pMandatoryThread = (DPTPMANDATORYTHREAD*) DNMalloc(sizeof(DPTPMANDATORYTHREAD)); if (pMandatoryThread == NULL) { DPFX(DPFPREP, 0, "Couldn't allocate memory for tracking mandatory thread!"); hr = DPNERR_OUTOFMEMORY; goto Failure; } pMandatoryThread->Sig[0] = 'M'; pMandatoryThread->Sig[1] = 'N'; pMandatoryThread->Sig[2] = 'D'; pMandatoryThread->Sig[3] = 'T'; pMandatoryThread->pDPTPObject = pDPTPObject; pMandatoryThread->hStartedEvent = hStartedEvent; pMandatoryThread->pfnMsgHandler = (WORKQUEUE_FOR_CPU(pDPTPObject, 0))->pfnMsgHandler; pMandatoryThread->pvMsgHandlerContext = (WORKQUEUE_FOR_CPU(pDPTPObject, 0))->pvMsgHandlerContext; pMandatoryThread->lpStartAddress = lpStartAddress; pMandatoryThread->lpParameter = lpParameter; #ifdef DBG pMandatoryThread->dwThreadID = 0; pMandatoryThread->blList.Initialize(); #endif // DBG hThread = DNCreateThread(lpThreadAttributes, dwStackSize, DPTPMandatoryThreadProc, pMandatoryThread, dwFlags, &dwThreadID); if (hThread == NULL) { #ifdef DBG dwResult = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create thread (err = %u)!", dwResult); #endif // DBG hr = DPNERR_GENERIC; goto Failure; } ahWaitObjects[0] = hStartedEvent; ahWaitObjects[1] = hThread; dwResult = DNWaitForMultipleObjects(2, ahWaitObjects, FALSE, INFINITE); switch (dwResult) { case WAIT_OBJECT_0: { // // The thread started successfully. Drop through. // break; } case WAIT_OBJECT_0 + 1: { // // The thread shut down prematurely. // GetExitCodeThread(HANDLE_FROM_DNHANDLE(hThread), &dwResult); if ((HRESULT) dwResult == DPNERR_NOTALLOWED) { DPFX(DPFPREP, 0, "Thread started while in DoWork mode!"); hr = DPNERR_NOTALLOWED; } else { DPFX(DPFPREP, 0, "Thread shutdown prematurely (exit code = %u)!", dwResult); hr = DPNERR_GENERIC; } goto Failure; break; } default: { DPFX(DPFPREP, 0, "Thread failed waiting (result = %u)!", dwResult); hr = DPNERR_GENERIC; goto Failure; break; } } // // At this point, the thread owns the pMandatoryThread object, and could // delete it at any time. We must not reference it again. // // // Return the thread ID and handle to the caller. // *lpThreadId = dwThreadID; *phThread = HANDLE_FROM_DNHANDLE(hThread); hr = DPN_OK; Exit: // // Close the started event, we no longer need it. // if (hStartedEvent != NULL) { DNCloseHandle(hStartedEvent); hStartedEvent = NULL; } DPFX(DPFPREP, DPF_ENTRYLEVEL, "Returning: [0x%lx]", hr); return hr; Failure: if (hThread != NULL) { DNCloseHandle(hThread); hThread = NULL; } if (pMandatoryThread != NULL) { DNFree(pMandatoryThread); pMandatoryThread = NULL; } goto Exit; #endif // ! DPNBUILD_ONLYONETHREAD } // DPTPW_CreateMandatoryThread #endif // DPNBUILD_MANDATORYTHREADS #ifndef DPNBUILD_ONLYONEPROCESSOR #undef DPF_MODNAME #define DPF_MODNAME "ChooseWorkQueue" //============================================================================= // ChooseWorkQueue //----------------------------------------------------------------------------- // // Description: Selects the best CPU for a given operation, and returns a // pointer to its work queue object. // // Arguments: // DPTHREADPOOLOBJECT * pDPTPObject - Pointer to interface object. // // Returns: Pointer to work queue selected. //============================================================================= DPTPWORKQUEUE * ChooseWorkQueue(DPTHREADPOOLOBJECT * const pDPTPObject) { DPTPWORKQUEUE * pWorkQueue; DPTPWORKERTHREAD * pWorkerThread; // // If this is a thread pool thread, choose the CPU associated with this // thread. // pWorkerThread = (DPTPWORKERTHREAD*) TlsGetValue((WORKQUEUE_FOR_CPU(pDPTPObject, 0))->dwWorkerThreadTlsIndex); if (pWorkerThread != NULL) { pWorkQueue = pWorkerThread->pWorkQueue; goto Exit; } DNEnterCriticalSection(&pDPTPObject->csLock); // // If we are in DoWork mode, or no threads have been started, just use // processor 0's work queue. // if ((pDPTPObject->dwTotalUserThreadCount == 0) || ((pDPTPObject->dwTotalUserThreadCount == -1) && (pDPTPObject->dwTotalDesiredWorkThreadCount == -1))) { DNLeaveCriticalSection(&pDPTPObject->csLock); pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, 0); goto Exit; } // // Otherwise keep cycling through each CPU to distribute items equally // round-robin style. Don't queue items for CPUs that don't have any // running threads, though. // do { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, pDPTPObject->dwCurrentCPUSelection); pDPTPObject->dwCurrentCPUSelection++; if (pDPTPObject->dwCurrentCPUSelection >= NUM_CPUS(pDPTPObject)) { pDPTPObject->dwCurrentCPUSelection = 0; } } while (pWorkQueue->dwNumRunningThreads == 0); DNLeaveCriticalSection(&pDPTPObject->csLock); Exit: return pWorkQueue; } // ChooseWorkQueue #endif // ! DPNBUILD_ONLYONEPROCESSOR #ifndef DPNBUILD_ONLYONETHREAD #undef DPF_MODNAME #define DPF_MODNAME "SetTotalNumberOfThreads" //============================================================================= // SetTotalNumberOfThreads //----------------------------------------------------------------------------- // // Description: Modifies the total number of threads for all processors. // // The DPTHREADPOOLOBJECT lock is assumed to be held. // // Arguments: // DPTHREADPOOLOBJECT * pDPTPObject - Pointer to interface object. // DWORD dwNumThreads - New desired totla number of threads. // // Returns: HRESULT // DPN_OK - Setting the number of threads was successful. // DPNERR_OUTOFMEMORY - Not enough memory to alter the number of threads. //============================================================================= HRESULT SetTotalNumberOfThreads(DPTHREADPOOLOBJECT * const pDPTPObject, const DWORD dwNumThreads) { HRESULT hr = DPN_OK; DWORD dwNumThreadsPerProcessor; DWORD dwExtraThreads; DWORD dwTemp; DPTPWORKQUEUE * pWorkQueue; DWORD dwDelta; #ifdef DPNBUILD_USEIOCOMPLETIONPORTS dwNumThreadsPerProcessor = dwNumThreads; dwExtraThreads = 0; #else // ! DPNBUILD_USEIOCOMPLETIONPORTS dwNumThreadsPerProcessor = dwNumThreads / NUM_CPUS(pDPTPObject); dwExtraThreads = dwNumThreads % NUM_CPUS(pDPTPObject); #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS if (pDPTPObject->dwFlags & DPTPOBJECTFLAG_THREADCOUNTCHANGING) { AssertCriticalSectionIsTakenByThisThread(&pDPTPObject->csLock, FALSE); } else { AssertCriticalSectionIsTakenByThisThread(&pDPTPObject->csLock, TRUE); } // // Loop through each of the CPU specific work queues and adjust their // thread counts. // #ifdef DPNBUILD_USEIOCOMPLETIONPORTS dwTemp = 0; #else // ! DPNBUILD_USEIOCOMPLETIONPORTS for(dwTemp = 0; dwTemp < NUM_CPUS(pDPTPObject); dwTemp++) #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS { pWorkQueue = WORKQUEUE_FOR_CPU(pDPTPObject, dwTemp); dwDelta = dwNumThreadsPerProcessor - pWorkQueue->dwNumRunningThreads; if (dwTemp < dwExtraThreads) { dwDelta++; } if ((int) dwDelta > 0) { // // We need to add threads. // hr = StartThreads(pWorkQueue, dwDelta); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't start %u threads!", dwDelta); goto Exit; } } else if ((int) dwDelta < 0) { // // We need to remove threads. // dwDelta = (int) dwDelta * -1; // get absolute value hr = StopThreads(pWorkQueue, dwDelta); if (hr != DPN_OK) { DPFX(DPFPREP, 0, "Couldn't stop %u threads!", dwDelta); goto Exit; } } else { // // The thread count is already correct. // } if (dwTemp < dwExtraThreads) { DNASSERT(pWorkQueue->dwNumRunningThreads == (dwNumThreadsPerProcessor + 1)); } else { DNASSERT(pWorkQueue->dwNumRunningThreads == dwNumThreadsPerProcessor); } } Exit: return hr; } // SetTotalNumberOfThreads #endif // ! DPNBUILD_ONLYONETHREAD