|
|
//-----------------------------------------------------------------------------
//
//
// File: domain.cpp
//
// Description: Implementation of CDomainMapping, CDomainEntry, and
// CDomainMappingTable.
//
// The DomainMappingTable is a domain name hash table that contains the
// mappings from final destination to queues.
//
// Author: mikeswa
//
// Copyright (C) 1997 Microsoft Corporation
//
//-----------------------------------------------------------------------------
#include "aqprecmp.h"
#include "aqroute.h"
#include "localq.h"
#include "asyncq.h"
#include "mailadmq.h"
#include "tran_evntlog.h"
const DWORD LOCAL_DOMAIN_NAME_SIZE = 512;
//Max mislabled queues in empty list, before we will clean the list
const DWORD MAX_MISPLACED_QUEUES_IN_EMPTY_LIST = 100;
//Callback for retry
void CDomainMappingTable::SpecialRetryCallback(PVOID pvContext) { CDomainMappingTable *pdnt = (CDomainMappingTable *) pvContext; _ASSERT(pdnt); _ASSERT(DOMAIN_MAPPING_TABLE_SIG == pdnt->m_dwSignature);
dwInterlockedUnsetBits(&(pdnt->m_dwFlags), DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); pdnt->ProcessSpecialLinks(0, FALSE); }
//---[ ReUnreachableErrorToAqueueError ]---------------------------------------
//
//
// Description:
// Translates a HRESULT returned from GetNextHop to one that is meaningful
// to aqueue DSN generation.
// Parameters:
// IN HRESULT reErr -- Error from routing.
// IN OUT HRESULT aqErr -- Corresponding aqueue error code.
// Returns:
// Nothing.
// History:
// GPulla created.
//
//-----------------------------------------------------------------------------
void ReUnreachableErrorToAqueueError(HRESULT reErr, HRESULT *aqErr) { if(E_ACCESSDENIED == reErr) *aqErr = AQUEUE_E_ACCESS_DENIED;
else if(HRESULT_FROM_WIN32(ERROR_MESSAGE_EXCEEDS_MAX_SIZE) == reErr) *aqErr = AQUEUE_E_MESSAGE_TOO_LARGE;
else *aqErr = AQUEUE_E_NO_ROUTE;
}
//---[ DeinitDomainEntryIteratorFn ]--------------------------------------------
//
//
// Description:
// Deletes and releases all internal domain info objects in table
// Parameters:
// IN pvContext - pointer to context (ignored)
// IN pvData - data entry to look at
// IN fWildcardData - TRUE if data is a wildcard entry (ignored)
// OUT pfContinue - TRUE if iterator should continue to the next entry
// OUT pfRemoveEntry - TRUE if entry should be deleted
// Returns:
// -
// History:
// 6/17/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
VOID DeinitDomainEntryIteratorFn(PVOID pvContext, PVOID pvData, BOOL fWildcard, BOOL *pfContinue, BOOL *pfDelete) { CDomainEntry *pdentry = (CDomainEntry *) pvData; *pfDelete = FALSE; *pfContinue = TRUE; pdentry->HrDeinitialize(); }
//---[ ReleaseDomainEntryIteratorFn ]------------------------------------------
//
//
// Description:
// Deletes and releases all internal domain info objects in table
// Parameters:
// IN pvContext - pointer to context (ignored)
// IN pvData - data entry to look at
// IN fWildcardData - TRUE if data is a wildcard entry (ignored)
// OUT pfContinue - TRUE if iterator should continue to the next entry
// OUT pfRemoveEntry - TRUE if entry should be deleted
// Returns:
// -
// History:
// 6/17/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
VOID ReleaseDomainEntryIteratorFn(PVOID pvContext, PVOID pvData, BOOL fWildcard, BOOL *pfContinue, BOOL *pfDelete) { ULONG cRefs; CDomainEntry *pdentry = (CDomainEntry *) pvData; *pfDelete = TRUE; *pfContinue = TRUE; cRefs = pdentry->Release(); _ASSERT(!cRefs && "leaking domain entries"); }
//***[ CDomainMapping Methods ]************************************************
//---[ CDomainMapping::Clone ]-------------------------------------------------
//
//
// Description: Fills the current mapping with data from another DomainMapping
//
// Parameters:
// IN pdmap CDomainMapping to clone
//
// Returns:
// -
//
//-----------------------------------------------------------------------------
void CDomainMapping::Clone(IN CDomainMapping *pdmap) { TraceFunctEnterEx((LPARAM) this, "CDomainMapping::Clone"); Assert(pdmap); m_pdentryDomainID = pdmap->m_pdentryDomainID; m_pdentryQueueID = pdmap->m_pdentryQueueID; TraceFunctLeave(); }
//---[ CDomainMapping::HrGetDestMsgQueue ]-------------------------------------
//
//
// Description: Returns a pointer to the queue that this mapping points to
//
// Parameters:
// IN paqmt Message Type to get queue for
// OUT ppdmq pointer returned
//
// Returns:
// S_OK on success
// AQUEUE_E_INVALID_DOMAIN
//
//-----------------------------------------------------------------------------
HRESULT CDomainMapping::HrGetDestMsgQueue(IN CAQMessageType *paqmt, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMapping::HrGetDestMsgQueue"); HRESULT hr = S_OK; Assert(ppdmq);
if (m_pdentryQueueID == NULL) { hr = AQUEUE_E_INVALID_DOMAIN; goto Exit; }
hr = m_pdentryQueueID->HrGetDestMsgQueue(paqmt, ppdmq);
Exit: TraceFunctLeave(); return hr; }
//***[ CDomainEntry Methods ]**************************************************
//---[ CDomainEntry::CDomainEntry() ]------------------------------------------
//
//
// Description: CDomainEntry constructor
//
// Parameters:
// paqinst - ptr to the virtual server object
//
// Returns:
// -
//
//-----------------------------------------------------------------------------
CDomainEntry::CDomainEntry(CAQSvrInst *paqinst) : m_slPrivateData("CDomainEntry") { _ASSERT(paqinst); TraceFunctEnterEx((LPARAM) this, "CDomainEntry::CDomainEntry"); //Create a mapping that is not compressed
m_dmap.m_pdentryDomainID = this; m_dmap.m_pdentryQueueID = this; m_dwSignature = DOMAIN_ENTRY_SIG;
//init pointers
m_szDomainName = NULL; m_cbDomainName = 0; InitializeListHead(&m_liDestQueues); InitializeListHead(&m_liLinks);
m_cLinks = 0; m_cQueues = 0;
m_paqinst = paqinst; m_paqinst->AddRef();
TraceFunctLeave(); }
//---[ CDomainEntry::~CDomainEntry() ]-----------------------------------------
//
//
// Description: CDomainEntry destructor
//
// Parameters:
// -
//
// Returns:
// -
//
//-----------------------------------------------------------------------------
CDomainEntry::~CDomainEntry() { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::~CDomainEntry"); PLIST_ENTRY pli = NULL; //used to iterate over lists
CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL;
//Remove all DestMsgQueues from list
while (!IsListEmpty(&m_liDestQueues)) { pli = m_liDestQueues.Flink; _ASSERT((pli != &m_liDestQueues) && "List Macros are broken"); pdmq = CDestMsgQueue::pdmqGetDMQFromDomainListEntry(pli); pdmq->RemoveQueueFromDomainList(); pdmq->Release(); m_cQueues--; }
//Remove all links from list
while (!IsListEmpty(&m_liLinks)) { pli = m_liLinks.Flink; plmq = CLinkMsgQueue::plmqGetLinkMsgQueue(pli); plmq->fRemoveLinkFromList(); plmq->Release(); m_cLinks--; _ASSERT((pli != &m_liLinks) && "List Macros are broken"); }
FreePv(m_szDomainName);
if (m_paqinst) m_paqinst->Release();
TraceFunctLeave(); }
//---[ CDomainEntry::HrInitialize ]--------------------------------------------
//
//
// Description: Initilizer for CDomainEntry. This should be called BEFORE the
// entry is inserted into the DMT where other threads can access it.
//
// Parameters:
// szDomainName string of domain name for entry, will *NOT* be copied, this
// object will take control of this. This will save a unneeded
// buffer copy and allocation per domain entry
// pdentryQueueID ptr to the primary entry for this domain (usually this)
// pdmq ptr to DestMsgQueue
// plmq ptr to LinkMsgQueue to allocate
// Returns:
// S_OK on success
// E_OUTOFMEMORY if any allocation fails
//
// It is expected that this is only called by the DMT while creating an entry
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrInitialize(DWORD cbDomainName, LPSTR szDomainName, CDomainEntry *pdentryQueueID, CDestMsgQueue *pdmq, CLinkMsgQueue *plmq) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrInitialize"); Assert(szDomainName); Assert((pdentryQueueID == this) || (pdmq == NULL));
HRESULT hr = S_OK;
m_cbDomainName = cbDomainName; m_szDomainName = szDomainName;
//write domain mapping
m_dmap.m_pdentryDomainID = this; m_dmap.m_pdentryQueueID = pdentryQueueID;
//add the queue to our list of queues
if (pdmq) { m_slPrivateData.ExclusiveLock(); m_cQueues++; pdmq->AddRef(); pdmq->InsertQueueInDomainList(&m_liDestQueues); m_slPrivateData.ExclusiveUnlock(); }
if (plmq) { m_slPrivateData.ExclusiveLock(); m_cLinks++; plmq->AddRef(); plmq->InsertLinkInList(&m_liLinks); m_slPrivateData.ExclusiveUnlock(); }
TraceFunctLeave(); return hr; }
//---[ CDomainEntry::HrDeinitialize ]------------------------------------------
//
//
// Description: Deinitializer for CDomainEntry
//
// Parameters:
// -
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrDeinitialize() { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrDeinitialize"); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; //used to iterate over lists
CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL;
m_slPrivateData.ExclusiveLock(); while (!IsListEmpty(&m_liDestQueues)) { pli = m_liDestQueues.Flink; _ASSERT((pli != &m_liDestQueues) && "List Macros are broken"); pdmq = CDestMsgQueue::pdmqGetDMQFromDomainListEntry(pli); pdmq->HrDeinitialize(); pdmq->RemoveQueueFromDomainList(); pdmq->Release(); m_cQueues--; pdmq = NULL; }
//Remove all links from list
while (!IsListEmpty(&m_liLinks)) { pli = m_liLinks.Flink; plmq = CLinkMsgQueue::plmqGetLinkMsgQueue(pli); plmq->HrDeinitialize(); plmq->fRemoveLinkFromList(); plmq->Release(); m_cLinks--; _ASSERT((pli != &m_liLinks) && "List Macros are broken"); }
if (m_paqinst) { m_paqinst->Release(); m_paqinst = NULL; }
m_slPrivateData.ExclusiveUnlock();
TraceFunctLeave(); return hr; }
//---[ CDomainEntry::HrGetDomainMapping ]--------------------------------------
//
//
// Description: Returns Domain Mapping for this object
//
// Parameters:
// OUT pdmap CDomainMapping for return information
//
// Returns:
// S_OK on success
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrGetDomainMapping(OUT CDomainMapping *pdmap) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetDomainMapping"); HRESULT hr = S_OK; _ASSERT(pdmap); pdmap->Clone(&m_dmap); TraceFunctLeave(); return S_OK; }
//---[ CDomainEntry::HrGetDomainName ]----------------------------------------------
//
//
// Description: Copies Domain Name. Caller is responsible for freeing string
//
// Parameters:
// OUT pszDomainName string of domain name for entry, will be copied
//
// Returns:
// S_OK on success
// E_OUTOFMEMORY if any allocation fails
//
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrGetDomainName(OUT LPSTR *pszDomainName) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetDomainName"); HRESULT hr = S_OK; Assert(pszDomainName);
if (m_szDomainName == NULL) { *pszDomainName = NULL; goto Exit; }
//Copy domain name
*pszDomainName = (LPSTR) pvMalloc(m_cbDomainName + sizeof(CHAR));
if (*pszDomainName == NULL) { hr = E_OUTOFMEMORY; goto Exit; }
strcpy(*pszDomainName, m_szDomainName);
Exit: TraceFunctLeave(); return hr; }
//---[ CDomainEntry::HrGetDestMsgQueue ]---------------------------------------
//
//
// Description: Returns a pointer to the queue that this entry points to
//
// Parameters:
// IN paqmt Message Type to get domain for
// OUT ppdmq pointer returned
//
// Returns:
// S_OK on success
// E_FAIL no queue matching message type is found
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrGetDestMsgQueue(IN CAQMessageType *paqmt, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetDestMsgQueue"); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CDestMsgQueue *pdmq = NULL; _ASSERT(ppdmq); _ASSERT(m_dmap.m_pdentryDomainID == this); DEBUG_DO_IT(DWORD cQueues = 0);
if (m_dmap.m_pdentryQueueID == m_dmap.m_pdentryDomainID) { //this must be the primary entry... scan our own list of queues
m_slPrivateData.ShareLock(); pli = m_liDestQueues.Flink; while (pli != &m_liDestQueues) { _ASSERT(m_cQueues >= cQueues); DEBUG_DO_IT(cQueues++); pdmq = CDestMsgQueue::pdmqIsSameMessageType(paqmt, pli); if (pdmq) break;
pli = pli->Flink; } m_slPrivateData.ShareUnlock();
if (!pdmq) hr = E_FAIL; //no such queue
else { pdmq->AddRef(); *ppdmq = pdmq; } } else { //we are not primary
_ASSERT(0 && "Non-primary domain entry... currently only primary entries are supported"); _ASSERT(IsListEmpty(&m_liDestQueues)); //make sure it matches the profile
hr = m_dmap.m_pdentryQueueID->HrGetDestMsgQueue(paqmt, ppdmq); }
TraceFunctLeave(); return hr; }
//---[ CDomainEntry::HrAddUniqueDestMsgQueue ]---------------------------------
//
//
// Description:
// Adds a queue to this entry's list of queues if a queue with the same
// message type does not already exist.
//
// Will appropriately AddRef domain.
// Parameters:
// IN pdmqNew - CDestMsgQueue to add
// OUT ppdmqCurrent - Set to curent CDestMsgQueue on failure
// Returns:
// S_OK on success
// E_FAIL if a CDestMsgQueue with same Message type alread exists.
// History:
// 5/28/98 - MikeSwa Created
// 9/8/98 - MikeSwa Modified to use AddRef/Relase for queues
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrAddUniqueDestMsgQueue(IN CDestMsgQueue *pdmqNew, OUT CDestMsgQueue **ppdmqCurrent) { _ASSERT(pdmqNew); _ASSERT(ppdmqCurrent); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CDestMsgQueue *pdmq = NULL; CAQMessageType *paqmt = pdmqNew->paqmtGetMessageType(); DEBUG_DO_IT(DWORD cQueues = 0);
*ppdmqCurrent = NULL;
m_slPrivateData.ExclusiveLock(); pli = m_liDestQueues.Flink;
//First look through list and make sure that there isn't already a
//queue with this message type
while (pli != &m_liDestQueues) { _ASSERT(m_cQueues >= cQueues); pdmq = CDestMsgQueue::pdmqIsSameMessageType(paqmt, pli); if (pdmq) { hr = E_FAIL; pdmq->AddRef(); *ppdmqCurrent = pdmq; goto Exit; } DEBUG_DO_IT(cQueues++); pli = pli->Flink; }
pdmqNew->AddRef(); pdmqNew->InsertQueueInDomainList(&m_liDestQueues); m_cQueues++;
Exit: m_slPrivateData.ExclusiveUnlock(); return hr; }
//---[ CDomainEntry::HrGetLinkMsgQueue ]---------------------------------------
//
//
// Description:
// Gets a link for the given schedule id
// Parameters:
// IN paqsched - ScheduleID to search for
// OUT pplmq - returned queue
// Returns:
// S_OK on success
// E_FAIL if no link matching the schudule ID can be found
// History:
// 6/11/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrGetLinkMsgQueue(IN CAQScheduleID *paqsched, OUT CLinkMsgQueue **pplmq) { TraceFunctEnterEx((LPARAM) this, "CDomainEntry::HrGetLinkMsgQueue"); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CLinkMsgQueue *plmq = NULL; _ASSERT(pplmq); _ASSERT(m_dmap.m_pdentryDomainID == this); DEBUG_DO_IT(DWORD cLinks = 0);
m_slPrivateData.ShareLock(); pli = m_liLinks.Flink; while (pli != &m_liLinks) { _ASSERT(m_cLinks >= cLinks); DEBUG_DO_IT(cLinks++); plmq = CLinkMsgQueue::plmqIsSameScheduleID(paqsched, pli); if (plmq) { plmq->AddRef(); break; }
pli = pli->Flink; } m_slPrivateData.ShareUnlock();
if (!plmq) hr = E_FAIL; //no such queue
else *pplmq = plmq;
TraceFunctLeave(); return hr; }
//---[ CDomainEntry::HrAddUniqueLinkMsgQueue ]---------------------------------
//
//
// Description:
// Inserts a link with a unique schedule ID
// Parameters:
// IN plmqNew New link to insert
// OUT plmqCurrent Current link with schedule ID on insert failure
// Returns:
// S_OK if insert succeeds
// E_FAIL if insert fails
// History:
// 6/11/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntry::HrAddUniqueLinkMsgQueue(IN CLinkMsgQueue *plmqNew, OUT CLinkMsgQueue **pplmqCurrent) { _ASSERT(plmqNew); _ASSERT(pplmqCurrent); HRESULT hr = S_OK; PLIST_ENTRY pli = NULL; CLinkMsgQueue *plmq = NULL; CAQScheduleID *paqsched = plmqNew->paqschedGetScheduleID(); DEBUG_DO_IT(DWORD cLinks = 0);
*pplmqCurrent = NULL;
m_slPrivateData.ExclusiveLock(); pli = m_liLinks.Flink;
//First look through list and make sure that there isn't already a
//queue with this schedule ID
while (pli != &m_liLinks) { _ASSERT(m_cLinks >= cLinks); plmq = CLinkMsgQueue::plmqIsSameScheduleID(paqsched, pli); if (plmq) { hr = E_FAIL; *pplmqCurrent = plmq; plmq->AddRef(); goto Exit; } DEBUG_DO_IT(cLinks++); pli = pli->Flink; }
plmqNew->InsertLinkInList(&m_liLinks); plmqNew->AddRef(); m_cLinks++;
Exit: m_slPrivateData.ExclusiveUnlock(); return hr; }
//---[ CDomainEntry::RemoveDestMsgQueue ]--------------------------------------
//
//
// Description:
// Removes empty DMQ from entry.
// Parameters:
// IN pdmq DMQ to remove from domain entry
// Returns:
// -
// History:
// 9/14/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDomainEntry::RemoveDestMsgQueue(IN CDestMsgQueue *pdmq) { _ASSERT(pdmq && "INVALID Param for internal function"); m_slPrivateData.ExclusiveLock(); pdmq->RemoveQueueFromDomainList(); pdmq->HrDeinitialize(); pdmq->Release(); m_cQueues--; m_slPrivateData.ExclusiveUnlock(); }
//---[ CDomainEntry::RemoveLinkMsgQueue ]--------------------------------------
//
//
// Description:
// Removes an empty LinkMsgQueue from the domain entry
// Parameters:
// IN plmq Link to remove
// Returns:
// -
// History:
// 9/14/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDomainEntry::RemoveLinkMsgQueue(IN CLinkMsgQueue *plmq) { _ASSERT(plmq && "INVALID Param for internal function"); m_slPrivateData.ExclusiveLock(); BOOL fRemove = plmq->fRemoveLinkFromList(); if (fRemove) m_cLinks--; m_slPrivateData.ExclusiveUnlock();
if (fRemove) {
//do *NOT* call HrDeinitialize here since it will deadlock
plmq->RemovedFromDMT(); plmq->Release(); } }
//***[ CDomainMappingTable Methods ]*******************************************
//---[ CDomainMappingTable::CDomainMappingTable ]------------------------------
//
//
// Description: CDomainMappingTable constructor
//
// Parameters: -
//
// Returns: -
//
//
//-----------------------------------------------------------------------------
CDomainMappingTable::CDomainMappingTable() : m_slPrivateData("CDomainMappingTable") { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::CDomainMappingTable"); m_paqinst = NULL; m_dwSignature = DOMAIN_MAPPING_TABLE_SIG; m_dwInternalVersion = 0; m_cOutstandingExternalShareLocks = 0; m_cThreadsForEmptyDMQList = 0;
m_plmqLocal = NULL; m_plmqCurrentlyUnreachable = NULL; m_plmqUnreachable = NULL; m_pmmaqPreCategorized = NULL; m_pmmaqPreRouting = NULL; m_cSpecialRetryMinutes = 0; m_cResetRoutesRetriesPending = 0;
m_dwFlags = 0; InitializeListHead(&m_liEmptyDMQHead); TraceFunctLeave(); }
//---[ CDomainMappingTable::~CDomainMappingTable ]------------------------------------------------------------
//
//
// Description: CDomainMappingTable destructor
//
// Parameters: -
//
// Returns: -
//
//
//-----------------------------------------------------------------------------
CDomainMappingTable::~CDomainMappingTable() { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::~CDomainMappingTable");
//Remove everything from the table
m_dnt.HrIterateOverSubDomains(NULL, ReleaseDomainEntryIteratorFn, NULL);
if (m_paqinst) { m_paqinst->Release(); m_paqinst = NULL; }
if (m_plmqLocal) m_plmqLocal->Release();
if (m_plmqCurrentlyUnreachable) m_plmqCurrentlyUnreachable->Release();
if (m_plmqUnreachable) m_plmqUnreachable->Release();
if (m_pmmaqPreCategorized) m_pmmaqPreCategorized->Release();
if (m_pmmaqPreRouting) m_pmmaqPreRouting->Release();
if (m_pmmaqPreSubmission) m_pmmaqPreSubmission->Release();
_ASSERT(!m_cOutstandingExternalShareLocks); //there should be no outstanding sharelocks
TraceFunctLeave(); }
//---[ CDomainMappingTable::HrInitialize ]-------------------------------------
//
//
// Description: Performs initialization that may return an error code
//
// Parameters:
// IN paqinst AQ Svr Inst
// IN paradmq Local Async Queue (passed to local link as part
// Returns: S_OK on success
//
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrInitialize(CAQSvrInst *paqinst, CAsyncAdminMsgRefQueue *paradmq, CAsyncAdminMailMsgQueue *pammqPreCatQ, CAsyncAdminMailMsgQueue *pammqPreRoutingQ, CAsyncAdminMailMsgQueue *pammqPreSubmissionQ) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrInitialize"); HRESULT hr = S_OK; HRESULT hrCurrent = S_OK; _ASSERT(paqinst); m_paqinst = paqinst; m_paqinst->AddRef();
//------Link for local Queue-----------------------------------------------
m_plmqLocal = new CLocalLinkMsgQueue(paradmq, g_sGuidLocalLink, m_paqinst); if (!m_plmqLocal) hr = E_OUTOFMEMORY;
hrCurrent = HrInializeGlobalLink(LOCAL_LINK_NAME, sizeof(LOCAL_LINK_NAME) - sizeof(CHAR), (CLinkMsgQueue **) &m_plmqLocal, LA_KICK | LA_FREEZE | LA_THAW, LI_TYPE_LOCAL_DELIVERY); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
//------Link for currently unreachable Queue-------------------------------
hrCurrent = HrInializeGlobalLink(CURRENTLY_UNREACHABLE_LINK_NAME, sizeof(CURRENTLY_UNREACHABLE_LINK_NAME) - sizeof(CHAR), &m_plmqCurrentlyUnreachable, 0, LI_TYPE_CURRENTLY_UNREACHABLE); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
hrCurrent = HrInializeGlobalLink(UNREACHABLE_LINK_NAME, sizeof(UNREACHABLE_LINK_NAME) - sizeof(CHAR), &m_plmqUnreachable, 0, LI_TYPE_INTERNAL); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
//-------Link for precat Queue---------------------------------------------
m_pmmaqPreCategorized = new CMailMsgAdminLink(g_sGuidPrecatLink, PRECAT_QUEUE_NAME, pammqPreCatQ, LI_TYPE_PENDING_CAT, m_paqinst);
if (!m_pmmaqPreCategorized) hr = E_OUTOFMEMORY;
if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
//-------Link for prerouting Queue-----------------------------------------
m_pmmaqPreRouting = new CMailMsgAdminLink(g_sGuidPreRoutingLink, PREROUTING_QUEUE_NAME, pammqPreRoutingQ, LI_TYPE_PENDING_ROUTING, m_paqinst);
if (!m_pmmaqPreRouting) hr = E_OUTOFMEMORY;
if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
//-------Link for presubmissionQueue--------------------------------------
m_pmmaqPreSubmission = new CMailMsgAdminLink(g_sGuidPreSubmissionLink, PRESUBMISSION_QUEUE_NAME, pammqPreSubmissionQ, LI_TYPE_PENDING_SUBMIT, m_paqinst);
if (!m_pmmaqPreSubmission) hr = E_OUTOFMEMORY;
if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent; //-------------------------------------------------------------------------
hrCurrent = m_dnt.HrInit(); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::HrDeinitialize ]-----------------------------------
//
//
// Description: Performs cleanup that may return an error code
//
// Parameters: -
//
// Returns: S_OK on success
//
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrDeinitialize() { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrDeinitialize"); HRESULT hr = S_OK; HRESULT hrCurrent = S_OK;
hr = m_dnt.HrIterateOverSubDomains(NULL, DeinitDomainEntryIteratorFn, NULL);
//Deinitialize global special links
hrCurrent = HrDeinitializeGlobalLink((CLinkMsgQueue **) &m_plmqLocal); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
hrCurrent = HrDeinitializeGlobalLink(&m_plmqCurrentlyUnreachable); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
hrCurrent = HrDeinitializeGlobalLink(&m_plmqUnreachable); if (FAILED(hrCurrent) && SUCCEEDED(hr)) hr = hrCurrent;
//NOTE: This *must* come after Deinitialize of entries
if (m_paqinst) { m_paqinst->Release(); m_paqinst = NULL; }
TraceFunctLeave(); return hr; }
//---[ <CDomainMappingTable::HrInializeGlobalLink ]-----------------------------
//
//
// Description:
// Initializes a single global link for the DMT. Configures link to not
// send notifications to the connection manager, and to
// Parameters:
// IN szLinkName The link name to use for the link
// IN cbLinkName The string length of the link name
// OUT pplmq Link to allocate/initialize
// IN dwSupportedActions Bitmask specifying what actions are supported on this link
// IN dwLinkType Link type to be returned to admin (LI_TYPE)
// Returns:
//
// History:
// 1/27/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrInializeGlobalLink(IN LPCSTR szLinkName, IN DWORD cbLinkName, OUT CLinkMsgQueue **pplmq, DWORD dwSupportedActions, DWORD dwLinkType) { HRESULT hr = S_OK;
_ASSERT(pplmq);
if (!*pplmq) *pplmq = new CLinkMsgQueue();
if (*pplmq) { //Initialize local queue
hr = (*pplmq)->HrInitialize(m_paqinst, NULL, cbLinkName, (LPSTR) szLinkName, eLinkFlagsAQSpecialLinkInfo, NULL);
//Set flags so no connections will be made for this link
(*pplmq)->dwModifyLinkState( LINK_STATE_PRIV_NO_NOTIFY | LINK_STATE_PRIV_NO_CONNECTION, LINK_STATE_NO_ACTION );
(*pplmq)->SetSupportedActions(dwSupportedActions); (*pplmq)->SetLinkType(dwLinkType); } else hr = E_OUTOFMEMORY;
return hr; }
//---[ CDomainMappingTable::HrDeinitializeGlobalLink ]-------------------------
//
//
// Description:
// Deinitializes a single global link for the DMT
// Parameters:
// IN OUT pplmq Link to deinitialze / set to NULL
// Returns:
// S_OK on success
// ERROR code from CLinkMsgQueue::HrDeinitialize();
// History:
// 1/27/99 - MikeSwa Created
// 7/21/99 - MikeSwa Modified - removed free of link domain
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrDeinitializeGlobalLink(IN OUT CLinkMsgQueue **pplmq) { HRESULT hr = S_OK;
_ASSERT(pplmq); if (pplmq && *pplmq) { hr = (*pplmq)->HrDeinitialize(); (*pplmq)->Release(); *pplmq = NULL; } return hr; }
//---[ CDomainMappingTable::HrMapDomainName ]----------------------------------
//
//
// Description:
// Looks up a DomainName in the DMT. Will create a new entry if necessary
//
// Parameters:
// IN szDomainName Domain Name to map
// IN paqmtMessageType Message type as returned by routing
// IN pIMessageRouter IMessageRouter for this message
// OUT pdmap Mapping returned - allocated by caller
// OUT ppdmq ptr to Queue
// Returns:
// S_OK on success
// E_OUTOFMEMORY if an allocation fails
// HRESULT_FROM_WIN32(ERROR_RETRY) if mapping data changes and entire
// message should be re-mapped
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrMapDomainName( IN LPSTR szDomainName, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, OUT CDomainMapping *pdmap, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrMapDomainName"); _ASSERT(pdmap); _ASSERT(ppdmq); _ASSERT(szDomainName); _ASSERT(szDomainName[0] && "unsupported config - RAID #68208"); _ASSERT(pIMessageRouter);
HRESULT hr = S_OK; CDomainEntry *pdentryResult = NULL; CDomainEntry *pdentryExisting = NULL; DWORD cbDomainName = 0; CInternalDomainInfo *pIntDomainInfo= NULL; BOOL fLocal = FALSE; //Is delivery local?
BOOL fWalkEmptyList= FALSE; DOMAIN_STRING strDomain; //allows quicker lookups/inserts
CLinkMsgQueue *plmq = NULL;
*ppdmq = NULL; cbDomainName = strlen(szDomainName)*sizeof(CHAR); INIT_DOMAIN_STRING(strDomain, cbDomainName, szDomainName);
m_slPrivateData.ShareLock(); hr = m_dnt.HrFindDomainName(&strDomain, (PVOID *) &pdentryResult);
//
// If succeeded aquire usage lock before we give up DMT lock.
// Handle failure cases after releasing lock.
//
if (SUCCEEDED(hr)) { pdentryResult->AddRef(); }
fWalkEmptyList = fNeedToWalkEmptyQueueList(); m_slPrivateData.ShareUnlock();
//
// Check and see if we need to delete empty queues.
//
if (fWalkEmptyList) { if (fDeleteExpiredQueues()) { //something has changes
hr = HRESULT_FROM_WIN32(ERROR_RETRY); goto Exit; } }
if (hr == DOMHASH_E_NO_SUCH_DOMAIN) //gotta create a new entry
{ DebugTrace((LPARAM) this, "Creating new DMT entry"); pdentryResult = new CDomainEntry(m_paqinst); if (NULL == pdentryResult) { hr = E_OUTOFMEMORY; goto Exit; }
_ASSERT(m_paqinst); hr = m_paqinst->HrGetInternalDomainInfo(cbDomainName, szDomainName, &pIntDomainInfo); if (FAILED(hr)) { //It must match the "*" domain at least
_ASSERT(AQUEUE_E_INVALID_DOMAIN != hr); goto Exit; } else { _ASSERT(pIntDomainInfo); if (pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_LOCAL_MAILBOX) { DebugTrace((LPARAM) NULL, "INFO: Local delivery queued."); fLocal = TRUE; } }
//perform initialization of domain entry... create queues if needed
if (fLocal) { hr = HrInitLocalDomain(pdentryResult, &strDomain, paqmtMessageType, pdmap); } else { hr = HrInitRemoteDomain(pdentryResult, &strDomain, pIntDomainInfo, paqmtMessageType, pIMessageRouter, pdmap, ppdmq, &plmq); }
if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Initializing %s domain %s - hr 0x%08X", (fLocal ? "local" : "remote"), szDomainName, hr); goto Exit; }
m_slPrivateData.ExclusiveLock();
hr = HrPrvInsertDomainEntry(&strDomain, pdentryResult, FALSE, &pdentryExisting);
//Need to release exclusive lock in if/else clause
if (SUCCEEDED(hr)) //the insertion succeeded
{ pdentryResult->AddRef(); m_slPrivateData.ExclusiveUnlock(); DebugTrace((LPARAM) szDomainName, "INFO: Creating new entry in DMT for domain %s", szDomainName); _ASSERT((fLocal || *ppdmq) && "Out param should be set here!"); //skip past getting value from table
if (!fLocal) { hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) { // Remove DMQ from link w/o notify since we never added it
(*ppdmq)->RemoveDMQFromLink(FALSE);
// Release the link we couldn't use and get the
// currently unreachable link
plmq->Release(); plmq = plmqGetCurrentlyUnreachable(); if (plmq) { (*ppdmq)->SetRouteInfo(plmq); hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) (*ppdmq)->RemoveDMQFromLink(FALSE); }
// Schedule a reset routes to clean up the currently
// unreachable queue
m_paqinst->ResetRoutes(RESET_NEXT_HOPS); } }
goto Exit; } else if (DOMHASH_E_DOMAIN_EXISTS == hr) //another inserted first
{ hr = S_OK; //not really a failure
DebugTrace((LPARAM) this, "Another thread inserted in the the DMT before us"); pdentryExisting->AddRef(); m_slPrivateData.ExclusiveUnlock();
_ASSERT(pdentryExisting != pdentryResult);
//release entry that we could not insert and replace with entry currently
//in the table
pdentryResult->HrDeinitialize(); pdentryResult->Release(); pdentryResult = NULL; pdentryResult = pdentryExisting;
//Release queue if we have one
if (*ppdmq) { (*ppdmq)->Release(); *ppdmq = NULL; }
} else { m_slPrivateData.ExclusiveUnlock(); //general failure to insert
//We must deinitialize the entry to force it to release any
//queues and links associated with it.
pdentryResult->HrDeinitialize(); goto Exit; } }
if (!*ppdmq & !fLocal) //we did not create entry in the table
{ _ASSERT(pdentryResult);
//
// Prefix wants us to to more than assert.
// If HrFindDomainName() fails silently or
// fails with an error other than AQUEUE_E_INVALID_DOMAIN,
// will will hit this code path
//
if (!pdentryResult) { //
// Make sure HR is set.
//
if (SUCCEEDED(hr)) hr = E_FAIL;
goto Exit; }
//Domain Name already exists in table
//At this point, we need to pull an existing entry from the mapping
//get domain mapping
hr = pdentryResult->HrGetDomainMapping(pdmap); if (FAILED(hr)) goto Exit;
//get queue
hr = pdentryResult->HrGetDestMsgQueue(paqmtMessageType, ppdmq); if (FAILED(hr)) { //entry exists, but no queue for our message type
_ASSERT(NULL == *ppdmq); //cannot fail if we create queue
//$$TODO cache domain config on entry
if (!pIntDomainInfo) { hr = m_paqinst->HrGetInternalDomainInfo(cbDomainName, szDomainName, &pIntDomainInfo); if (FAILED(hr)) { //It must match the "*" domain at least
_ASSERT(AQUEUE_E_INVALID_DOMAIN != hr); goto Exit; } }
_ASSERT(pIntDomainInfo); if (!(pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_LOCAL_MAILBOX)) { //this is a not a local domain entry
hr = HrCreateQueueForEntry(pdentryResult, &strDomain, pIntDomainInfo, paqmtMessageType, pIMessageRouter, pdmap, ppdmq); if (FAILED(hr)) goto Exit; } else { fLocal = TRUE; }
} } _ASSERT((*ppdmq || fLocal) && "Non-local domains must have queue ptrs!");
Exit:
if (FAILED(hr)) //cleanup
{ if (pdentryResult) { pdentryResult->Release(); }
if (*ppdmq) { (*ppdmq)->Release(); *ppdmq = NULL; } } else { if (*ppdmq) { // send link state notification saying that the link has
// been created
(*ppdmq)->SendLinkStateNotification(); } if (pdentryResult) pdentryResult->Release(); }
if (plmq) plmq->Release();
if (pIntDomainInfo) pIntDomainInfo->Release();
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::HrPrvGetDomainEntry ]------------------------------
//
//
// Description:
// Internal private function to lookup Domain entry for given domain
// Parameters:
// IN cbDomainnameLength Length of string to search for
// IN szDomainName Domain Name to search for
// IN fDMTLocked TRUE if locks are already
// OUT ppdentry Domain Entry for domain (from DMT)
// Returns:
// S_OK on success
// AQUEUE_E_INVALID_DOMAIN if domain is not found
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrPrvGetDomainEntry(IN DWORD cbDomainNameLength, IN LPSTR szDomainName, BOOL fDMTLocked, OUT CDomainEntry **ppdentry) { HRESULT hr = S_OK; BOOL fLocked = FALSE; DOMAIN_STRING strDomain;
_ASSERT(cbDomainNameLength); _ASSERT(szDomainName); _ASSERT(ppdentry);
INIT_DOMAIN_STRING(strDomain, cbDomainNameLength, szDomainName);
if (!fDMTLocked) { m_slPrivateData.ShareLock(); fLocked = TRUE; }
hr = m_dnt.HrFindDomainName(&strDomain, (PVOID *) ppdentry); if (FAILED(hr)) { if (DOMHASH_E_NO_SUCH_DOMAIN == hr) hr = AQUEUE_E_INVALID_DOMAIN; _ASSERT(NULL == *ppdentry); goto Exit; }
(*ppdentry)->AddRef();
Exit:
if (fLocked) m_slPrivateData.ShareUnlock();
return hr; }
//---[ CDomainMappingTable::HrPrvInsertDomainEntry ]---------------------------
//
//
// Description:
// Private wrapper function for HrInsertDomainName
// Parameters:
// IN pstrDomainName Domain Name to insert in DNT
// IN pdnetryNew DomainEntry to insert
// IN fTreadAsWildcard TRUE if DNT to be told to treat as wildcard
// OUT pdentryOld Existing DomainEntry if there is one
// Returns:
//
// History:
// 10/5/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrPrvInsertDomainEntry( IN PDOMAIN_STRING pstrDomainName, IN CDomainEntry *pdentryNew, IN BOOL fTreatAsWildcard, OUT CDomainEntry **ppdentryOld) { HRESULT hr = S_OK;
hr = m_dnt.HrInsertDomainName(pstrDomainName, pdentryNew, fTreatAsWildcard, (PVOID *) ppdentryOld); if (E_INVALIDARG == hr) hr = PHATQ_BAD_DOMAIN_SYNTAX;
return hr;
}
//---[ CDomainMappingTable::HrInitLocalDomain ]--------------------------------
//
//
// Description:
// Performs initialization needed for a local domain when an entry is
// created in the DMT
// Parameters:
// IN OUT pdentry - entry to init
// IN pStrDomain - domain name of entry
// IN paqmtMessageType Message Type of message
// OUT pdmap - Domain Mapping for domain
// Returns:
// S_OK - when all succeeds
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrInitLocalDomain( IN CDomainEntry *pdentry, IN DOMAIN_STRING *pStrDomain, IN CAQMessageType *paqmtMessageType, OUT CDomainMapping *pdmap) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrInitLocalDomain"); HRESULT hr = S_OK; LPSTR szKey = NULL;
_ASSERT(pdentry); _ASSERT(pStrDomain); _ASSERT(pdmap); _ASSERT('\0' == pStrDomain->Buffer[pStrDomain->Length]);
//make copy of string to store in domain entry
szKey = (LPSTR) pvMalloc(pStrDomain->Length + sizeof(CHAR)); if (szKey == NULL) { hr = E_OUTOFMEMORY; goto Exit; }
strcpy(szKey, pStrDomain->Buffer);
//passes ownership of szKey
hr = pdentry->HrInitialize(pStrDomain->Length, szKey, pdentry, NULL, NULL); if (FAILED(hr)) goto Exit;
hr = pdentry->HrGetDomainMapping(pdmap); if (FAILED(hr)) goto Exit;
Exit:
if (FAILED(hr) && szKey) FreePv(szKey);
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::HrInitRemoteDomain ]--------------------------------
//
//
// Description:
// Performs initialization needed for a remote domain when an entry is
// created in the DMT.
// Parameters:
// IN pdentry - entry to init
// IN pStrDomain - domain name of entry
// IN pIntDomainInfo - Internal config info for domain
// IN paqmtMessageType - Message type returned by routing
// IN pIMessageRouter - Message Router interface for this message
// OUT pdmap - Domain Mapping for domain
// OUT ppdmq - destmsgqueue for domain
// OUT pplmq - LinkMsgQueue that this queue should be associated with
// caller should call HrAddQueue once entry is in DMT
// Returns:
// S_OK on success.
// E_OUTOFMEMORY when allocations fail
// History:
// 6/24/98 - Mikeswa Modified... added pplmq param and removed call to
// HrAddQueue
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrInitRemoteDomain( IN CDomainEntry *pdentry, IN DOMAIN_STRING *pStrDomain, IN CInternalDomainInfo *pIntDomainInfo, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, OUT CDomainMapping *pdmap, OUT CDestMsgQueue **ppdmq, OUT CLinkMsgQueue **pplmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrInitRemoteDomain"); HRESULT hr = S_OK; HRESULT hrRoutingDiag = S_OK; LPSTR szKey = NULL; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; BOOL fEntryInit = FALSE;
_ASSERT(ppdmq); _ASSERT(pplmq); _ASSERT(pdentry); _ASSERT(pStrDomain); _ASSERT(pdmap); _ASSERT(pIMessageRouter); _ASSERT('\0' == pStrDomain->Buffer[pStrDomain->Length]);
//Initialze out params
*ppdmq = NULL; *pplmq = NULL;
//make copy of string to store in domain entry
szKey = (LPSTR) pvMalloc(pStrDomain->Length + sizeof(CHAR)); if (szKey == NULL) { hr = E_OUTOFMEMORY; goto Exit; }
strcpy(szKey, pStrDomain->Buffer);
hr = HrGetNextHopLink(pdentry, szKey, pStrDomain->Length, pIntDomainInfo, paqmtMessageType, pIMessageRouter, FALSE, &plmq, &hrRoutingDiag); if (FAILED(hr)) goto Exit;
pdmq = new CDestMsgQueue(m_paqinst, paqmtMessageType, pIMessageRouter); if (!pdmq) { hr = E_OUTOFMEMORY; goto Exit; }
//passes ownership of szKey & pdmq
hr = pdentry->HrInitialize(pStrDomain->Length, szKey, pdentry, pdmq, NULL); if (FAILED(hr)) goto Exit;
fEntryInit = TRUE; //we cannot delete pdmq or szKey now
//get the newly created domain mapping so we can initialize the queue
hr = pdentry->HrGetDomainMapping(pdmap); if (FAILED(hr)) goto Exit;
//Initialize queue to use this domain mapping using the DomainMapping we just got
hr = pdmq->HrInitialize(pdmap); if (FAILED(hr)) goto Exit;
//Associate link with DMQ
pdmq->SetRouteInfo(plmq);
//Set routing error if there was one.
pdmq->SetRoutingDiagnostic(hrRoutingDiag);
*ppdmq = pdmq; *pplmq = plmq;
Exit:
//Cleanup failure cases
if (FAILED(hr) && !fEntryInit) { if (szKey) FreePv(szKey);
if (NULL != pdmq) { //once domain entry has been initialized, it owns pdmq
pdmq->HrDeinitialize(); pdmq->Release(); _ASSERT(NULL == *ppdmq); }
if (NULL != plmq) { plmq->HrDeinitialize(); } }
if (plmq && !*pplmq) //we haven't passed refernce to OUT param
plmq->Release();
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::HrCreateQueueForEntry ]----------------------------
//
//
// Description:
// Create a new queue for an already existing domain entry.
//
// Currently, this is done by creating a new queue and link, and
// attempting to associate the queue with the domain entry.
// Parameters:
// IN pdentry - entry to add queue to
// IN pStrDomain - domain name of entry
// IN pIntDomainInfo - Internal config info for domain
// IN paqmtMessageType - Message type returned by routing
// IN pIMessageRouter - Message Router interface for this message
// IN pdmap - Domain Mapping for domain
// OUT ppdmq - destmsgqueue for domain
// Returns:
// S_OK on succcess
// History:
// 6/2/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrCreateQueueForEntry( IN CDomainEntry *pdentry, IN DOMAIN_STRING *pStrDomain, IN CInternalDomainInfo *pIntDomainInfo, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, IN CDomainMapping *pdmap, OUT CDestMsgQueue **ppdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrCreateQueueForEntry"); _ASSERT(pdentry); HRESULT hr = S_OK; HRESULT hrRoutingDiag = S_OK; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; LPSTR szKey = pdentry->szGetDomainName();
*ppdmq = NULL; _ASSERT(pStrDomain); _ASSERT(pdmap); _ASSERT(pIMessageRouter); _ASSERT('\0' == pStrDomain->Buffer[pStrDomain->Length]);
hr = HrGetNextHopLink(pdentry, szKey, pStrDomain->Length, pIntDomainInfo, paqmtMessageType, pIMessageRouter, FALSE, &plmq, &hrRoutingDiag); if (FAILED(hr)) goto Exit;
pdmq = new CDestMsgQueue(m_paqinst, paqmtMessageType, pIMessageRouter); if (NULL == pdmq) { hr = E_OUTOFMEMORY; goto Exit; } DebugTrace((LPARAM) szKey, "INFO: Creating new Destination Message Queue for domain %s", szKey);
hr = pdmq->HrInitialize(pdmap); if (FAILED(hr)) goto Exit;
//Associate link with DMQ
pdmq->SetRouteInfo(plmq);
//Set routing error if there was one.
pdmq->SetRoutingDiagnostic(hrRoutingDiag);
//Now attempt to associate newly created queue/link pair with domain entry
hr = pdentry->HrAddUniqueDestMsgQueue(pdmq, ppdmq);
if (SUCCEEDED(hr)) { *ppdmq = pdmq;
//Only add the queue in this case... if a queue is already in the entry, then
//the other thread must have already (or soon will) call HrAddQueue... we
//should not call it twice.
hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) { // Remove DMQ from link w/o notify since we never added it
(*ppdmq)->RemoveDMQFromLink(FALSE);
// Release the link we couldn't use and get the
// currently unreachable link
plmq->Release(); plmq = plmqGetCurrentlyUnreachable(); if (plmq) { pdmq->SetRouteInfo(plmq); hr = plmq->HrAddQueue(*ppdmq); if (FAILED(hr)) { (*ppdmq)->RemoveDMQFromLink(FALSE); } }
// Schedule a reset routes to clean up the currently
// unreachable queue
m_paqinst->ResetRoutes(RESET_NEXT_HOPS);
// If we still have a failed hr, goto exit
if (FAILED(hr)) goto Exit; } } else { DebugTrace((LPARAM) this, "INFO: Thread swap while trying to add queue for domain %s", szKey); _ASSERT(*ppdmq != pdmq); _ASSERT(*ppdmq && "HrAddUniqueDestMsgQueue failed without returning an error code"); hr = S_OK; //return new value
//Remove link from DMQ... since we will never call HrAddQueue
//don't notify link since it was never added
pdmq->RemoveDMQFromLink(FALSE); }
Exit:
//Cleanup failure cases (including if queue created is not used)
if (FAILED(hr) || (*ppdmq != pdmq)) { if (NULL != pdmq) { //once domain entry has been initialized, it owns pdmq
pdmq->Release(); } }
if (NULL != plmq) plmq->Release();
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::LogDomainUnreachableEvent] ------------------------
//
//
// Description:
// Logs an event for an unreachable domain
// Parameters:
// IN fCurrentlyUnreachable Is the domain currently unreachable or
// completely unreachable?
// IN szDomain Final destination domain
// History:
// 3/8/99 - AWetmore Created
//
//-----------------------------------------------------------------------------
void CDomainMappingTable::LogDomainUnreachableEvent(BOOL fCurrentlyUnreachable, LPCSTR szDomain) {
DWORD dwMessageId = (fCurrentlyUnreachable) ? AQUEUE_DOMAIN_CURRENTLY_UNREACHABLE : AQUEUE_DOMAIN_UNREACHABLE;
LPSTR rgszSubStrings[1];
rgszSubStrings[0] = (char*)szDomain;
if (m_paqinst) { m_paqinst->HrTriggerLogEvent( dwMessageId, // Message ID
TRAN_CAT_QUEUE_ENGINE, // Category
1, // Word count of substring
(const char**)&rgszSubStrings[0], // Substring
EVENTLOG_WARNING_TYPE, // Type of the message
0, // No error code
LOGEVENT_LEVEL_MINIMUM, // Logging level
"", // Key to identify this event
LOGEVENT_FLAG_PERIODIC // Event logging option
); } }
//---[ CDomainMappingTable::HrGetNextHopLink ]------------------------------
//
//
// Description:
// Creates and initializes the CLinkMsgQueue object for this message
// (if neccessary). Calls router to get next hop info
// Parameters:
// IN pdentry Entry that is being initialized for destination
// IN szDomain Final destination domain
// IN cbDomain string length in bytes of domain (without \0)
// IN pIntDomainInfo Domain info for final destination domain
// IN paqmtMessageType Message type of this message
// IN pIMessageRouter Routing interface for this message
// IN fDMTLocked TRUE if DMT is already locked
// OUT pplmq Resulting link msg queue
// OUT phrRoutingDiag If next hop is unreachable, this tells us why
// Returns:
// S_OK on success
// History:
// 6/19/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrGetNextHopLink( IN CDomainEntry *pdentry, IN LPSTR szDomain, IN DWORD cbDomain, IN CInternalDomainInfo *pIntDomainInfo, IN CAQMessageType *paqmtMessageType, IN IMessageRouter *pIMessageRouter, IN BOOL fDMTLocked, OUT CLinkMsgQueue **pplmq, OUT HRESULT *phrRoutingDiag) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrGetNextHopLink"); HRESULT hr = S_OK; BOOL fCalledGetNextHop = FALSE; BOOL fValidSMTP = FALSE; BOOL fOwnsScheduleId = FALSE; LPSTR szRouteAddressType = NULL; LPSTR szRouteAddress = NULL; LPSTR szRouteAddressClass = NULL; LPSTR szConnectorName = NULL; DWORD dwScheduleID = 0; DWORD dwNextHopType = 0; CLinkMsgQueue *plmq = NULL; CLinkMsgQueue *plmqTmp = NULL; LPSTR szOwnedDomain = NULL; // string buffer that is "owned" by an entry
CDomainEntry *pdentryLink = NULL; //entry for link
CDomainEntry *pdentryTmp = NULL; DOMAIN_STRING strNextHop; DWORD cbRouteAddress = 0; CAQScheduleID aqsched; IMessageRouterLinkStateNotification *pILinkStateNotify = NULL; LinkFlags lf = eLinkFlagsExternalSMTPLinkInfo; *phrRoutingDiag = S_OK;
_ASSERT(pdentry); _ASSERT(szDomain); _ASSERT(pIntDomainInfo); _ASSERT(paqmtMessageType); _ASSERT(pIMessageRouter); _ASSERT(pplmq);
hr = pIMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); if (FAILED(hr)) { pILinkStateNotify = NULL; hr = S_OK; }
//If we can route this domain.... call router to get next hop
//We do not route TURN/ETRN domains... or local drop domains
//Also check to see if the domain is configured as a local domain...
//if it is, return the local link
if (DOMAIN_INFO_LOCAL_MAILBOX & pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags) { //The likely scenario of this happening is if a domain was previously
//configured as remote and then reconfigured as local.
m_plmqLocal->AddRef(); *pplmq = m_plmqLocal; goto Exit; } else if (!(pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY | DOMAIN_INFO_LOCAL_DROP))) { hr = pIMessageRouter->GetNextHop(MTI_ROUTING_ADDRESS_TYPE_SMTP, szDomain, paqmtMessageType->dwGetMessageType(), &szRouteAddressType, &szRouteAddress, &dwScheduleID, &szRouteAddressClass, &szConnectorName, &dwNextHopType);
fCalledGetNextHop = TRUE; *pplmq = NULL;
if(MTI_NEXT_HOP_TYPE_UNREACHABLE == dwNextHopType) { //If the next hop is unreachable, store the reason for the unreachable
//error into *phrRoutingDiag (which is used by aqueue DSN generation).
const char *rgszStrings[2] = { szDomain, NULL };
if (m_paqinst) { m_paqinst->HrTriggerLogEvent( PHATQ_UNREACHABLE_DOMAIN, // Message ID
TRAN_CAT_QUEUE_ENGINE, // Category
2, // Word count of substring
rgszStrings, // Substring
EVENTLOG_WARNING_TYPE, // Type of the message
hr, // error code
LOGEVENT_LEVEL_FIELD_ENGINEERING, // Logging level
"phatq", // key to this event
LOGEVENT_FLAG_PERIODIC, // Logging option
1, // index of format message string in rgszStrings
GetModuleHandle(AQ_MODULE_NAME) // module handle to format a message
); }
ReUnreachableErrorToAqueueError(hr, phrRoutingDiag); hr = S_OK; }
if (FAILED(hr)) { RequestResetRoutesRetryIfNecessary(); ErrorTrace((LPARAM) this, "GetNextHop failed with hr - 0x%08X", hr);
//treat all failures as a routing currently unreachable
hr = S_OK; dwNextHopType = MTI_NEXT_HOP_TYPE_CURRENTLY_UNREACHABLE; }
if (MTI_NEXT_HOP_TYPE_CURRENTLY_UNREACHABLE == dwNextHopType) { LogDomainUnreachableEvent(TRUE, szDomain); *pplmq = m_plmqCurrentlyUnreachable; } else if (MTI_NEXT_HOP_TYPE_UNREACHABLE == dwNextHopType) { LogDomainUnreachableEvent(FALSE, szDomain); *pplmq = m_plmqUnreachable; } else if ((MTI_NEXT_HOP_TYPE_SAME_VIRTUAL_SERVER == dwNextHopType) || (szRouteAddressType && lstrcmpi(MTI_ROUTING_ADDRESS_TYPE_SMTP, szRouteAddressType))) { //Handle any cases that might be considered local delivery
*pplmq = m_plmqLocal; } else if (!szRouteAddressType || ('\0' == *szRouteAddressType) || !szRouteAddress || ('\0' == *szRouteAddress)) { //This is a bogus combination of values... try try again
hr = E_FAIL; goto Exit; } else { fValidSMTP = TRUE; fOwnsScheduleId = TRUE; //At this point we should have valid SMTP values for the address
_ASSERT(szRouteAddressType); _ASSERT(szRouteAddress); _ASSERT(!lstrcmpi(MTI_ROUTING_ADDRESS_TYPE_SMTP, szRouteAddressType));
if (MTI_NEXT_HOP_TYPE_PEER_SMTP1_BYPASS_CONFIG_LOOKUP == dwNextHopType || MTI_NEXT_HOP_TYPE_PEER_SMTP2_BYPASS_CONFIG_LOOKUP == dwNextHopType) { lf = eLinkFlagsInternalSMTPLinkInfo; }
}
if (!fValidSMTP) { //Must be going to one of them-there global queues.
hr = S_OK;
if (*pplmq) (*pplmq)->AddRef(); else hr = E_FAIL;
//Our work here is done
goto Exit; }
}
if ((!fCalledGetNextHop) || (!lstrcmpi(szDomain, szRouteAddress))) { //final destination and next hop are the same
DebugTrace((LPARAM) this, "DEBUG: Routing case 1 - same next hop and final dest");
// See if there is already a link for this schedule ID
aqsched.Init(pIMessageRouter, dwScheduleID);
hr = pdentry->HrGetLinkMsgQueue(&aqsched, &plmq); if (FAILED(hr)) { hr = S_OK;
//link does not exist for this schedule id yet
plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; } fOwnsScheduleId = FALSE; //link now owns it
DebugTrace((LPARAM) szDomain, "INFO: Creating new Link for domain %s", szDomain);
hr = plmq->HrInitialize(m_paqinst, pdentry, cbDomain, szDomain, lf, szConnectorName); if (FAILED(hr)) goto Exit;
hr = pdentry->HrAddUniqueLinkMsgQueue(plmq, &plmqTmp); if (FAILED(hr)) { //Another link was inserted since we called get link msg queue
DebugTrace((LPARAM) this, "DEBUG: Routing case 2(a) - next hop link created by other thread"); _ASSERT(plmqTmp); plmq->HrDeinitialize(); plmq->Release(); plmq = plmqTmp; hr = S_OK; } } else { DebugTrace((LPARAM) this, "DEBUG: Routing case 2(b) - next hop link created by other thread"); } } else { //next hop is different from final destination
cbRouteAddress = strlen(szRouteAddress);
//First see if there is an entry for this link
hr = HrPrvGetDomainEntry(cbRouteAddress, szRouteAddress, fDMTLocked, &pdentryLink); if (AQUEUE_E_INVALID_DOMAIN == hr) { //an entry for this link does not exist... add one for this link
hr = S_OK; DebugTrace((LPARAM) this, "DEBUG: Routing case 3 - next hop entry does not exist");
szOwnedDomain = (LPSTR) pvMalloc(sizeof(CHAR)*(cbRouteAddress+1)); if (!szOwnedDomain) { hr = E_OUTOFMEMORY; goto Exit; } lstrcpy(szOwnedDomain, szRouteAddress);
pdentryLink = new CDomainEntry(m_paqinst); if (!pdentryLink) { hr = E_OUTOFMEMORY; goto Exit; }
plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; }
fOwnsScheduleId = FALSE; //link now owns it
//passes ownership of szOwnedDomain
hr = pdentryLink->HrInitialize(cbRouteAddress, szOwnedDomain, pdentryLink, NULL, plmq); if (FAILED(hr)) goto Exit;
hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, lf, szConnectorName); if (FAILED(hr)) goto Exit;
//insert entry in DMT
strNextHop.Length = (USHORT) cbRouteAddress; strNextHop.Buffer = szOwnedDomain; strNextHop.MaximumLength = (USHORT) cbRouteAddress;
if (!fDMTLocked) m_slPrivateData.ExclusiveLock();
hr = HrPrvInsertDomainEntry(&strNextHop, pdentryLink, FALSE, &pdentryTmp);
if (hr == DOMHASH_E_DOMAIN_EXISTS) { DebugTrace((LPARAM) this, "DEBUG: Routing case 4 - next hop entry did not exist... inserted by other thread"); plmq->Release(); plmq = NULL; pdentryTmp->AddRef(); pdentryLink->HrDeinitialize(); pdentryLink->Release(); pdentryLink = pdentryTmp; hr = S_OK;
//Will fall through to case as if an entry was found by HrGetDomainEntry
} else if (SUCCEEDED(hr)) { pdentryLink->AddRef(); }
if (!fDMTLocked) m_slPrivateData.ExclusiveUnlock();
if (FAILED(hr)) goto Exit;
} else if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: General DMT failure - hr 0x%08X", hr); //general failure... bail
goto Exit; }
if (!plmq) { DebugTrace((LPARAM) this, "DEBUG: Routing case 5 - next hop entry exists"); //An entry exists for this next hop... use link if possible
// 1 - Get link for this schedule ID.. if it exists use it
// 2 - Create another link and attempt to insert it
_ASSERT(pdentryLink); aqsched.Init(pIMessageRouter, dwScheduleID);
hr = pdentryLink->HrGetLinkMsgQueue(&aqsched, &plmq); if (FAILED(hr)) { hr = S_OK; //link does not exist for this schedule id yet
DebugTrace((LPARAM) this, "DEBUG: Routing case 6 - next hop link does not exist");
szOwnedDomain = pdentryLink->szGetDomainName();
plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; }
fOwnsScheduleId = FALSE; //link now owns it
hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, lf, szConnectorName); if (FAILED(hr)) goto Exit;
hr = pdentryLink->HrAddUniqueLinkMsgQueue(plmq, &plmqTmp); if (FAILED(hr)) { //Another link was inserted since we called get link msg queue
DebugTrace((LPARAM) this, "DEBUG: Routing case 7 - next hop link created by other thread"); _ASSERT(plmqTmp); plmq->Release(); plmq = plmqTmp; hr = S_OK; } } else { DebugTrace((LPARAM) this, "DEBUG: Routing case 8 - next hop link exists"); }
}
}
_ASSERT(plmq && "We should have allocated a link by this point"); *pplmq = plmq;
Exit:
if (pdentryLink) pdentryLink->Release();
if (fCalledGetNextHop) { //
// If we have not passed the schedule ID on to a link, we
// must notify routing that we are not using it (to avoid a leak).
// This needs to be be done *before* we release the strings
// and routing interfaces. If we hit this case, we have
// either failed to create a link, or another link has
// been created by another thread.
//
if (fOwnsScheduleId && pILinkStateNotify && pIMessageRouter) { FILETIME ftNotUsed = {0,0}; DWORD dwSetNotUsed = LINK_STATE_NO_ACTION; DWORD dwUnsetNotUsed = LINK_STATE_NO_ACTION; pILinkStateNotify->LinkStateNotify( szDomain, pIMessageRouter->GetTransportSinkID(), dwScheduleID, szConnectorName, LINK_STATE_LINK_NO_LONGER_USED, 0, //consecutive failures
&ftNotUsed, &dwSetNotUsed, &dwUnsetNotUsed); }
//
// Free Strings returned by GetNextHop
//
_VERIFY(SUCCEEDED(pIMessageRouter->GetNextHopFree( MTI_ROUTING_ADDRESS_TYPE_SMTP, szDomain, szConnectorName, szRouteAddressType, szRouteAddress, szRouteAddressClass)));
}
if (pILinkStateNotify) pILinkStateNotify->Release();
if (FAILED(hr) && plmq) plmq->Release();
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::HrGetOrCreateLink ]--------------------------------
//
//
// Description:
// Gets or creates a link object for a domain name
// Parameters:
// IN szRouteAddress Final destination domain
// IN cbRouteAddress string length in bytes of domain (without \0)
// IN dwScheduleID Schedule ID for link (used in create)
// IN szConnectorName Name (stringized GUID) of connector in DS
// IN pIMessageRouter Routing interface for this message (used in create)
// IN fCreateIfNotExist Create the link if it doesn't exist?
// OUT pplmq Resulting link msg queue
// OUT pfRemoveOwnedSchedule FALSE if a new link was created, TRUE on errors
// and in case the link was addref'ed.
// Returns:
// S_OK on success
// History:
// 7/6/1999 - AWetmore Created
// 12/30/1999 - MikeSwa Modified to not notify until connection attempt
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrGetOrCreateLink( IN LPSTR szRouteAddress, IN DWORD cbRouteAddress, IN DWORD dwScheduleID, IN LPSTR szConnectorName, IN IMessageRouter *pIMessageRouter, IN BOOL fCreateIfNotExist, IN DWORD linkFlags, OUT CLinkMsgQueue **pplmq, OUT BOOL *pfRemoveOwnedSchedule) { TraceFunctEnter("CDomainMappingTable::HrGetOrCreateLink"); HRESULT hr = S_OK; BOOL fValidSMTP = FALSE; LPSTR szRouteAddressType = NULL; LPSTR szRouteAddressClass = NULL; DWORD dwNextHopType = 0; CLinkMsgQueue *plmq = NULL; CLinkMsgQueue *plmqTmp = NULL; LPSTR szOwnedDomain = NULL; // string buffer that is "owned" by an entry
CDomainEntry *pdentryLink = NULL; //entry for link
CDomainEntry *pdentryTmp = NULL; DOMAIN_STRING strNextHop; CAQScheduleID aqsched; IMessageRouterLinkStateNotification *pILinkStateNotify = NULL; CAQStats aqstats; *pfRemoveOwnedSchedule = TRUE;
_ASSERT(szRouteAddress); _ASSERT(pplmq);
//First see if there is an entry for this link
hr = HrPrvGetDomainEntry(cbRouteAddress, szRouteAddress, FALSE, &pdentryLink); if (AQUEUE_E_INVALID_DOMAIN == hr && fCreateIfNotExist) { _ASSERT(pIMessageRouter); hr = pIMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); if (FAILED(hr)) { pILinkStateNotify = NULL; goto Exit; }
//an entry for this link does not exist... add one for this link
hr = S_OK; DebugTrace((LPARAM) this, "DEBUG: Routing case 3 - next hop entry does not exist");
szOwnedDomain = (LPSTR) pvMalloc(sizeof(CHAR)*(cbRouteAddress+1)); if (!szOwnedDomain) { hr = E_OUTOFMEMORY; goto Exit; } lstrcpy(szOwnedDomain, szRouteAddress);
pdentryLink = new CDomainEntry(m_paqinst); if (!pdentryLink) { hr = E_OUTOFMEMORY; goto Exit; }
plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; }
*pfRemoveOwnedSchedule = FALSE;
//passes ownership of szOwnedDomain
hr = pdentryLink->HrInitialize(cbRouteAddress, szOwnedDomain, pdentryLink, NULL, plmq); if (FAILED(hr)) goto Exit;
hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, (LinkFlags) linkFlags, szConnectorName); if (FAILED(hr)) goto Exit;
//insert entry in DMT
strNextHop.Length = (USHORT) cbRouteAddress; strNextHop.Buffer = szOwnedDomain; strNextHop.MaximumLength = (USHORT) cbRouteAddress;
m_slPrivateData.ExclusiveLock();
hr = HrPrvInsertDomainEntry(&strNextHop, pdentryLink, FALSE, &pdentryTmp);
if (hr == DOMHASH_E_DOMAIN_EXISTS) { DebugTrace((LPARAM) this, "DEBUG: Routing case 4 - next hop entry did not exist... inserted by other thread"); plmq->Release(); plmq = NULL; pdentryTmp->AddRef(); pdentryLink->HrDeinitialize(); pdentryLink->Release(); pdentryLink = pdentryTmp; hr = S_OK;
//Will fall through to case as if an entry was found by HrGetDomainEntry
} else if (SUCCEEDED(hr)) { pdentryLink->AddRef(); }
m_slPrivateData.ExclusiveUnlock();
if (FAILED(hr)) goto Exit;
} else if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: General DMT failure - hr 0x%08X", hr); //general failure... bail
goto Exit; }
if (!plmq) { _ASSERT(pdentryLink); DebugTrace((LPARAM) this, "DEBUG: Routing case 5 - next hop entry exists"); //An entry exists for this next hop... use link if possible
// 1 - Get link for this schedule ID.. if it exists use it
// 2 - Create another link and attempt to insert it
_ASSERT(pdentryLink); aqsched.Init(pIMessageRouter, dwScheduleID);
hr = pdentryLink->HrGetLinkMsgQueue(&aqsched, &plmq); if (FAILED(hr) && fCreateIfNotExist) { hr = S_OK; //link does not exist for this schedule id yet
DebugTrace((LPARAM) this, "DEBUG: Routing case 6 - next hop link does not exist");
szOwnedDomain = pdentryLink->szGetDomainName();
if (!pILinkStateNotify) { _ASSERT(pIMessageRouter); hr = pIMessageRouter->QueryInterface(IID_IMessageRouterLinkStateNotification, (VOID **) &pILinkStateNotify); if (FAILED(hr)) { pILinkStateNotify = NULL; goto Exit; } }
plmq = new CLinkMsgQueue(dwScheduleID, pIMessageRouter, pILinkStateNotify); if (!plmq) { hr = E_OUTOFMEMORY; goto Exit; }
*pfRemoveOwnedSchedule = FALSE;
hr = plmq->HrInitialize(m_paqinst, pdentryLink, cbRouteAddress, szOwnedDomain, (LinkFlags) linkFlags, szConnectorName); if (FAILED(hr)) goto Exit;
hr = pdentryLink->HrAddUniqueLinkMsgQueue(plmq, &plmqTmp); if (FAILED(hr)) { //Another link was inserted since we called get link msg queue
DebugTrace((LPARAM) this, "DEBUG: Routing case 7 - next hop link created by other thread"); _ASSERT(plmqTmp); plmq->Release(); plmq = plmqTmp; hr = S_OK; } } else { DebugTrace((LPARAM) this, "DEBUG: Routing case 8 - next hop link exists"); }
}
_ASSERT(plmq && "We should have allocated a link by this point");
if (plmq) {
//We count a SetLinkState as a link state notification.... do not
//notify again before attempting a connection or we will break link
//state notifications.
plmq->dwModifyLinkState(LINK_STATE_PRIV_HAVE_SENT_NOTIFICATION, LINK_STATE_NO_ACTION); aqstats.m_dwNotifyType = NotifyTypeNewLink | NotifyTypeLinkMsgQueue; aqstats.m_plmq = plmq; hr = m_paqinst->HrNotify(&aqstats, TRUE); }
if (SUCCEEDED(hr)) *pplmq = plmq;
Exit:
if (pdentryLink) pdentryLink->Release();
if (pILinkStateNotify) pILinkStateNotify->Release();
if (FAILED(hr) && plmq) plmq->Release();
TraceFunctLeave(); return hr; }
//---[ CDomainMappingTable::AddDMQToEmptyList ]--------------------------------
//
//
// Description:
// Used by DMQ to add itself to the list of empty queues. This function
// will aquire (and release) the appropriate locks
// Parameters:
// IN pdmq DestMsgQueue to add to list
// Returns:
// -
// History:
// 9/12/98 - MikeSwa Created
// 5/5/99 - MikeSwa Changed to TryExclusiveLock to avoid potential
// deadlock.
//
//-----------------------------------------------------------------------------
void CDomainMappingTable::AddDMQToEmptyList(CDestMsgQueue *pdmq) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::AddDMQToEmptyList"); if (m_slPrivateData.TryExclusiveLock()) { pdmq->InsertQueueInEmptyQueueList(&m_liEmptyDMQHead); m_slPrivateData.ExclusiveUnlock(); } else { DebugTrace((LPARAM) this, "AddDMQToEmptyList could not get m_slPrivateData Lock"); } TraceFunctLeave(); }
//---[ CDomainMappingTable::fNeedToWalkEmptyQueueList ]------------------------
//
//
// Description:
// Checks list of Empty queues to see if we need to call
// fDeleteExpiredQueues(). The caller of this function *must* have
// m_slPrivateData is shared mode, if it returns TRUE, the caller *must*
// release the lock and call fDeleteExpiredQueues() (which will aquire
// the lock Exclusively)
// Parameters:
// -
// Returns:
// TRUE if fDeleteExpiredQueues should be called.
// History:
// 9/12/98 - MikeSwa Created
// 6/27/2000 - MikeSwa fixed short-circuit logic
//
//-----------------------------------------------------------------------------
BOOL CDomainMappingTable::fNeedToWalkEmptyQueueList() { BOOL fRet = FALSE; PLIST_ENTRY pli = m_liEmptyDMQHead.Flink; CDestMsgQueue *pdmq = NULL; DWORD dwDMQState = 0; DWORD cMisplacedQueues = 0; //# of queues in list that should not be
_ASSERT(pli);
// Only one thread at a time should be searching.
if (1 != InterlockedIncrement((PLONG) &m_cThreadsForEmptyDMQList)) { InterlockedDecrement((PLONG) &m_cThreadsForEmptyDMQList); return FALSE; }
if(m_dwFlags & DMT_FLAGS_RESET_ROUTES_IN_PROGRESS) return fRet; // we shouldn't remove queues during reset routes
while (&m_liEmptyDMQHead != pli) { pdmq = CDestMsgQueue::pdmqGetDMQFromEmptyListEntry(pli); _ASSERT(pdmq); dwDMQState = pdmq->dwGetDMQState();
//See if it is empty and expired.. if so, we have a winner
if (dwDMQState & CDestMsgQueue::DMQ_EMPTY) { if (dwDMQState & CDestMsgQueue::DMQ_EXPIRED) { fRet = TRUE; break; } } else { //
// The queue is no longer empty... we will remove it
// from this list the next time we have the exclusive
// lock.
//
cMisplacedQueues++;
//
// If there are a large number of non-empty DMQs, we
// want wish to return TRUE even though there are no DMQs to
// delete... just so we can clean the list of non-empty DMQs.
//
if (MAX_MISPLACED_QUEUES_IN_EMPTY_LIST < cMisplacedQueues) { fRet = TRUE; break; } } pli = pli->Flink;
}
// If we decide to delete queues we keep this count until we have done
// the work, if we decide not to then we release the count so someone
// else will walk the list again
if(fRet == FALSE) { InterlockedDecrement((PLONG) &m_cThreadsForEmptyDMQList); }
//NOTE: The reason there may be non-empty queues in this list, is that
//we cannot remove the queues from the list when we ENQUEUE a message
//because it would deadlock (we already have the m_slPrivateData lock
//in shared mode), and removing the queue requires this lock in exclusive
//mode.
return fRet; }
//---[ CDomainMappingTable::fDeleteExpiredQueues ]-----------------------------
//
//
// Description:
// Removes DMQs from empty list. DMQs will be deleted if they have expired
// and do not have any messages on them. Non-empty DMQs will be removed
// from the list as well.
//
// The DMT m_slPrivateData lock should *not* be held when this is called.
// This function will aquire it exclusively.
//
// Parameters:
// -
// Returns:
// TRUE if any queues, links, or entries were deleted
// FALSE if no queues were deleted
// History:
// 9/12/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CDomainMappingTable::fDeleteExpiredQueues() { PLIST_ENTRY pli = NULL; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; DWORD dwDMQState = 0; CDomainMapping *pdmap = NULL; CDomainEntry *pdentry = NULL; CDomainEntry *pdentryOld = NULL; BOOL fRemovedQueues = FALSE; HRESULT hr = S_OK; DOMAIN_STRING strDomain;
// Don't attempt this if it would require waiting for a lock. This
// decreases contention and also prevents multiple threads from trying
// to do this work
if (!m_slPrivateData.TryExclusiveLock()) goto Exit;
pli = m_liEmptyDMQHead.Flink; while (&m_liEmptyDMQHead != pli) { _ASSERT(pli); pdmq = CDestMsgQueue::pdmqGetDMQFromEmptyListEntry(pli); _ASSERT(pdmq); dwDMQState = pdmq->dwGetDMQState();
if (!(dwDMQState & CDestMsgQueue::DMQ_EMPTY)) { //If it is not empty - remove it from the list
pli = pli->Flink; pdmq->RemoveQueueFromEmptyQueueList(); continue; } else if (!(dwDMQState & CDestMsgQueue::DMQ_EXPIRED)) { //If this queue hasn't expired... check the next to see if it is empty
pli = pli->Flink; continue; } else { //We need to delete this DMQ
pli = pli->Flink; //get next LIST_ENTRY before we delete the queue
//Add a reference to the DMQ, so we can guarantee its lifespan
pdmq->AddRef();
//Remove the queue from the list of empty queues
pdmq->RemoveQueueFromEmptyQueueList();
//Get the domain mapping (and domain entry) for this DMQ
pdmq->GetDomainMapping(&pdmap); _ASSERT(pdmap); pdentry = pdmap->pdentryGetQueueEntry();
//Remove the DMQ from its associated link
plmq = pdmq->plmqGetLink(); pdmq->RemoveDMQFromLink(TRUE); if (plmq) { //Remove the link from the DMT if it is empty
plmq->RemoveLinkIfEmpty(); plmq->Release(); plmq = NULL; }
//Now that we have the domain entry, we can remove it the DMQ
//from it.
_ASSERT(pdentry); pdentry->RemoveDestMsgQueue(pdmq);
//Remove Entry if needed
if (pdentry->fSafeToRemove()) { //There are no links or queues left on this entry... we
//can remove it from the hash table and delete it
pdentry->InitDomainString(&strDomain); hr = m_dnt.HrRemoveDomainName(&strDomain, (void **) &pdentryOld);
_ASSERT(DOMHASH_E_NO_SUCH_DOMAIN != hr); if (SUCCEEDED(hr)) { _ASSERT(pdentryOld == pdentry); pdentryOld = NULL; pdentry->Release(); pdentry = NULL; } }
//If there are no enqueues pending, this will be the last reference
//for the DMQ. If there is a enqueue pending, then there may be
//references outstanding that will be released when they see the
//updated DMT version number.
pdmq->Release();
fRemovedQueues = TRUE; } }
//Update version number, so other threads will know that queues
//have been removed from the DMT.
if (fRemovedQueues) m_dwInternalVersion++;
m_slPrivateData.ExclusiveUnlock();
Exit: // Release our count so someone else can search the list and possibly
// remove queues ...
InterlockedDecrement((PLONG) &m_cThreadsForEmptyDMQList); return fRemovedQueues; }
//---[ CDomainMappingTable::RequestResetRoutesRetryIfNecessary ]---------------
//
//
// Description:
// This is called when routing fails and we need to call reset routes
// at a later time to try it.
// Parameters:
// -
// Returns:
// -
// History:
// 11/15/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDomainMappingTable::RequestResetRoutesRetryIfNecessary() { HRESULT hr = S_OK;
dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_GET_NEXT_HOP_FAILED);
if (DMT_FLAGS_RESET_ROUTES_IN_PROGRESS & m_dwFlags) return;
//We need to reqest a callback for a later reset routes. We should
//only allow one callback pending at a time. If this thread increments
//the count for a 0->1 transition, then we can request the callback
if (1 == InterlockedIncrement((PLONG) &m_cResetRoutesRetriesPending)) { hr = m_paqinst->SetCallbackTime( CDomainMappingTable::RetryResetRoutes, this, g_cResetRoutesRetryMinutes);
if (FAILED(hr)) InterlockedDecrement((PLONG) &m_cResetRoutesRetriesPending); } }
//---[ CDomainMappingTable::RetryResetRoutes ]--------------------------------
//
//
// Description:
// Handles callback for reset routes. This codepath will be used if
// GetNextHop failes. Routing has no internal logic to remember if
// a failure has happened... and no method of sceduling a callback. By
// periodically call reset routes, we can centrally solve this problem
// for all routing sinks.
// Parameters:
// pvThis "this" pointer for CDomainMappingTable
// Returns:
// -
// History:
// 11/15/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDomainMappingTable::RetryResetRoutes(PVOID pvThis) { _ASSERT(pvThis); CDomainMappingTable *pdmt = (CDomainMappingTable *)pvThis; CAQSvrInst *paqinst = pdmt->m_paqinst;
//Make sure shutdown has not been started. This instance is waits for
//all threads before deleting itself, so it is safe to call in to the local
//variable
if (!paqinst) return;
//Decrement the count, so another request can be queued up.
InterlockedDecrement((PLONG) &(pdmt->m_cResetRoutesRetriesPending));
//Kick off another reset routes
paqinst->ResetRoutes(RESET_NEXT_HOPS); }
//---[ CDomainMappingTable::HrBeginRerouteDomains ]----------------------------
//
//
// Description:
// Begins the process of handing a ResetRoutes. This function sets
// DMT_FLAGS_RESET_ROUTES_IN_PROGRESS and then moves all queues into
// the CurrentlyUnreachable link so that they will not be processed
// until they have been re-routed.
// Parameters:
// NONE
// Returns:
// S_OK on success
// History:
// 11/5/1998 - MikeSwa Created
// 10/31/2000 - dbraun - renamed / was part of HrRerouteDomains
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrBeginRerouteDomains() { HRESULT hr = S_OK; DWORD dwFlags = 0;
m_slPrivateData.ExclusiveLock();
//Clear the failed bit before we make any calls into routing. This way
//we can detect a failure that has happened during this reset routes
dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_GET_NEXT_HOP_FAILED);
//Make sure this flag is set. This will prevent a reset routes request
//from being generated while this thread is resetting routes. If
//GetNextHop is still failing, we want to start the retry timer
//*after* we finish we reset routes, or we will get stuck in a loop
//of constant reset routes until GetNextHop succeeds.
dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS);
//Move all of the domains to the currently unreachable link
hr = m_dnt.HrIterateOverSubDomains(NULL, CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable, this); m_slPrivateData.ExclusiveUnlock();
//Handle a failure to move the domains - if this function fails then
//the caller won't call HrCompleteRerouteDomains so we need to clear
//the reset routes in progress bit and potentially request another
//ResetRoutes
if(FAILED(hr)) { dwFlags = dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS);
if (DMT_FLAGS_GET_NEXT_HOP_FAILED & dwFlags) { //This reset routes failed... we must try again later.
RequestResetRoutesRetryIfNecessary(); } }
return hr; }
//---[ CDomainMappingTable::HrCompleteRerouteDomains ]-------------------------
//
//
// Description:
// This function completes the handling of a ResetRoutes. Mail flow has
// been restarted and this function will go through all the domains, now
// sitting in CurrentlyUnreachable, and will re-route them according to
// the new routing configuration.
// Parameters:
// NONE
// Returns:
// S_OK on success
// History:
// 11/5/1998 - MikeSwa Created
// 10/31/2000 - dbraun - renamed / was part of HrRerouteDomains
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrCompleteRerouteDomains() { HRESULT hr = S_OK; DWORD dwFlags = 0;
/* -- this would only be applicable if we supported a "partial retry
reset routes" that only called this function not the Begin above
// It is possible that we are being called WITHOUT HrBeginRerouteDomains
// being called first (if we failed here before the retry will only retry
// this part of rerouting) ... we need to make sure that
// DMT_FLAGS_RESET_ROUTES_IN_PROGRESS is set to ensure this proceeds safely
// and we need to make sure that DMT_FLAGS_GET_NEXT_HOP_FAILED is not set
// so we can pick up a failure during rerouting
dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS); dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_GET_NEXT_HOP_FAILED); */
// Rebuild routing information queues in the CurrentlyUnreachable link.
hr = HrRerouteLink(m_plmqCurrentlyUnreachable);
dwFlags = dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_RESET_ROUTES_IN_PROGRESS);
if (DMT_FLAGS_GET_NEXT_HOP_FAILED & dwFlags) { // This reset routes failed... we must try again later.
RequestResetRoutesRetryIfNecessary(); }
return hr; }
//---[ CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable ]-------------
//
//
// Description:
// This iterator moves a given domain's queues to the CurrentlyUnreachable
// link to prevent SMTP from operating on them until they can be re-routed.
// Parameters:
// IN pvContext - pointer to context
// (pointer to BOOL that tells if we force a re-route)
// IN pvData - CDomainEntry for the given domain
// IN fWildcardData - TRUE if data is a wildcard entry
// OUT pfContinue - TRUE if iterator should continue to the next entry
// OUT pfDelete - TRUE if entry should be deleted
// Returns:
// -
// History:
// 10/31/2000 - dbraun - created
//
//-----------------------------------------------------------------------------
VOID CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable( PVOID pvContext, PVOID pvData, BOOL fWildcard, BOOL *pfContinue, BOOL *pfDelete) { TraceFunctEnterEx((LPARAM) pvContext, "CDomainMappingTable::MakeSingleDomainCurrentlyUnreachable");
CDomainEntry *pdentry = (CDomainEntry *) pvData; HRESULT hr = S_OK; CDestMsgQueue *pdmq = NULL; CDomainMappingTable *pThis = (CDomainMappingTable *) pvContext; CDomainEntryQueueIterator deqit; CAQStats aqstat;
_ASSERT(pfContinue); _ASSERT(pfDelete);
*pfDelete = FALSE; *pfContinue = TRUE;
if (fWildcard) return; //we shouldn't care about wildcard entries
_ASSERT(pdentry);
//
// Initialize the aqstat to type reroute
//
aqstat.m_dwNotifyType = NotifyTypeReroute;
//Loop through dmq's and blow away their routing information -
//then route them to the currently unreachable link
hr = deqit.HrInitialize(pdentry); if (FAILED(hr)) pdmq = NULL; else pdmq = deqit.pdmqGetNextDestMsgQueue(pdmq); while (pdmq) { //Remove this queue from it's link
pdmq->RemoveDMQFromLink(TRUE);
//
// Figure out if it is useful to route this queue
// before we call into routing
//
if (pdmq->fIsEmptyAndAbandoned()) pdentry->RemoveDestMsgQueue(pdmq); else { //Add this queue to the currently unreachable link
hr = pThis->m_plmqCurrentlyUnreachable->HrAddQueue(pdmq); if (FAILED(hr)) { //We failed to add the queue to the currently unreachable link
ErrorTrace((LPARAM) pvContext, "HrAddQueue failed on reroute hr - 0x%08X", hr); hr = S_OK; } pdmq->SetRouteInfo(pThis->m_plmqCurrentlyUnreachable);
//
// Make sure the link is associated with the connection manager
//
hr = pThis->m_plmqCurrentlyUnreachable->HrNotify(&aqstat, TRUE); if (FAILED(hr)) { //If this fails... we *may* leak an unused link until the
//next reset routes (or shutdown).
ErrorTrace((LPARAM) pvContext, "HrNotify failed on reroute hr - 0x%08X", hr); hr = S_OK; } }
//On to the next queue for this entry
pdmq = deqit.pdmqGetNextDestMsgQueue(pdmq); }
//
// If it is safe to clean this up... then do so
//
if (pdentry->fSafeToRemove()) { *pfDelete = TRUE; pdentry->Release(); pdentry = NULL; }
TraceFunctLeave(); }
//---[ CDomainMappingTable::HrRerouteLink ]------------------------------------
//
//
// Description:
// Function that is used to re-route all queues on a given link.
// Parameters:
// IN plmq - pointer link to be rerouted
// Returns:
// -
// History:
// 11/8/2000 - dbraun created
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrRerouteLink(CLinkMsgQueue *plmqReroute) { TraceFunctEnterEx((LPARAM) this, "CDomainMappingTable::HrRerouteLink"); CDomainEntry *pdentry = NULL; CDestMsgQueue *pdmq = NULL; CLinkMsgQueue *plmq = NULL; CInternalDomainInfo *pIntDomainInfo = NULL; HRESULT hr = S_OK; HRESULT hrRoutingDiag = S_OK; CQuickList *pqlQueueList = NULL; DWORD cQueues = 0; DWORD dwIndex = 0; PVOID pvContext = NULL; CDomainMapping *pdmap = NULL; CAQStats aqstat;
_ASSERT(plmqReroute);
// Initialize the aqstat to type reroute
aqstat.m_dwNotifyType = NotifyTypeReroute;
// Loop through dmq's and re-route them
hr = plmqReroute->HrGetQueueListSnapshot(&pqlQueueList); if (FAILED(hr)) { cQueues = 0; } else { cQueues = pqlQueueList->dwGetCount(); }
for (dwIndex = 0; dwIndex < cQueues; dwIndex++) { // Get the dest msg queue
pdmq = (CDestMsgQueue *) pqlQueueList->pvGetItem(dwIndex, &pvContext);
// Get the domain for this queue
pdmq->GetDomainMapping(&pdmap); _ASSERT(pdmap); pdentry = pdmap->pdentryGetQueueEntry();
// Get the internal domain info for this domain
hr = m_paqinst->HrGetInternalDomainInfo( pdentry->cbGetDomainNameLength(), pdentry->szGetDomainName(), &pIntDomainInfo);
if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to get config for domain - hr 0x%08X", hr); pIntDomainInfo = NULL; }
if (pIntDomainInfo) { hr = HrGetNextHopLink( pdentry, pdentry->szGetDomainName(), pdentry->cbGetDomainNameLength(), pIntDomainInfo, pdmq->paqmtGetMessageType(), pdmq->pIMessageRouterGetRouter(), FALSE, // DMT is not locked
&plmq, &hrRoutingDiag);
if (FAILED(hr)) { //$$TODO - Deal with more exotic Get next hop errors
ErrorTrace((LPARAM) this, "ERROR: Unable to get next hop for domain - hr 0x%08X", hr); }
// Update with new routing info if we can
if (SUCCEEDED(hr)) { // Remove from the old link
pdmq->RemoveDMQFromLink(TRUE);
// Add to the new one
hr = plmq->HrAddQueue(pdmq); if (FAILED(hr)) { // Release the link we couldn't use and get the
// currently unreachable link
plmq->Release(); plmq = plmqGetCurrentlyUnreachable(); if (plmq) hr = plmq->HrAddQueue(pdmq);
// If we still have a failed hr, remove the DMQ from
// it's link, otherwise, make sure it is associated with
// the link we just added it to
if (FAILED(hr)) pdmq->RemoveDMQFromLink(TRUE); else pdmq->SetRouteInfo(plmq);
// Request reset routes retry
RequestResetRoutesRetryIfNecessary(); } else pdmq->SetRouteInfo(plmq);
// Set routing error if there was one.
pdmq->SetRoutingDiagnostic(hrRoutingDiag);
//
// Make sure the link is associated with the connection manager
//
hr = plmq->HrNotify(&aqstat, TRUE); if (FAILED(hr)) { // If this fails... we *may* leak an unused link until the
// next reset routes (or shutdown).
ErrorTrace((LPARAM) this, "HrNotify failed on reroute hr - 0x%08X", hr); hr = S_OK; } plmq->Release(); plmq = NULL; }
// Release the domain info
pIntDomainInfo->Release(); pIntDomainInfo = NULL; } }
_ASSERT(!pIntDomainInfo);
if (pqlQueueList) delete pqlQueueList;
TraceFunctLeave();
return hr; }
//---[ CDomainMappingTable::ProcessSpecialLinks ]------------------------------
//
//
// Description:
// Processes all the special global links to handle things like local
// delivery.
// Parameters:
// IN cSpecialRetryMinutes minutes to retry currently unreachable
// if 0, will use previous value
// IN fRoutingLockHeld
// Returns:
// -
// History:
// 1/26/99 - MikeSwa Created
// 3/25/99 - MikeSwa Added fRoutingLockHeld to fix GetNextMsgRef deadlock
//
//-----------------------------------------------------------------------------
void CDomainMappingTable::ProcessSpecialLinks(DWORD cSpecialRetryMinutes, BOOL fRoutingLockHeld) { HRESULT hr = S_OK; CMsgRef *pmsgref = NULL; CAQSvrInst *paqinst = m_paqinst; BOOL fSchedRetry = FALSE; BOOL fShutdownLock = FALSE;
//If this thread has the routing lock... we are safe from shutdown
_ASSERT(m_paqinst || !fRoutingLockHeld); if (!paqinst) return; //we must be shutting down
if (!fRoutingLockHeld) { //It is safe to access paqinst to acquire the shutdown lock because
//all threads that can call this function will be gone before the
//reference count on the server instance is 0 (however, this call
//could happen during deinialization which would cause m_aqinst
//to be NULL and the following call to return FALSE).
if (!paqinst->fTryShutdownLock()) return; //we are shutting down
//Now we have the shutdown lock... m_aqinst must be safe
_ASSERT(m_paqinst); if (!m_paqinst) { //might as well be defensive in retail about this.
paqinst->ShutdownUnlock(); return; } fShutdownLock = TRUE; }
if (!(DMT_FLAGS_SPECIAL_DELIVERY_SPINLOCK & dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_SPINLOCK))) { //we have the lock... only 1 thread at a time should do this
//Loop over queue and enqueue for local delivery
while (SUCCEEDED(hr) && m_plmqLocal) { hr = m_plmqLocal->HrGetNextMsgRef(fRoutingLockHeld, &pmsgref); if (FAILED(hr)) break;
_ASSERT(pmsgref); m_paqinst->QueueMsgForLocalDelivery(pmsgref, TRUE); pmsgref->Release(); pmsgref = NULL; }
//Tell link to walk queues so they will be added to the empty list
//for deletion
if (m_plmqLocal) { m_plmqLocal->GenerateDSNsIfNecessary(TRUE /*check if empty */, TRUE /*merge retry queue only*/); }
//NDR all messages in the unreachable link
if (m_plmqUnreachable) { //We must hold the routing lock for DSN generation
if (!fRoutingLockHeld) m_paqinst->RoutingShareLock();
m_plmqUnreachable->SetLastConnectionFailure(AQUEUE_E_NDR_ALL); m_plmqUnreachable->GenerateDSNsIfNecessary(FALSE, FALSE);
if (!fRoutingLockHeld) m_paqinst->RoutingShareUnlock();
}
//Process the currently unreachable link ... unless we are currently
//doing a ResetRoutes. If we are we must skip this because the
//currently unreachable link contains queues we should not process
//here - this function will be called again when ResetRoutes completes
if (m_plmqCurrentlyUnreachable && !(m_dwFlags & DMT_FLAGS_RESET_ROUTES_IN_PROGRESS)) { if (cSpecialRetryMinutes) { //new time is sooner... we better ask for a retry
if (cSpecialRetryMinutes < m_cSpecialRetryMinutes) fSchedRetry = TRUE;
InterlockedExchange((PLONG)&m_cSpecialRetryMinutes, cSpecialRetryMinutes); }
if (!(DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK & dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK))) { //We are the only thread doing this
//We must hold the routing lock for DSN generation
if (!fRoutingLockHeld) m_paqinst->RoutingShareLock();
m_plmqCurrentlyUnreachable->GenerateDSNsIfNecessary(FALSE, FALSE); dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); fSchedRetry = TRUE;
if (!fRoutingLockHeld) m_paqinst->RoutingShareUnlock(); }
//periodically check the currently unreachable link... to genrate NDRs
if (fSchedRetry) { dwInterlockedSetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); if (m_paqinst) { hr = m_paqinst->SetCallbackTime( CDomainMappingTable::SpecialRetryCallback, this, m_cSpecialRetryMinutes); if (FAILED(hr)) { //Unmark bits to we try next time
dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_CALLBACK); hr = S_OK; } } } }
//Release the lock
dwInterlockedUnsetBits(&m_dwFlags, DMT_FLAGS_SPECIAL_DELIVERY_SPINLOCK); }
if (fShutdownLock) { _ASSERT(m_paqinst); m_paqinst->ShutdownUnlock(); } }
//---[ CDomainMappingTable::HrPrepareForLocalDelivery ]------------------------
//
//
// Description:
// Prepares a message for local delivery using the m_plmqLocal Link.
// Parameters:
// IN pmsgref MsgRef to prepare for delivery
// 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
// E_FAIL if m_plmqLocal is not initialized
// History:
// 1/26/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainMappingTable::HrPrepareForLocalDelivery( IN CMsgRef *pmsgref, IN BOOL fDelayDSN, IN OUT CDeliveryContext *pdcntxt, OUT DWORD *pcRecips, OUT DWORD **prgdwRecips) { if (m_plmqLocal) { return m_plmqLocal->HrPrepareDelivery(pmsgref, TRUE, fDelayDSN, pdcntxt, pcRecips, prgdwRecips); } else return E_FAIL; }
DWORD CDomainMappingTable::GetCurrentlyUnreachableTotalMsgCount() { return m_plmqCurrentlyUnreachable->cGetTotalMsgCount(); }
//---[ CDomainMappingTable::plmqGetLocalLink ]---------------------------------
//
//
// Description:
// Returns an addref'd pointer to the local link object
// Parameters:
// -
// Returns:
// AddRef'd pointer to the local link object
// History:
// 2/22/99 - MikeSwa Created
// 1/28/2000 - MikeSwa Modified to make shutdown safe
//
//-----------------------------------------------------------------------------
CLinkMsgQueue *CDomainMappingTable::plmqGetLocalLink() { CAQSvrInst *paqinst = m_paqinst; CLinkMsgQueue *plmq = NULL;
//
// If paqinst is non-NULL, then we have the possibility of not being
// shutdown. If we are shutdown, it is possible that m_plmqLocal is
// in the process of being released (which would be a really bad
// time to addref/release it). We can safely access paqinst, since
// this class is a member of CAQSvrInst.
//
if (paqinst && paqinst->fTryShutdownLock()) { plmq = m_plmqLocal; if (plmq) plmq->AddRef(); paqinst->ShutdownUnlock(); }
return plmq; }
//---[ CDomainMappingTable::plmqGetCurrentlyUnreachable]---------------------------------
//
//
// Description:
// Returns an addref'd pointer to the currently unreachable link object
// Parameters:
// -
// Returns:
// AddRef'd pointer to the currently unreachable link object
// History:
// 6/21/99 - GPulla Created
//
//-----------------------------------------------------------------------------
CLinkMsgQueue *CDomainMappingTable::plmqGetCurrentlyUnreachable() { if(m_plmqCurrentlyUnreachable) m_plmqCurrentlyUnreachable->AddRef();
return m_plmqCurrentlyUnreachable; }
//---[ CDomainMappingTable::pmmaqGetPreCategorized]---------------------------------
//
//
// Description:
// Returns an addref'd pointer to the precategorized queue object
// Parameters:
// -
// AddRef'd pointer to the precategorized link object.
// History:
// 6/21/99 - GPulla Created
//
//-----------------------------------------------------------------------------
CMailMsgAdminLink *CDomainMappingTable::pmmaqGetPreCategorized() { if(m_pmmaqPreCategorized) m_pmmaqPreCategorized->AddRef();
return m_pmmaqPreCategorized; }
//---[ CDomainMappingTable::pmmaqGetPreRouting]---------------------------------
//
//
// Description:
// Returns an addref'd pointer to the pre routing queue object
// Parameters:
// -
// AddRef'd pointer to the precategorized link object.
// History:
// 6/21/99 - GPulla Created
//
//-----------------------------------------------------------------------------
CMailMsgAdminLink *CDomainMappingTable::pmmaqGetPreRouting() { if(m_pmmaqPreRouting) m_pmmaqPreRouting->AddRef();
return m_pmmaqPreRouting; }
//---[ CDomainMappingTable::pmmaqGetPreRouting]---------------------------------
//
//
// Description:
// Returns an addref'd pointer to the pre routing queue object
// Parameters:
// -
// AddRef'd pointer to the precategorized link object.
// History:
// 6/21/99 - GPulla Created
//
//-----------------------------------------------------------------------------
CMailMsgAdminLink *CDomainMappingTable::pmmaqGetPreSubmission() { if(m_pmmaqPreSubmission) m_pmmaqPreSubmission->AddRef();
return m_pmmaqPreSubmission; }
//---[ CDomainEntryIterator::CDomainEntryIterator ]----------------------------
//
//
// Description:
// Constructor for CDomainEntryIterator
// Parameters:
// -
// Returns:
// -
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CDomainEntryIterator::CDomainEntryIterator() { m_dwSignature = DOMAIN_ENTRY_ITERATOR_SIG; m_cItems = 0; m_iCurrentItem = 0; m_rgpvItems = NULL; }
//---[ CDomainEntryIterator::Recycle ]-----------------------------------------
//
//
// Description:
// CDomainEntryIterator Recycle... destroys using virtual functions
// Parameters:
// -
// Returns:
// -
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
VOID CDomainEntryIterator::Recycle() { DWORD iCurrentItem = 0;
for (iCurrentItem = 0; iCurrentItem < m_cItems; iCurrentItem++) { _ASSERT(m_rgpvItems); if (!m_rgpvItems) break;
if (m_rgpvItems[iCurrentItem]) { ReleaseItem(m_rgpvItems[iCurrentItem]); m_rgpvItems[iCurrentItem] = NULL; } }
if (m_rgpvItems) { FreePv(m_rgpvItems); m_rgpvItems = NULL; } m_cItems = 0; m_iCurrentItem = 0; }
//---[ CDomainEntryIterator::pvGetNext ]---------------------------------------
//
//
// Description:
// Gets the next Item
// Parameters:
// -
// Returns:
// The next item in the iterator
// NULL if at last item
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PVOID CDomainEntryIterator::pvGetNext() { PVOID pvRet = NULL; if (m_rgpvItems && (m_iCurrentItem < m_cItems)) { pvRet = m_rgpvItems[m_iCurrentItem]; _ASSERT(pvRet); m_iCurrentItem++; } return pvRet; }
//---[ CDomainEntryIterator::HrInitialize ]------------------------------------
//
//
// Description:
// Initializes a CDomainEntryIterator from a given CDomainEntry
// Parameters:
// pdentry CDomainEntry to initialize from
// Returns:
// S_OK on success
// E_POINTER if pdentry is NULL
// E_INVALIDARG if HrInitialize has already be called for this iterator
// E_OUTOFMEMORY if we cannot allocate memory for iterator
// History:
// 8/20/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
HRESULT CDomainEntryIterator::HrInitialize(CDomainEntry *pdentry) { TraceFunctEnterEx((LPARAM) this, "CDomainEntryIterator::HrInitialize"); HRESULT hr = S_OK; BOOL fEntryLocked = FALSE; DWORD cAddedItems = 0; DWORD cItems = 0; PLIST_ENTRY pli = NULL; PLIST_ENTRY pliHead = NULL; CLinkMsgQueue *plmq = NULL;
_ASSERT(pdentry); _ASSERT(!m_rgpvItems && "Iterator initialized twice");
if (!pdentry) { hr = E_POINTER; ErrorTrace((LPARAM) this, "NULL pdentry used to initialized iterator"); goto Exit; }
if (m_rgpvItems) { hr = E_INVALIDARG; ErrorTrace((LPARAM) this, "Iterator initialized twice!"); goto Exit; }
pdentry->m_slPrivateData.ShareLock(); fEntryLocked = TRUE;
cItems = cItemsFromDomainEntry(pdentry); if (!cItems) //Empty entry
goto Exit;
m_rgpvItems = (PVOID *) pvMalloc(sizeof(PVOID) * cItems);
if (!m_rgpvItems) { hr = E_OUTOFMEMORY; ErrorTrace((LPARAM) this, "Unable to allocate memory for iterator of size %d", cItems); goto Exit; }
ZeroMemory(m_rgpvItems, sizeof(PVOID)*cItems);
pliHead = pliHeadFromDomainEntry(pdentry); _ASSERT(pliHead); pli = pliHead->Flink;
while(pliHead != pli) { _ASSERT(pli); m_rgpvItems[cAddedItems] = pvItemFromListEntry(pli); _ASSERT(m_rgpvItems[cAddedItems]); if (m_rgpvItems[cAddedItems]) cAddedItems++;
pli = pli->Flink; _ASSERT(cAddedItems <= cItems); //We've run out of room
if (cAddedItems > cItems) break; } _ASSERT(cAddedItems == cItems); m_cItems = cAddedItems;
Exit: if (fEntryLocked) pdentry->m_slPrivateData.ShareUnlock();
TraceFunctLeave(); return hr; }
//---[ CDomainEntryLinkIterator ]---------------------------------------------
//
//
// Description:
// Releases a CLinkMsgQueue during the CDomainEntryQueueIterator
// destructor
// Parameters:
// pvItem LMQ to release
// Returns:
// -
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
VOID CDomainEntryLinkIterator::ReleaseItem(PVOID pvItem) { CLinkMsgQueue *plmq = (CLinkMsgQueue *) pvItem; _ASSERT(plmq); plmq->Release(); }
//---[ CDomainEntryLinkIterator::pvItemFromListEntry ]-------------------------
//
//
// Description:
// Returns a CLinkMsgQueue item from the given LIST_ENTRY
// Parameters:
// pli LIST_ENTRY to get LMQ from... *must* be non-NULL
// Returns:
// PVOID for Addref'd CLinkMsgQueue
// History:
// 8/20/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PVOID CDomainEntryLinkIterator::pvItemFromListEntry(PLIST_ENTRY pli) { CLinkMsgQueue *plmq = CLinkMsgQueue::plmqGetLinkMsgQueue(pli); _ASSERT(plmq); _ASSERT(pli);
plmq->AddRef(); return plmq; }
//---[ CDomainEntryLinkIterator::plmqGetNextLinkMsgQueue ]---------------------
//
//
// Description:
// Gets the next LMQ for this iterator
// Parameters:
// plmq to release... addref'd by previous call
// Returns:
// Addref'd Next LMQ.
// NULL if no more left
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CLinkMsgQueue *CDomainEntryLinkIterator::plmqGetNextLinkMsgQueue( CLinkMsgQueue *plmq) { CLinkMsgQueue *plmqNext = (CLinkMsgQueue *) pvGetNext();
if (plmqNext) plmqNext->AddRef();
if (plmq) plmq->Release();
return plmqNext; }
//---[ CDomainEntryQueueIterator::pdmqGetNextDestMsgQueue ]--------------------
//
//
// Description:
// Get the next DMQ in the iterator
// Parameters:
// pdmq CDestMsgQueue to release... addref'd by previous call
// Returns:
// Addref'd next DMQ
// NULL if no more DMQ's for iterator
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CDestMsgQueue *CDomainEntryQueueIterator::pdmqGetNextDestMsgQueue( CDestMsgQueue *pdmq) { CDestMsgQueue *pdmqNext = (CDestMsgQueue *) pvGetNext();
if (pdmqNext) { pdmqNext->AssertSignature(); pdmqNext->AddRef(); }
if (pdmq) { pdmq->AssertSignature(); pdmq->Release(); }
return pdmqNext; }
//---[ CDomainEntryDestIterator::pvItemFromListEntry ]-------------------------
//
//
// Description:
// Returns a CDestMsgQueue item from the given LIST_ENTRY
// Parameters:
// pli LIST_ENTRY to get DMQ from... *must* be non-NULL
// Returns:
// PVOID for Addref'd CDestMsgQueue
// History:
// 8/20/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PVOID CDomainEntryQueueIterator::pvItemFromListEntry(PLIST_ENTRY pli) { CDestMsgQueue *pdmq = CDestMsgQueue::pdmqGetDMQFromDomainListEntry(pli); _ASSERT(pdmq); _ASSERT(pli);
pdmq->AssertSignature(); pdmq->AddRef(); return (PVOID) pdmq; }
//---[ CDomainEntryQueueIterator ]---------------------------------------------
//
//
// Description:
// Releases a CDestMsgQueue during the CDomainEntryQueueIterator
// destructor
// Parameters:
// pvItem DMQ to release
// Returns:
// -
// History:
// 8/19/99 - MikeSwa Created
//
//-----------------------------------------------------------------------------
VOID CDomainEntryQueueIterator::ReleaseItem(PVOID pvItem) { CDestMsgQueue *pdmq = (CDestMsgQueue *) pvItem; _ASSERT(pdmq); pdmq->AssertSignature(); pdmq->Release(); }
|