|
|
//-----------------------------------------------------------------------------
//
//
// File: linkmsgq.cpp
//
// Description: Implementation of CLinkMsgQueue object.
//
// Author: mikeswa
//
// Copyright (C) 1997 Microsoft Corporation
//
//-----------------------------------------------------------------------------
#include "aqprecmp.h"
#include "dcontext.h"
#include "aqnotify.h"
#include "connmgr.h"
#include "domcfg.h"
#include "smtpconn.h"
#include "smproute.h"
#define CONNECTION_BUFFER_SIZE 10
LinkFlags li; //encourage symbols to appear debug versions
//---[ CLinkMsgQueue::RestartDSNGenerationIfNecessary ]------------------------
//
//
// Description:
// Static wrapper to continue generating DSNs after we have hit our
// limit of time spent in DSNs generation
// Parameters:
// pvContext - "this" pointer for CLinkMsgQueue
// dwStatus - Completion Status
// Returns:
// TRUE always
// History:
// 11/10/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CLinkMsgQueue::fRestartDSNGenerationIfNecessary(PVOID pvContext, DWORD dwStatus) { TraceFunctEnterEx((LPARAM) pvContext, "CLinkMsgQueue::fRestartDSNGenerationIfNecessary"); CLinkMsgQueue *plmq = (CLinkMsgQueue *) pvContext; BOOL fHasShutdownLock = FALSE; BOOL fHasRoutingLock = FALSE;
_ASSERT(plmq); _ASSERT(LINK_MSGQ_SIG == plmq->m_dwSignature);
DebugTrace((LPARAM) plmq, "Attempting to restart DSN generation");
//Don't try DSN generation if this is not a normal completion
if (dwStatus != ASYNC_WORK_QUEUE_NORMAL) goto Exit;
//Only attempt to continue DSN genration if we cannot create a connection
//now and have no current connections
if (plmq->m_cConnections) { DebugTrace((LPARAM) plmq, "We have %d connections... skipping DSN generation", plmq->m_cConnections); goto Exit; }
if (fFlagsAllowConnection(plmq->m_dwLinkStateFlags)) { DebugTrace((LPARAM) plmq, "We can create a connection, skipping DSN generation - flags 0x%X", plmq->m_dwLinkStateFlags); goto Exit; }
//We need to grab the shutdown and routing lock... just like
//normal DSN generation.
if (!plmq->m_paqinst->fTryShutdownLock()) goto Exit;
fHasShutdownLock = TRUE;
if (!plmq->m_paqinst->fTryRoutingShareLock()) goto Exit;
fHasRoutingLock = TRUE;
//Call to generate DSNs... pass in parameters to always check the
//queues and walk for DSN generation (not just remerge).
plmq->GenerateDSNsIfNecessary(TRUE, FALSE);
Exit: if (fHasRoutingLock) plmq->m_paqinst->RoutingShareUnlock();
if (fHasShutdownLock) plmq->m_paqinst->ShutdownUnlock();
plmq->Release(); TraceFunctLeave(); return TRUE; }
//---[ CLinkMsgQueue::HrGetInternalInfo ]---------------------------------------
//
//
// Description:
// Private function to get cached link info, and update cached data if
// needed.
//
// NOTE: This is the only way the cached data should be access (other than
// startup and shutdown).
// Parameters:
// OUT ppIntDomainInfo (can be NULL)
// Returns:
// S_OK on success
// AQUEUE_E_SHUTDOWN if queue is shutting down.
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetInternalInfo(OUT CInternalDomainInfo **ppIntDomainInfo) { HRESULT hr = S_OK; _ASSERT(m_cbSMTPDomain); _ASSERT(m_szSMTPDomain);
if (ppIntDomainInfo) *ppIntDomainInfo = NULL;
//If we don't currently have domain info and it was not a failure
//condition... don't reget domain info
if (!m_pIntDomainInfo && !(eLinkFlagsGetInfoFailed & m_dwLinkFlags)) goto Exit;
m_slInfo.ShareLock();
//Verify Domain Config Info
while (!m_pIntDomainInfo || (m_pIntDomainInfo->m_dwIntDomainInfoFlags & INT_DOMAIN_INFO_INVALID)) { m_slInfo.ShareUnlock(); m_slInfo.ExclusiveLock(); //another may have gotten exclusive lock in meantime
if (m_pIntDomainInfo && !(m_pIntDomainInfo->m_dwIntDomainInfoFlags & INT_DOMAIN_INFO_INVALID)) { //another thread has updated info
m_slInfo.ExclusiveUnlock(); m_slInfo.ShareLock(); continue; }
//Domain info is no longer valid at this point
if (m_pIntDomainInfo) { m_pIntDomainInfo->Release(); m_pIntDomainInfo = NULL; }
if (m_dwLinkFlags & eLinkFlagsExternalSMTPLinkInfo) { hr = m_paqinst->HrGetInternalDomainInfo(m_cbSMTPDomain, m_szSMTPDomain, &m_pIntDomainInfo); } else { hr = m_paqinst->HrGetDefaultDomainInfo(&m_pIntDomainInfo); }
if (FAILED(hr)) { dwInterlockedSetBits(&m_dwLinkFlags, eLinkFlagsGetInfoFailed); m_slInfo.ExclusiveUnlock(); _ASSERT(AQUEUE_E_SHUTDOWN == hr); goto Exit; }
_ASSERT(m_pIntDomainInfo); //Handle change of TURN/ETRN
if (m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY)) { if (!(m_dwLinkStateFlags & LINK_STATE_PRIV_CONFIG_TURN_ETRN)) { //Modify link flags to account for TURN/ETRN
dwInterlockedSetBits(&m_dwLinkStateFlags, LINK_STATE_PRIV_CONFIG_TURN_ETRN); } } else if (m_dwLinkStateFlags & LINK_STATE_PRIV_CONFIG_TURN_ETRN) { //We used to be TURN/ETRN, but are no-longer configured as such
dwInterlockedUnsetBits(&m_dwLinkStateFlags, LINK_STATE_PRIV_CONFIG_TURN_ETRN); }
m_slInfo.ExclusiveUnlock(); m_slInfo.ShareLock(); }
//Now we have info... set out param and addref
if (ppIntDomainInfo) { *ppIntDomainInfo = m_pIntDomainInfo; m_pIntDomainInfo->AddRef(); }
//Clear failure bit if set
if (eLinkFlagsGetInfoFailed & m_dwLinkFlags) dwInterlockedUnsetBits(&m_dwLinkFlags, eLinkFlagsGetInfoFailed);
m_slInfo.ShareUnlock();
Exit: return hr; }
//---[ CLinkMsgQueue::InternalInit ]------------------------------------------
//
//
// Description:
// Default constructor for CLinkMsgQueue.
// Parameters:
// -
// Returns:
// -
// History:
// 1/25/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::InternalInit() { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::InternalInit"); m_dwSignature = LINK_MSGQ_SIG; m_dwLinkFlags = eLinkFlagsClear; m_dwLinkStateFlags = LINK_STATE_SCHED_ENABLED | LINK_STATE_RETRY_ENABLED | LINK_STATE_PRIV_IGNORE_DELETE_IF_EMPTY; m_paqinst = NULL; m_cQueues = 0; m_cConnections = 0; m_szSMTPDomain = NULL; m_cbSMTPDomain = 0; m_pIntDomainInfo = NULL; m_pdentryLink = NULL; m_lConnMgrCount = 0; m_lConsecutiveConnectionFailureCount = 0; m_lConsecutiveMessageFailureCount = 0; m_liLinks.Flink = NULL; m_liLinks.Blink = NULL; m_szConnectorName = NULL; m_dwRoundRobinIndex = 0; m_pILinkStateNotify = NULL; m_hrDiagnosticError = S_OK; m_szDiagnosticVerb[0] = '\0'; m_szDiagnosticResponse[0]= '\0'; m_hrLastConnectionFailure= S_OK;
ZeroMemory(&m_ftNextRetry, sizeof(FILETIME)); ZeroMemory(&m_ftNextScheduledCallback, sizeof(FILETIME)); ZeroMemory(&m_ftEmptyExpireTime, sizeof(FILETIME));
AssertPrivateLinkStateFlags();
//normally links are for remote delivery, in special cases like the currently unreachable
//queue they are not. so we need a type field to differentiate between links, so that some
//actions can be performed differently for the special links.
SetLinkType(LI_TYPE_REMOTE_DELIVERY);
//all actions are supported by default, but special links like currently unreachable may
//set this bitmask to specify that certain actions are unsupported. when such an unsupported
//action is commanded, nothing happens.
SetSupportedActions(LA_KICK | LA_FREEZE | LA_THAW);
InitializeListHead(&m_liConnections); TraceFunctLeave(); }
//---[ CLinkMsgQueue::CLinkMsgQueue ]------------------------------------------
//
//
// Description:
// Class constuctor
// Parameters:
// IN dwScheduleID Schedule ID to associate with link
// IN pIMessageRouter Router for this link
// IN pILinkStateNotify Scheduler Interface for this link
// Returns:
// -
//-----------------------------------------------------------------------------
CLinkMsgQueue::CLinkMsgQueue(DWORD dwScheduleID, IMessageRouter *pIMessageRouter, IMessageRouterLinkStateNotification *pILinkStateNotify) : m_aqsched(pIMessageRouter, dwScheduleID), m_slQueues("CLinkMsgQueue::m_slQueues"), m_slConnections("CLinkMsgQueue::m_slConnections"), m_slInfo("CLinkMsgQueue::m_slInfo") { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::CLinkMsgQueue2");
InternalInit();
m_pILinkStateNotify = pILinkStateNotify; if (m_pILinkStateNotify) m_pILinkStateNotify->AddRef();
TraceFunctLeave(); }
//---[ CLinkMsgQueue::~CLinkMsgQueue ]------------------------------------------------------------
//
//
// Description:
// Class destructor
// Parameters:
// -
// Returns:
// -
//
//-----------------------------------------------------------------------------
CLinkMsgQueue::~CLinkMsgQueue() { // tell routing that this link is going away
DWORD dw = dwModifyLinkState(LINK_STATE_LINK_NO_LONGER_USED, 0); if (!(dw & LINK_STATE_LINK_NO_LONGER_USED)) SendLinkStateNotification();
if (NULL != m_paqinst) { m_paqinst->DecNextHopCount(); m_paqinst->Release(); }
if (NULL != m_pIntDomainInfo) m_pIntDomainInfo->Release();
if (NULL != m_pdentryLink) m_pdentryLink->Release();
if (m_szConnectorName) FreePv(m_szConnectorName);
if (m_szSMTPDomain) FreePv(m_szSMTPDomain);
if (m_pILinkStateNotify) m_pILinkStateNotify->Release();
_ASSERT(IsListEmpty(&m_liConnections) && "Leaked connections");
}
//---[ CLinkMsgQueue::HrInitialize ]-----------------------------------------
//
//
// Description: Performs initialization that may return an error code
//
// Parameters:
// IN paqinst Server Instance Object
// IN pdmap Domain Mapping of SMTP Domain this link is for
// IN cbSMTPDomain
// IN szSMTPDomain SMTP Domain that link is being created for
// IN paqsched Schedule ID returned by routing sink
// IN szConnectorName
// Returns: S_OK on success
//
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrInitialize(CAQSvrInst *paqinst, CDomainEntry *pdentryLink, DWORD cbSMTPDomain, LPSTR szSMTPDomain, LinkFlags lf, LPSTR szConnectorName) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrInitialize"); HRESULT hr = S_OK; DWORD cbConnectorName = 0;
_ASSERT(szSMTPDomain); _ASSERT(cbSMTPDomain); _ASSERT(paqinst);
m_paqinst = paqinst; if (m_paqinst) { m_paqinst->AddRef(); m_paqinst->IncNextHopCount(); }
m_pdentryLink = pdentryLink; if (m_pdentryLink) m_pdentryLink->AddRef();
m_szSMTPDomain = (LPSTR) pvMalloc(cbSMTPDomain+sizeof(CHAR)); if (!m_szSMTPDomain) { hr = E_OUTOFMEMORY; ErrorTrace((LPARAM) this, "Error unable to allocate m_szSMTPDomain"); goto Exit; }
strcpy(m_szSMTPDomain, szSMTPDomain); m_cbSMTPDomain = cbSMTPDomain;
if (szConnectorName) { cbConnectorName = lstrlen(szConnectorName) + sizeof(CHAR); m_szConnectorName = (LPSTR) pvMalloc(cbConnectorName); if (!m_szConnectorName) { hr = E_OUTOFMEMORY; ErrorTrace((LPARAM) this, "Error unable to allocate m_szConnectorName"); goto Exit; } strcpy(m_szConnectorName, szConnectorName);
}
if (lf == eLinkFlagsInternalSMTPLinkInfo) {
hr = m_paqinst->HrGetDefaultDomainInfo(&m_pIntDomainInfo);
} else if (lf == eLinkFlagsExternalSMTPLinkInfo) {
hr = m_paqinst->HrGetInternalDomainInfo( cbSMTPDomain, szSMTPDomain, &m_pIntDomainInfo); } else {
// linkInfoType can only be one of these 3 bits. Since we tested for
// the above two, assert that it is the third type.
_ASSERT(lf == eLinkFlagsAQSpecialLinkInfo);
}
m_dwLinkFlags |= lf;
if (m_pIntDomainInfo && m_pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY)) { //Modify link flags to account for TURN/ETRN
dwInterlockedSetBits(&m_dwLinkStateFlags, LINK_STATE_PRIV_CONFIG_TURN_ETRN); }
Exit:
//Turn off notifications if we failed
if (FAILED(hr)) dwModifyLinkState(LINK_STATE_LINK_NO_LONGER_USED, LINK_STATE_NO_ACTION);
TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::HrDeinitialize ]-----------------------------------------
//
//
// Description: Release link to m_paqinst object.
//
// Parameters: -
//
// Returns: S_OK on success
//
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrDeinitialize() { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrDeinitialize"); HRESULT hr = S_OK;
dwModifyLinkState(LINK_STATE_NO_ACTION, LINK_STATE_PRIV_IGNORE_DELETE_IF_EMPTY); RemoveAllQueues();
TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::RemovedFromDMT ]-----------------------------------------
//
//
// Description: Notification to the link that the DMT is removing it
//
// Parameters: -
//
// Returns: -
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::RemovedFromDMT() { TraceFunctEnter("CLinkMsgQueue::RemovedFromDMT");
// tell routing that this link is going away
DWORD dw = dwModifyLinkState(LINK_STATE_LINK_NO_LONGER_USED, 0); if (!(dw & LINK_STATE_LINK_NO_LONGER_USED)) SendLinkStateNotification();
TraceFunctLeave(); }
//---[ CLinkMsgQueue::AddConnection ]----------------------------------------
//
//
// Description:
// Add a connection instance to this link
// Parameters:
// IN pSMTPConn Connection to add to link
// Returns:
// -
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::AddConnection(CSMTPConn *pSMTPConn) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrAddConnection"); _ASSERT(pSMTPConn);
_ASSERT(!(m_dwLinkStateFlags & LINK_STATE_PRIV_NO_CONNECTION));
InterlockedIncrement((PLONG) &m_cConnections);
m_slConnections.ExclusiveLock(); pSMTPConn->InsertConnectionInList(&m_liConnections); m_slConnections.ExclusiveUnlock();
DebugTrace((LPARAM) this, "Adding connection #%d to link", m_cConnections); TraceFunctLeave(); } //---[ CLinkMsgQueue::RemoveConnection ]---------------------------------------
//
//
// Description:
// Remove a connection from the link
// Parameters:
// IN pSMTPConn Connection to remove from link
// IN fForceDSNGeneration Force DSN generation
// Returns:
// - Always succeeds
//-----------------------------------------------------------------------------
void CLinkMsgQueue::RemoveConnection(IN CSMTPConn *pSMTPConn, IN BOOL fForceDSNGeneration) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::RemoveConnection"); BOOL fNoConnections = FALSE; BOOL fMergeOnly = fFlagsAllowConnection(m_dwLinkStateFlags) && !fForceDSNGeneration; _ASSERT(pSMTPConn);
_ASSERT(!(m_dwLinkStateFlags & LINK_STATE_PRIV_NO_CONNECTION));
if (!m_paqinst) return;
m_paqinst->RoutingShareLock(); m_slConnections.ExclusiveLock();
pSMTPConn->RemoveConnectionFromList(); InterlockedDecrement((PLONG) &m_cConnections);
fNoConnections = IsListEmpty(&m_liConnections);
m_slConnections.ExclusiveUnlock(); //Only generate DSNs if we have been kicked into retry
if (fNoConnections) { //Generate DSNs if we cannot connect connect
GenerateDSNsIfNecessary(TRUE, fMergeOnly); dwInterlockedUnsetBits(&m_dwLinkFlags, eLinkFlagsConnectionVerifed); } m_paqinst->RoutingShareUnlock(); DebugTrace((LPARAM) this, "Removing connection #%d from link", m_cConnections); TraceFunctLeave(); }
//---[ CLinkMsgQueue::GenerateDSNsIfNecessary ]--------------------------------
//
//
// Description:
// Walks queues and generates DSNs if necessary.
// Parameters:
// BOOL fCheckIfEmpty - check queues even if we think we are empty
// This is an optimization that should be used
// when we know there are no messages in the
// retry queues (like the Unreachable or
// CurrentlyUnreachable link)
// BOOL fMergeOnly - Only merge retry queues, do not walk for DSNs
// This improves perf for the cases were we have not
// had a connection error and really don't need to
// walk the queues.
// Returns:
// -
// History:
// 1/27/99 - MikeSwa Created (pulled from RemoveConnection)
// 2/2/99 - MikeSwa Added fMergeOnly flag
// 11/10/1999 - MikeSwa Updated to be more release locks after generating
// a max number of DSNs.
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::GenerateDSNsIfNecessary(BOOL fCheckIfEmpty, BOOL fMergeOnly) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::GenerateDSNsIfNecessary"); DWORD iQueues = 0; CDestMsgQueue *pdmq = NULL; PVOID pvContext = NULL; HRESULT hrDSN = m_hrLastConnectionFailure; HRESULT hr = S_OK; BOOL fRestartLater = FALSE; DWORD dwDSNContext = 0;
//If this link is configured as a TURN/ETRN link, we do not want to
//immediately NDR the domain because if subject us to DOS attacks.
//We only want to generate expire DSNs
if (LINK_STATE_PRIV_CONFIG_TURN_ETRN & m_dwLinkStateFlags) hrDSN = AQUEUE_E_HOST_NOT_RESPONDING;
//
// For servers within an orginization, there is an expectation that
// that authoritative DNS failures are network issues that need to be
// resolved by email administrators (as opposed to user errors in
// typing the email address).
//
// This link state flag allows the router to control behavior in the
// face of authoritative DNS failures.
//
// If the next hop is something derived from a user address (like
// direct DNS routing to external machines or a direct-DNS connector),
// then a router can use this flag to tell queuing to treat
// authoritative DNS failures as retryable.
//
// The default behavior is to treat authoritative DNS failures as
// fatal errors (and NDR the messages).
//
if ((AQUEUE_E_SMTP_GENERIC_ERROR == hrDSN) && (LINK_STATE_RETRY_ALL_DNS_FAILURES & m_dwLinkStateFlags)) { hrDSN = AQUEUE_E_HOST_NOT_RESPONDING; ErrorTrace((LPARAM) this, "hard failure (DNS) made retryable for %s (flags 0x%08X)", m_szSMTPDomain, m_dwLinkStateFlags); }
// m_cMsgs doesn't include # of msgs in retry queue.
if (!fCheckIfEmpty && !m_aqstats.m_cMsgs && !m_aqstats.m_cRetryMsgs) return;
if (!(LINK_STATE_PRIV_GENERATING_DSNS & dwInterlockedSetBits(&m_dwLinkStateFlags, LINK_STATE_PRIV_GENERATING_DSNS))) {
if (m_paqinst && m_paqinst->fTryShutdownLock()) { //Don't attempt to requeue if we are shutting down
m_slQueues.ShareLock(); pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem(iQueues, &pvContext); while (pdmq) { pdmq->AssertSignature(); if (fMergeOnly) pdmq->MergeRetryQueue(); else hr = pdmq->HrGenerateDSNsIfNecessary(&m_qlstQueues, hrDSN, &dwDSNContext);
if (FAILED(hr) && (HRESULT_FROM_WIN32(E_PENDING) == hr)) { fRestartLater = TRUE; break; }
iQueues++; pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem(iQueues, &pvContext); _ASSERT(iQueues <= m_cQueues); }
m_slQueues.ShareUnlock(); m_paqinst->ShutdownUnlock(); }
if (fRestartLater) { //We have hit our limit on the number of messages to process
//at one time. Schedule a callback to process more later
DebugTrace((LPARAM) this, "Will continue DSN generation at a later time - 0x%X", hr);
AddRef(); //Completion function will release on failure
m_paqinst->HrQueueWorkItem(this, CLinkMsgQueue::fRestartDSNGenerationIfNecessary); } dwInterlockedUnsetBits(&m_dwLinkStateFlags, LINK_STATE_PRIV_GENERATING_DSNS); } TraceFunctLeave(); }
//---[ CLinkMsgQueue::HrGetDomainInfo ]----------------------------------------
//
//
// Description:
// Returns domain info for SMTP connection.
// Parameters:
// OUT pcbSMTPDomain String length of domain name
// OUT pszSMTPDomain String containing domain info (memory managed byt DMT)
// OUT ppIntDomainInfo Internal Domain Info for link's next hop
// Returns:
// S_OK on success
// AQUEUE_E_LINK_INVALID if link is no longer valud
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetDomainInfo(OUT DWORD *pcbSMTPDomain, OUT LPSTR *pszSMTPDomain, OUT CInternalDomainInfo **ppIntDomainInfo) { HRESULT hr = S_OK;
_ASSERT(pcbSMTPDomain); _ASSERT(pszSMTPDomain); _ASSERT(ppIntDomainInfo);
hr = HrGetInternalInfo(ppIntDomainInfo); if (FAILED(hr)) { goto Exit; } else if (!*ppIntDomainInfo) { //If HrGetInternalInfoFails the first time, it will return NULL
//subsequent times. Make sure we do not return success and a
//NULL pointer. When this happens, the link will go into retry
hr = E_FAIL; goto Exit; }
*pcbSMTPDomain = m_cbSMTPDomain; *pszSMTPDomain = m_szSMTPDomain;
Exit: return hr; }
//---[ CLinkMsgQueue::HrGetSMTPDomain ]----------------------------------------
//
//
// Description:
// Returns the SMTP Domain for this link.
// Parameters:
// OUT pcbSMTPDomain String length of returned domain
// OUT pszSMTPDomain Returned SMTP Domain string. The Link will manager
// The memory for this, and will remain valid as long
// as the link is in existance.
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetSMTPDomain(OUT DWORD *pcbSMTPDomain, OUT LPSTR *pszSMTPDomain) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrGetSMTPDomain"); HRESULT hr = S_OK; _ASSERT(pcbSMTPDomain); _ASSERT(pszSMTPDomain);
if (m_dwLinkFlags & eLinkFlagsInvalid) { hr = AQUEUE_E_LINK_INVALID; goto Exit; }
*pcbSMTPDomain = m_cbSMTPDomain; *pszSMTPDomain = m_szSMTPDomain;
if (NULL == m_szSMTPDomain) { hr = AQUEUE_E_LINK_INVALID; }
Exit: TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::HrAddQueue ]---------------------------------------------
//
//
// Description:
// Add DestMsgQueues to the link.
// Parameters:
//
// Returns:
// S_OK on success
// E_OUTOFMEMORY if unable to allocate space to store queue
// AQUEUE_E_LINK_INVALID Failed to add queue to link (link is invalid)
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrAddQueue(IN CDestMsgQueue *pdmqNew) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrAddQueue"); HRESULT hr = S_OK; DWORD dwIndex = 0; CDestMsgQueue *pdmqOld = NULL; PVOID pvContext = NULL;
m_slQueues.ExclusiveLock();
// Verify that this link is still in use (there exists a window
// where we have decided to use this link but it still gets removed)
if (LINK_STATE_LINK_NO_LONGER_USED & m_dwLinkStateFlags) { // Keep track of how often this failure occurs because it results
// in a reset routes every time ...
InterlockedIncrement((PLONG) &g_cFailedToAddQueueToRemovedLink);
ErrorTrace((LPARAM) this, "Failed to add queue to removed link - this has occured %d times since startup", g_cFailedToAddQueueToRemovedLink);
// Fail here and let the caller try putting the queue elsewhere
hr = AQUEUE_E_LINK_INVALID; goto Exit; }
//
// Clear the marked as empty bit (if set)
//
dwInterlockedUnsetBits(&m_dwLinkFlags, eLinkFlagsMarkedAsEmpty);
_ASSERT(pdmqNew);
#ifdef DEBUG
// We have seen cases where it looks like a DMQ has been added to a link
// multiple times (via this call). We need to make assert that this
// is not the case here.
for (dwIndex = 0; dwIndex < m_cQueues; dwIndex++) { pdmqOld = (CDestMsgQueue *) m_qlstQueues.pvGetItem(dwIndex, &pvContext);
//If these match, it means that someone is adding this queue twice...
if (pdmqOld == pdmqNew) { _ASSERT(0 && "Adding queue twice to link... get mikeswa"); } } #endif //DEBUG
dwIndex = 0; pdmqOld = NULL; pvContext = NULL; pdmqNew->AddRef(); hr = m_qlstQueues.HrAppendItem(pdmqNew, &dwIndex);
if (FAILED(hr)) goto Exit;
//Set DMQ's link context to index inserted in quick list
pdmqNew->SetLinkContext(ULongToPtr(dwIndex)); m_cQueues++;
Exit: m_slQueues.ExclusiveUnlock();
//Now that the first queue has been added, this can be deleted if empty
dwModifyLinkState(LINK_STATE_NO_ACTION, LINK_STATE_PRIV_IGNORE_DELETE_IF_EMPTY); TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::RemoveQueue ]--------------------------------------------
//
//
// Description:
// Removes a given queue from the link. Queue *must* be associated with
// link (this will be asserted).
// Parameters:
// IN pdmq DMQ to remove from link
// IN paqstats Stats associated with DMQ
// Returns:
// -
// History:
// 9/14/98 - MikeSwa Created
// 5/14/99 - MikeSwa Removed code to automatically remove link
// from DMT if there are no queues. This is now done in
// CLinkMsgQueue::RemoveLinkIfEmpty
// 8/10/99 - MikeSwa added check of pdmqOther. While operartions on
// the quick list are thread safe, there is nothing procting us
// from another thread calling RemoveQueue or RemoveAllQueues
// before we get the lock. If this is the case, then we have
// the change to double-decrement m_cQueues, which could lead to
// an AV in GetNextMessage
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::RemoveQueue(IN CDestMsgQueue *pdmq, IN CAQStats *paqstats) { TraceFunctEnterEx((LPARAM) this, "RemoveQueue"); _ASSERT(pdmq); CDestMsgQueue *pdmqOther = NULL; CDestMsgQueue *pdmqCheck = NULL; PVOID pvContext = NULL; DWORD dwIndex = 0; BOOL fFoundQueue = FALSE;
//Aquire exclusive lock and remove DMQ from list
m_slQueues.ExclusiveLock();
//While the follow line *may* produce a sundown warning is is 100% correct
//The context is created and "owned" by this object. Currently it is an
//array index, but eventually it may be a pointer to more interesting context
//structure.
dwIndex = (DWORD) (DWORD_PTR)pdmq->pvGetLinkContext();
pdmqOther = (CDestMsgQueue *) m_qlstQueues.pvGetItem(dwIndex, &pvContext); if (pdmqOther && (pdmqOther == pdmq)) { fFoundQueue = TRUE;
//Now that we found it... remove it from the link
pdmqCheck = (CDestMsgQueue *) m_qlstQueues.pvDeleteItem(dwIndex, &pvContext); m_cQueues--;
//The link context should be the index of the DMQ
_ASSERT(pdmqCheck == pdmqOther);
//Get new item at old index & update context
pdmqOther = (CDestMsgQueue *) m_qlstQueues.pvGetItem(dwIndex, &pvContext);
//If pdmqOther is NULL, then we have no more queues
//(or it was the last in the list)
_ASSERT(pdmqOther || !m_cQueues || (dwIndex == m_cQueues));
//Update change in stats
m_aqstats.UpdateStats(paqstats, FALSE);
if (m_cQueues) { if (pdmqOther) pdmqOther->SetLinkContext(ULongToPtr(dwIndex)); } } else { //While not technically an error, this means that another thread has removed
//this (or all) queues, and it is not in the link
ErrorTrace((LPARAM) this, "Found Queue 0x%0X instead of 0x%08X at index %d", (DWORD_PTR) pdmqOther, (DWORD_PTR) pdmq, dwIndex); }
m_slQueues.ExclusiveUnlock();
//Release reference to DMQ
if (fFoundQueue) pdmq->Release();
TraceFunctLeave(); }
//---[ CLinkMsgQueue::HrGetQueueListSnapshot ]---------------------------------
//
//
// Description:
// Gets a snapshot of the queue list - the caller is responsible for
// deleting this list
// Parameters:
// IN / OUT : ppql - pp to place the new CQuickList in
// Returns:
// S_OK on success
// History:
// 11/9/2000 - dbraun - created
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetQueueListSnapshot(CQuickList **ppql) { HRESULT hr = S_OK;
// Lock the queue list
m_slQueues.ShareLock();
// Get a clone of the queue list
hr = m_qlstQueues.Clone(ppql);
// Unlock the queue list
m_slQueues.ShareUnlock();
return hr; }
//---[ CLinkMsgQueue::RemoveLinkIfEmpty ]--------------------------------------
//
//
// Description:
// Removes a link from the DomainEntry if it is empty. This behavior
// used to be part of RemoveQueue, but was removed because it could
// lead to a link being removed from the DMT hash table, but still
// creating connections.
// Parameters:
// -
// Returns:
// -
// History:
// 5/14/99 - MikeSwa Created (as potential Windows2000 Beta3 QFE fix)
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::RemoveLinkIfEmpty() { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::RemoveLinkIfEmpty"); DWORD dwLinkFlags = 0; DWORD dwRoutingInterestedFlags = LINK_STATE_CONNECT_IF_NO_MSGS | LINK_STATE_DO_NOT_DELETE | LINK_STATE_PRIV_IGNORE_DELETE_IF_EMPTY | LINK_STATE_ADMIN_HALT | LINK_STATE_DO_NOT_DELETE_UNTIL_NEXT_NOTIFY;
//Bail early if we know we don't need to grab the lock
if (m_cQueues || !m_pdentryLink) return;
if (m_slQueues.TryExclusiveLock()) { if (!m_cQueues && !(dwRoutingInterestedFlags & m_dwLinkStateFlags)) { //It might be prudent to to delete this link if:
// - There are no messages
// - Routing has not shown a interest in this queue
// - This link has also expired
//
// Mark link as empty
//
dwLinkFlags = dwInterlockedSetBits(&m_dwLinkFlags, eLinkFlagsMarkedAsEmpty);
//
// If we set the flag, then set the expire timer. Otherwise remove
// the link.
//
if (!(eLinkFlagsMarkedAsEmpty & dwLinkFlags)) { m_paqinst->GetExpireTime(EMPTY_LMQ_EXPIRE_TIME_MINUTES, &m_ftEmptyExpireTime, NULL); } else if (m_paqinst->fInPast(&m_ftEmptyExpireTime, NULL)) { if (m_pdentryLink) { DebugTrace((LPARAM) this, "Removing empty link %s with flags 0x%08X", (m_szSMTPDomain ? m_szSMTPDomain : "(NULL)"), m_dwLinkStateFlags);
// INSTRUMENTATION : Sleep to slow link removal
if (g_fEnableTestSettings && g_cDelayLinkRemovalSeconds) { // Delay the deletion to open up the window for
// adding a queue to this about-to-be removed link
StateTrace((LPARAM) this, "Link expiring - delaying %d seconds", g_cDelayLinkRemovalSeconds); Sleep (g_cDelayLinkRemovalSeconds * 1000); StateTrace((LPARAM) this, "Link expiring - proceeding with delete"); } // END INSTRUMENTATION
m_pdentryLink->RemoveLinkMsgQueue(this); m_pdentryLink->Release(); m_pdentryLink = NULL; }
//We need to artificially increase the connection manager count
//so it will not be enqueue'd in the connmgr again
IncrementConnMgrCount(); } }
m_slQueues.ExclusiveUnlock(); } TraceFunctLeave(); }
//---[ CLinkMsgQueue::HrGetNextMsg ]-------------------------------------------
//
//
// Description:
// Gets the next message from the queue
// Parameters:
// IN OUT CDeliveryContext *pdcntxt - delivery context for connection
// OUT IMailMsgProperties **ppIMailMsgProperties - IMsg dequeued
// OUT DWORD *pcIndexes - size of array
// OUT DWORD **prgdwRecipIndex - Array of recipient indexes
// Returns:
// S_OK on success
// E_INVALIDARG if invalid parameters are given
//
// History:
// 6/17/98 - MikeSwa Modified to use connection's delivery context
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetNextMsg(IN OUT CDeliveryContext *pdcntxt, OUT IMailMsgProperties **ppIMailMsgProperties, OUT DWORD *pcIndexes, OUT DWORD **prgdwRecipIndex) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrGetNextMsg"); Assert(ppIMailMsgProperties); Assert(pdcntxt); Assert(prgdwRecipIndex); Assert(pcIndexes);
HRESULT hr = S_OK; CMsgRef *pmsgref = NULL; CMsgBitMap *pmbmap = NULL; DWORD cDomains = 0; BOOL fLockedShutdown = FALSE; BOOL fLockedQueues = FALSE; DWORD iQueues = 0; DWORD dwCurrentRoundRobinIndex = m_dwRoundRobinIndex; CDestMsgQueue *pdmq = NULL; CDestMsgRetryQueue *pdmrq = NULL; PVOID pvContext = NULL; BOOL fDoneWithQueue = FALSE; DWORD dwCurrentPriority = eEffPriHigh;
if (m_dwLinkFlags & eLinkFlagsInvalid) { hr = AQUEUE_E_LINK_INVALID; goto Exit; }
//Don't even bother to wait for queue lock if routing change is pending
if (m_dwLinkFlags & eLinkFlagsRouteChangePending) { hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
//Make sure domain info is updated & we should still be sending messages
//If we can't schedule... we still might be allowed to send messages because
//of TURN.
if (!fCanSchedule() && !(m_dwLinkStateFlags & LINK_STATE_PRIV_TURN_ENABLED)) { hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
if (!m_paqinst->fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; }
m_paqinst->RoutingShareLock(); fLockedShutdown = TRUE;
m_slQueues.ShareLock(); fLockedQueues = TRUE;
if (m_cQueues == 0) { //There are currently no queue associated with this link
hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
//
// A priority ordering is imposed on these queues by first requesting
// only messages of a certain priority and then requesting lower priorities
//
do { //
// Sanity check our current priority
//
_ASSERT(dwCurrentPriority < NUM_PRIORITIES);
for (iQueues = 0; iQueues < m_cQueues && SUCCEEDED(hr) && !pmsgref; iQueues++) { pmsgref = NULL; pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem( (iQueues+dwCurrentRoundRobinIndex)%m_cQueues, &pvContext);
_ASSERT(pdmq); pdmq->AssertSignature();
//Loop until the queue is empty or we get a message that hasn't
//already been delivered by another queue on this link
do { //Usually, we only want to attempt to dequeue from a queue once.
fDoneWithQueue = TRUE;
//Release retry interface if we have one
if (pdmrq) { pdmrq->Release(); pdmrq = NULL; }
//get msg reference
hr = pdmq->HrDequeueMsg(dwCurrentPriority, &pmsgref, &pdmrq); if (FAILED(hr)) {
if (AQUEUE_E_QUEUE_EMPTY == hr) { hr = S_OK; continue; //get message from next queue
} else { goto Exit; } }
//prepare for delivery and generate delivery context
hr = pmsgref->HrPrepareDelivery(FALSE /*remote only */, FALSE /*not a Delay DSN */, &m_qlstQueues, pdmrq, pdcntxt, pcIndexes, prgdwRecipIndex);
if (AQUEUE_E_MESSAGE_HANDLED == hr) { //the message has already been handled for this queue
pmsgref->Release(); pmsgref = NULL; hr = S_OK;
//We want to stay on this queue until it is empty
fDoneWithQueue = FALSE; } else if ((AQUEUE_E_MESSAGE_PENDING == hr) || ((FAILED(hr)) && pmsgref->fShouldRetry())) { //AQUEUE_E_MESSAGE_PENDING means that the message
//is currently pending delivery for another connection
//on this link. We will requeue it, and remove only after it
//has been completly delivered for this link
hr = pdmrq->HrRetryMsg(pmsgref); if (FAILED(hr)) pmsgref->RetryOnDelete();
pmsgref->Release(); pmsgref = NULL; hr = S_OK;
//We want to stay on this queue until it is empty
fDoneWithQueue = FALSE; } else if (FAILED(hr)) { //The message has been deleted out from underneath us
pmsgref->Release(); pmsgref = NULL; hr = S_OK;
//We want to stay on this queue until it is empty
fDoneWithQueue = FALSE; } } while (!fDoneWithQueue);
}
//
// On to the next priority
//
if (dwCurrentPriority == eEffPriLow) break; dwCurrentPriority --; } while (dwCurrentPriority < NUM_PRIORITIES);
//Visit a new queue on every GetNextMsg
InterlockedIncrement((PLONG) &m_dwRoundRobinIndex);
if (pmsgref && SUCCEEDED(hr)) //we got a message
{ *ppIMailMsgProperties = pmsgref->pimsgGetIMsg(); pmsgref = NULL; } else //We have failed or do not have a message
{ *ppIMailMsgProperties = NULL; if (SUCCEEDED(hr)) //don't overwrite other error
hr = AQUEUE_E_QUEUE_EMPTY; else ErrorTrace((LPARAM) this, "GetNextMsg returning hr - 0x%08X", hr); }
Exit:
if (pdmrq) pdmrq->Release();
if (NULL != pmsgref) pmsgref->Release();
if (fLockedQueues) m_slQueues.ShareUnlock();
if (fLockedShutdown) { m_paqinst->RoutingShareUnlock(); m_paqinst->ShutdownUnlock(); }
TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::HrAckMsg ]-------------------------------------------------
//
//
// Description:
// Acknowledges the delivery of a message (success/error codes are put in
// the envelope by the transport).
//
// Parameters:
// IN pIMsg IMsg to acknowledge
// IN dwMsgContext Context that was returned by GetNextMessage
// IN eMsgStatus Summary of Delivery status of message
// IN dwStatusCode Status code returned by protocol
// IN cbExtendedStatus Size of extended status buffer
// IN szExtendedStatus String containing extended status returned by
// remote server
// Returns:
// S_OK on success
// E_INVALIDARG if dwMsgContext is invalid
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrAckMsg(MessageAck *pMsgAck) { HRESULT hr = S_OK; CInternalDomainInfo *pIntDomainInfo = NULL; _ASSERT(m_paqinst);
if (NULL == pMsgAck->pvMsgContext) { hr = E_INVALIDARG; goto Exit; }
if (MESSAGE_STATUS_ALL_DELIVERED & pMsgAck->dwMsgStatus) { m_lConsecutiveMessageFailureCount = 0; if (!(m_dwLinkFlags & eLinkFlagsConnectionVerifed)) dwInterlockedSetBits(&m_dwLinkFlags, eLinkFlagsConnectionVerifed); if (LINK_STATE_PRIV_CONFIG_TURN_ETRN & m_dwLinkStateFlags) { //We delivered successfully as TURN/ETRN... we need to update count
m_paqinst->IncTURNETRNDelivered(); } }
hr = HrGetInternalInfo(&pIntDomainInfo); if (SUCCEEDED(hr) && pIntDomainInfo) { if (DOMAIN_INFO_LOCAL_DROP & pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags) { pMsgAck->dwMsgStatus |= MESSAGE_STATUS_DROP_DIRECTORY; } pIntDomainInfo->Release(); } hr = m_paqinst->HrAckMsg(pMsgAck);
Exit: return hr; }
//---[ CLinkMsgQueue::HrNotify ]----------------------------------------------
//
//
// Description:
// Recieve notification from one of our DestMsgQueues.
// Parameters:
// IN paqstats Notification object sent
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrNotify(IN CAQStats *paqstats, BOOL fAdd) { HRESULT hr = S_OK; DWORD dwTmp = 0; DWORD dwNotifyType = 0; BOOL fCheckIfNotifyShouldContinue = FALSE; _ASSERT(paqstats);
//Update our own version of stats
m_aqstats.UpdateStats(paqstats, fAdd);
//Don't notify if we're configured not to
if (LINK_STATE_PRIV_NO_NOTIFY & m_dwLinkStateFlags) return hr;
//See if new message update
if (paqstats->m_dwNotifyType & NotifyTypeDestMsgQueue) { //$$NOTE:
//At some point it may be interesting to use the information passed by the
//DMQ to adjust it's place in the queue (ie. priority). Currently, we
//don't care.
fCheckIfNotifyShouldContinue = TRUE; }
//
// If this is a reroute... this may be a new link (with no messages), we
// should make sure we add it to the connection manager.
//
if (paqstats->m_dwNotifyType & NotifyTypeReroute) fCheckIfNotifyShouldContinue = TRUE;
if (fCheckIfNotifyShouldContinue && fAdd) { //Wait until we have messages or are rerouting before sending a
//notification that might add this link to the connection manager
if ((m_aqstats.m_cMsgs || (paqstats->m_dwNotifyType & NotifyTypeReroute)) && !(eLinkFlagsSentNewNotification & m_dwLinkFlags)) {
//Attempt to set first notification flag
dwTmp = m_dwLinkFlags; //if already set before while, make sure IF fails
while (!(eLinkFlagsSentNewNotification & m_dwLinkFlags)) { dwTmp = m_dwLinkFlags; dwTmp = InterlockedCompareExchange((PLONG) &m_dwLinkFlags, (LONG) (dwTmp | eLinkFlagsSentNewNotification), (LONG) dwTmp); } if (!(dwTmp & eLinkFlagsSentNewNotification)) //this thread set it
{
// Set the type to notify new link so it will be added to
// the connection manager
dwNotifyType |= NotifyTypeNewLink; } }
}
if (fAdd) //only send notifcation on when we are adding a new message
{ //If we are adding messages... this should not be set
_ASSERT(!(LINK_STATE_LINK_NO_LONGER_USED & m_dwLinkStateFlags));
//Change into link notification
//Connection manager needs to know, in case this link deserves another
//connection
paqstats->m_dwNotifyType = dwNotifyType | NotifyTypeLinkMsgQueue; paqstats->m_plmq = this; hr = m_paqinst->HrNotify(paqstats, fAdd); }
return hr; }
//---[ CLinkMsgQueue::HrNotify ]----------------------------------------------
//
//
// Description:
// Recieve notification from one of our DestMsgQueues that (only) retry queue has been enqueue/dequeue'd.
// Unlike HrNotify, here we only need to update our stat without notifying m_paqinst and connection manager.
// Parameters:
// IN BOOL fAdd: add or remove
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrNotifyRetryStatChange(BOOL fAdd) {
m_aqstats.UpdateRetryStats(fAdd); return S_OK; }
//---[ CLinkMsgQueue::dwModifyLinkState ]--------------------------------------
//
//
// Description:
// Sets and unsets state flags for this link
// Parameters:
// IN dwLinkStateToSet Combination of flags to set
// IN dwLinkStateToUnset Combination of flags to unset
//
// NOTE: dwLinkStateToSet and dwLinkStateToUnset should not overlap
// Returns:
// Original state of links
// History:
// 9/22/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
DWORD CLinkMsgQueue::dwModifyLinkState(IN DWORD dwLinkStateToSet, IN DWORD dwLinkStateToUnset) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::dwModifyLinkState"); DWORD dwOrigState = m_dwLinkStateFlags; DWORD dwIntermState = m_dwLinkStateFlags; DWORD dwSetBits = dwLinkStateToSet & ~dwLinkStateToUnset; DWORD dwUnsetBits = dwLinkStateToUnset & ~dwLinkStateToSet;
//we shouldn't do this internally... lets make the operations cancel each other
_ASSERT(!(dwLinkStateToSet & dwLinkStateToUnset)); _ASSERT(dwSetBits == dwLinkStateToSet); _ASSERT(dwUnsetBits == dwLinkStateToUnset);
//If info is being updated, we should let it set config-related bits
m_slInfo.ShareLock(); if (dwSetBits) dwOrigState = dwInterlockedSetBits(&m_dwLinkStateFlags, dwSetBits);
if (dwUnsetBits) dwIntermState = dwInterlockedUnsetBits(&m_dwLinkStateFlags, dwUnsetBits);
//Make sure we return the correct return value
if (dwUnsetBits && !dwSetBits) dwOrigState = dwIntermState;
m_slInfo.ShareUnlock();
DebugTrace((LPARAM) this, "ModifyLinkState set:%08X unset:%08X orig:%08X new:%08X", dwLinkStateToSet, dwLinkStateToUnset, dwOrigState, m_dwLinkStateFlags);
TraceFunctLeave(); return dwOrigState; }
//---[ CLinkMsgQueue::ScheduledCallback ]--------------------------------------
//
//
// Description:
// Callback function for scheduled connection callbacks
// Parameters:
// pvContext this pointer for CLinkMsgQueue
// Returns:
// -
// History:
// 1/16/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::ScheduledCallback(PVOID pvContext) { CLinkMsgQueue *plmq = (CLinkMsgQueue *) pvContext; HRESULT hr = S_OK; IConnectionManager *pIConnectionManager = NULL; CConnMgr *pConnMgr = NULL; DWORD dwLinkState = 0;
_ASSERT(plmq); _ASSERT(LINK_MSGQ_SIG == plmq->m_dwSignature);
plmq->SendLinkStateNotification(); dwLinkState = plmq->m_dwLinkStateFlags;
//If connections are now allowed... we should kick the connection manager
_ASSERT(plmq->m_paqinst); if (plmq->m_paqinst && plmq->fFlagsAllowConnection(dwLinkState)) { hr = plmq->m_paqinst->HrGetIConnectionManager(&pIConnectionManager); if (SUCCEEDED(hr)) { _ASSERT(pIConnectionManager);
pConnMgr = (CConnMgr *) pIConnectionManager; if (pConnMgr) pConnMgr->KickConnections(); } }
//Release AddRef from callback
plmq->Release();
}
//---[ CLinkMsgQueue::SendLinkStateNotification ]------------------------------
//
//
// Description:
// Sends notification if to scheduler/routing sink
// Parameters:
// -
// Returns:
// -
// History:
// 1/11/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::SendLinkStateNotification() { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::SendLinkStateNotification"); HRESULT hr = S_OK; GUID guidRouter = GUID_NULL; DWORD dwStateToSet = LINK_STATE_NO_ACTION; DWORD dwStateToUnset = LINK_STATE_NO_ACTION; DWORD dwResultingLinkState = LINK_STATE_NO_ACTION; DWORD dwOriginalLinkState = LINK_STATE_NO_ACTION; DWORD dwHiddenStateMask = ~(LINK_STATE_RESERVED | LINK_STATE_CONNECT_IF_NO_MSGS | LINK_STATE_DO_NOT_DELETE_UNTIL_NEXT_NOTIFY); FILETIME ftNextAttempt; BOOL fSendNotify = TRUE; DWORD dwCurrentLinkState = m_dwLinkStateFlags;
//
// We should not send any notifications after we have notified routing
// that we are going away. We copy dwCurrentLinkState to a stack
// variable so that this is thread safe. If another thread sets
// LINK_STATE_LINK_NO_LONGER_USED after this check, we will still pass
// in the original value.
//
if (dwCurrentLinkState & LINK_STATE_PRIV_HAVE_SENT_NO_LONGER_USED) { fSendNotify = FALSE; } else if (dwCurrentLinkState & LINK_STATE_LINK_NO_LONGER_USED) { //
// Try to be the first thread to set this. If we are then we can
// continue with the notification
//
dwOriginalLinkState = dwModifyLinkState(LINK_STATE_PRIV_HAVE_SENT_NO_LONGER_USED, LINK_STATE_NO_ACTION); if (dwOriginalLinkState & LINK_STATE_PRIV_HAVE_SENT_NO_LONGER_USED) fSendNotify = FALSE; }
if (m_pILinkStateNotify && fSendNotify) { ZeroMemory(&ftNextAttempt, sizeof(FILETIME)); m_aqsched.GetGUID(&guidRouter); hr = m_pILinkStateNotify->LinkStateNotify(m_szSMTPDomain, guidRouter, m_aqsched.dwGetScheduleID(), m_szConnectorName, (dwHiddenStateMask & dwCurrentLinkState), (DWORD) m_lConsecutiveConnectionFailureCount, &ftNextAttempt, &dwStateToSet, &dwStateToUnset);
DebugTrace((LPARAM) this, "LinkStateNotify set:0x%08X unset:0x%08X hr:0x%08x", dwStateToSet, dwStateToUnset, hr); //Modify link state only on success and when we aren't deleting it
if (SUCCEEDED(hr) && !(m_dwLinkStateFlags & LINK_STATE_LINK_NO_LONGER_USED)) { // schedule a callback if one was requested
if (ftNextAttempt.dwLowDateTime != 0 || ftNextAttempt.dwHighDateTime != 0) { DebugTrace((LPARAM) this, "Schedule with FileTime %x:%x provided", ftNextAttempt.dwLowDateTime, ftNextAttempt.dwHighDateTime); InternalUpdateFileTime(&m_ftNextScheduledCallback, &ftNextAttempt); //callback with next attempt
AddRef(); //Addref self as context
hr = m_paqinst->SetCallbackTime( CLinkMsgQueue::ScheduledCallback, this, &ftNextAttempt); if (FAILED(hr)) Release(); //callback will not happen... release context
}
if (!(LINK_STATE_CONNECT_IF_NO_MSGS & dwStateToSet)) { //Routing has not explicitly set LINK_STATE_CONNECT_IF_NO_MSGS.
//We must unset it because we hid it from routing. The reason
//we do this is to allow Routers that are not interested in
//link-lifetime managment to set LINK_STATE_CONNECT_IF_NO_MSGS
//and not have to worry about race conditions that could
//cause a link to be delted while they are requesting a ping
dwStateToUnset |= LINK_STATE_CONNECT_IF_NO_MSGS; }
//
// If not explicitly set, this bit is reset. Similar reasons
// as above.
//
if (!(LINK_STATE_DO_NOT_DELETE_UNTIL_NEXT_NOTIFY & dwStateToSet)) { dwStateToUnset |= LINK_STATE_DO_NOT_DELETE_UNTIL_NEXT_NOTIFY; }
if (!(m_dwLinkStateFlags & LINK_STATE_PRIV_HAVE_SENT_NOTIFICATION)) { dwStateToSet |= LINK_STATE_PRIV_HAVE_SENT_NOTIFICATION; } } } else if (!m_pILinkStateNotify) { // Even if we don't have an ILinkStateNotify interface, we still
// need to reset this flag to prevent looping on connect
if (!(LINK_STATE_CONNECT_IF_NO_MSGS & dwStateToSet)) { dwStateToUnset |= LINK_STATE_CONNECT_IF_NO_MSGS; } }
// Update the link state flags if we have any changes to make
if ((LINK_STATE_NO_ACTION != dwStateToSet) || (LINK_STATE_NO_ACTION != dwStateToUnset)) { dwModifyLinkState(dwStateToSet, dwStateToUnset); }
TraceFunctLeave(); }
//---[ CLinkMsgQueue::fShouldConnect ------------------------------------------
//
//
// Description:
// Function that is used to determine if a connection should be
// made.
// Uses a heursitic to decide if a connection should be made if multiple
// queues are routed to this link.. and thus the message count may be
// larger than it should be (since a message is counted once for each
// DMQ it is on).
// Parameters:
// IN cMaxLinkConnections Maximum # of connections per link
// IN cMinMessagesPerConnection Minimum # of messages per link before
// creating an additional connection.
// Returns:
// TRUE If a connection should be created
// FALSE Otherwise
// History:
// 11/5/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CLinkMsgQueue::fShouldConnect(IN DWORD cMaxLinkConnections, IN DWORD cMinMessagesPerConnection) { BOOL fConnect = FALSE; DWORD cHeuristicMsgs = 0; DWORD cHeuristicCheck = 0; DWORD cCurrentMsgs = m_aqstats.m_cMsgs; DWORD cTotalQueues = m_paqinst->cGetDestQueueCount(); DWORD cQueues = m_cQueues; //so it doesn't change on us
//If we have more than 1 queue and there is a total of more than 1 queue
//we can use a heurisitc to estimate actual # of messages to send, but
//don't bother with Heuristic if we are already over our max # of
//connections.
if ((m_cConnections < cMaxLinkConnections) && (1 < cTotalQueues) && (1 < cQueues) && cCurrentMsgs) { //m_aqstats.m_cOtherDomainsMsgSpread is the total # of *other* DMQs
//a message is associated with (per DMQ). If all of the DMQs for all
//messages are on this link then:
// m_cOtherDomainsMsgSpread
//is equal to:
// m_cMsgs*m_cMsgs*(cQueues-1).
//The following function... uses a probabilistic estimate to
//determine the number of messages that will be sent out this link.
//Since we cannot always assume that all messages on this link were
//queued to DMQ's assoicated with this link... we adjust the
//counted value by a factor of:
// ((cQueues-1)/(cTotalQueues-1))
//To determine the average # of domains per message (and hence the
//# of times is it counted), we use:
// (m_cOtherDomainsMsgSpread+m_cMsgs)/m_cMsgs
//To get a more accurate average, we modify the m_cOtherDomainsMsgSpread
//by the probability factor above.
//Finally:
//To get our heuristic, we divide the number of msgs by the average
//number of domains.
cHeuristicCheck = cCurrentMsgs + m_aqstats.m_cOtherDomainsMsgSpread * ((cQueues-1)/(cTotalQueues-1));
//This should be non-zero... but can happen if the counts are wrong and
//m_aqstats.m_cOtherDomainsMsgSpread is negative
_ASSERT(cHeuristicCheck);
if (cHeuristicCheck) //but we might as well be defensive
{ cHeuristicMsgs = (cCurrentMsgs*cCurrentMsgs)/cHeuristicCheck; //Don't let the heuristic make us think there are no msgs to deliver
if (!cHeuristicMsgs && cCurrentMsgs) cHeuristicMsgs = cCurrentMsgs; } else { cHeuristicMsgs = cCurrentMsgs; }
} else { cHeuristicMsgs = cCurrentMsgs; }
if ((m_cConnections < cMaxLinkConnections) && (cHeuristicMsgs > m_cConnections*cMinMessagesPerConnection) && fCanSchedule()) { //If we have no had a successful message only open 1 connection
if (!(m_dwLinkFlags & eLinkFlagsConnectionVerifed)) { if (m_cConnections < 3) fConnect = TRUE; } else fConnect = TRUE; } else if (fCanSchedule() && !cHeuristicMsgs && ((LINK_STATE_CONNECT_IF_NO_MSGS & m_dwLinkStateFlags) && !m_cConnections)) { //We want to create a connection to probe link state
fConnect = TRUE; }
return fConnect; }
//---[ CLinkMsgQueue::HrCreateConnectionIfNeeded ]-----------------------------
//
//
// Description:
//
// Parameters:
// IN cMaxLinkConnections Maximum # of connections per link
// IN cMinMessagesPerConnection Minimum # of messages per link before
// creating an additional connection.
// IN cMaxMessagesPerConnection Max messages to send on a single
// connection (0 is unlimited)
// IN pConnMgr Ptr to instance connection manger
// OUT pSMTPConn New connection object for this link
// Returns:
// S_OK on success if connection is needed
// S_FALSE on success if connection is not needed
// E_OUTOFMEMORY if connection object could not be created
// History:
// 11/5/98 - MikeSwa Created
// 12/7/1999 - MikeSwa Updated to make sure linkstate notify happens first
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrCreateConnectionIfNeeded( IN DWORD cMaxLinkConnections, IN DWORD cMinMessagesPerConnection, IN DWORD cMaxMessagesPerConnection, IN CConnMgr *pConnMgr, OUT CSMTPConn **ppSMTPConn) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrCreateConnectionIfNeeded"); HRESULT hr = S_FALSE; _ASSERT(ppSMTPConn); *ppSMTPConn = NULL; CSMTPConn *pSMTPConn = NULL;
//We cannot create a connection until we have done our link state
//notification, because routing needs to have an opportunity to
//set the schedule for a link
SendLinkStateNotificationIfNew();
//Should we create a connection?
if (!fShouldConnect(cMaxLinkConnections, cMinMessagesPerConnection)) goto Exit;
//Try and be the thread that can create a connection
if (((DWORD) InterlockedIncrement((PLONG) &m_cConnections)) > cMaxLinkConnections) { InterlockedDecrement((PLONG) &m_cConnections); goto Exit; }
*ppSMTPConn = new CSMTPConn(pConnMgr, this, cMaxMessagesPerConnection);
if (!*ppSMTPConn) { InterlockedDecrement((PLONG) &m_cConnections); hr = E_OUTOFMEMORY; goto Exit; }
//Grab lock and insert into list
m_slConnections.ExclusiveLock(); (*ppSMTPConn)->InsertConnectionInList(&m_liConnections); m_slConnections.ExclusiveUnlock();
Exit:
//Make sure our return result is correct.
if (SUCCEEDED(hr)) { if (*ppSMTPConn) { DebugTrace((LPARAM) this, "Creating connection - linkstate:0x%08X", m_dwLinkStateFlags); hr = S_OK; } else { DebugTrace((LPARAM) this, "Not creating connection - linkstate 0x%08X", m_dwLinkStateFlags); hr = S_FALSE; } }
TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::RemoveAllQueues ]----------------------------------------
//
//
// Description:
// Removes all queues from a link, without deleting or invalidating a
// link.
// Parameters:
// -
// Returns:
// -
// History:
// 11/5/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::RemoveAllQueues() { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::RemoveAllQueues"); PVOID pvContext = NULL; CDestMsgQueue *pdmq = NULL;
//Walk list of queues and release them
dwInterlockedSetBits(&m_dwLinkFlags, eLinkFlagsRouteChangePending); m_slQueues.ExclusiveLock(); pdmq = (CDestMsgQueue *) m_qlstQueues.pvDeleteItem(0, &pvContext); while (pdmq) { m_cQueues--; pdmq->AssertSignature(); pdmq->RemoveDMQFromLink(FALSE); pdmq->Release(); pdmq = (CDestMsgQueue *) m_qlstQueues.pvDeleteItem(0, &pvContext); } m_aqstats.Reset(); dwInterlockedUnsetBits(&m_dwLinkFlags, eLinkFlagsRouteChangePending); m_slQueues.ExclusiveUnlock();
TraceFunctLeave(); }
//---[ CLinkMsgQueue::HrGetLinkInfo ]------------------------------------------
//
//
// Description:
// Fills in the details for a LINK_INFO struct. RPC is resonsible for
// freeing memory.
// Parameters:
// IN OUT pliLinkInfo Ptr to link info struct to fill
// Returns:
// S_OK if successful
// E_OUTOFMEMORY if unable to allocate memory
// History:
// 12/3/98 - MikeSwa Created
// 2/22/99 - MikeSwa Modified to be IQueueAdminLink method
// 6/10/99 - MikeSwa Modified to support new QueueAdmin functionality
// 7/1/99 - MikeSwa Added LinkDiagnostic
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::HrGetLinkInfo(LINK_INFO *pliLinkInfo, HRESULT *phrLinkDiagnostic) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrGetLinkInfo"); pliLinkInfo->cMessages = m_aqstats.m_cMsgs; pliLinkInfo->fStateFlags = 0; FILETIME ftCurrent; FILETIME ftOldest; FILETIME *pftNextConnection = NULL; BOOL fFoundOldest = FALSE; DWORD iQueues = 0; PVOID pvContext = NULL; CDestMsgQueue *pdmq = NULL; HRESULT hr = S_OK;
//
//Determine the state... check in order of most to least important
//
if (GetLinkType() == LI_TYPE_CURRENTLY_UNREACHABLE) pliLinkInfo->fStateFlags = LI_READY; else if (LINK_STATE_ADMIN_HALT & m_dwLinkStateFlags) pliLinkInfo->fStateFlags = LI_FROZEN; else if (m_cConnections) pliLinkInfo->fStateFlags = LI_ACTIVE; else if (!(LINK_STATE_RETRY_ENABLED & m_dwLinkStateFlags)) pliLinkInfo->fStateFlags = LI_RETRY; else if (!(LINK_STATE_SCHED_ENABLED & m_dwLinkStateFlags)) pliLinkInfo->fStateFlags = LI_SCHEDULED; else if (m_lConsecutiveConnectionFailureCount) pliLinkInfo->fStateFlags = LI_RETRY; else if (LINK_STATE_PRIV_CONFIG_TURN_ETRN & m_dwLinkStateFlags) pliLinkInfo->fStateFlags = LI_REMOTE; else //default to ready
pliLinkInfo->fStateFlags = LI_READY;
pliLinkInfo->fStateFlags |= GetLinkType();
//Write diagnostic
if (phrLinkDiagnostic) *phrLinkDiagnostic = m_hrDiagnosticError;
//Get Size
pliLinkInfo->cbLinkVolume.QuadPart = m_aqstats.m_uliVolume.QuadPart;
//Find stOldestMessage
m_slQueues.ShareLock(); for (iQueues = 0;iQueues < m_cQueues; iQueues++) { pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem(iQueues, &pvContext);
if (!pdmq) continue;
pdmq->GetOldestMsg(&ftCurrent); //
// If we got a valid time, and it is earlier than the time we have
// now, then use it as the oldest for the link
//
if ((ftCurrent.dwLowDateTime || ftCurrent.dwHighDateTime) && (!fFoundOldest || (0 < CompareFileTime(&ftOldest, &ftCurrent)))) { memcpy(&ftOldest, &ftCurrent, sizeof(FILETIME)); fFoundOldest = TRUE; }
// Also count the failed messages (they're counted separately in the DMQ)
pliLinkInfo->cMessages += pdmq->cGetFailedMsgs(); } m_slQueues.ShareUnlock();
//If we have not found an oldest, and the time is non-zero
//and we have messages, then report it.
if (fFoundOldest && (ftOldest.dwLowDateTime || ftOldest.dwHighDateTime) && pliLinkInfo->cMessages) { QueueAdminFileTimeToSystemTime(&ftOldest, &pliLinkInfo->stOldestMessage); } else { ZeroMemory(&pliLinkInfo->stOldestMessage, sizeof(SYSTEMTIME)); }
//
// Get next connection attempt time based on the state we are reporting
//
if (LI_RETRY & pliLinkInfo->fStateFlags) { pftNextConnection = &m_ftNextRetry; } else if (LI_SCHEDULED & pliLinkInfo->fStateFlags) { pftNextConnection = &m_ftNextScheduledCallback; }
//
// If we are reporting a time, and it is non-zero, convert it to
// a system time.
//
if (pftNextConnection && (pftNextConnection->dwHighDateTime || pftNextConnection->dwLowDateTime)) { QueueAdminFileTimeToSystemTime(pftNextConnection, &pliLinkInfo->stNextScheduledConnection); if (LI_SCHEDULED & pliLinkInfo->fStateFlags) { //
// Currently times are displayed at :02, :17, :32:, and :47... we will
// fudge the display time to that it actually says :00, :15, :30, :45
// to give the admin a better "admin experience" by rouding off to
// the nearest 5 minutes.
//
pliLinkInfo->stNextScheduledConnection.wMinute -= (pliLinkInfo->stNextScheduledConnection.wMinute % 5); } } else { //
// In this case, we don't have a time.
//
ZeroMemory(&pliLinkInfo->stNextScheduledConnection, sizeof(SYSTEMTIME)); }
if (m_szConnectorName) { pliLinkInfo->szLinkDN = wszQueueAdminConvertToUnicode(m_szConnectorName, 0); if (!pliLinkInfo->szLinkDN) { hr = E_OUTOFMEMORY; goto Exit; } } else { pliLinkInfo->szLinkDN = NULL; }
//$$TODO - Fill in pliLinkInfo->szExtendedStateInfo as appropriate
pliLinkInfo->szExtendedStateInfo = NULL;
if (!fRPCCopyName(&pliLinkInfo->szLinkName)) { hr = E_OUTOFMEMORY; goto Exit; }
pliLinkInfo->dwSupportedLinkActions = m_dwSupportedActions;
Exit: if (FAILED(hr)) { //Cleanup allocated memory
if (pliLinkInfo->szLinkDN) { QueueAdminFree(pliLinkInfo->szLinkDN); pliLinkInfo->szLinkDN = NULL; }
if (pliLinkInfo->szLinkName) { QueueAdminFree(pliLinkInfo->szLinkName); pliLinkInfo->szLinkName = NULL; }
if (pliLinkInfo->szExtendedStateInfo) { QueueAdminFree(pliLinkInfo->szExtendedStateInfo); pliLinkInfo->szExtendedStateInfo = NULL; } }
//
// Sanity checks to make sure we aren't passing back a zero'd
// FILETIME converted to a system time (which would have a
// year of 1601).
//
_ASSERT(1601 != pliLinkInfo->stNextScheduledConnection.wYear); _ASSERT(1601 != pliLinkInfo->stOldestMessage.wYear);
TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::HrGetLinkID ]---------------------------------------------
//
//
// Description:
// Fills in the QUEUELINK_ID structure for this link. Caller must free
// memory allocated for link name
// Parameters:
// IN OUT pLinkID Ptr to link id struct to fill in
// Returns:
// S_OK on success
// E_OUTOFMEMORY if memory allocation fails
// History:
// 12/3/98 - MikeSwa Created
// 2/22/99 - MikeSwa Modified to IQueueAdminLink method
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetLinkID(QUEUELINK_ID *pLinkID) { pLinkID->qltType = QLT_LINK; pLinkID->dwId = m_aqsched.dwGetScheduleID(); m_aqsched.GetGUID(&pLinkID->uuid);
if (!fRPCCopyName(&pLinkID->szName)) return E_OUTOFMEMORY; else return S_OK; }
//---[ CLinkMsgQueue::HrGetQueueIDs ]--------------------------------------------
//
//
// Description:
// Gets the Queue IDs for DMQs associated with this link. Used by Queue
// Admin.
// Parameters:
// IN OUT pcQueues Sizeof array/ number of queues found
// IN OUT rgQueues Array to dump queue info into
// Returns:
// S_OK on success
// E_OUTOFMEMORY on out of memory failure
// HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if array is too small
// History:
// 12/3/98 - MikeSwa Created
// 2/22/99 - MikeSwa Updated to IQueueAdminLink function
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::HrGetQueueIDs(DWORD *pcQueues, QUEUELINK_ID *rgQueues) { _ASSERT(pcQueues); _ASSERT(rgQueues); HRESULT hr = S_OK; DWORD iQueues = 0; PVOID pvContext = NULL; CDestMsgQueue *pdmq = NULL; QUEUELINK_ID *pCurrentQueueID = rgQueues;
m_slQueues.ShareLock();
if (*pcQueues < m_cQueues) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); goto Exit; }
*pcQueues = 0;
//Iterate over all queues and get IDs
for (iQueues = 0; iQueues < m_cQueues && SUCCEEDED(hr); iQueues++) { pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem(iQueues, &pvContext);
_ASSERT(pdmq); hr = pdmq->HrGetQueueID(pCurrentQueueID); if (FAILED(hr)) goto Exit;
pCurrentQueueID++; (*pcQueues)++; }
Exit: m_slQueues.ShareUnlock(); return hr; }
//---[ CLinkMsgQueue::HrApplyQueueAdminFunction ]------------------------------
//
//
// Description:
// Used by queue admin to apply a function all queues on this link
// Parameters:
// IN pIQueueAdminMessageFilter
// Returns:
// S_OK on success
// History:
// 12/11/98 - MikeSwa Created
// 2/22/99 - MikeSwa Modified to IQueueAdminAction interface
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::HrApplyQueueAdminFunction( IQueueAdminMessageFilter *pIQueueAdminMessageFilter) { HRESULT hr = S_OK; DWORD iQueues = 0; PVOID pvListContext = NULL; CDestMsgQueue *pdmq = NULL; IQueueAdminAction *pIQueueAdminAction = NULL;
m_slQueues.ShareLock();
//Iterate over all queues and get IDs
for (iQueues = 0; iQueues < m_cQueues && SUCCEEDED(hr); iQueues++) { pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem(iQueues, &pvListContext);
_ASSERT(pdmq);
hr = pdmq->QueryInterface(IID_IQueueAdminAction, (void **) &pIQueueAdminAction); if (FAILED(hr)) goto Exit;
_ASSERT(pIQueueAdminAction);
hr = pIQueueAdminAction->HrApplyQueueAdminFunction( pIQueueAdminMessageFilter); if (FAILED(hr)) goto Exit; }
Exit: m_slQueues.ShareUnlock();
if (pIQueueAdminAction) pIQueueAdminAction->Release();
return hr; }
//---[ CLinkMsgQueue::InternalUpdateFileTime ]---------------------------------
//
//
// Description:
// Updates an internal filetime in a thread safe manner. This does not
// guarantee that the filetime will be updated, but does guarantee that
// if it is updated, the filetime is not corrupt.
//
// NOTE: these file times are used only for display purposes by the
// Queue Admin
// Parameters:
// pftDest Ptr to member variable to update
// pftSrc Pft to source filetime
// Returns:
// -
// History:
// 1/11/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::InternalUpdateFileTime(FILETIME *pftDest, FILETIME *pftSrc) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::InternalUpdateFileTime"); if (pftDest && pftSrc) { DebugTrace((LPARAM) this, "Updating filetime from %x:%x to %x:%x", pftDest->dwLowDateTime, pftDest->dwHighDateTime, pftSrc->dwLowDateTime, pftSrc->dwHighDateTime);
if (!(eLinkFlagsFileTimeSpinLock & dwInterlockedSetBits(&m_dwLinkFlags, eLinkFlagsFileTimeSpinLock))) { //We got the spinlock
memcpy(pftDest, pftSrc, sizeof(FILETIME)); dwInterlockedUnsetBits(&m_dwLinkFlags, eLinkFlagsFileTimeSpinLock); } } TraceFunctLeave(); }
//---[ CLinkMsgQueue::HrGetNextMsgRef ]----------------------------------------
//
//
// Description:
// Returns the next MsgRef to be delivered without doing the
// PrepareDelivery step. This is used in gateway delivery to route the
// message to the locally. Additionally, this will mark all a the
// gateway DMQ's as local on the msgref, so that a subsequent reroute
// will only affect messages that have not already been put in the
// local delivery queue.
// Parameters:
// IN fRoutingLockHeld TRUE is routing lock is already held
// OUT ppmsgref Returned Msg
// Returns:
// S_OK on success
// AQUEUE_E_QUEUE_EMPTY otherwise
// History:
// 1/26/99 - MikeSwa Created
// 3/25/99 - MikeSwa Added fRoutingLockHeld to fix deadlock
// 2/17/2000 - MikeSwa Modified for gateway delivery reroute
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrGetNextMsgRef(IN BOOL fRoutingLockHeld, OUT CMsgRef **ppmsgref) { TraceFunctEnterEx((LPARAM) this, "CLinkMsgQueue::HrGetNextMsg"); _ASSERT(ppmsgref);
HRESULT hr = S_OK; BOOL fLockedShutdown = FALSE; BOOL fLockedQueues = FALSE; DWORD iQueues = 0; DWORD dwCurrentRoundRobinIndex = m_dwRoundRobinIndex; CDestMsgQueue *pdmq = NULL; PVOID pvContext = NULL;
if (m_dwLinkFlags & eLinkFlagsInvalid) { hr = AQUEUE_E_LINK_INVALID; goto Exit; }
//Don't even bother to wait for queue lock if routing change is pending
if ((m_dwLinkFlags & eLinkFlagsRouteChangePending) || !m_aqstats.m_cMsgs) { hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
//Make sure domain info is updated & we should still be sending messages
if (!m_paqinst->fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; }
//Current implementation of sharelocks are not share reentrant. Only
//grab lock if caller has not.
if (!fRoutingLockHeld) m_paqinst->RoutingShareLock();
fLockedShutdown = TRUE;
m_slQueues.ShareLock(); fLockedQueues = TRUE;
if (m_cQueues == 0) { //There are currently no queue associated with this link
hr = AQUEUE_E_QUEUE_EMPTY; goto Exit; }
//$$TODO impose some ordering on these queues
for (iQueues = 0; iQueues < m_cQueues && SUCCEEDED(hr) && !(*ppmsgref); iQueues++) { *ppmsgref = NULL; pdmq = (CDestMsgQueue *) m_qlstQueues.pvGetItem( (iQueues+dwCurrentRoundRobinIndex)%m_cQueues, &pvContext);
_ASSERT(pdmq); pdmq->AssertSignature();
//get msg reference
hr = pdmq->HrDequeueMsg(eEffPriLow, ppmsgref, NULL); if (FAILED(hr)) { if (AQUEUE_E_QUEUE_EMPTY == hr) hr = S_OK; else goto Exit; }
//
// Mark this as a local queue for this message
//
if (*ppmsgref) (*ppmsgref)->MarkQueueAsLocal(pdmq); }
//Visit a new queue on every GetNextMsg
InterlockedIncrement((PLONG) &m_dwRoundRobinIndex);
Exit:
if (fLockedQueues) m_slQueues.ShareUnlock();
if (fLockedShutdown) { //If routing lock is not held by caller, then we must release it
if (!fRoutingLockHeld) m_paqinst->RoutingShareUnlock(); m_paqinst->ShutdownUnlock(); }
if (!*ppmsgref) hr = AQUEUE_E_QUEUE_EMPTY;
TraceFunctLeave(); return hr; }
//---[ CLinkMsgQueue::HrPrepareDelivery ]--------------------------------------
//
//
// Description:
// Prepares delivery for a message for this link
// Parameters:
// IN pmsgref MsgRef to prepare for delivery
// IN fQueuesLock TRUE is m_slQueues is locked already
// IN fLocal Prepare delivery for all domains with NULL queues
// IN fDelayDSN Check/Set Delay bitmap (only send 1 Delay DSN).
// IN pqlstQueues QuickList of DMQ's
// IN OUT pdcntxt context that must be returned on Ack
// OUT pcRecips # of recips to deliver for
// OUT prgdwRecips Array of recipient indexes
// Returns:
// S_OK on success
// Failure code from CMsgRef::HrPrepareDelivery
// History:
// 1/26/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CLinkMsgQueue::HrInternalPrepareDelivery( IN CMsgRef *pmsgref, IN BOOL fQueuesLocked, IN BOOL fLocal, IN BOOL fDelayDSN, IN OUT CDeliveryContext *pdcntxt, OUT DWORD *pcRecips, OUT DWORD **prgdwRecips) { HRESULT hr = S_OK; BOOL fQueuesLockedByUs = FALSE;
_ASSERT(pmsgref);
if (!pmsgref) { hr = E_INVALIDARG; goto Exit; }
if (!fQueuesLocked) { m_slQueues.ShareLock(); fQueuesLockedByUs = TRUE; }
hr = pmsgref->HrPrepareDelivery(fLocal, fDelayDSN, &m_qlstQueues, NULL, pdcntxt, pcRecips, prgdwRecips);
if (FAILED(hr)) goto Exit;
Exit:
if (fQueuesLockedByUs) m_slQueues.ShareUnlock();
return hr;
}
//---[ CLinkMsgQueue::SetDiagnosticInfo ]--------------------------------------
//
//
// Description:
// Sets the diagnostic information for this link
// Parameters:
// IN hrDiagnosticError Error code... if SUCCESS we thow away
// the rest of the information
// IN szDiagnosticVerb String pointing to the protocol
// verb that caused the failure.
// IN szDiagnosticResponse String that contains the remote
// servers response.
// Returns:
// -
// History:
// 2/18/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::SetDiagnosticInfo( IN HRESULT hrDiagnosticError, IN LPCSTR szDiagnosticVerb, IN LPCSTR szDiagnosticResponse) { m_slInfo.ExclusiveLock(); m_hrDiagnosticError = hrDiagnosticError;
//zero original buffers
ZeroMemory(&m_szDiagnosticVerb, sizeof(m_szDiagnosticVerb)); ZeroMemory(&m_szDiagnosticResponse, sizeof(m_szDiagnosticResponse));
//copy buffers
if (szDiagnosticVerb) strncpy(m_szDiagnosticVerb, szDiagnosticVerb, sizeof(m_szDiagnosticVerb)-1);
if (szDiagnosticResponse) strncpy(m_szDiagnosticResponse, szDiagnosticResponse, sizeof(m_szDiagnosticResponse)-1);
m_slInfo.ExclusiveUnlock(); }
//---[ CLinkMsgQueue::GetDiagnosticInfo ]--------------------------------------
//
//
// Description:
// Gets the diagnostic information for this link
// Parameters:
// IN LPSTR szDiagnosticVerb - buffer to receive the verb that
// caused the error
// IN DWORD cDiagnosticVerb - length of the buffer
// IN LPSTR szDiagnosticResponse- buffer to recieve the response
// of the error
// IN DWORD cbDiagnosticResponse- length of buffer
// OUT HRESULT *phrDiagnosticError - HRESULT for error
// Returns:
// -
// History:
// 3/9/99 - AWetmore Created
// 8/2/99 - Mikeswa...updated to use m_slInfo.
//
//-----------------------------------------------------------------------------
void CLinkMsgQueue::GetDiagnosticInfo( IN LPSTR szDiagnosticVerb, IN DWORD cbDiagnosticVerb, IN LPSTR szDiagnosticResponse, IN DWORD cbDiagnosticResponse, OUT HRESULT *phrDiagnosticError) { if (szDiagnosticVerb) ZeroMemory(szDiagnosticVerb, cbDiagnosticVerb);
if (szDiagnosticResponse) ZeroMemory(szDiagnosticResponse, cbDiagnosticResponse);
m_slInfo.ShareLock(); if (phrDiagnosticError) *phrDiagnosticError = m_hrDiagnosticError;
//copy buffers
if (*m_szDiagnosticVerb && szDiagnosticVerb) strncpy(szDiagnosticVerb, m_szDiagnosticVerb, cbDiagnosticVerb);
if (*m_szDiagnosticResponse && szDiagnosticResponse) strncpy(szDiagnosticResponse, m_szDiagnosticResponse, cbDiagnosticResponse);
m_slInfo.ShareUnlock(); }
//---[ CLinkMsgQueue::HrApplyActionToMessage ]---------------------------------
//
//
// Description:
// Applies an action to this message for this queue. This will be called
// by the IQueueAdminMessageFilter during a queue enumeration function.
//
// This code path is currently not executed... eventually me may consider
// doing this to allow a DSN to be generated per link.
//
// For this to be called. CLinkMsgQueue::HrApplyActionToMessage would
// need to iterate over the DMQ's queues with it's own IQueueAdminAction
// iterface pointed to by the filter intead of the DMQ's.
//
// Parameters:
// IN *pIUnknownMsg ptr to message abstraction
// IN ma Message action to perform
// IN pvContext Context set on IQueueAdminFilter
// OUT pfShouldDelete TRUE if the message should be deleted
// Returns:
// S_OK on success
// History:
// 2/21/99 - MikeSwa Created
// 4/2/99 - MikeSwa Added context
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::HrApplyActionToMessage( IUnknown *pIUnknownMsg, MESSAGE_ACTION ma, PVOID pvContext, BOOL *pfShouldDelete) { _ASSERT(0 && "Not reachable"); return E_NOTIMPL; }
//---[ CLinkMsgQueue::HrApplyActionToLink ]------------------------------------
//
//
// Description:
// Applies the specified QueueAdmin action to this link
// Parameters:
// IN la Link action to apply
// Returns:
// S_OK on success
// E_INVALIDARG if bogus action is given
// History:
// 2/22/99 - MikeSwa Created (moved most of code from
// CAQSvrInst::SetLinkState)
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::HrApplyActionToLink(LINK_ACTION la) { DWORD dwLinkFlagsToSet = LINK_STATE_NO_ACTION; DWORD dwLinkFlagsToUnset = LINK_STATE_NO_ACTION; HRESULT hr = S_OK;
//Is action supported?
if (!fActionIsSupported(la)) goto Exit;
//figure out how we want to change the link state
if (LA_KICK == la) { //kick the link
dwLinkFlagsToSet = LINK_STATE_RETRY_ENABLED | LINK_STATE_ADMIN_FORCE_CONN | LINK_STATE_CONNECT_IF_NO_MSGS; dwLinkFlagsToUnset = LINK_STATE_ADMIN_HALT; } else if (LA_FREEZE == la) { //Admin wants this link to stop sending mail outbound
dwLinkFlagsToSet = LINK_STATE_ADMIN_HALT; dwLinkFlagsToUnset = LINK_STATE_ADMIN_FORCE_CONN; } else if (LA_THAW == la) { //Unset frozen flags
dwLinkFlagsToUnset = LINK_STATE_ADMIN_HALT; } else { //invalid arg
hr = E_INVALIDARG; goto Exit; }
dwModifyLinkState(dwLinkFlagsToSet, dwLinkFlagsToUnset);
Exit: return hr;
}
//---[ CLinkMsgQueue::QueryInterface ]-----------------------------------------
//
//
// Description:
// QueryInterface for CDestMsgQueue that supports:
// - IQueueAdminAction
// - IUnknown
// - IQueueAdminLink
// Parameters:
//
// Returns:
//
// History:
// 2/21/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::QueryInterface(REFIID riid, LPVOID *ppvObj) { HRESULT hr = S_OK;
if (!ppvObj) { hr = E_POINTER; goto Exit; }
if (IID_IUnknown == riid) { *ppvObj = static_cast<IQueueAdminAction *>(this); } else if (IID_IQueueAdminAction == riid) { *ppvObj = static_cast<IQueueAdminAction *>(this); } else if (IID_IQueueAdminLink == riid) { *ppvObj = static_cast<IQueueAdminLink *>(this); } else { *ppvObj = NULL; hr = E_NOINTERFACE; goto Exit; }
static_cast<IUnknown *>(*ppvObj)->AddRef();
Exit: return hr; }
//---[ CLinkMsgQueue::HrGetNumQueues ]-----------------------------------------
//
//
// Description:
// Returns the number of queues on this link
// Parameters:
// OUT pcQueues # numbr of queues
// Returns:
// S_OK on success
// E_POINTER if pcQueues is not valid
// History:
// 2/22/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
STDMETHODIMP CLinkMsgQueue::HrGetNumQueues(DWORD *pcQueues) { HRESULT hr = S_OK;
_ASSERT(pcQueues); if (!pcQueues) { hr = E_POINTER; goto Exit; }
*pcQueues = cGetNumQueues(); Exit: return hr; }
//---[ CLinkMsgQueue::fMatchesID ]---------------------------------------------
//
//
// Description:
// Used to determine if this link matches a given scheduleID/link pair
// Parameters:
// IN QueueLinkID ID to match against
// Returns:
// TRUE if it matches
// FALSE if it does not
// History:
// 2/23/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL STDMETHODCALLTYPE CLinkMsgQueue::fMatchesID(QUEUELINK_ID *pQueueLinkID) { _ASSERT(pQueueLinkID); _ASSERT(pQueueLinkID->szName); CAQScheduleID aqsched(pQueueLinkID->uuid, pQueueLinkID->dwId);
if (!fIsSameScheduleID(&aqsched)) return FALSE;
if (!fBiStrcmpi(m_szSMTPDomain, pQueueLinkID->szName)) return FALSE;
//Everything matched!
return TRUE; }
|