You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2820 lines
92 KiB
2820 lines
92 KiB
//-----------------------------------------------------------------------------
|
|
//
|
|
//
|
|
// 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;
|
|
}
|
|
|