Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1354 lines
44 KiB

//-----------------------------------------------------------------------------
//
//
// File: asyncq.inl
//
// Description: Implementation of templated CAsyncQueue class.
//
// Author: Mike Swafford (MikeSwa)
//
// History:
// 7/17/98 - MikeSwa Created
//
// Copyright (C) 1998 Microsoft Corporation
//
//-----------------------------------------------------------------------------
#ifndef __ASYNCQ_INL__
#define __ASYNCQ_INL__
#include "asyncq.h"
#include "fifoqimp.h"
#include "aqinst.h"
//---[ CAsyncQueueBase::CAsyncQueueBase ]--------------------------------------
//
//
// Description:
// Default constructor for CAsyncQueueBase class
// Parameters:
// dwTemplateSignature - Signature used to identify the type of
// templated super class this is associated with
// when an ATQ completion routine is called
// Returns:
// -
// History:
// 7/18/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CAsyncQueueBase::CAsyncQueueBase(DWORD dwTemplateSignature) :
CStateMachineBase(ASYNC_QUEUE_STATUS_NORMAL, ASYNC_QUEUE_STATE_MACHINE_SIG)
{
m_dwSignature = ASYNC_QUEUE_SIG;
m_dwTemplateSignature = dwTemplateSignature;
m_cMaxSyncThreads = 0;
m_cCurrentSyncThreads = 0;
m_cCurrentAsyncThreads = 0;
m_cItemsPending = 0;
m_cItemsPerATQThread = 0;
m_cItemsPerSyncThread = 0;
m_cScheduledWorkItems = 0;
m_cCurrentCompletionThreads = 0;
m_cCompletionThreadsRequested = 0;
m_pvContext = NULL;
m_pAtqContext = NULL;
m_hAtqHandle = INVALID_SOCKET;
m_cTotalAsyncCompletionThreads = 0;
m_cTotalSyncCompletionThreads = 0;
m_cTotalShortCircuitThreads = 0;
m_cPendingAsyncCompletions = 0;
m_cMaxPendingAsyncCompletions = 0;
m_cThreadsNeeded = 0;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::CAsyncQueue<PQDATA, TEMPLATE_SIG> ]--
//
//
// Description:
// Default constructor for CAsyncQueue
// Parameters:
// -
// Returns:
// -
// History:
// 7/17/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
CAsyncQueue<PQDATA, TEMPLATE_SIG>::CAsyncQueue<PQDATA, TEMPLATE_SIG>() :
CAsyncQueueBase(TEMPLATE_SIG)
{
m_pfnQueueCompletion = NULL;
m_pfnQueueFailure = NULL;
m_pfnFailedItem = NULL;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::~CAsyncQueue<PQDATA, TEMPLATE_SIG> ]--
//
//
// Description:
// Default desctructor for CAsyncQueue. Call Queue-mapping function to
// to clear out the queue
// Parameters:
// -
// Returns:
// -
// History:
// 7/17/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
CAsyncQueue<PQDATA, TEMPLATE_SIG>::~CAsyncQueue<PQDATA, TEMPLATE_SIG>()
{
TraceFunctEnterEx((LPARAM) this, "CAsyncQueue<PQDATA, TEMPLATE_SIG>::~CAsyncQueue<PQDATA, TEMPLATE_SIG>");
HRESULT hr = S_OK;
DWORD cItems = 0;
//
// If this is off, then we may not actually be processing anything
// (if we have hit the limit).
//
_ASSERT(!m_cPendingAsyncCompletions);
hr = m_fqQueue.HrMapFn(HrClearQueueMapFn, m_pvContext, &cItems);
if (FAILED(hr))
ErrorTrace((LPARAM) this, "ERROR: Unable to Cleanup CAsyncQueue - hr 0x%08X", hr);
else
DecrementPendingCount(-((LONG) cItems));
if (m_pAtqContext)
{
//Freeing context will close handle
AtqFreeContext(m_pAtqContext, FALSE);
}
TraceFunctLeave();
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrInitialize ]-----------------------
//
//
// Description:
// Initializes CAsyncQueue with the neccessary information
// Parameters:
// cMaxSyncThreads The maximum # of threads that will be "stolen" from
// the enqueuing threads and used to process items
// from the front of the queue
// cItemsPerATQThread Max # of items an ATQ thread will process from the
// front of the queue before being released
// cItemsPerSyncThread Max # of items a stolen sync thread will process
// from the fron of the queeu before being released
// pvContext Context pass to completion routines and queue-map
// functions (can be NULL)
// pfnQueueCompletion Function called to process a single item from
// the front of the queue.
// pfnFailedItem Function called if an internal resource failure
// prevents an item from being queued or requeued
// pfnQueueFailure Function called to walk the queues when the
// completion function fails
//
// Note:
// Queue completion functino has the following prototype
// BOOL (*QCOMPFN)(PQDATA pqData, PVOID pvContext)
// Returns TRUE if the item has been handles
// FALSE if the item needs to be requeued
//
// Returns:
// S_OK on success
// E_INVALIDARG if the params are invalid:
// cItemsPerThread is 0
// pfnQueueCompletion is NULL
// History:
// 7/17/98 - MikeSwa Created
// 2/3/99 - MikeSwa Added pfnFailedItem
// 12/11/2000 - MikeSwa Added t-toddc's state table work
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrInitialize(
DWORD cMaxSyncThreads,
DWORD cItemsPerATQThread,
DWORD cItemsPerSyncThread,
PVOID pvContext,
QCOMPFN pfnQueueCompletion,
QCOMPFN pfnFailedItem,
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueFailure,
DWORD cMaxPendingAsyncCompletions)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrInitialize");
HRESULT hr = S_OK;
ThreadPoolInitialize();
if (!cItemsPerATQThread || !cItemsPerSyncThread || !pfnQueueCompletion)
{
hr = E_INVALIDARG;
goto Exit;
}
m_cMaxSyncThreads = cMaxSyncThreads;
m_cItemsPerATQThread = (DWORD) cItemsPerATQThread;
m_cItemsPerSyncThread = (DWORD) cItemsPerSyncThread;
_ASSERT(m_cItemsPerATQThread > 0);
m_pvContext = pvContext;
m_pfnFailedItem = pfnFailedItem;
m_pfnQueueCompletion = pfnQueueCompletion;
m_pfnQueueFailure = pfnQueueFailure;
m_cMaxPendingAsyncCompletions = cMaxPendingAsyncCompletions;
//Create a dummy socket to handle async completion
m_hAtqHandle = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == m_hAtqHandle)
{
hr = HRESULT_FROM_WIN32(WSAGetLastError());
ErrorTrace((LPARAM) this, "ERROR socket() failed - hr 0x%08X", hr);
if (SUCCEEDED(hr))
hr = E_FAIL;
goto Exit;
}
//associate socket handle with ATQ
if (!AtqAddAsyncHandle(&m_pAtqContext, NULL, this,
AsyncQueueAtqCompletion, INFINITE, (HANDLE) m_hAtqHandle))
{
hr = HRESULT_FROM_WIN32(GetLastError());
ErrorTrace((LPARAM) this, "ERROR AtqAddAsyncHandle failed - hr 0x%08X", hr);
if (SUCCEEDED(hr))
hr = E_FAIL;
goto Exit;
}
// make sure state transition table is valid
if (!fValidateStateTable())
{
_ASSERT(0 && "State Transition Table Invalid");
hr = E_FAIL;
goto Exit;
}
Exit:
if (FAILED(hr))
ThreadPoolDeinitialize();
TraceFunctLeave();
return hr;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize ]---------------------
//
//
// Description:
// Walks queues with given function for shutdown
// Parameters:
// pfnQueueShutdown Queue-mapping function called on shutdown to
// clean queues. If NULL, it will substitute
// HrClearQueueMapFn which walks the queues and
// releases all PQDATA in it
// paqinst Shutdown context with server stop hint function
// Returns:
// S_OK on success
// History:
// 7/20/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize(
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueShutdown,
CAQSvrInst *paqinst)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize");
HRESULT hr = S_OK;
_ASSERT(paqinst);
DWORD cItems = 0;
//shutdown action has occurred.
dwGetNextState(ASYNC_QUEUE_ACTION_SHUTDOWN);
//wait until all requested threads have returned
while (m_cCurrentCompletionThreads || m_cCompletionThreadsRequested)
{
if (paqinst)
paqinst->ServerStopHintFunction();
Sleep(1000);
}
//map shutdown function
hr = m_fqQueue.HrMapFn(pfnQueueShutdown, paqinst, &cItems);
if (FAILED(hr))
ErrorTrace((LPARAM) this, "ERROR: Unable to Cleanup CAsyncQueue - hr 0x%08X", hr);
else
DecrementPendingCount(-((LONG)cItems));
ThreadPoolDeinitialize();
TraceFunctLeave();
return hr;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest ]---------------------
//
//
// Description:
// Queues request for async completion.
// Parameters:
// pqdata Data to pass to completion function
// fRetry TRUE => Put item at front of queue (and don't use this
// thread to process it).
// FALSE => Queue normaly
// Returns:
// S_OK on success
// E_OUTOFMEMORY if queue-related resources could not be allocated
// History:
// 7/17/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest(PQDATA pqdata,
BOOL fRetry)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest");
HRESULT hr = S_OK;
DWORD cCurrentSyncThreads;
_ASSERT(m_pfnQueueCompletion);
cCurrentSyncThreads = InterlockedIncrement((PLONG) &m_cCurrentSyncThreads);
//If we are shutting down... do not bother to queue the message
if (fInShutdown())
goto Exit;
IncrementPendingCount();
//Only enqueue if there are others waiting
if (fRetry || (m_cItemsPending > 1) ||
(m_cMaxSyncThreads < cCurrentSyncThreads) || fShouldStopProcessing())
{
//Enqueue data
if (fRetry && g_fRetryAtFrontOfAsyncQueue)
hr = m_fqQueue.HrRequeue(pqdata);
else
hr = m_fqQueue.HrEnqueue(pqdata);
if (FAILED(hr))
{
DecrementPendingCount();
ErrorTrace((LPARAM) this, "ERROR: Unable to queue item for async handling - hr 0x%08X", hr);
goto Exit;
}
//see if we can steal this thread thread to process queue entries
//Only steal a thread if the following conditions are met:
// - We have not exceeded our sync thread limit
// - There are no async threads that could be doing the work
// - We are not retrying something
if (!fRetry && !fShouldStopProcessing() &&
(m_cMaxSyncThreads >= cCurrentSyncThreads) &&
!m_cCurrentAsyncThreads && !m_cCompletionThreadsRequested)
{
//Make sure there is work to be done
if (fThreadNeededAndMarkWorkPending(TRUE))
{
//Steal thread
StartThreadCompletionRoutine(TRUE);
}
}
}
else
{
//Steal this thread thread to proccess this item w/o hitting queue
DecrementPendingCount();
InterlockedIncrement((PLONG) &m_cTotalShortCircuitThreads);
//Process Item & handle failure case
if (!m_pfnQueueCompletion(pqdata, m_pvContext))
{
fHandleCompletionFailure(pqdata);
}
}
//Always make sure there are enough threads to do the work (unless we are retrying)
if (!fRetry)
RequestCompletionThreadIfNeeded();
Exit:
InterlockedDecrement((PLONG) &m_cCurrentSyncThreads);
return hr;
TraceFunctLeave();
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::ProcessSingleQueueItem ]-------------
//
//
// Description:
// Processes a single item at the head of the queue. All failures need
// to be handled internally
// Parameters:
// -
// Returns:
// SUCCEEDED(hr)) on success and we should continue
// AQUEUE_E_QUEUE_EMPTY when there are no more items to process
// E_FAIL if the completion call failed
// Error code from HrDequeue on other failure.
// History:
// 7/17/98 - MikeSwa Created
// 2/3/2000 - MikeSwa Modified to return an HRESULT
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrProcessSingleQueueItem()
{
HRESULT hr = S_OK;
PQDATA pqdata = NULL;
DWORD cItemsLeft = 0;
BOOL fSucceeded = TRUE;
hr = m_fqQueue.HrDequeue(&pqdata);
if (SUCCEEDED(hr))
{
DecrementPendingCount();
//We have data item - now process it
fSucceeded = m_pfnQueueCompletion(pqdata, m_pvContext);
if (fSucceeded || fHandleCompletionFailure(pqdata))
{
//Request another thread if
// - we had at least 1 success.
// - handle failure told use to continue
RequestCompletionThreadIfNeeded();
//If fHandleCompletionFailure said we succeeded, then continue
fSucceeded = TRUE;
}
pqdata->Release();
}
//
// If the dequeue succeeded but the completion failed, then return E_FAIL
//
if (!fSucceeded && (SUCCEEDED(hr)))
hr = E_FAIL;
return hr;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::StartThreadCompletionRoutine ]-------
//
//
// Description:
// Starting point to completion threads. Each thread will attempt to
// process m_cItemsPerATQThread items from the front of the queue
// Parameters:
// fSync TRUE if a sync thread, FALSE... this is an ATQ thread
// Returns:
// -
// History:
// 7/17/98 - MikeSwa Created
// 2/3/2000 - MikeSwa Modified to fix window that would leave items
// "stranded" in queue.
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::StartThreadCompletionRoutine(BOOL fSync)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncQueue::StartThreadCompletionRoutine");
DWORD cItemsToProcess = (DWORD) (fSync ? m_cItemsPerSyncThread: m_cItemsPerATQThread);
HRESULT hr = S_OK;
DWORD dwInitialTickCount = 0;
DWORD dwCurrentTickCount = 0;
BOOL fRequestNewThread = TRUE;
InterlockedIncrement((PLONG) &m_cCurrentCompletionThreads);
if (fSync)
InterlockedIncrement((PLONG) &m_cTotalSyncCompletionThreads);
// obtain tick count immediately before processing
dwInitialTickCount = GetTickCount();
//process items until we fail or are done
while (cItemsToProcess) { // all cases for quitting are handled in the loop
hr = HrProcessSingleQueueItem();
// If we fail to process an item we will stop working in this thread
if (FAILED(hr)) {
// We failed, do not request a thread to replace this one unless
// the failure was "AQUEUE_E_QUEUE_EMPTY" in which case we need
// to try another thread in case an item is added between here
// and this thread's termination.
if (hr != AQUEUE_E_QUEUE_EMPTY) {
fRequestNewThread = FALSE;
InterlockedIncrement((PLONG)&s_cThreadCompletion_Failure);
}
else {
InterlockedIncrement((PLONG)&s_cThreadCompletion_QueueEmpty);
}
break;
}
// A note about cItemsToProcess and m_cScheduledWorkItems : It is
// important that when this function completes we have decremented the
// original value of cItemsToProcess from m_cScheduledWorkItems because
// that is the number that was added when this thread was requested. We
// will either subtract them here one by one (items that were processed)
// or at the end (items that were not processed) but what is most
// important is that the exact number is subtracted when the thread
// completes (to maintain the validity of m_cScheduledWorkItems)
// Decrement number of items to process and scheduled work count
cItemsToProcess--;
InterlockedDecrement((PLONG)&m_cScheduledWorkItems);
// If we have processed all our scheduled items - drop out now
if (!cItemsToProcess) {
InterlockedIncrement((PLONG)&s_cThreadCompletion_CompletedScheduledItems);
break;
}
// If there's nothing left in the queue - drop out now
if (!m_cItemsPending) {
InterlockedIncrement((PLONG)&s_cThreadCompletion_QueueEmpty);
break;
}
// If we have been paused - drop out now
if (fShouldStopProcessing()){
InterlockedIncrement((PLONG)&s_cThreadCompletion_Paused);
break;
}
// If we are using too many threads - drop out now
if (!fIsThreadCountAcceptable()) {
InterlockedIncrement((PLONG)&s_cThreadCompletion_UnacceptableThreadCount);
break;
}
// If we have been processing for too long - drop out now
dwCurrentTickCount = GetTickCount();
if (dwCurrentTickCount - dwInitialTickCount > g_cMaxTicksPerATQThread) {
InterlockedIncrement((PLONG)&s_cThreadCompletion_Timeout);
break;
}
}
// Subtract from the scheduled item count the number of items we did not process
if (cItemsToProcess) {
InterlockedExchangeAdd((PLONG)&m_cScheduledWorkItems, -((LONG)cItemsToProcess));
}
InterlockedDecrement((PLONG) &m_cCurrentCompletionThreads);
// Always request another thread when completing unless we failed - we will let
// the ThreadsNeeded logic handle throttling how many threads act on this queue
// at a time.
if (fRequestNewThread)
RequestCompletionThreadIfNeeded();
TraceFunctLeave();
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::fThreadNeededAndMarkWorkPending ]----
//
//
// Description:
// Determines if another worker thread is needed, and adjusts
// m_cScheduledWorkItems accordingly. Callee is repsonsible for determining
// if a thread can be allocated.
// Parameters:
// fSync TRUE if checking for a sync thread, FALSE... checking for an ATQ thread
// Returns:
// TRUE if another thread is needed (and member values adjusted accordingly)
// FALSE if another thread is not needed to do work
// History:
// 7/18/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
BOOL CAsyncQueue<PQDATA, TEMPLATE_SIG>::fThreadNeededAndMarkWorkPending(BOOL fSync)
{
if (fInShutdown())
{
_ASSERT(!fSync && "CAQSvrInst should not call now!!!");
return FALSE;
}
else if (fShouldStopProcessing())
{
return FALSE;
}
else if (m_cScheduledWorkItems < m_cItemsPending)
{
// There are unscheduled items - we need a thread
// Schedule the right number of items
InterlockedExchangeAdd((PLONG)&m_cScheduledWorkItems,
(fSync ? m_cItemsPerSyncThread : m_cItemsPerATQThread));
return TRUE;
}
else
{
return FALSE;
}
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::RequestCompletionThreadIfNeeded ]----
//
//
// Description:
// Requests a queue completion thread if needed. Uses ATQ and handle
// allocated to POQS for another thread. Makes sure that we do not
// exceed the max # of async threads.
// Parameters:
// -
// Returns:
// -
// History:
// 7/18/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::RequestCompletionThreadIfNeeded()
{
DWORD cThreadsRequested = m_cCompletionThreadsRequested;
BOOL fThreadRequested = FALSE;
// Can we have a thread?
InterlockedIncrement((PLONG) &m_cCompletionThreadsRequested);
if (fIsThreadCountAcceptable()) {
// Do we want a thread?
if (fThreadNeededAndMarkWorkPending(FALSE)) {
// Request a thread
fThreadRequested = TRUE;
AtqPostCompletionStatus(m_pAtqContext, GetTickCount());
}
}
// If we didn't request a thread, decrement the request count
if (!fThreadRequested)
InterlockedDecrement((PLONG) &m_cCompletionThreadsRequested);
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::fHandleCompletionFailure ]------------
//
//
// Description:
// Called when async completion function returns false... handles requeuing
// data and record-keeping. Needs to handle the following:
// Parameters:
// pqdata - Data that triggered failure
// Returns:
// -
// History:
// 7/18/98 - MikeSwa Created
// 8/14/98 - MikeSwa Modified to add failure handling
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
BOOL CAsyncQueue<PQDATA, TEMPLATE_SIG>::fHandleCompletionFailure(PQDATA pqdata)
{
HRESULT hr;
DWORD cItemsRemoved = 0;
if (fInShutdown())
return FALSE;
if (g_fRetryAtFrontOfAsyncQueue)
hr = m_fqQueue.HrRequeue(pqdata);
else
hr = m_fqQueue.HrEnqueue(pqdata);
if (SUCCEEDED(hr))
{
IncrementPendingCount();
}
else
HandleDroppedItem(pqdata);
//call failure routine (if present)
if (m_pfnQueueFailure)
{
hr = m_fqQueue.HrMapFn(m_pfnQueueFailure, m_pvContext, &cItemsRemoved);
if (SUCCEEDED(hr))
{
//Adjust appropriate counters
DecrementPendingCount(-((LONG)cItemsRemoved));
}
}
return FALSE;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::HandleDroppedItem ]------------------
//
//
// Description:
// Handles a dropped PQDATA by calling the callback provided at start
// up
// Parameters:
// pqData
// Returns:
//
// History:
// 2/3/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::HandleDroppedItem(PQDATA pqData)
{
if (m_pfnFailedItem)
m_pfnFailedItem(pqData, m_pvContext);
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrMapFn ]----------------------------
//
//
// Description:
// Calls a function on every message in the queue
// Parameters:
// IN pfnQueueFn Function to call for every message
// IN pvContext Context passed to completion function
// Returns:
// S_OK on success
// Error code from CFifoQueue<PQDATA>::HrMapFn
// History:
// 2/23/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrMapFn(
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueFn,
PVOID pvContext)
{
DWORD cItems = 0;
HRESULT hr = S_OK;
hr = m_fqQueue.HrMapFn(pfnQueueFn, pvContext, &cItems);
if (SUCCEEDED(hr))
{
DecrementPendingCount(-((LONG)cItems));
}
return hr;
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::UnpauseQueue ]-----------------------
//
//
// Description:
// Unpauses a queue by unsetting the ASYNC_QUEUE_STATUS_PAUSED bit and
// requesting threads if neccessary.
// Parameters:
// -
// Returns:
// -
// History:
// 1/24/2000 - MikeSwa Created
// 6/12/2000 - t-toodc modified to use state machine functionality
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::UnpauseQueue()
{
BOOL fStopped = fShouldStopProcessing();
dwGetNextState(ASYNC_QUEUE_ACTION_UNPAUSE);
//
// The queue *was* paused. We should make sure that we reqest threads
// if there are items to process.
//
if (fStopped && !fShouldStopProcessing()) {
UpdateThreadsNeeded();
RequestCompletionThreadIfNeeded();
}
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::ThawQueue ]-----------------------
//
// Description:
// Thaws a queue and sets the next state, requesting a completion thread
// if necessary
// Parameters:
// -
// Returns:
// -
// History:
// 1/24/2000 - MikeSwa Created
// 6/12/2000 - t-toodc modified to use state machine functionality
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::ThawQueue()
{
BOOL fStopped = fShouldStopProcessing();
dwGetNextState(ASYNC_QUEUE_ACTION_THAW);
//
// The queue *was* frozen. We should make sure that we reqest threads
// if there are items to process.
//
if (fStopped && !fShouldStopProcessing())
{
// Update threads needed, we need threads again
UpdateThreadsNeeded();
RequestCompletionThreadIfNeeded();
}
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::IncPendingAsyncCompletions ]---------
//
//
// Description:
// Increments the pending async completion count. If the async queue
// feeds into something that may complete async (like CatMsg). In this
// case, we may want to throttle the number of outstanding completions
// we have (i.e.- too avoid having too many active messages)
//
// If we have hit our limit, then this call with pause the queue.
// Parameters:
// -
// Returns:
// -
// History:
// 1/24/2000 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::IncPendingAsyncCompletions()
{
InterlockedIncrement((PLONG) &m_cPendingAsyncCompletions);
//
// Check against limit if we have one
//
if (m_cMaxPendingAsyncCompletions &&
(m_cPendingAsyncCompletions > m_cMaxPendingAsyncCompletions))
{
PauseQueue();
}
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::DecPendingAsyncCompletions ]---------
//
//
// Description:
// Decrements the pending async completion count. If we drop below our
// threshold, then we will unpause the queue.
// Parameters:
//
// Returns:
//
// History:
// 1/24/2000 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::DecPendingAsyncCompletions()
{
InterlockedDecrement((PLONG) &m_cPendingAsyncCompletions);
if (m_cMaxPendingAsyncCompletions &&
(m_cPendingAsyncCompletions < m_cMaxPendingAsyncCompletions))
{
UnpauseQueue();
}
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::fNoPendingAsyncCompletions ]---------
//
//
// Description:
// Are there any pending async completions?
// Parameters:
//
// Returns:
// BOOL
//
// History:
// 11/01/2000 - Awetmore created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
BOOL CAsyncQueue<PQDATA, TEMPLATE_SIG>::fNoPendingAsyncCompletions()
{
return (m_cPendingAsyncCompletions == 0);
}
//---[ CAsyncQueue<PQDATA, TEMPLATE_SIG>::dwQueueAdminLinkGetLinkState ]-------
//
//
// Description:
// Gets the Queue admin state of this queue. This is different depending
// on the type of async queue this is (normal vs. retry).
// Parameters:
// -
// Returns:
// returns the QAPI link flags describing what state this link is in
// History:
// 3/3/2000 - MikeSwa Created (moved from mailadmq.cpp)
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
DWORD CAsyncQueue<PQDATA, TEMPLATE_SIG>::dwQueueAdminLinkGetLinkState()
{
//
//Queue is in retry if there are items pending and no threads are
//processing them or it is active if there are items pending and
//threads processing them. If there are no items then it is ready.
//
if (fIsFrozen())
return LI_FROZEN;
else if (fIsPaused())
return LI_READY;
else if (0 != cGetItemsPending() && 0 == dwGetTotalThreads())
return LI_RETRY;
else if (0 != m_pammq->cGetItemsPending())
return LI_ACTIVE;
else
return LI_READY;
}
//---[ CAsyncQueueBase::UpdateThreadsNeeded ]----------------------------------
//
//
// Description:
// Update the threads needed counter locally and globally. This thread
// need is only the need for ASYNC threads but does take into account the
// fact that some items may already be scheduled for sync threads.
// Parameters:
// -
// Returns:
// -
// History:
// 11/16/2000 - dbraun - created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncQueue<PQDATA, TEMPLATE_SIG>::UpdateThreadsNeeded()
{
DWORD cNewThreadsNeeded = 0;
DWORD cOldThreadsNeeded = 0;
LONG lUnscheduledItems = 0; // may be negative if we over-committed
// We only get threads if we are not paused and we have items
if(!fShouldStopProcessing() && m_cItemsPending) {
// We always need the threads we have (or have requested)
cNewThreadsNeeded = m_cCompletionThreadsRequested + m_cCurrentCompletionThreads;
// Number of pending items that we have not already scheduled threads for
lUnscheduledItems = m_cItemsPending - m_cScheduledWorkItems;
// If we have unscheduled items, we need some more threads than we have
if (lUnscheduledItems > 0) {
_ASSERT(m_cItemsPerATQThread);
cNewThreadsNeeded += (lUnscheduledItems / m_cItemsPerATQThread) + 1;
}
}
if (cNewThreadsNeeded == m_cThreadsNeeded)
return; // nothing to do here ...
cOldThreadsNeeded = InterlockedExchange ((LPLONG) &m_cThreadsNeeded, cNewThreadsNeeded);
InterlockedExchangeAdd((LPLONG) &g_cTotalThreadsNeeded, cNewThreadsNeeded - cOldThreadsNeeded);
}
//---[ CAsyncQueueBase::fIsThreadCountAcceptable ]-----------------------------
//
//
// Description:
// Checks whether the current thread count for this queue is acceptable.
// This is used to release threads and to allow new threads to start on
// this queue.
// Parameters:
// -
// Returns:
// TRUE : Count is acceptable
// History:
// 11/16/2000 - dbraun - created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
BOOL CAsyncQueue<PQDATA, TEMPLATE_SIG>::fIsThreadCountAcceptable()
{
// Get these values once to make sure we are consistent and to prevent div/0
DWORD cGlobalThreadsNeeded = g_cTotalThreadsNeeded;
DWORD cThreadsNeeded = m_cThreadsNeeded;
DWORD cThreadsAllowed = 0;
DWORD cThreadsActiveAndPending = m_cCompletionThreadsRequested + m_cCurrentCompletionThreads;
// We are only allowed threads if we are not paused and we have items pending
if(!fShouldStopProcessing() && m_cItemsPending) {
// Below we calculate how many threads are allowed. This can be more or less than
// the number of threads needed and is only used to limit thread counts, it does
// not mean that we will actually use the total threads allowed.
// The number of threads allowed is based on the max threads and how many threads
// this queue needs when compared with the rest of the queues that want threads
if (cGlobalThreadsNeeded) {
cThreadsAllowed = s_cDefaultMaxAsyncThreads * cThreadsNeeded / cGlobalThreadsNeeded;
}
// One thread is always acceptable
if (!cThreadsAllowed) {
cThreadsAllowed = 1;
}
}
return (cThreadsAllowed >= cThreadsActiveAndPending);
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::CAsyncRetryQueue ]--------------
//
//
// Description:
// Default constructor for CAsyncRetryQueue
// Parameters:
// -
// Returns:
// -
// History:
// 2/5/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::CAsyncRetryQueue()
{
m_dwRetrySignature = ASYNC_RETRY_QUEUE_SIG;
m_cRetryItems = 0;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::~CAsyncRetryQueue ]-------------
//
//
// Description:
// Default destructor for CAsyncRetryQueue. Walks retry queue to release
// items on it.
// Parameters:
// -
// Returns:
// -
// History:
// 2/5/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::~CAsyncRetryQueue()
{
m_fqRetryQueue.HrMapFn(HrClearQueueMapFn, m_pvContext, NULL);
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize ]----------------
//
//
// Description:
// Walks queues with given function for shutdown
// Parameters:
// pfnQueueShutdown Queue-mapping function called on shutdown to
// clean queues. If NULL, it will substitute
// HrClearQueueMapFn which walks the queues and
// releases all PQDATA in it
// paqinst Shutdown context with server stop hint function
// Returns:
// S_OK on success
// History:
// 2/5/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize(
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueShutdown,
CAQSvrInst *paqinst)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize");
HRESULT hr = S_OK;
DWORD cItems = 0;
_ASSERT(paqinst);
CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrDeinitialize(pfnQueueShutdown,
paqinst);
//map shutdown function
hr = m_fqRetryQueue.HrMapFn(pfnQueueShutdown, paqinst, &cItems);
if (FAILED(hr))
ErrorTrace((LPARAM) this, "ERROR: Unable to Cleanup CAsyncQueue - hr 0x%08X", hr);
else
dwInterlockedAddSubtractDWORD(&m_cRetryItems, cItems, FALSE);
TraceFunctLeave();
return hr;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::fHandleCompletionFailure ]------
//
//
// Description:
// Called when async completion function returns false... handles requeuing
// data to the retry queue
// Parameters:
// pqdata - Data that triggered failure
// Returns:
// -
// History:
// 2/5/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
BOOL CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::fHandleCompletionFailure(PQDATA pqdata)
{
HRESULT hr;
DWORD cItemsRemoved = 0;
if (fInShutdown())
return FALSE;
//Requeue failed item to retry queue... a possible interesting thing to do
//here would be to run the failed item through the failure function
//(without the queue) to generate DSNs and see if the item actually need to
//be queues.
hr = m_fqRetryQueue.HrRequeue(pqdata);
if (SUCCEEDED(hr))
InterlockedIncrement((PLONG) &m_cRetryItems);
else
HandleDroppedItem(pqdata);
return TRUE;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest ]----------------
//
//
// Description:
// Queue a request for a retry queue.
// Parameters:
// pqdata Data to pass to completion function
// fRetry TRUE => Put item in retry queue until queue is kicked
// FALSE => Queue normaly
// Returns:
// S_OK on success
// E_OUTOFMEMORY if queue-related resources could not be allocated
// History:
// 3/3/2000 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest(PQDATA pqdata,
BOOL fRetry)
{
TraceFunctEnterEx((LPARAM) this, "CAsyncRetryQueue<>::HrQueueRequest");
HRESULT hr = S_OK;
//
// Handle as failure if retry (will put the item in the retry queue).
// Otherwise pass to base implementation (queue to normal asyncq).
//
if (fRetry)
fHandleCompletionFailure(pqdata);
else
hr = CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest(pqdata, fRetry);
TraceFunctLeave();
return hr;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrMapFn ]-----------------------
//
//
// Description:
// Calls a function on every message in the queue
// Parameters:
// IN pfnQueueFn Function to call for every message
// IN pvContext Context passed to completion function
// Returns:
// S_OK on success
// Error code from CFifoQueue<PQDATA>::HrMapFn
// History:
// 2/23/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrMapFn(
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueFn,
PVOID pvContext)
{
HRESULT hr = S_OK;
hr = HrMapFnBaseQueue(pfnQueueFn, pvContext);
if (FAILED(hr))
goto Exit;
hr = HrMapFnRetryQueue(pfnQueueFn, pvContext);
if (FAILED(hr))
goto Exit;
Exit:
return hr;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrMapFnBaseQueue ]--------------
//
//
// Description:
// Calls a function on every message in the base (non-retry) queue
// Parameters:
// IN pfnQueueFn Function to call for every message
// IN pvContext Context passed to completion function
// Returns:
// S_OK on success
// Error code from CFifoQueue<PQDATA>::HrMapFn
// History:
// 2/23/99 - MikeSwa Created
// 1/10/2001 - MikeSwa Modified from HrMapFn
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrMapFnBaseQueue(
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueFn,
PVOID pvContext)
{
HRESULT hr = S_OK;
hr = CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrMapFn(pfnQueueFn, pvContext);
return hr;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrMapFnRetryQueue ]-------------
//
//
// Description:
// Calls a function on every message in the queue retry
// Parameters:
// IN pfnQueueFn Function to call for every message
// IN pvContext Context passed to completion function
// Returns:
// S_OK on success
// Error code from CFifoQueue<PQDATA>::HrMapFn
// History:
// 2/23/99 - MikeSwa Created
// 1/10/2001 - MikeSwa Modified from HrMapFn
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
HRESULT CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::HrMapFnRetryQueue(
typename CFifoQueue<PQDATA>::MAPFNAPI pfnQueueFn,
PVOID pvContext)
{
DWORD cItems = 0;
HRESULT hr = S_OK;
hr = m_fqRetryQueue.HrMapFn(pfnQueueFn, pvContext, &cItems);
if (SUCCEEDED(hr))
dwInterlockedAddSubtractDWORD(&m_cRetryItems, cItems, FALSE);
return hr;
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::MergeRetryQueue ]---------------
//
//
// Description:
// Merges retry queue into normal queue
// Parameters:
// -
// Returns:
// -
// History:
// 2/5/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
void CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::MergeRetryQueue()
{
DWORD cItemsRemoved = 0;
PQDATA pqData = NULL;
HRESULT hr = S_OK;
if (fInShutdown())
return;
//call failure routine (if present)
if (m_pfnQueueFailure)
{
hr = m_fqRetryQueue.HrMapFn(m_pfnQueueFailure, m_pvContext, &cItemsRemoved);
if (SUCCEEDED(hr))
{
//Adjust appropriate counters
InterlockedExchangeAdd((PLONG) &m_cRetryItems, -((LONG) cItemsRemoved));
}
}
//Now remerge queue
hr = S_OK;
while (SUCCEEDED(hr))
{
pqData = NULL;
hr = m_fqRetryQueue.HrDequeue(&pqData);
if (FAILED(hr))
break;
_ASSERT(pqData);
InterlockedDecrement((PLONG) &m_cRetryItems);
//Queue request as retry so we know thread will not be stolen
hr = CAsyncQueue<PQDATA, TEMPLATE_SIG>::HrQueueRequest(pqData, TRUE);
if (FAILED(hr))
HandleDroppedItem(pqData);
pqData->Release();
}
}
//---[ CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::dwQueueAdminLinkGetLinkState ]--
//
//
// Description:
// Gets the Queue admin state of this queue. This is different depending
// on the type of async queue this is (normal vs. retry).
// Parameters:
// -
// Returns:
// returns the QAPI link flags describing what state this link is in
// History:
// 3/3/2000 - MikeSwa Created (moved from localq.cpp)
//
//-----------------------------------------------------------------------------
template<class PQDATA, DWORD TEMPLATE_SIG>
DWORD CAsyncRetryQueue<PQDATA, TEMPLATE_SIG>::dwQueueAdminLinkGetLinkState()
{
//If we items in retry and others... mark it in retry
//If we have items pending.. it is active
//Otherwise it is ready
if (fIsFrozen())
return LI_FROZEN;
else if (fIsPaused())
return LI_READY;
else if ((0 != cGetItemsPendingRetry()) && (0 == cGetItemsPending()))
return LI_RETRY;
else if (0 != cGetItemsPending())
return LI_ACTIVE;
else
return LI_READY;
}
#endif //__ASYNCQ_INL__