//-----------------------------------------------------------------------------
//
//
//  File: msgguid.cpp
//
//  Description: Implementation of AQMsgGuidList and CAQMsgGuidListEntry
//      classes which provide the functionality to supersede outdated
//      msg ID's.
//
//  Author: Mike Swafford (MikeSwa)
//
//  History:
//      10/10/98 - MikeSwa Created 
//
//  Copyright (C) 1998 Microsoft Corporation
//
//-----------------------------------------------------------------------------

#include "aqprecmp.h"
#include "msgguid.h"

CPool CAQMsgGuidListEntry::s_MsgGuidListEntryPool(MSGGUIDLIST_ENTRY_SIG);
//
//  A brief note about locks for thess classes.
//
//  The CAMsgGuidList* classes are protected by a single per-virtual server
//  ShareLock (m_slPrivateData of course).  These locks are non-reentrant, so
//  it is critical that we do not hold these locks while doing something that
//  may cause a locking call back into us (like releasing a CMsgReference).
//

//---[ CAQMsgGuidListEntry::CAQMsgGuidListEntry ]------------------------------
//
//
//  Description: 
//      Constructor for CAQMsgGuidListEntry
//  Parameters:
//      pmsgref         Ptr to CMsgRef for this ID
//      pguid           GUID ID of this message
//      pliHead         Head of list to add to
//      pmgl            List this entry belongs to
//  Returns:
//      -
//  History:    
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
CAQMsgGuidListEntry::CAQMsgGuidListEntry(CMsgRef *pmsgref, GUID *pguid, 
                                         PLIST_ENTRY pliHead, CAQMsgGuidList *pmgl) 
{
    _ASSERT(pmsgref);
    _ASSERT(pguid);
    _ASSERT(pmgl);
    _ASSERT(pliHead);

    m_dwSignature = MSGGUIDLIST_ENTRY_SIG;
    m_pmsgref = pmsgref;
    m_pmsgref->AddRef();
    m_pmgl = pmgl;

    memcpy(&m_guidMsgID, pguid, sizeof(GUID));

    InsertHeadList(pliHead, &m_liMsgGuidList);
}


//---[ CAQMsgGuidListEntry::~CAQMsgGuidListEntry ]-----------------------------
//
//
//  Description: 
//      Destructor for CAQMsgGuidListEntry
//  Parameters:
//      -
//  Returns:
//      -
//  History:
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
CAQMsgGuidListEntry::~CAQMsgGuidListEntry()
{
    //we should not still be in a list
    _ASSERT(!m_liMsgGuidList.Flink);
    _ASSERT(!m_liMsgGuidList.Blink);

    m_dwSignature = MSGGUIDLIST_ENTRY_SIG_INVALID;

    //It is safe to release the message ref here, since there is no way it 
    //can call back into use (unless there is a ref-counting bug).
    if (m_pmsgref)
        m_pmsgref->Release();

    m_pmgl = NULL;
}

//---[ CAQMsgGuidListEntry::pmgleGetEntry ]------------------------------------
//
//
//  Description: 
//      Static function to get entry from LIST_ENTRY
//
//      NOTE: inline function for use by CAQMsgGuidList only
//  Parameters:
//      pli         LIST ENTRY
//  Returns:
//      pointer to associated CAQMsgGuidListEntry
//  History:
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
CAQMsgGuidListEntry *CAQMsgGuidListEntry::pmgleGetEntry(PLIST_ENTRY pli)
{
    _ASSERT(pli);
    CAQMsgGuidListEntry *pmgle = CONTAINING_RECORD(pli, 
                CAQMsgGuidListEntry, m_liMsgGuidList);
    ASSERT(pmgle->m_dwSignature == MSGGUIDLIST_ENTRY_SIG);
    return pmgle;
}
    
//---[ CAQMsgGuidListEntry::fCompareGuid ]-------------------------------------
//
//
//  Description: 
//      Function used by CAQMsgGuidList to determine if this has a the GUID
//      matching the superseded ID.
//
//      NOTE: inline function for use by CAQMsgGuidList only
//  Parameters:
//      pguid       GUID to check against
//  Returns:
//      TRUE if they match
//      FALSE otherwise
//  History:
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
BOOL CAQMsgGuidListEntry::fCompareGuid(GUID *pguid)
{
    _ASSERT(pguid);
    return (0 == memcmp(pguid, &m_guidMsgID, sizeof(GUID)));
}

