Leaked source code of windows server 2003
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

//-----------------------------------------------------------------------------
//
//
// 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;
}