//----------------------------------------------------------------------------- // // // File: destmsgq.h // // Description: // Header file for CDestMsgQueue class. // // Author: mikeswa // // Copyright (C) 1997 Microsoft Corporation // //----------------------------------------------------------------------------- #ifndef _DESTMSGQ_H_ #define _DESTMSGQ_H_ #include "cmt.h" #include #include #include "domain.h" #include "aqroute.h" #include #include "aqutil.h" #include "aqinst.h" #include "aqstats.h" #include "aqadmsvr.h" class CLinkMsgQueue; class CMsgRef; class CAQSvrInst; class CQuickList; #define DESTMSGQ_SIG ' QMD' #define DESTMSGRETRYQ_SIG 'QRMD' #define EMPTY_DMQ_EXPIRE_TIME_MINUTES 1 class CDestMsgQueue; //---[ CDestMsgRetryQueue ]---------------------------------------------------- // // // Hungarian: dmrq, pmdrq // // Provides a retry interface for requeuing messages to DMQ. If there are // any outstanding messages for a queue, then someone must hold a reference // to this intertace to requeue it. // // This class can only be created as as member of a CDestMsgQueue // //----------------------------------------------------------------------------- class CDestMsgRetryQueue { protected: DWORD m_dwSignature; //Reference count for retry interface. //Count is used to determine if it is safe to remove this DMQ from the //DMT. This queue will only be removed when it has no messages and this //count is zero. The count represents the total number of //messages pending Ack on this queue. This is held while the message //is sent over the wire, and we determine if the message needs //to be retried. DWORD m_cRetryReferenceCount; CDestMsgQueue *m_pdmq; friend class CDestMsgQueue; CDestMsgRetryQueue(); ~CDestMsgRetryQueue() {_ASSERT(!m_cRetryReferenceCount);}; public: DWORD AddRef() {return InterlockedIncrement((PLONG) &m_cRetryReferenceCount);}; DWORD Release() {return InterlockedDecrement((PLONG) &m_cRetryReferenceCount);}; HRESULT HrRetryMsg(IN CMsgRef *pmsgref); //put message on retry queue VOID CheckForStaleMsgsNextDSNGenerationPass(); }; //---[ CDestMsgQueue ]--------------------------------------------------------- // // // Hungarian: dmq, pmdq // // Provides a priority queue of MsgRef's for the CMT //----------------------------------------------------------------------------- class CDestMsgQueue : public IQueueAdminAction, public IQueueAdminQueue, public CBaseObject { public: CDestMsgQueue(CAQSvrInst *paqinst, CAQMessageType *paqmtMessageType, IMessageRouter *pIMessageRouter); ~CDestMsgQueue(); HRESULT HrInitialize(IN CDomainMapping *pdmap); HRESULT HrDeinitialize(); //Set the routing information for this domain void SetRouteInfo(CLinkMsgQueue *plmq); //Queue operations inline HRESULT HrEnqueueMsg(IN CMsgRef *pmsgref, BOOL fOwnsTypeRef); //Dequeue a message for delivery. All OUT params are ref-counted, and //caller is responsable for releasing HRESULT HrDequeueMsg( IN DWORD dwLowestPriority, //Lowest priority message that will be dequeued OUT CMsgRef **ppmsgref, //MsgRef dequeued OUT CDestMsgRetryQueue **ppdmrq); //retry interface (optional) inline void GetDomainMapping(OUT CDomainMapping **ppdmap); //Remerge the retry queue with queues & generate DSNs if required HRESULT HrGenerateDSNsIfNecessary(IN CQuickList *pqlQueues, IN HRESULT hrConnectionStatus, IN OUT DWORD *pdwContext); //functions used to manipulate lists of queues inline CAQMessageType *paqmtGetMessageType(); inline IMessageRouter *pIMessageRouterGetRouter(); inline BOOL fIsSameMessageType(CAQMessageType *paqmt); static inline CDestMsgQueue *pdmqIsSameMessageType( CAQMessageType *paqmt, PLIST_ENTRY pli); static inline CDestMsgQueue *pdmqGetDMQFromDomainListEntry(PLIST_ENTRY pli); //Accessor functions for DomainEntry list inline void InsertQueueInDomainList(PLIST_ENTRY pliHead); inline void RemoveQueueFromDomainList(); inline PLIST_ENTRY pliGetNextDomainListEntry(); //Accessor functions for "empty-queue" list void MarkQueueEmptyIfNecessary(); inline void InsertQueueInEmptyQueueList(PLIST_ENTRY pliHead); inline void RemoveQueueFromEmptyQueueList(); inline PLIST_ENTRY pliGetNextEmptyQueueListEntry(); inline DWORD dwGetDMQState(); inline void MarkDMQInvalid(); void RemoveDMQFromLink(BOOL fNotifyLink); //Addref and get link (returns NULL if not routed) CLinkMsgQueue *plmqGetLink(); static inline CDestMsgQueue *pdmqGetDMQFromEmptyListEntry(PLIST_ENTRY pli); //Method that external users can use to verify the signature for //DMQ's passed as contexts or LIST_ENTRY's inline void AssertSignature() {_ASSERT(DESTMSGQ_SIG == m_dwSignature);}; static HRESULT HrWalkDMQForDSN(IN CMsgRef *pmsgref, IN PVOID pvContext, OUT BOOL *pfContinue, OUT BOOL *pfDelete); static HRESULT HrWalkQueueForShutdown(IN CMsgRef *pmsgref, IN PVOID pvContext, OUT BOOL *pfContinue, OUT BOOL *pfDelete); static HRESULT HrWalkRetryQueueForShutdown(IN CMsgRef *pmsgref, IN PVOID pvContext, OUT BOOL *pfContinue, OUT BOOL *pfDelete); //Called by link to get & set link context inline PVOID pvGetLinkContext() {return m_pvLinkContext;}; inline void SetLinkContext(IN PVOID pvLinkContext) {m_pvLinkContext = pvLinkContext;}; inline BOOL fIsRouted() {return (m_plmq ? TRUE : FALSE);}; //update stats after adding or removing a message //This should only be called by member functions and queue iterators void UpdateMsgStats( IN CMsgRef *pmsgref, //Msg that was added/removed IN BOOL fAdd); //TRUE => message was added //update stats after adding or removing a message on retry queue void UpdateRetryStats( IN BOOL fAdd); //TRUE => message was added //Returns an approximation of the age of the oldest message in the queue inline void GetOldestMsg(FILETIME *pft); //Walk retry queue and remerge messages into normal queues void MergeRetryQueue(); void SendLinkStateNotification(void); //Returns TRUE if queue is routed remotely. BOOL fIsRemote(); //Describes DMQ state. Returned by dwGetDMQState and cached in m_dwFlags enum { DMQ_INVALID = 0x00000001, //This DMQ is no longer valid DMQ_IN_EMPTY_QUEUE_LIST = 0x00000002, //This DMQ is in empty list DMQ_SHUTDOWN_SIGNALED = 0x00000004, //Shutdown has been signaled DMQ_EMPTY = 0x00000010, //DMQ has no messages DMQ_EXPIRED = 0x00000020, //DMQ has expired in empty list DMQ_QUEUE_ADMIN_OP_PENDING = 0x00000040, //A queue admin operation is pending DMQ_UPDATING_OLDEST_TIME = 0x00000100, //Spinlock for updating oldest time DMQ_CHECK_FOR_STALE_MSGS = 0x00000200, //Do check filehandles during DSN gen }; // // Since queues start out empty... there some error paths that can cause a queue // to be marked as empty, but not actually put in the empty list. We should // clean these up during reset routes. This tells us if it is safe to do so. // BOOL fIsEmptyAndAbandoned() { return (!m_aqstats.m_cMsgs && !m_dmrq.m_cRetryReferenceCount && !m_fqRetryQueue.cGetCount() && (m_dwFlags & DMQ_EMPTY) && !(m_dwFlags & DMQ_IN_EMPTY_QUEUE_LIST)); } public: //IUnknown STDMETHOD(QueryInterface)(REFIID riid, LPVOID * ppvObj); STDMETHOD_(ULONG, AddRef)(void) {return CBaseObject::AddRef();}; STDMETHOD_(ULONG, Release)(void) {return CBaseObject::Release();}; public: //IQueueAdminAction STDMETHOD(HrApplyQueueAdminFunction)( IQueueAdminMessageFilter *pIQueueAdminMessageFilter); STDMETHOD(HrApplyActionToMessage)( IUnknown *pIUnknownMsg, MESSAGE_ACTION ma, PVOID pvContext, BOOL *pfShouldDelete); STDMETHOD_(BOOL, fMatchesID) (QUEUELINK_ID *QueueLinkID); STDMETHOD(QuerySupportedActions)(DWORD *pdwSupportedActions, DWORD *pdwSupportedFilterFlags) { return QueryDefaultSupportedActions(pdwSupportedActions, pdwSupportedFilterFlags); }; public: //IQueueAdminQueue STDMETHOD(HrGetQueueInfo)( QUEUE_INFO *pliQueueInfo); STDMETHOD(HrGetQueueID)( QUEUELINK_ID *pQueueID); public: // Return # of failed messages: They are not counted in the m_aqstats of the DMQ DWORD cGetFailedMsgs() { return m_fqRetryQueue.cGetCount(); } // Set error code from routing void SetRoutingDiagnostic(HRESULT hr) { m_hrRoutingDiag = hr; } protected: DWORD m_dwSignature; DWORD m_dwFlags; LIST_ENTRY m_liDomainEntryDMQs; //Type of message (as returned by routing) that is on this queue. CAQMessageType m_aqmt; DWORD m_cMessageTypeRefs; IMessageRouter *m_pIMessageRouter; //Errorcode from routing. This is set to S_OK if there's no errorcode. //Currently this indicates the reason why a destination is unreachable. HRESULT m_hrRoutingDiag; //Members used for DMQ deletion (maintaining a list of empty queues) LIST_ENTRY m_liEmptyDMQs; FILETIME m_ftEmptyExpireTime; //expiration time of empty DMQ DWORD m_cRemovedFromEmptyList; //# of times on list w/o //being deleted. CShareLockNH m_slPrivateData; //Share lock to protect access to m_rgpfqQueues //The following three fields encapsulate all of the routing data //for this DMQ. The actual routing data is the pointer to the link, //and the context is used by the link to optimize access CLinkMsgQueue *m_plmq; PVOID m_pvLinkContext; CAQSvrInst *m_paqinst; //Array of FIFO queues (used to make a priority queue CFifoQueue *m_rgpfqQueues[NUM_PRIORITIES]; //Retry Qeueue for failed messages CFifoQueue m_fqRetryQueue; //class used to store stats CAQStats m_aqstats; //which domain is represented in this destination CDomainMapping m_dmap; FILETIME m_ftOldest; CDestMsgRetryQueue m_dmrq; DWORD m_cCurrentThreadsEnqueuing; protected: //internal interfaces //Add Message to front or back of priority queues HRESULT HrAddMsg( IN CMsgRef *pmsgref, //Msg to add IN BOOL fEnqueue, //TRUE => enqueue,FALSE => requeue IN BOOL fNotify); //TRUE => send notification if needed void UpdateOldest(FILETIME *pft); //Callers must use CDestMsgRetryQueueClass HRESULT HrRetryMsg(IN CMsgRef *pmsgref); //put message on retry queue friend class CDestMsgRetryQueue; }; //---[ CDestMsgQueue::HrEnqueueMsg ]------------------------------------------- // // // Description: // Enqueues a message for remote delivery for a given final destination // and message type // Parameters: // pmsgref AQ Message Reference to enqueue // fOwnsTypeRef TRUE if this queue is responsible for calling // IMessageRouter::ReleaseMessageType // Returns: // S_OK on success // Error code from HrAddMsg // History: // 5/21/98 - MikeSwa added fOwnsTypeRef // //----------------------------------------------------------------------------- HRESULT CDestMsgQueue::HrEnqueueMsg(IN CMsgRef *pmsgref, BOOL fOwnsTypeRef) { HRESULT hr = S_OK; hr = HrAddMsg(pmsgref, TRUE, TRUE); if (fOwnsTypeRef && SUCCEEDED(hr)) InterlockedIncrement((PLONG) &m_cMessageTypeRefs); //Callers should have shutdown lock _ASSERT(!(m_dwFlags & (DMQ_INVALID | DMQ_SHUTDOWN_SIGNALED))); return hr; } //---[ CDestMsgQueue::paqmtGetMessageType ]------------------------------------ // // // Description: // Get the message type for this queue // Parameters: // - // Returns: // CAQMessageType * of this queue's message type // History: // 5/28/98 - MikeSwa Created // //----------------------------------------------------------------------------- CAQMessageType *CDestMsgQueue::paqmtGetMessageType() { return (&m_aqmt); } //---[ CDestMsgQueue::fIsSameMessageType ]------------------------------------- // // // Description: // Tells if the message type of this queue is the same as the given // message type. // Parameters: // paqmt - ptr to CAQMessageType to test // Returns: // TRUE if they match, FALSE if they do not // History: // 5/26/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CDestMsgQueue::fIsSameMessageType(CAQMessageType *paqmt) { _ASSERT(paqmt); return m_aqmt.fIsEqual(paqmt); } //---[ CDestMsgQueue::pdmqIsSameMessageType ]---------------------------------- // // // Description: // STATIC function used to determine if a LIST_ENTRY refers to a // CDestMsgQueue with a given message type. // Parameters: // paqmt - ptr to CAQMessageType to check against // pli - ptr to list entry to check (must refer to a CDestMsgQueue) // Returns: // Ptr to CDestMsgQueue if LIST_ENTRY refers to a CDestMsgQueue with // the given message type. // NULL if no match is not found // History: // 5/27/98 - MikeSwa Created // //----------------------------------------------------------------------------- CDestMsgQueue *CDestMsgQueue::pdmqIsSameMessageType( CAQMessageType *paqmt, PLIST_ENTRY pli) { CDestMsgQueue *pdmq = NULL; pdmq = CONTAINING_RECORD(pli, CDestMsgQueue, m_liDomainEntryDMQs); _ASSERT(DESTMSGQ_SIG == pdmq->m_dwSignature); //if not the same message type return NULL if (!pdmq->fIsSameMessageType(paqmt)) pdmq = NULL; return pdmq; } //---[ CDestMsgQueue::pdmqGetDMQFromDomainListEntry ]-------------------------- // // // Description: // Returns the CDestMsgQueue associated with a list entry // Parameters: // IN pli ptr to list entry to get CDestMsgQueue from // Returns: // ptr to CDestMsgQueue // History: // 5/28/98 - MikeSwa Created // //----------------------------------------------------------------------------- CDestMsgQueue *CDestMsgQueue::pdmqGetDMQFromDomainListEntry(PLIST_ENTRY pli) { _ASSERT(DESTMSGQ_SIG == (CONTAINING_RECORD(pli, CDestMsgQueue, m_liDomainEntryDMQs))->m_dwSignature); return (CONTAINING_RECORD(pli, CDestMsgQueue, m_liDomainEntryDMQs)); } //---[ CDestMsgQueue::InsertQueueInDomainList ]--------------------------------- // // // Description: // Inserts this CDestMsgQueue into the given linked list of queues // Parameters: // pliHead - PLIST_ENTRY for list head // Returns: // - // History: // 5/27/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDestMsgQueue::InsertQueueInDomainList(PLIST_ENTRY pliHead) { _ASSERT(NULL == m_liDomainEntryDMQs.Flink); _ASSERT(NULL == m_liDomainEntryDMQs.Blink); InsertHeadList(pliHead, &m_liDomainEntryDMQs); } //---[ CDestMsgQueue::RemoveQueueFromDomainList ]------------------------------- // // // Description: // Removes this queue from a list of queues // Parameters: // - // Returns: // - // History: // 5/27/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDestMsgQueue::RemoveQueueFromDomainList() { RemoveEntryList(&m_liDomainEntryDMQs); m_liDomainEntryDMQs.Flink = NULL; m_liDomainEntryDMQs.Blink = NULL; } //---[ CDestMsgQueue::pliGetNextDomainListEntry ]------------------------------- // // // Description: // Gets the pointer to the next list entry for this queue. // Parameters: // - // Returns: // The Flink of the queues LIST_ENTRY // History: // 6/16/98 - MikeSwa Created // //----------------------------------------------------------------------------- PLIST_ENTRY CDestMsgQueue::pliGetNextDomainListEntry() { return m_liDomainEntryDMQs.Flink; } //---[ CDestMsgQueue::InsertQueueInEmptyQueueList ]---------------------------- // // // Description: // Inserts queue at *tail* of DMT empty queue list. The queue that has // been empty the longest should be at the As with the other EmptyQueue // list functions this is called by the DMT, when it has the appropriate // lock for the head of the list. // // Upon insertion, an "expire time" is stamped on the queue. If the queue // is still in the list, then it is a candidate for deletion, and will be // delete the next time the DMT looks at the queue (everytime HrMapDomain // is called). // // NOTE"We need to make sure this function is thread-safe. Since the // DMQ lock is aquired exclusively before this is called, we know that // no one will ENQUEUE a messsage. This function call is tiggered after // the retry queues are emptied when a connection finished, so we can // also ensure that no one will call this while there are messages to // retry. // It is however (remotely) possible for 2 threads to finish connections // for this queue and thus cause 2 threads to be in this function. // The thread that successfully modified the EMPTY bit will be allowed // to add the queue to the list. // Parameters: // IN pliHead The head of the list to insert into // Returns: // - // History: // 9/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDestMsgQueue::InsertQueueInEmptyQueueList(PLIST_ENTRY pliHead) { _ASSERT(m_paqinst); //Now that we have the exclusive lock recheck to make sure there are no messages if (m_aqstats.m_cMsgs || m_fqRetryQueue.cGetCount()) return; //Attempt to set the DMQ_EMPTY bit if (DMQ_EMPTY & dwInterlockedSetBits(&m_dwFlags, DMQ_EMPTY)) { //Another thread has set it, we cannot modify the LIST_ENTRY return; } //If it is already in queue, that means that the queue has gone //from empty to non-empty to empty. Insert at tail of list with new time if (m_dwFlags & DMQ_IN_EMPTY_QUEUE_LIST) { _ASSERT(NULL != m_liEmptyDMQs.Flink); _ASSERT(NULL != m_liEmptyDMQs.Blink); RemoveEntryList(&m_liEmptyDMQs); m_cRemovedFromEmptyList++; } else { _ASSERT(NULL == m_liEmptyDMQs.Flink); _ASSERT(NULL == m_liEmptyDMQs.Blink); } //Get expire time for this queue m_paqinst->GetExpireTime(EMPTY_DMQ_EXPIRE_TIME_MINUTES, &m_ftEmptyExpireTime, NULL); //Mark queue as in empty queue dwInterlockedSetBits(&m_dwFlags, DMQ_IN_EMPTY_QUEUE_LIST); //Insert into queue InsertTailList(pliHead, &m_liEmptyDMQs); _ASSERT(pliHead->Blink == &m_liEmptyDMQs); _ASSERT(!m_aqstats.m_cMsgs); //No other thread should be able to add msgs } //---[ DestMsgQueue::RemoveQueueFromEmptyQueueList ]--------------------------- // // // Description: // Removed the queue from the empty list. Caller *must* have DMT lock // to call this. DMQ will not call this directly, but will call into // DMT . // Parameters: // - // Returns: // - // History: // 9/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDestMsgQueue::RemoveQueueFromEmptyQueueList() { RemoveEntryList(&m_liEmptyDMQs); //Increment count now that queue is being removed from empty list m_cRemovedFromEmptyList++; //Mark queue as not in empty queue dwInterlockedUnsetBits(&m_dwFlags, DMQ_IN_EMPTY_QUEUE_LIST); m_liEmptyDMQs.Flink = NULL; m_liEmptyDMQs.Blink = NULL; } //---[ CDestMsgQueue::pliGetNextEmptyQueueListEntry ]-------------------------- // // // Description: // Gets next queue entry in empty list. // Parameters: // - // Returns: // Next entry pointed to by list entry // History: // 9/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- PLIST_ENTRY CDestMsgQueue::pliGetNextEmptyQueueListEntry() { return m_liEmptyDMQs.Flink; } //---[ CDestMsgQueue::dwGetDMQState ]------------------------------------------ // // // Description: // Returns the state of the DMQ and caches that state in m_dwFlags. May // update DMQ_EXPIRED if DMQ is in empty list and it has expired // Parameters: // - // Returns: // Current DMQ state // History: // 9/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- DWORD CDestMsgQueue::dwGetDMQState() { _ASSERT(DESTMSGQ_SIG == m_dwSignature); _ASSERT(m_paqinst); if (DMQ_IN_EMPTY_QUEUE_LIST & m_dwFlags) { //If it is empty and not expired..check if expired if ((DMQ_EMPTY & m_dwFlags) && !(DMQ_EXPIRED & m_dwFlags)) { if (m_paqinst->fInPast(&m_ftEmptyExpireTime, NULL)) dwInterlockedSetBits(&m_dwFlags, DMQ_EXPIRED); } } return m_dwFlags; } //---[ CDestMsgQueue::MarkDMQInvalid ]------------------------------------------ // // // Description: // Marks this queue as invalid. Queue *must* be empty for this to happen // Parameters: // - // Returns: // - // History: // 9/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDestMsgQueue::MarkDMQInvalid() { _ASSERT(DESTMSGQ_SIG == m_dwSignature); _ASSERT(DMQ_EMPTY & m_dwFlags); dwInterlockedSetBits(&m_dwFlags, DMQ_INVALID); } //---[ CDestMsgQueue::pdmqGetDMQFromEmptyListEntry ]--------------------------- // // // Description: // Returns the DMQ corresponding to a given Empty Queue LIST_ENTRY. // // Will assert that DMQ signature is valid // Parameters: // IN pli Pointer to LIST_ENTRY for queue // Returns: // // History: // 9/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- CDestMsgQueue *CDestMsgQueue::pdmqGetDMQFromEmptyListEntry(PLIST_ENTRY pli) { _ASSERT(DESTMSGQ_SIG == (CONTAINING_RECORD(pli, CDestMsgQueue, m_liEmptyDMQs))->m_dwSignature); return (CONTAINING_RECORD(pli, CDestMsgQueue, m_liEmptyDMQs)); } //---[ CDestMsgQueue::GetDomainMapping ]--------------------------------------- // // // Description: // Returns the domain mapping for this queue. // Parameters: // OUT ppdmap Returned domain mapping // Returns: // - // History: // 9/14/98 - MikeSwa Modified to not have a return value // //----------------------------------------------------------------------------- void CDestMsgQueue::GetDomainMapping(OUT CDomainMapping **ppdmap) { _ASSERT(ppdmap); *ppdmap = &m_dmap; } IMessageRouter *CDestMsgQueue::pIMessageRouterGetRouter() { return m_pIMessageRouter; } //---[ CDestMsgQueue::GetOldestMsg ]------------------------------------------- // // // Description: // Retruns an approximation of the oldest message in the queue // Parameters: // OUT pft FILTIME of "oldest" Messate // Returns: // - // History: // 12/13/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDestMsgQueue::GetOldestMsg(FILETIME *pft) { _ASSERT(pft); if (m_aqstats.m_cMsgs) memcpy(pft, &m_ftOldest, sizeof(FILETIME)); else ZeroMemory(pft, sizeof (FILETIME)); } #endif //_DESTMSGQ_H_