//---[ CAQMsgGuidListEntry::RemoveFromList ]-----------------------------------
//
//
//  Description: 
//      Used by CMsgRef to remove an entry from the list once delivery is 
//      complete.
//  Parameters:
//      -
//  Returns:
//      -
//  History:
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
void CAQMsgGuidListEntry::RemoveFromList()
{
    _ASSERT(m_pmgl);
    m_pmgl->RemoveFromList(&m_liMsgGuidList);
}

//---[ CAQMsgGuidListEntry::pmsgrefGetAndClearMsgRef ]-------------------------
//
//
//  Description: 
//      First phase shutdown/deletion of object. Will set to NULL and return 
//      orginal msgref pointer.  When caller releases lock, they should release
//      the returned msgref.
// 
//      NOTE: Releasing the msgref while holding onto m_slPrivateData can 
//      lead to a deadlock.  m_slPrivateData should be held while this 
//  Parameters:
//      -
//  Returns:
//      -
//  History:
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
CMsgRef *CAQMsgGuidListEntry::pmsgrefGetAndClearMsgRef()
{
    CMsgRef *pmsgref = m_pmsgref;
    m_pmsgref = NULL;
    return pmsgref;
}

//---[ CAQMsgGuidListEntry::SupersedeMsg ]-------------------------------------
//
//
//  Description: 
//      Function to supersede msg associated with this object.  Will flag the
//      associated CMsgRef as "non-deliverable"
//
//      NOTE: Should have MsgGuidList Write lock when calling
//  Parameters:
//      -
//  Returns:
//      -
//  History:
//      10/10/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
void CAQMsgGuidListEntry::SupersedeMsg()
{
    m_pmsgref->SupersedeMsg();
    m_pmsgref->Release();
    m_pmsgref = NULL;
}

//---[ CAQMsgGuidList::CAQMsgGuidList ]-----------------------------------------
//
//
//  Description: 
//      Constructor for CAQMsgGuidList.
//  Parameters:
//      pcSupersededMsgs        Ptr to DWORD to InterlockedIncrement for
//                              a count of superseded messages.
//                              (can be NULL if no counters are wanted)
//  Returns:
//      -
//  History:    
//      10/11/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
CAQMsgGuidList::CAQMsgGuidList(DWORD *pcSupersededMsgs)
{
    m_dwSignature = MSGGUIDLIST_SIG;
    m_pcSupersededMsgs = pcSupersededMsgs;
    InitializeListHead(&m_liMsgGuidListHead);
}

//---[ CAQMsgGuidList::~CAQMsgGuidList ]---------------------------------------
//
//
//  Description: 
//      Desctructor for CAQMsgGuidList
//  Parameters:
//      -
//  Returns:
//      -
//  History:
//      10/11/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
CAQMsgGuidList::~CAQMsgGuidList()
{
    Deinitialize(NULL);
    _ASSERT(IsListEmpty(&m_liMsgGuidListHead));
}

//---[ CAQMsgGuidList::pmgleAddMsgGuid ]---------------------------------------
//
//
//  Description: 
//      Adds a message ID/Msg to the list of msg GUIDs.  Will also search for 
//      the superseded msg GUID ID from the tail of the list.
//
//      This is meant as a server-side optimization.  There is *no* attempt
//      to recover from out of memory situations.
//  Parameters:
//      pmsgref             MsgRef associated with this ID
//      pguidID             GUID ID of this message
//      pguidSuperseded     GUID ID of message superseded by this message
//      
//  Returns:
//      Pointer to list entry for this msg (caller *must* Release)
//      NULL if no entry allocated
//  History:
//      10/11/98 - MikeSwa Created 
//      05/08/99 - MikeSwa Fixed AV 
//
//-----------------------------------------------------------------------------
CAQMsgGuidListEntry *CAQMsgGuidList::pmgleAddMsgGuid(CMsgRef *pmsgref, 
                                                     GUID *pguidID, 
                                                     GUID *pguidSuperseded)
{
    _ASSERT(pmsgref);
    _ASSERT(pguidID);
    CAQMsgGuidListEntry *pmgle = NULL;
    PLIST_ENTRY pliCurrent = NULL;
    CMsgRef *pmsgrefSuperseded = NULL;

    //First search list for matching GUID
    m_slPrivateData.ShareLock();
    pliCurrent = m_liMsgGuidListHead.Blink;
    while (pliCurrent && (pliCurrent != &m_liMsgGuidListHead))
    {
        pmgle = CAQMsgGuidListEntry::pmgleGetEntry(pliCurrent);
        if (pguidSuperseded && pmgle->fCompareGuid(pguidSuperseded))
        {
            //we found a match... addref it and stop looking
            pmgle->AddRef();
            break;
        }

        //NOTE: We may want to consider adding functionality that 
        //would allow us to supersede messages that are added to the 
        //system later... if some layer of abstaction (like the pickup dir)
        //causes out of order submission, this would allow us to handle
        //that case.  It could require:
        //  - Additional check of current ID against all superseded ID's (2x cost)
        //  - Additional storage of original superseded ID's.

        pmgle = NULL;
        pliCurrent = pliCurrent->Blink;
    }
    m_slPrivateData.ShareUnlock();

    m_slPrivateData.ExclusiveLock();
    if (pmgle)
    {
        //make sure someone else hasn't removed it from the list
        if (pliCurrent->Blink && pliCurrent->Flink)
        {
            //If we found a match supersede
            if (m_pcSupersededMsgs)
                InterlockedIncrement((PLONG) m_pcSupersededMsgs);
            pmgle->SupersedeMsg();
            RemoveEntryList(pliCurrent);
            pliCurrent->Flink = NULL;
            pliCurrent->Blink = NULL;

            pmsgrefSuperseded = pmgle->pmsgrefGetAndClearMsgRef();
            //Release once for entry in list, and once for AddRef above
            _VERIFY(pmgle->Release());
            pmgle->Release();
        }
    }

    pmgle = new CAQMsgGuidListEntry(pmsgref, pguidID, &m_liMsgGuidListHead, this);
    if (pmgle)
        pmgle->AddRef();

    m_slPrivateData.ExclusiveUnlock();

    if (pmsgrefSuperseded)
        pmsgrefSuperseded->Release();

    return pmgle;
}

