|
|
//-----------------------------------------------------------------------------
//
//
// File: fifoqimp.h
//
// Description: Implementation for Fifo Queue template
//
// Author: mikeswa
//
// Copyright (C) 1997 Microsoft Corporation
//
//-----------------------------------------------------------------------------
#include <fifoq.h>
#define FIFOQ_ASSERT_QUEUE
//#define SHARELOCK_TRY_BROKEN
//some constants used
const DWORD FIFOQ_QUEUE_PAGE_SIZE = 127; //number of entires per page
const DWORD FIFOQ_QUEUE_MAX_FREE_PAGES = 200; //maximum # of free pages kept
//$$REVIEW: It might be nice to pick a size that is page size friendly
// Current of objects is
// sizeof(PVOID) + sizeof(PVOID)*FIFOQ_QUEUE_PAGE_SIZE = 512 bytes
//---[ CFifoQueuePage ]--------------------------------------------------------
//
//
// Hungarian: fqp, pfqp
//
// Single page of a FIFO queue. Most operations are handled within the actual
// CFifoQueue class. FQPAGE is a typedef for this template class within
// the scope of the CFifoQueue class
//-----------------------------------------------------------------------------
template<class PQDATA> class CFifoQueuePage { public: friend class CFifoQueue<PQDATA>; CFifoQueuePage() {Recycle();}; protected: inline void Recycle(); inline bool FIsOutOfBounds(IN PQDATA *ppqdata); CFifoQueuePage<PQDATA> *m_pfqpNext; //Next page in linked list
CFifoQueuePage<PQDATA> *m_pfqpPrev; //previous page in linked list
PQDATA m_rgpqdata[FIFOQ_QUEUE_PAGE_SIZE]; #ifdef FIFOQ_ASSERT_QUEUE
//# of entries on this page that have been removed out of order
//- Used in assertion routines
DWORD m_cHoles; #endif //FIFOQ_ASSERT_QUEUE
};
//---[ CFifoQueuePage::Recycle ]-----------------------------------------------
//
//
// Description:
// Performs initialization of a page. Called when a page is created as
// well as when it is retrieved from the free list
// Parameters:
// -
// Returns:
// -
//
//-----------------------------------------------------------------------------
template<class PQDATA> void CFifoQueuePage<PQDATA>::Recycle() { m_pfqpNext = NULL; m_pfqpPrev = NULL; #ifdef FIFOQ_ASSERT_QUEUE
m_cHoles = 0; #endif //FIFOQ_ASSERT_QUEUE
}
//---[ CFifoQueuePage::FIsOutOfBounds ]----------------------------------------
//
//
// Description:
// Tests to see if a PQDATA ptr is within range of this page
// Parameters:
// IN ppqdata - PQDATA ptr to test
// Returns:
// TRUE if in bounds
// FALSE if ptr is out of bounds
//-----------------------------------------------------------------------------
template<class PQDATA> bool CFifoQueuePage<PQDATA>::FIsOutOfBounds(PQDATA *ppqdata) { return ((ppqdata < m_rgpqdata) || ((m_rgpqdata + (FIFOQ_QUEUE_PAGE_SIZE-1)) < ppqdata)); }
#ifdef DEBUG
#ifdef FIFOQ_ASSERT_QUEUE
//---[ CFifoQueue::AssertQueueFn() ]-------------------------------------------
//
//
// Description:
// Perform some rather involved validation of the queue. Including:
// - Check Head and Tail page to make sure they conform various
// retrictions of our data structure
// - Check count to make sure it reflects data
// At some point we may wish to add further checking (ie walking the linked
// list in both directions to validate it).
// Parameters:
// fHaveLocks - set to true if the caller has both the head and tail locked
// Default value is FALSE.
// Returns:
// -
//
//-----------------------------------------------------------------------------
template <class PQDATA> void CFifoQueue<PQDATA>::AssertQueueFn(BOOL fHaveLocks) { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::AssertQueue"); FQPAGE *pfqpTmp = NULL; //used to count entries
DWORD cEntries = 0; //what we think count should be
_ASSERT(FIFOQ_SIG == m_dwSignature); //include text in assert, to have it appear in dialog box (if applicable)
if (!fHaveLocks) { m_slHead.ShareLock(); m_slTail.ShareLock(); } if ((m_pfqpHead != NULL) && (NULL != m_pfqpHead->m_pfqpPrev)) { //If Head is not NULL, it should not have a pervious page
DebugTrace((LPARAM) this, "Queue Assert: Head's Previous ptr is non-NULL"); Assert(0 && "Queue Assert: Head's Previous is non-NULL"); }
if ((m_pfqpTail != NULL) && (NULL != m_pfqpTail->m_pfqpNext)) { //If Tail is not NULL, it should not have a next page
DebugTrace((LPARAM) this, "Queue Assert: Tail's Next ptr is non-NULL"); Assert(0 && "Queue Assert: Tail's Next is non-NULL"); }
if ((m_pfqpHead != NULL) && (m_pfqpTail != NULL)) { Assert(m_ppqdataTail); Assert(m_ppqdataHead);
if (m_pfqpHead != m_pfqpTail) { // If Tail and Head are non-NULL and not equal to each other, then they
// must have non-NULL Prev and Next ptrs (respectively).
if (NULL == m_pfqpTail->m_pfqpPrev) { DebugTrace((LPARAM) this, "Queue Assert: Tail's Prev ptr is NULL, Head != Tail"); Assert(0 && "Queue Assert: Tail's Prev ptr is NULL, Head != Tail"); } if (NULL == m_pfqpHead->m_pfqpNext) { DebugTrace((LPARAM) this, "Queue Assert: Head's Next ptr is NULL, Head != Tail"); Assert(0 && "Queue Assert: Head's Next ptr is NULL, Head != Tail"); }
//Check count when Head and Tail differ
pfqpTmp = m_pfqpTail->m_pfqpPrev; while (NULL != pfqpTmp) { cEntries += FIFOQ_QUEUE_PAGE_SIZE - pfqpTmp->m_cHoles; pfqpTmp = pfqpTmp->m_pfqpPrev; } cEntries += (DWORD)(m_ppqdataTail - m_pfqpTail->m_rgpqdata); //tail page
cEntries -= m_pfqpTail->m_cHoles; cEntries -= (DWORD)(m_ppqdataHead - m_pfqpHead->m_rgpqdata); //head page
if (cEntries != m_cQueueEntries) { DebugTrace((LPARAM) this, "Queue Assert: Count is %d when it should be %d", m_cQueueEntries, cEntries); Assert(0 && "Queue Assert: Entry Count is inaccurate"); } } else //Head and Tail are same
{ Assert(m_pfqpHead == m_pfqpTail); cEntries = (DWORD)(m_ppqdataTail - m_ppqdataHead) - m_pfqpTail->m_cHoles; if (cEntries != m_cQueueEntries) { DebugTrace((LPARAM) this, "Queue Assert: Count is %d when it should be %d", m_cQueueEntries, cEntries); Assert(0 && "Queue Assert: Entry Count is inaccurate"); } } } else if ((m_pfqpHead != NULL) && (m_pfqpTail == NULL)) { //If Tail is NULL, then Head should be as well
DebugTrace((LPARAM) this, "Queue Assert: Tail is NULL while Head is non-NULL"); Assert(0 && "Queue Assert: Tail is NULL while Head is non-NULL"); } else if (m_pfqpTail != NULL) { Assert(m_pfqpHead == NULL); //should fall out of if/else
if (NULL == m_pfqpTail->m_pfqpPrev) { //count is easy here :)
if (m_cQueueEntries != (size_t) (m_ppqdataTail - m_pfqpTail->m_rgpqdata)) { DebugTrace((LPARAM) this, "Queue Assert: Count is %d when it should be %d", m_cQueueEntries, (m_ppqdataTail - m_pfqpTail->m_rgpqdata)); Assert(0 && "Queue Assert: Entry Count is inaccurate"); } } else //there is more than 1 page, but head is still NULL
{ pfqpTmp = m_pfqpTail->m_pfqpPrev; while (NULL != pfqpTmp) { cEntries += FIFOQ_QUEUE_PAGE_SIZE - pfqpTmp->m_cHoles; pfqpTmp = pfqpTmp->m_pfqpPrev; } cEntries += (DWORD)(m_ppqdataTail - m_pfqpTail->m_rgpqdata) - m_pfqpTail->m_cHoles; if (cEntries != m_cQueueEntries) { DebugTrace((LPARAM) this, "Queue Assert: Count is %d when it should be %d", m_cQueueEntries, cEntries); Assert(0 && "Queue Assert: Entry Count is inaccurate"); } } } else //both head and tail are NULL
{ Assert((m_pfqpHead == NULL) && (m_pfqpTail == NULL)); //falls out of if/else
if (m_cQueueEntries != 0) { //If both Head and Tail are NULL, them m_cQueueEntries == 0
DebugTrace((LPARAM) this, "Queue Assert: Entry Counter is %d when queue should be empty", m_cQueueEntries); Assert(0 && "Queue Assert: Entry Counter is non-zero when queue should be empty"); } }
if (!fHaveLocks) //we aquired the locks in this function
{ m_slTail.ShareUnlock(); m_slHead.ShareUnlock(); }
TraceFunctLeave();
}
#define AssertQueue() AssertQueueFn(FALSE)
#define AssertQueueHaveLocks() AssertQueueFn(TRUE)
#else //FIFOQ_ASSERT_QUEUE
#define AssertQueue()
#define AssertQueueHaveLocks()
#endif //FIFOQ_ASSERT_QUEUE
#else //not DEBUG
#define AssertQueue()
#define AssertQueueHaveLocks()
#endif //DEBUG
//---[ CFifoQueue Static Variables ]-------------------------------------------
template <class PQDATA> volatile CFifoQueuePage<PQDATA> *CFifoQueue<PQDATA>::s_pfqpFree = NULL;
template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cFreePages = 0; template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cFifoQueueObj = 0; template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cStaticRefs = 0; template <class PQDATA> CRITICAL_SECTION CFifoQueue<PQDATA>::s_csAlloc;
#ifdef DEBUG
template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cAllocated = 0; template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cDeleted = 0; template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cFreeAllocated = 0; template <class PQDATA> DWORD CFifoQueue<PQDATA>::s_cFreeDeleted = 0; #endif //DEBUG
//---[ CFifoQueue::CFifoQueue ]------------------------------------------------
//
//
// Description: CFifoQueue constructor
//
// Parameters: -
//
// Returns: -
//
//
//-----------------------------------------------------------------------------
template <class PQDATA> CFifoQueue<PQDATA>::CFifoQueue<PQDATA>() { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::CFifoQueue");
m_dwSignature = FIFOQ_SIG; m_cQueueEntries = 0; //set count of entries to 0
m_pfqpHead = NULL; //Initialize page pointers
m_pfqpTail = NULL; m_pfqpCursor = NULL; m_ppqdataHead = NULL; //Initialize data pointers
m_ppqdataTail = NULL; m_ppqdataCursor = NULL;
InterlockedIncrement((PLONG) &s_cFifoQueueObj); TraceFunctLeave(); }
//---[ CFifoQueue::~CFifoQueue ]------------------------------------------------
//
//
// Description: CFifoQueue destructor
//
// Parameters: -
//
// Returns: -
//
//
//-----------------------------------------------------------------------------
template <class PQDATA> CFifoQueue<PQDATA>::~CFifoQueue<PQDATA>() { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::~CFifoQueue"); FQPAGE *pfqpTmp = NULL;
if (m_cQueueEntries != 0) { PQDATA pqdata = NULL; int iLeft = m_cQueueEntries;
for (int i = iLeft; i > 0; i--) { if (FAILED(HrDequeue(&pqdata))) break; Assert(NULL != pqdata); pqdata->Release(); } }
while (m_pfqpHead) { //If last dequeue could not delete page, then make sure pages
//are freed
pfqpTmp = m_pfqpHead->m_pfqpNext; FreeQueuePage(m_pfqpHead); m_pfqpHead = pfqpTmp; }
InterlockedDecrement((PLONG) &s_cFifoQueueObj);
TraceFunctLeave(); }
//---[ CFifoQueue::StaticInit() ]--------------------------------------------
//
//
// Description: Initialization routines for CFifoQueue. This
// is excplcitly single threaded. The limitations are:
// - Only one thread in this function
// - You cannot use any queues until this has completed
//
// Parameters: -
//
// Returns: -
//
//
//-----------------------------------------------------------------------------
template <class PQDATA> void CFifoQueue<PQDATA>::StaticInit() { TraceFunctEnter("CFifoQueue::HrStaticInit()"); DWORD cRefs = 0;
//
// Add a static ref for each call to this
//
cRefs = InterlockedIncrement((PLONG) &s_cStaticRefs);
if (1 == cRefs) { InitializeCriticalSection(&s_csAlloc); }
//
// Catch unsafe callers
//
_ASSERT(cRefs == s_cStaticRefs);
TraceFunctLeave(); }
//---[ CFifoQueue::StaticDeinit() ]------------------------------------------
//
//
// Description: Deinitialization routines for CFifoQueue
//
// Parameters: -
//
// Returns:
// -
//
//-----------------------------------------------------------------------------
template <class PQDATA> void CFifoQueue<PQDATA>::StaticDeinit() { TraceFunctEnter("CFifoQueue::HrStaticDeinit()"); LONG lRefs = 0; lRefs = InterlockedDecrement((PLONG) &s_cStaticRefs); DWORD cLost = 0; DEBUG_DO_IT(cLost = s_cAllocated - s_cDeleted - s_cFreePages);
if (lRefs == 0) { if (0 != cLost) ErrorTrace((LPARAM) NULL, "ERROR: CFifoQueue Deinit with %d Lost Pages", cLost);
//This assert will catch if the any queue pages were allocated but not freed
_ASSERT(!cLost && "We are leaking some queue pages");
//There should be no other threads calling into this
//note quite true, there are still outstanding refs at the time
FQPAGE *pfqpCur = (FQPAGE *) s_pfqpFree; while (NULL != pfqpCur) { s_pfqpFree = pfqpCur->m_pfqpNext; delete pfqpCur; pfqpCur = (FQPAGE *) s_pfqpFree; s_cFreePages--;
//It is possible to stop all server instances without
//unloading the DLL. The cLost Assert will fire on the next
//shutdown if we don't increment the deleted counter as well...
//even though we aren't leaking any pages.
DEBUG_DO_IT(s_cDeleted++);
} //This assert catches if there are any free pages left after we walk the list
Assert(s_cFreePages == 0);
DeleteCriticalSection(&s_csAlloc); }
TraceFunctLeave(); }
//---[ CFifoQueue::HrEnqueue ]-------------------------------------------------
//
//
// Description: Enqueue a new item to the tail of the queue
//
// Parameters:
// IN PQDATA pqdata Data to enqueue
// Returns:
// S_OK on success
// E_OUTOFMEMORY if unable to allocate page
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrEnqueue(IN PQDATA pqdata) { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::HrEnqueue"); HRESULT hr = S_OK; FQPAGE *pfqpNew = NULL; //newly allocated page
AssertQueue(); Assert(pqdata); pqdata->AddRef();
m_slTail.ExclusiveLock();
if ((m_pfqpTail == NULL) || //Queue is empty or needs a new queue page
(m_pfqpTail->FIsOutOfBounds(m_ppqdataTail))) { //assert that tail is NULL or 1 past end of previous tail page
Assert((m_ppqdataTail == NULL) || (m_ppqdataTail == (m_pfqpTail->m_rgpqdata + FIFOQ_QUEUE_PAGE_SIZE))); Assert((m_cQueueEntries == 0) || (m_pfqpTail != NULL));
hr = HrAllocQueuePage(&pfqpNew); if (FAILED(hr)) goto Exit;
Assert(pfqpNew);
if (NULL != m_pfqpTail) //Update Next & prev ptr if not first page
{ Assert(NULL == m_pfqpTail->m_pfqpNext); m_pfqpTail->m_pfqpNext = pfqpNew; pfqpNew->m_pfqpPrev = m_pfqpTail; } #ifndef SHARELOCK_TRY_BROKEN
else { if (m_slHead.TryExclusiveLock()) { //can update head stuff with impunity
m_pfqpHead = pfqpNew; m_ppqdataHead = pfqpNew->m_rgpqdata; m_slHead.ExclusiveUnlock(); } //else requeue or MapFn has lock
} #endif //SHARELOCK_TRY_BROKEN
m_pfqpTail = pfqpNew; m_ppqdataTail = pfqpNew->m_rgpqdata;
}
Assert(!m_pfqpTail->FIsOutOfBounds(m_ppqdataTail)); Assert(m_ppqdataTail);
*m_ppqdataTail = pqdata; m_ppqdataTail++;
//increment count
InterlockedIncrement((PLONG) &m_cQueueEntries);
m_slTail.ExclusiveUnlock();
Exit: AssertQueue(); if (FAILED(hr)) pqdata->Release(); TraceFunctLeave(); return hr; }
//---[ CFifoQueue::HrDequeue ]-------------------------------------------------
//
//
// Description: Dequeue an item from the queue
//
// Parameters:
// OUT PQDATA *ppqdata Data dequeued
//
// Returns:
// S_OK on success
// AQUEUE_E_QUEUE_EMPTY if the queue is empty
// E_NOTIMPL if fPrimary is FALSE (for now)
//
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrDequeue(OUT PQDATA *ppqdata) { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::HrDequeue"); HRESULT hr = S_OK;
AssertQueue(); Assert(ppqdata);
if (m_cQueueEntries == 0) { hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
m_slHead.ExclusiveLock();
hr = HrAdjustHead(); if (FAILED(hr)) { m_slHead.ExclusiveUnlock(); goto Exit; }
*ppqdata = *m_ppqdataHead; *m_ppqdataHead = NULL;
InterlockedDecrement((PLONG) &m_cQueueEntries);
m_ppqdataHead++; //If it crosses page boundary, then HrAdjustQueue
//will fix it on next dequeue
#ifndef SHARELOCK_TRY_BROKEN
//Deal with brand new way of deleting last page
if ((m_cQueueEntries == 0) && (m_slTail.TryExclusiveLock())) { //If we cannot access tail ptr, the enqueue in progress and
//we should not delete the page they are enqueueing on
if (m_cQueueEntries == 0) //gotta be thread safe
{ Assert(m_pfqpHead == m_pfqpTail);
m_pfqpTail = NULL; m_ppqdataTail = NULL;
m_slTail.ExclusiveUnlock();
m_ppqdataHead = NULL;
FreeQueuePage(m_pfqpHead); m_pfqpHead = NULL; } else m_slTail.ExclusiveUnlock();
} #endif //SHARELOCK_TRY_BROKEN
m_slHead.ExclusiveUnlock();
Exit: AssertQueue();
if (FAILED(hr)) *ppqdata = NULL; #ifdef DEBUG
else Assert(NULL != *ppqdata); #endif //DEBUG
TraceFunctLeave(); return hr; }
//---[ CFifoQueue::HrRequeue ]--------------------------------------------------
//
//
// Description:
// Requeues a message to the head of the queue (like an enqueue that occurs
// at the head.
//
// Parameters:
// IN PQDATA pqdata data to be enqueued
// Returns:
// S_OK on success
// E_OUTOFMEMORY if an allocation error occurs
//
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrRequeue(IN PQDATA pqdata) { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::HrRequeue"); HRESULT hr = S_OK; PQDATA *ppqdataNewHead = NULL; BOOL fHeadLocked = FALSE;
AssertQueue(); Assert(pqdata); pqdata->AddRef();
m_slHead.ExclusiveLock(); fHeadLocked = TRUE; ppqdataNewHead = m_ppqdataHead - 1;
//There are 2 cases to worry about here
// CASE 0: Head page is NULL - Either Queue is empty, or head has not
// been updated yet... may be changed into CASE 2 if queue non-empty
// CASE 1: Head page is valid and decremented Headptr is on a Head page
// In this case, the data can be requeued. Having m_slHead will make
// sure that the Head Page is not deleted from underneath us
// CASE 2: New Head ptr is invalid. We need to allocate a new page to
// put requeued data on.
if (NULL == m_pfqpHead) { //CASE 0
hr = HrAdjustHead(); if (FAILED(hr)) { if (AQUEUE_E_QUEUE_EMPTY == hr) { //Queue is empty... just enqueue
//But first, release head lock so enqueue has can allocate
//first page etc.... Otherwise, we would guarantee failure
//of enqueue TryExlusiveLock and for HrAdjustHead to do the
//work next time.
m_slHead.ExclusiveUnlock(); fHeadLocked = FALSE;
hr = HrEnqueue(pqdata); if (SUCCEEDED(hr)) pqdata->Release(); }
goto Exit; } //else will fall through to case 2
}
if ((m_pfqpHead != NULL) && !m_pfqpHead->FIsOutOfBounds(ppqdataNewHead)) { //CASE 1
*ppqdataNewHead = pqdata; m_ppqdataHead = ppqdataNewHead; } else { //CASE 2
FQPAGE *pfqpNew = NULL;
hr = HrAllocQueuePage(&pfqpNew); if (FAILED(hr)) goto Exit;
//make sure next points to the head page
pfqpNew->m_pfqpNext = m_pfqpHead;
//prev needs to point to the new page
if (m_pfqpHead) m_pfqpHead->m_pfqpPrev = pfqpNew;
m_pfqpHead = pfqpNew;
//write the data & update local copy of head
m_ppqdataHead = &(pfqpNew->m_rgpqdata[FIFOQ_QUEUE_PAGE_SIZE-1]); *m_ppqdataHead = pqdata;
}
InterlockedIncrement((PLONG) &m_cQueueEntries);
Exit: if (fHeadLocked) m_slHead.ExclusiveUnlock(); AssertQueue();
if (FAILED(hr)) pqdata->Release();
TraceFunctLeave(); return hr; }
//---[ CFifoQueue::HrPeek ]-----------------------------------------------------
//
//
// Description:
// Peeks at the head data on the queue.
// Parameters:
// OUT PQDATA *ppqdata returned data
// Returns:
// S_OK on success
// AQUEUE_E_QUEUE_EMPTY if the queue has no data in it
// possibly E_FAIL or E_OUTOFMEMORY if one of the supporting functions fail
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrPeek(OUT PQDATA *ppqdata) { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::HrPeek"); HRESULT hr = S_OK;
AssertQueue(); Assert(ppqdata);
if (m_cQueueEntries == 0) { hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
m_slHead.ExclusiveLock();
hr = HrAdjustHead(); if (FAILED(hr)) goto Exit;
*ppqdata = *m_ppqdataHead; (*ppqdata)->AddRef();
Exit: m_slHead.ExclusiveUnlock(); AssertQueue(); TraceFunctLeave(); return hr; }
//---[ CFifoQueue::HrMapFn ]---------------------------------
//
//
// Description:
// Advances a secondary cursor until supplied function returns FALSE
// Parameters:
// IN pFunc - must be a function with the following prototype:
//
// HRESULT pvFunc(
// IN PQDATA pqdata, //ptr to data on queue
// IN PVOID pvContext,
// OUT BOOL *pfContinue, //TRUE if we should continue
// OUT BOOL *pfDelete); //TRUE if item should be deleted
// pvFunc must NOT release pqdata.. if it is no longer valid, it should
// return TRUE in pfDelete, and the calling code will remove it from
// the queue and release it.
//
// OUT pcItems - count of queue items removed from queue
//
// Returns:
// S_OK on success
// E_INVALIDARG if pvFunc is not valid
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrMapFn( IN MAPFNAPI pFunc, IN PVOID pvContext, OUT DWORD *pcItems) { //$$TODO: Test the context handle feature
TraceFunctEnterEx((LPARAM) this, "CFifoQueue::HrMapFn"); HRESULT hr = S_OK; FQPAGE *pfqpCurrent = NULL; //The current page we are looking at
FQPAGE *pfqpTmp = NULL; PQDATA *ppqdataCurrent = NULL; //The current queue data we are looking at
PQDATA *ppqdataLastValid = NULL; //The last non-NULL queue data
DWORD cItems = 0; BOOL fPageInUse = FALSE; BOOL fContinue = FALSE; BOOL fDelete = FALSE; BOOL fLocked = FALSE;
//Variables that make it easier to debug this function
PQDATA *ppqdataOldTail = NULL; PQDATA *ppqdataOldHead = NULL;
if (NULL != pcItems) *pcItems = 0;
if (NULL == pFunc) //$$REVIEW - more validation than this?
{ hr = E_INVALIDARG; goto Exit; }
if (0 == m_cQueueEntries) //don't even bother if nothing is in the queue
goto Exit;
m_slHead.ExclusiveLock(); m_slTail.ExclusiveLock();
fLocked = TRUE; DebugTrace((LPARAM) this, "MapFn Has Exclusive Locks");
//make sure that head pointer is adjusted properly
hr = HrAdjustHead(); if (FAILED(hr)) { _ASSERT((AQUEUE_E_QUEUE_EMPTY == hr) && "HrAdjustHead failed without AQUEUE_E_QUEUE_EMPTY!!!!"); hr = S_OK; }
AssertQueueHaveLocks();
pfqpCurrent = m_pfqpHead; //start at head and work backwards
ppqdataCurrent = m_ppqdataHead;
_ASSERT(pfqpCurrent || !m_cQueueEntries);
while (NULL != pfqpCurrent) { DEBUG_DO_IT(ppqdataOldTail = m_ppqdataTail); DEBUG_DO_IT(ppqdataOldHead = m_ppqdataHead);
if (m_cQueueEntries == 0) { Assert(m_pfqpHead == m_pfqpTail); Assert(m_pfqpHead == pfqpCurrent); m_pfqpHead = NULL; m_pfqpTail = NULL; m_ppqdataHead = NULL; m_ppqdataTail = NULL; FreeQueuePage(pfqpCurrent); pfqpCurrent = NULL; goto Exit; }
if (pfqpCurrent->FIsOutOfBounds(ppqdataCurrent) || ((m_pfqpTail == pfqpCurrent) && (ppqdataCurrent >= m_ppqdataTail))) { //We are ready to set pfqpCurrent to point to the next page.. may need to
//free the old page.
if (fPageInUse) { //don't delete the page if there is still something on there
pfqpCurrent = pfqpCurrent->m_pfqpNext; } else { pfqpTmp = pfqpCurrent->m_pfqpNext; if (NULL != pfqpTmp) pfqpTmp->m_pfqpPrev = pfqpCurrent->m_pfqpPrev; else { Assert(pfqpCurrent == m_pfqpTail); //It must be the tail
//point the tail to the next page
m_pfqpTail = m_pfqpTail->m_pfqpPrev; m_ppqdataTail = ppqdataLastValid + 1; //If last page was not deleted, then the last valid ptr should be on it
Assert((NULL == m_pfqpTail) || !m_pfqpTail->FIsOutOfBounds(ppqdataLastValid)); #ifdef FIFOQ_ASSERT_QUEUE
//fixup Hole count
//will not touch count if Tail ptr is after end of tail page
for (PQDATA *ppqdataTmp = m_ppqdataTail; ppqdataTmp < m_pfqpTail->m_rgpqdata + FIFOQ_QUEUE_PAGE_SIZE; ppqdataTmp++) { if (NULL == *ppqdataTmp) m_pfqpTail->m_cHoles--; } #endif //FIFOQ_ASSERT_QUEUE
ppqdataLastValid = NULL; }
if (NULL != pfqpCurrent->m_pfqpPrev) { Assert(pfqpCurrent->m_pfqpPrev->m_pfqpNext == pfqpCurrent); pfqpCurrent->m_pfqpPrev->m_pfqpNext = pfqpTmp; } else { //if it does not have a prev pointer is should be the head
Assert(pfqpCurrent == m_pfqpHead); Assert(NULL == pfqpCurrent->m_pfqpPrev); if (m_pfqpTail == m_pfqpHead) //be sure to make tail valid
{ Assert(0); //the 1st if/else now handles this
} m_pfqpHead = pfqpTmp; m_ppqdataHead = m_pfqpHead->m_rgpqdata; }
AssertQueueHaveLocks();//try to see what has happened before freeing
FreeQueuePage(pfqpCurrent); pfqpCurrent = pfqpTmp; if (NULL != m_pfqpHead) { Assert(NULL == m_pfqpHead->m_pfqpPrev); }
AssertQueueHaveLocks();
} if (NULL == pfqpCurrent) break; ppqdataCurrent = pfqpCurrent->m_rgpqdata; fPageInUse = FALSE; }
Assert(ppqdataCurrent); //the above should guarantee this
if (NULL != *ppqdataCurrent) { hr = pFunc(*ppqdataCurrent, pvContext, &fContinue, &fDelete); if (FAILED(hr)) goto Exit;
if (fDelete) { InterlockedDecrement((PLONG) &m_cQueueEntries); (*ppqdataCurrent)->Release(); *ppqdataCurrent = NULL; #ifdef FIFOQ_ASSERT_QUEUE
pfqpCurrent->m_cHoles++; //adjust Hole counter for assertions
#endif //FIFOQ_ASSERT_QUEUE
cItems++; } else { fPageInUse = TRUE; ppqdataLastValid = ppqdataCurrent; } if (!fContinue) break; } ppqdataCurrent++; }
Exit: if (fLocked) { AssertQueueHaveLocks(); m_slTail.ExclusiveUnlock(); m_slHead.ExclusiveUnlock(); } else { AssertQueue(); }
if (NULL != pcItems) *pcItems = cItems; TraceFunctLeave(); return hr; }
//---[ CFifoQueue::HrAdjustHead ]----------------------------------------------
//
//
// Description:
// Adjust Head ptr and Head Page ptr if necessary for pending dequeue or
// peek. To keep operations thread-safe, you MUST have the head lock
//
// This function is used because there are many operations that may leave
// the head page/ptr in an inconsistant state, but very few that actually
// need them to be consistant. Rather than running the risk of missing
// a case where the head ptr is inconsistant, we call this function when
// we need them to be consistant
//
// Head page and head ptr may be updated as a side-effect
// Parameters:
// -
// Returns:
// S_OK on success
// AQUEUE_E_QUEUE_EMPTY if the queue is empty (or becomes empty)
//
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrAdjustHead() { TraceFunctEnterEx((LPARAM) this, "CFifoQueue::HrAdjustHead"); HRESULT hr = S_OK;
//AssertQueue(); // the locks we are using are not re-entrant.
//Make sure that something hasn't been dequeued from underneath us
//at least from our perception of the ptrs
if (m_cQueueEntries == 0) { hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
while (TRUE) //handle holes in queue (marked as NULL ptrs)
{ //now find an appropriate value for the head ptr
// Case 0: if Head Page is NULL, then find first page by searching from
// Tail page. This case happens when the queue is truely empty
// or first enqueue could not get Tail lock.
// Case 1: if Head data pointer is NULL, or invalid and not just
// past end of head page, then set it to first thing on
// head page.
//
// $$REVIEW - I don't think there are any cases that
// can cause (and not case 0). I will put an assert in to
// make sure this is truely the case.
// Case 2: if just past end of page, attempt to update head page,
// and set to first thing on new head page. This means
// that the last item on that page has been dequeued.
// Case 3: Within current head page boundaries, keep it as is.
// This is the 90% case that happens most often during
// normal operation.
if (NULL == m_pfqpHead) { //case 0
DebugTrace((LPARAM) this, "Searching list for Head page"); m_pfqpHead = m_pfqpTail; if (NULL == m_pfqpHead) //there IS nothing in the queue
{ Assert(0 == m_cQueueEntries); hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
while (NULL != m_pfqpHead->m_pfqpPrev) //get to first page
{ m_pfqpHead = m_pfqpHead->m_pfqpPrev; } m_ppqdataHead = m_pfqpHead->m_rgpqdata; }
_ASSERT(m_pfqpHead); //otherwise should have returned AQUEUE_E_QUEUE_EMPTY
if ((m_ppqdataHead == NULL) || (m_pfqpHead->FIsOutOfBounds(m_ppqdataHead) && (m_ppqdataHead != (&m_pfqpHead->m_rgpqdata[FIFOQ_QUEUE_PAGE_SIZE])))) { //case 1
m_ppqdataHead = m_pfqpHead->m_rgpqdata;
_ASSERT(0 && "Non-fatal assert... get mikeswa to take a look at this case"); } else if (m_ppqdataHead == (&m_pfqpHead->m_rgpqdata[FIFOQ_QUEUE_PAGE_SIZE])) { //case 2
DebugTrace((LPARAM) this, "Deleting page 0x%08X", m_pfqpHead); //set new head page
FQPAGE *pfqpOld = m_pfqpHead; m_pfqpHead = m_pfqpHead->m_pfqpNext; Assert(m_pfqpHead->m_pfqpPrev == pfqpOld); Assert(m_pfqpHead); //There must be a next head if not empty
m_pfqpHead->m_pfqpPrev = NULL; m_ppqdataHead = m_pfqpHead->m_rgpqdata;
FreeQueuePage(pfqpOld);
_ASSERT(m_pfqpHead && (NULL == m_pfqpHead->m_pfqpPrev));
}
if (NULL != *m_ppqdataHead) break; else { //Case 3
m_ppqdataHead++; #ifdef FIFOQ_ASSERT_QUEUE
Assert(m_pfqpHead->m_cHoles >= 1); Assert(m_pfqpHead->m_cHoles <= FIFOQ_QUEUE_PAGE_SIZE); m_pfqpHead->m_cHoles--; #endif //FIFOQ_ASSERT_QUEUE
} }
Exit: TraceFunctLeave(); return hr; }
//---[ CFifoQueue::HrAllocQueuePage ]------------------------------------------
//
//
// Description: Allocates a queue page
//
// Parameters:
// OUT FQPAGE **ppfqp newly allocated page
// Returns:
// S_OK on success
// E_OUTOFMEMORY on failure
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT CFifoQueue<PQDATA>::HrAllocQueuePage(FQPAGE **ppfqp) { TraceFunctEnterEx((LPARAM) s_cFreePages, "CFifoQueue::HrAllocQueuePage"); HRESULT hr = S_OK; FQPAGE *pfqpNew = NULL; FQPAGE *pfqpNext = NULL; FQPAGE *pfqpCheck = NULL;
Assert(ppfqp); *ppfqp = NULL;
if (s_cFreePages) { //
// Grab critical section before looking at head of the free list
//
EnterCriticalSection(&s_csAlloc);
pfqpNew = (FQPAGE *) s_pfqpFree; if (NULL != pfqpNew) { pfqpNext = pfqpNew->m_pfqpNext; s_pfqpFree = pfqpNext; *ppfqp = pfqpNew; }
//
// Release the critical section now that we are done with the free list
//
LeaveCriticalSection(&s_csAlloc);
//
// If our allocation was successfull, bail and return the new page
//
if (*ppfqp) { InterlockedDecrement((PLONG) &s_cFreePages); #ifdef DEBUG
InterlockedIncrement((PLONG) &s_cFreeAllocated); #endif //DEBUG
pfqpNew->Recycle(); goto Exit; }
}
*ppfqp = new FQPAGE();
if (*ppfqp == NULL) { hr = E_OUTOFMEMORY; goto Exit; }
#ifdef DEBUG
InterlockedIncrement((PLONG) &s_cAllocated); #endif //DEBUG
Exit: TraceFunctLeave(); return hr; }
//---[ CFifoQueue::FreeQueuePage ]------------------------------------------------------------
//
//
// Description: Free's a queue page, by putting it on the free list.
//
// Parameters:
// FQPAGE *pfqp page to free
// Returns:
// -
//-----------------------------------------------------------------------------
template <class PQDATA> void CFifoQueue<PQDATA>::FreeQueuePage(FQPAGE *pfqp) { TraceFunctEnterEx((LPARAM) s_cFreePages, "CFifoQueue::FreeQueuePage"); Assert(pfqp); Assert(pfqp != s_pfqpFree); //check against pushing same thing twice in a row
FQPAGE *pfqpCheck = NULL; FQPAGE *pfqpFree = NULL;
if (s_cFreePages < FIFOQ_QUEUE_MAX_FREE_PAGES) { //
// Grab critical section before looking at head of the free list
//
EnterCriticalSection(&s_csAlloc);
//
// Update the free list
//
pfqpFree = (FQPAGE *) s_pfqpFree; pfqp->m_pfqpNext = pfqpFree; s_pfqpFree = pfqp;
//
// Release Critical section now that we have updated the freelist
//
LeaveCriticalSection(&s_csAlloc);
InterlockedIncrement((PLONG) &s_cFreePages); #ifdef DEBUG
InterlockedIncrement((PLONG) &s_cFreeDeleted); #endif //DEBUG
} else { delete pfqp; #ifdef DEBUG
InterlockedIncrement((PLONG) &s_cDeleted); #endif //DEBUG
} TraceFunctLeave(); }
//---[ HrClearQueueMapFn ]-----------------------------------------------------
//
//
// Description:
// Example default function to use with HrMapFn... will always return TRUE
// to continue and delete the current queued data
// Parameters:
// IN PQDATA pqdata, //ptr to data on queue
// IN PVOID pvContext - ignored
// OUT BOOL *pfContinue, //TRUE if we should continue
// OUT BOOL *pfDelete); //TRUE if item should be deleted
// Returns:
// S_OK
//
//-----------------------------------------------------------------------------
template <class PQDATA> HRESULT HrClearQueueMapFn(IN PQDATA pqdata, IN PVOID pvContext, OUT BOOL *pfContinue, OUT BOOL *pfDelete) { Assert(pfContinue); Assert(pfDelete); HRESULT hr = S_OK;
*pfContinue = TRUE; *pfDelete = TRUE;
return hr; }
|