//---[ CAQMsgGuidList::Deinitialize ]------------------------------------------
//
//
//  Description: 
//      Walks list and released all msg id objects.  Calls server stop hint 
//      function if provided.
//  Parameters:
//      painst      Ptr to virtual server object to call stop hint function
//  Returns:
//      -
//  History:
//      10/11/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
void CAQMsgGuidList::Deinitialize(CAQSvrInst *paqinst)
{
    PLIST_ENTRY pliCurrent = NULL;
    CAQMsgGuidListEntry *pmgle = NULL;
    CMsgRef *pmsgref = NULL;

    m_slPrivateData.ExclusiveLock();

    //Walk entire list and release all objects
    while (!IsListEmpty(&m_liMsgGuidListHead))
    {
        pliCurrent = m_liMsgGuidListHead.Flink;
        _ASSERT(pliCurrent);
        pmgle = CAQMsgGuidListEntry::pmgleGetEntry(pliCurrent);
        _ASSERT(pmgle);
        RemoveEntryList(pliCurrent);
        pliCurrent->Flink = NULL;
        pliCurrent->Blink = NULL;
    
        //we must unlock to Deinitalize and release won't deadlock
        m_slPrivateData.ExclusiveUnlock();
        //Send shutdown hint
        if (paqinst)
            paqinst->ServerStopHintFunction();

        pmsgref = pmgle->pmsgrefGetAndClearMsgRef();
        if (pmsgref)
            pmsgref->Release();

        pmgle->Release();

        //Lock so we can check if list is empty
        m_slPrivateData.ExclusiveLock();
    }
    m_slPrivateData.ExclusiveUnlock();
}

//---[ CAQMsgGuidList::RemoveFromList ]----------------------------------------
//
//
//  Description: 
//      Used by a CAQMsgGuidListEntry to remove itself from the list in a 
//      thread-safe manner.  The CAQMsgGuidListEntry is called by the CMsgRef
//      when it is completely handled.
//  Parameters:
//      pli         PLIST_ENTRY to remove from list
//  Returns:
//      -
//  History:
//      10/11/98 - MikeSwa Created 
//
//-----------------------------------------------------------------------------
void CAQMsgGuidList::RemoveFromList(PLIST_ENTRY pli)
{
    _ASSERT(pli);
    CAQMsgGuidListEntry *pmgle = CAQMsgGuidListEntry::pmgleGetEntry(pli);
    CMsgRef *pmsgref = NULL;
    m_slPrivateData.ExclusiveLock();

    if (pli->Flink && pli->Blink)
    {
        //Only remove from list once
        RemoveEntryList(pli);
        pli->Flink = NULL;
        pli->Blink = NULL;
        pmsgref = pmgle->pmsgrefGetAndClearMsgRef();
        //Caller must still have reference
        _VERIFY(pmgle->Release());
    }
    m_slPrivateData.ExclusiveUnlock();

    //Do not release while holding lock
    if (pmsgref)
        pmsgref->Release();
}