//----------------------------------------------------------------------------- // // // File: msgref.cpp // // Description: // Implementation of CMT Message reference // // Author: mikeswa // // Copyright (C) 1997 Microsoft Corporation // //----------------------------------------------------------------------------- #include "aqprecmp.h" #include "dcontext.h" #include "dsnevent.h" #include "aqutil.h" CPool CMsgRef::s_MsgRefPool(MSGREF_SIG); DWORD CMsgRef::s_cMsgsPendingBounceUsage = 0; DWORD CMsgRef::s_cCurrentMsgsPendingDelete = 0; DWORD CMsgRef::s_cTotalMsgsPendingDelete = 0; DWORD CMsgRef::s_cTotalMsgsDeletedAfterPendingDelete = 0; DWORD CMsgRef::s_cCurrentMsgsDeletedNotReleased = 0; #ifndef DEBUG #define _VERIFY_RECIPS_HANDLED(pIRecipList, iStartRecipIndex, cRecipCount) #else #define _VERIFY_RECIPS_HANDLED(pIRecipList, iStartRecipIndex, cRecipCount) \ VerifyRecipsHandledFn(pIRecipList, iStartRecipIndex, cRecipCount); //---[ VerifyRecipsHandleFn ]-------------------------------------------------- // // // Description: // Verifies that all recipients in a given range have been handled. // Parameters: // pIRecipList IMailMsgRecipients Interface for message // iStartRecipIndex The first recipient to verify // cRecipCount The count of recipients to verify // Returns: // - // Asserts on failures // History: // 10/5/1999 - MikeSwa Created // //----------------------------------------------------------------------------- void VerifyRecipsHandledFn(IMailMsgRecipients *pIRecipList, DWORD iStartRecipIndex, DWORD cRecipCount) { HRESULT hr = S_OK; DWORD dwCurrentRecipFlags = 0; for (DWORD j = iStartRecipIndex; j < cRecipCount + iStartRecipIndex; j++) { dwCurrentRecipFlags = 0; hr = pIRecipList->GetDWORD(j, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (SUCCEEDED(hr)) _ASSERT(RP_HANDLED & dwCurrentRecipFlags); } } #endif //DEBUG //#ifndef DEBUG #if 1 #define _VERIFY_QUEUE_PTR(paqinst, szCurrentDomain, pdmq, cQueues, rgpdmqQueues) #else //DEBUG defined #define _VERIFY_QUEUE_PTR(paqinst, szCurrentDomain, pdmq, cQueues, rgpdmqQueues) \ VerifyQueuePtrFn(paqinst, szCurrentDomain, pdmq, cQueues, rgpdmqQueues) //---[ VerifyQueuePtrFn ]------------------------------------------------------- // // // Description: // Debug function to verify that the queue ptr returned by HrMapDomain is // valid and does not confict with previous queue pointers. // Parameters: // paqinst - ptr to CMQ object // szCurrentDomain - Domain that ptr is for // pdmq - Queue ptr to verify // cQueues - Number of queues so far // rgpdmqQueues - Array of queues so far // Returns: // - // History: // 6/1/98 - MikeSwa Created // 8/14/98 - MikeSwa Modified to handled shutdown // //----------------------------------------------------------------------------- void VerifyQueuePtrFn(CAQSvrInst *paqinst, LPSTR szCurrentDomain, CDestMsgQueue *pdmq, DWORD cQueues, CDestMsgQueue **rgpdmqQueues) { HRESULT hr = S_OK; CInternalDomainInfo *pDomainInfo = NULL; DWORD j; //verify that non-local domains have queue ptrs hr = paqinst->HrGetInternalDomainInfo(strlen(szCurrentDomain), szCurrentDomain, &pDomainInfo); if (AQUEUE_E_SHUTDOWN == hr) return; _ASSERT(SUCCEEDED(hr)); if (!(pDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_LOCAL_MAILBOX)) _ASSERT(pdmq && "NULL DesMsgQueue returned for NON-LOCAL domain"); pDomainInfo->Release(); //verify that Recip list is not returning domains out of order if (pdmq) { for (j = 0; j < cQueues; j++) { //check if ptr has been used yet if (rgpdmqQueues[j] == pdmq) { _ASSERT(0 && "IMailMsg Domain interface is broken"); } } } } #endif //DEBUG //---[ fAllRecipsInRangeHandled ]---------------------------------------------- // // // Description: // Utility function that iterates through recipients in a range (usually // a single domain) and determines if all the recipients have been handled. // Parameters: // pIRecipList IMailMsgRecipients Interface for message // iStartRecipIndex The first recipient to verify // cRecipCount The count of recipients to verify // Returns: // TRUE if *all* of the recipients in the range are handle // FALSE if one or more of the recipients in the range are not handled, // History: // 10/25/1999 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL fAllRecipsInRangeHandled(IMailMsgRecipients *pIRecipList, DWORD iStartRecipIndex, DWORD cRecipCount) { HRESULT hr = S_OK; DWORD dwCurrentRecipFlags = 0; BOOL fAreHandled = TRUE; _ASSERT(cRecipCount); for (DWORD j = iStartRecipIndex; j < cRecipCount + iStartRecipIndex; j++) { dwCurrentRecipFlags = 0; hr = pIRecipList->GetDWORD(j, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (FAILED(hr) || !(RP_HANDLED & dwCurrentRecipFlags)) { fAreHandled = FALSE; break; } } if (fAreHandled) { _VERIFY_RECIPS_HANDLED(pIRecipList, iStartRecipIndex, cRecipCount); } return fAreHandled; } //---[ CMsgRef::new ]---------------------------------------------------------- // // // Description: // Overide the new operator to allow for the variable size of this class. // CPool is used for the 90% case allocations, while exchmem is used // for odd-size allocations. // Parameters: // cDomains the number of domains this message is being delivered to. // Returns: // - //----------------------------------------------------------------------------- void * CMsgRef::operator new(size_t stIgnored, unsigned int cDomains) { CPoolMsgRef *pcpmsgref = NULL; DWORD dwAllocationFlags = MSGREF_CPOOL_SIG; if (fIsStandardSize(cDomains)) { dwAllocationFlags |= MSGREF_STANDARD_SIZE; //if our expected standard size... then use cpool allocator pcpmsgref = (CPoolMsgRef *) s_MsgRefPool.Alloc(); if (pcpmsgref) { DEBUG_DO_IT(InterlockedIncrement((PLONG) &g_cDbgMsgRefsCpoolAllocated)); dwAllocationFlags |= MSGREF_CPOOL_ALLOCATED; } else { DEBUG_DO_IT(InterlockedIncrement((PLONG) &g_cDbgMsgRefsCpoolFailed)); } } if (!pcpmsgref) { // // Sanity check size. If cDomains is ~-1 size() will wrap. // This will firewall us against corrupt messages. // if (size(cDomains) >= cDomains) { pcpmsgref = (CPoolMsgRef *) pvMalloc(sizeof(CPoolMsgRef) - sizeof(CMsgRef) + size(cDomains)); if (pcpmsgref) { DEBUG_DO_IT(InterlockedIncrement((PLONG) &g_cDbgMsgRefsExchmemAllocated)); } } } if (pcpmsgref) { pcpmsgref->m_dwAllocationFlags = dwAllocationFlags; return ((void *) &(pcpmsgref->m_msgref)); } else { return NULL; } } //---[ CMsgRef::CMsgRef ]------------------------------------------------------ // // // Description: // Constructor for CMsgRef // Parameters: // cDomains the number of domains this msg is being delivered to // pimsg ptr to the imsg object for this message // Returns: // // //----------------------------------------------------------------------------- CMsgRef::CMsgRef(DWORD cDomains, IMailMsgQueueMgmt *pIMailMsgQM, IMailMsgProperties *pIMailMsgProperties, CAQSvrInst *paqinst, DWORD dwMessageType, GUID guidRouter) : m_aqmtMessageType(guidRouter, dwMessageType) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::CMsgRef"); _ASSERT(pIMailMsgQM); _ASSERT(pIMailMsgProperties); _ASSERT(paqinst); pIMailMsgQM->AddRef(); pIMailMsgProperties->AddRef(); paqinst->AddRef(); m_dwSignature = MSGREF_SIG; m_dwDataFlags = MSGREF_VERSION; m_cDomains = cDomains; m_pIMailMsgQM = pIMailMsgQM; m_pIMailMsgProperties = pIMailMsgProperties; m_pIMailMsgRecipients = NULL; m_paqinst = paqinst; m_pmgle = NULL; m_cbMsgSize = 0; //initialize from IMsg m_dwMsgIdHash = dwQueueAdminHash(NULL); m_cTimesRetried = 0; m_cInternalUsageCount = 1; //Initialize to 1 like the mailmsg usage count //initialize stuff that is past traditional end of object ZeroMemory(m_rgpdmqDomains, (size(cDomains)+sizeof(CDestMsgQueue *)-sizeof(CMsgRef))); TraceFunctLeave(); } //---[ CMsgRef::~CMsgRef ]------------------------------------------------------------ // // // Description: // CMsgRef Destructor // Parameters: // - // Returns: // - // //----------------------------------------------------------------------------- CMsgRef::~CMsgRef() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::~CMsgRef"); HRESULT hr = S_OK; DWORD i = 0; _ASSERT(m_paqinst); _ASSERT(!(MSGREF_USAGE_COUNT_IN_USE & m_dwDataFlags)); //this should never remain set //There should be corresponding calls to AddUsage/ReleaseUsage _ASSERT(m_cInternalUsageCount == 1); //Release MsgGuidListEntry if we have one if (m_pmgle) { m_pmgle->RemoveFromList(); m_pmgle->Release(); } ReleaseMailMsg(TRUE); //Force commit/delete/release of associated mailmsg //Update count of messages that have had ::Delete called on them //but not been released. if ((MSGREF_MAILMSG_DELETE_PENDING & m_dwDataFlags) && (MSGREF_MAILMSG_DELETED & m_dwDataFlags)) { InterlockedDecrement((PLONG) &s_cCurrentMsgsDeletedNotReleased); } //Release references to DestMsgQueues for (i = 0; i < m_cDomains; i++) { if (m_rgpdmqDomains[i]) { m_rgpdmqDomains[i]->Release(); m_rgpdmqDomains[i] = NULL; } } m_paqinst->Release(); TraceFunctLeave(); } //---[ CMsgRef::ReleaseMailMsg ]----------------------------------------------- // // // Description: // Release this objects mailmsg... do the necessary commit/delete // Parameters: // fForceRelease TRUE mailmsg must be released (used by desctructor) // FALSE release mailmsg if shutdown is happening // Returns: // - // History: // 7/7/99 - MikeSwa Created // 10/8/99 - MikeSwa Fixed problem when SpinLock fials // //----------------------------------------------------------------------------- void CMsgRef::ReleaseMailMsg(BOOL fForceRelease) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::ReleaseMailMsg"); HRESULT hr = S_OK; if (!fForceRelease && !m_paqinst->fShutdownSignaled()) return; //we'll let the actual release handle this case //Make sure no one is trying to bounce usage if (!fTrySpinLock(&m_dwDataFlags, MSGREF_USAGE_COUNT_IN_USE)) { //Someone is... this should not happen on the final release DebugTrace((LPARAM) this, "Someone else using mailmsg... bailing"); _ASSERT(!fForceRelease); return; } if (MSGREF_MAILMSG_RELEASED & dwInterlockedSetBits(&m_dwDataFlags, MSGREF_MAILMSG_RELEASED)) { //Someone else has already come along and done this ReleaseSpinLock(&m_dwDataFlags, MSGREF_USAGE_COUNT_IN_USE); return; } if (NULL != m_pIMailMsgQM) { if ((m_dwDataFlags & MSGREF_SUPERSEDED) || pmbmapGetHandled()->FAllSet(m_cDomains)) { //The message has been handled (or superseded)... we can delete it ThreadSafeMailMsgDelete(); } //Releasing the message will commit it if dirty (and not-deleted) m_pIMailMsgQM->Release(); m_pIMailMsgQM = NULL; m_paqinst->DecMsgsInSystem( m_dwDataFlags & MSGREF_MSG_REMOTE_RETRY, m_dwDataFlags & MSGREF_MSG_COUNTED_AS_REMOTE, m_dwDataFlags & MSGREF_MSG_LOCAL_RETRY); } if (m_dwDataFlags & MSGREF_MSG_RETRY_ON_DELETE) { //Retry msg DEBUG_DO_IT(InterlockedDecrement((PLONG) &g_cDbgMsgRefsPendingRetryOnDelete)); m_paqinst->HandleAQFailure(AQ_FAILURE_MSGREF_RETRY, E_FAIL, m_pIMailMsgProperties); } if (NULL != m_pIMailMsgProperties) { m_pIMailMsgProperties->Release(); m_pIMailMsgProperties = NULL; } if (NULL != m_pIMailMsgRecipients) { m_pIMailMsgRecipients->Release(); m_pIMailMsgRecipients = NULL; } ReleaseSpinLock(&m_dwDataFlags, MSGREF_USAGE_COUNT_IN_USE); TraceFunctLeave(); } //---[ CMsgRef::HrInitialize() ]----------------------------------------------- // // // Description: // Perform initialization for the msgref object. Queries the DMT for // queues for each of the domains that this message is being sent to. // // Can be called multiple times if needed (e.g., if the DMT version // changes before the messages are queued). // Parameters: // IN paqinst Ptr to CMQ object // IN pIRecipList interface for recipients of msg // IN pIMessageRouter Router for this message // IN dwMessageType Msg Type for message // OUT pcLocalRecips - # of local recipients // OUT pcRemoteRecips - # of remote recipients // OUT pcQueues # of queues for this message // IN OUT rgdmqQueues Array of queue ptrs // Allocated by calling routine (to ease memory management) // must be at least cDomains long // // Returns: // S_OK on success // E_INVALIDARG if the data we need is not preset // AQUEUE_E_INVALID_MSG_ID if the message id is too long?? // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrInitialize(IN IMailMsgRecipients *pIRecipList, IN IMessageRouter *pIMessageRouter, IN DWORD dwMessageType, OUT DWORD *pcLocalRecips, OUT DWORD *pcRemoteRecips, OUT DWORD *pcQueues, IN OUT CDestMsgQueue **rgpdmqQueues) { const DWORD IMSG_MAX_DOMAIN_LEN = 512; TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrInitialize"); HRESULT hr = S_OK; CDomainMapping dmap; //value returned by DMT CDestMsgQueue *pdmq = NULL; //ptr to queue returned by DMT DWORD i; //tmp counter BOOL fUsed = FALSE; BOOL fLocked = FALSE; //has the aqinst been locked? char szCurrentDomain[IMSG_MAX_DOMAIN_LEN +1]; //Current Domain name DWORD iCurrentLowRecip = 0; //current low recip index DWORD cCurrentRecipCount = 0; //current recipient count DWORD cQueues = 0; DWORD cCachedIMsgHandles = 0; _ASSERT(m_paqinst); _ASSERT(pcQueues); _ASSERT(rgpdmqQueues); _ASSERT(m_pIMailMsgQM); _ASSERT(m_pIMailMsgProperties); _ASSERT(pIMessageRouter); _ASSERT(pcLocalRecips); _ASSERT(pcRemoteRecips); _ASSERT(pIRecipList); *pcLocalRecips = 0; *pcRemoteRecips = 0; pIRecipList->AddRef(); //If being called for a 2nd time... release old info //even though it might be the same... we don't want to leak if (m_pIMailMsgRecipients) m_pIMailMsgRecipients->Release(); m_pIMailMsgRecipients = pIRecipList; //Reset Message type in case it was updated m_aqmtMessageType.SetMessageType(dwMessageType); if (!m_paqinst->fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } fLocked = TRUE; if (!(MSGREF_MSG_INIT & m_dwDataFlags)) { hr = HrOneTimeInit(); if (FAILED(hr)) goto Exit; } for (i = 0; i < m_cDomains; i++) { //$$REVIEW: When to we need to check and make sure that it is a // properly formatted Domain Name... currently X400 will work too!! hr = pIRecipList->DomainItem(i, IMSG_MAX_DOMAIN_LEN +1, szCurrentDomain, &iCurrentLowRecip, &cCurrentRecipCount); if (FAILED(hr)) goto Exit; // // This is a quick fix for Milestone 2 so that we can send // mail to external X400 addresses. Their szCurrentDomain // will be empty string. Substitute that with " " which is // added as a local domain // if(*szCurrentDomain == '\0') { // // jstamerj 1998/07/24 12:24:48: // We can't handle an empty string, so translate this to " " // szCurrentDomain[0] = ' '; szCurrentDomain[1] = '\0'; } if (fAllRecipsInRangeHandled(pIRecipList, iCurrentLowRecip, cCurrentRecipCount)) { //All recipients for this domain have been handled, we do not need to //queue it up for delivery. Typical reasons for this are: // - address that have been rejected by cat, but there are valid // local recips // - On restart if some recipients for a message still have not been // delivered pmbmapGetHandled()->HrMarkBits(m_cDomains, 1, &i, TRUE); if (FAILED(hr)) ErrorTrace((LPARAM) this, "HrMarkBits returned hr 0x%08X", hr); pdmq = NULL; } else { hr = m_paqinst->pdmtGetDMT()->HrMapDomainName(szCurrentDomain, &m_aqmtMessageType, pIMessageRouter, &dmap, &pdmq); if (FAILED(hr)) { if (PHATQ_BAD_DOMAIN_SYNTAX == hr) { //The domain name is malformed... we should not attempt delivery //for this domain, but should deliver rest of message _VERIFY_RECIPS_HANDLED(pIRecipList, iCurrentLowRecip, cCurrentRecipCount); ErrorTrace((LPARAM) this, "Encountered invalid domain %s", szCurrentDomain); //Set this domain as handled pmbmapGetHandled()->HrMarkBits(m_cDomains, 1, &i, TRUE); if (FAILED(hr)) ErrorTrace((LPARAM) this, "HrMarkBits returned hr 0x%08X", hr); } else goto Exit; } } //HrInitialize is being called a 2nd time... release old queues so we don't leak if (m_rgpdmqDomains[i]) m_rgpdmqDomains[i]->Release(); m_rgpdmqDomains[i] = pdmq; //keep track of local/remote recip counts if (pdmq) *pcRemoteRecips += cCurrentRecipCount; else *pcLocalRecips += cCurrentRecipCount; _ASSERT(cCurrentRecipCount); _VERIFY_QUEUE_PTR(m_paqinst, szCurrentDomain, pdmq, cQueues, rgpdmqQueues); rgpdmqQueues[cQueues] = pdmq; //save new ptr SetRecipIndex(cQueues, iCurrentLowRecip, cCurrentRecipCount + iCurrentLowRecip -1); cQueues++; //set bit in bitmap hr = pmbmapGetDomainBitmap(i)->HrMarkBits(m_cDomains, 1, &i, TRUE); _ASSERT(SUCCEEDED(hr) && "Bitmap code failed"); } *pcQueues = cQueues; Exit: if (FAILED(hr)) { //Release references to DestMsgQueues for (i = 0; i < m_cDomains; i++) { if (m_rgpdmqDomains[i]) { m_rgpdmqDomains[i]->Release(); m_rgpdmqDomains[i] = NULL; } } } if (fLocked) { m_paqinst->ShutdownUnlock(); } TraceFunctLeave(); return hr; } //---[ CMsgRef::HrOneTimeInit ]------------------------------------------------ // // // Description: // Perform 1-time message initialization (HrInitialize can be called // multiple times... this function encapuslates that things that only // need to be done once). // // Parameters: // - // Returns: // S_OK on success // History: // 10/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrOneTimeInit() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrOneTimeInit"); HRESULT hr = S_OK; DWORD cbProp = 0; GUID guidID = GUID_NULL; GUID *pguidID = NULL; GUID guidSupersedes; GUID *pguidSupersedes = NULL; BOOL fFoundMsgID = FALSE; CHAR szBuffer[300]; _ASSERT(!(MSGREF_MSG_INIT & m_dwDataFlags)); //get data we want from the IMsg //The info we want to include follows // -Time IMsg entered the system (creation time) // -Message Priority // -Message Size // -Supersedeable message ID // -Hash of Message ID szBuffer[(sizeof(szBuffer)-1)/sizeof(CHAR)] = '\0'; hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_RFC822_MSG_ID, sizeof(szBuffer), &cbProp,(BYTE *) szBuffer); //$$REVIEW - do we care about MsgID's bigger than 300 bytes if (SUCCEEDED(hr)) fFoundMsgID = TRUE; //Don't pass on error hr = S_OK; //Get Hash of message ID. if (fFoundMsgID) m_dwMsgIdHash = dwQueueAdminHash(szBuffer); if (!m_pmgle) { //Although, we know that this function has not successfully //completed... it is possible that it failed, so we check //to make sure we don't leak a m_pmgle hr = m_pIMailMsgProperties->GetStringA(IMMPID_MP_MSG_GUID, sizeof(szBuffer), szBuffer); if (SUCCEEDED(hr)) { //Try to parse GUID from the string if (fAQParseGuidString(szBuffer, sizeof(szBuffer), &guidID)) { pguidID = &guidID; hr = m_pIMailMsgProperties->GetStringA(IMMPID_MP_SUPERSEDES_MSG_GUID, sizeof(szBuffer), szBuffer); if (SUCCEEDED(hr)) { if (fAQParseGuidString(szBuffer, sizeof(szBuffer), &guidSupersedes)) { pguidSupersedes = &guidSupersedes; } } } } //If this message has a GUID... add it to the superseded this if (pguidID) { _ASSERT(m_paqinst); m_pmgle = m_paqinst->pmglGetMsgGuidList()->pmgleAddMsgGuid(this, pguidID, pguidSupersedes); //We don't care about the return results... if the allocation //fails, then we just treat it as if it did not have an ID. } } //Get time message was queued hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_ARRIVAL_FILETIME, sizeof(FILETIME), &cbProp, (BYTE *) &m_ftQueueEntry); if (FAILED(hr)) { //Message should not make it this far without being stamped with _ASSERT(MAILMSG_E_PROPNOTFOUND != hr); goto Exit; } //Get Various expire times hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_LOCAL_EXPIRE_DELAY, sizeof(FILETIME), &cbProp, (BYTE *) &m_ftLocalExpireDelay); if (hr == MAILMSG_E_PROPNOTFOUND) { // If this is not set, zero it so it will be calculated on demand memset(&m_ftLocalExpireDelay, 0, sizeof(FILETIME)); } else if (FAILED(hr)) { goto Exit; } hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_LOCAL_EXPIRE_NDR, sizeof(FILETIME), &cbProp, (BYTE *) &m_ftLocalExpireNDR); if (hr == MAILMSG_E_PROPNOTFOUND) { // If this is not set, zero it so it will be calculated on demand memset(&m_ftLocalExpireNDR, 0, sizeof(FILETIME)); } else if (FAILED(hr)) { goto Exit; } hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_EXPIRE_DELAY, sizeof(FILETIME), &cbProp, (BYTE *) &m_ftRemoteExpireDelay); if (hr == MAILMSG_E_PROPNOTFOUND) { // If this is not set, zero it so it will be calculated on demand memset(&m_ftRemoteExpireDelay, 0, sizeof(FILETIME)); } else if (FAILED(hr)) { goto Exit; } hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_EXPIRE_NDR, sizeof(FILETIME), &cbProp, (BYTE *) &m_ftRemoteExpireNDR); if (hr == MAILMSG_E_PROPNOTFOUND) { // If this is not set, zero it so it will be calculated on demand memset(&m_ftRemoteExpireNDR, 0, sizeof(FILETIME)); } else if (FAILED(hr)) { goto Exit; } //Get the size of the message hr = HrQADMGetMsgSize(m_pIMailMsgProperties, &m_cbMsgSize); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Failed to Get Message Size 0x%08X", hr); hr = S_OK; //This really is not a fatal error } //$$TODO: Map to effective routing priority - just use Normal now m_dwDataFlags |= (eEffPriNormal & MSGREF_PRI_MASK); m_dwDataFlags |= MSGREF_MSG_INIT; Exit: TraceFunctLeave(); return hr; } //---[ CMsgRef::HrPrepareDelivery ]-------------------------------------------- // // // Description: // Prepares a msgreference for delivery on the given list of queues. // // Caller is NOT responsible for freeing prgdwRecips, // This should automatically be freed with the message context. // // If this function succeeds, then a usage count will have been added // to this mailmsg. The caller is responsible for calling AckMessage // (which releases the usage count) or calling ReleaseUsage explicitly. // Parameters: // IN BOOL fLocal Prepare delivery for all domains with NULL queues // IN BOOL fDelayDSN - Check/Set Delay bitmap (only send 1 Delay DSN). // IN CQuickList *pqlstQueues array of DMQ's // IN CDestMsgRetryQueue *pmdrq - Retry interface for this delivery attempt // Will be NULL for local delivery, becasue we will never requeue a local // message to its original DMQ. // IN OUT CDeliveryContext *pdcntxt context that must be returned on Ack // OUT DWORD *pcRecips # of recips to deliver for // OUT CMsgBitMap **prgdwRecips Array of recipient indexes // Returns: // S_OK on success // AQUEUE_E_MESSAGE_HANDLED if the message has already been handled // AQUEUE_E_MESSAGE_PENDING if all the the requested recipients are // either handled or currently pending delivery for another thread // E_FAIL if any of the M1 requirements are not met //----------------------------------------------------------------------------- HRESULT CMsgRef::HrPrepareDelivery(IN BOOL fLocal, IN BOOL fDelayDSN, IN CQuickList *pqlstQueues, IN CDestMsgRetryQueue* pdmrq, OUT CDeliveryContext *pdcntxt, OUT DWORD *pcRecips, OUT DWORD **prgdwRecips) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrPrepareDelivery"); //assert IN parameters _ASSERT(fLocal || pqlstQueues); //assert OUT parameters _ASSERT(pdcntxt); _ASSERT(prgdwRecips); _ASSERT(pcRecips); //assume average of 4 recips per domain for return list const DWORD RECIPIENT_PREALLOCATION_FACTOR = 4; //we may need to tweak this. HRESULT hr = S_OK; CMsgBitMap *pmbmapTmp = NULL; CMsgBitMap *pmbmapCurrent = NULL; DWORD i,j,k = 0; //loop variables DWORD *pdwRecipBuffer = NULL; //The buffer for recipient indexes DWORD iCurrentIndex = 0; //Current index into the buffer DWORD cdwRecipBuffer = 0; //current size of recipient buffer (in DWORDS) DWORD iRecipLow = 0; //Recipient index limits DWORD iRecipHigh = 0; CDestMsgQueue *pdmq = NULL; PVOID pvContext = NULL; DWORD cDest = pqlstQueues ? pqlstQueues->dwGetCount() : 0; DWORD dwStartDomain = 0; DWORD dwLastDomain = 0; BOOL fAddedUsage = FALSE; BOOL fRecipsPending = FALSE; _ASSERT((fLocal || cDest) && "We must deliver somewhere"); _ASSERT(m_pIMailMsgRecipients); //check if msg has been superseded if (m_dwDataFlags & MSGREF_SUPERSEDED) { hr = AQUEUE_E_MESSAGE_HANDLED; goto Exit; } //Pre-allocate recipient buffer. It would be very interesting to determine //the best way to do this. It might involve tuning the default factor, or //using CPOOL to allocate the 90% case. cdwRecipBuffer = cDest * RECIPIENT_PREALLOCATION_FACTOR; pdwRecipBuffer = (DWORD *) pvMalloc(cdwRecipBuffer * sizeof(DWORD)); if (NULL == pdwRecipBuffer) { hr = E_OUTOFMEMORY; goto Exit; } //HOW IT WORKS: // Here are the steps used to prepare a msgref for delivery // 1-Determine which domains it needs to be delivered for on this queue // 2-Filter out any domains that have already been handled // 3-Mark the domains as pending (and make sure we aren't delivering // to domains that are pending). // The above accomplished by using logical operations on bitmap objects pmbmapTmp = new(m_cDomains) CMsgBitMap(m_cDomains); if (NULL == pmbmapTmp) { hr = E_OUTOFMEMORY; goto Exit; } if (cDest == 0) { //make sure local makes it through the loop once _ASSERT(fLocal); cDest = 1; } //Use group operation to group together like domains //Both lists of domain mappings must be strictly sorted for (i = 0; i < m_cDomains; i++) { for (j = 0; j < cDest; j++) { //$$NOTE: Currently this nested loop is O(n^2) if (pqlstQueues) { pdmq = (CDestMsgQueue *) pqlstQueues->pvGetItem(j, &pvContext); if(pdmq) pdmq->AssertSignature(); else _ASSERT(fLocal); } if ((m_rgpdmqDomains[i] == pdmq) || (fLocal && !m_rgpdmqDomains[i])) { pmbmapCurrent = pmbmapGetDomainBitmap(i); //The order of the following 5 bitmap operations is important //to ensure that 2 threads do not get the same recipients //The ACK performs the following similar operations: // - Sets handled bits // - Unsets pending bits //Check against handled if (pmbmapGetHandled()->FTest(m_cDomains, pmbmapCurrent)) continue; //Check against DSN if (fDelayDSN && pmbmapGetDSN()->FTest(m_cDomains,pmbmapCurrent)) continue; //Check (and set) against pending if (!pmbmapGetPending()->FTestAndSet(m_cDomains, pmbmapCurrent)) { fRecipsPending = TRUE; continue; } // Recheck against handled. If we remove this re-check, then // there is a possibility that a thread will come along // check handle, another thread will ack (setting handled // and unsetting pending), then the first thread comes // along and sets pending thereby defeating our locking // mechanism. if (pmbmapGetHandled()->FTest(m_cDomains, pmbmapCurrent)) { //We need to unset the pending bit we just set hr = pmbmapCurrent->HrFilterUnset(m_cDomains, pmbmapGetPending()); DebugTrace((LPARAM) this, "Backout pending bits in PrepareForDelivery - hr", hr); //This will succeed unless another thread has munged the //bitmap _ASSERT(SUCCEEDED(hr)); hr = S_OK; //caller can do nothing about this continue; } //Check (and set) against DSN if (fDelayDSN) { //This should not be able to happen, since another thread //must have come along and sent pending to get this far _VERIFY(pmbmapGetDSN()->FTestAndSet(m_cDomains,pmbmapCurrent)); } //Everything checks out... we can deliver to this domain hr = pmbmapTmp->HrGroupOr(m_cDomains, 1, &pmbmapCurrent); if (FAILED(hr)) goto Exit; //Create list of recipients. GetRecipIndex(i, &iRecipLow, &iRecipHigh); //get the limits //make sure we have enough room to do this if ((iCurrentIndex + (iRecipHigh - iRecipLow) + 1) >= cdwRecipBuffer) { DWORD *pdwTemp = NULL; // Temp ptr for realloc //reallocate generously (enough for now + our estimate for future) cdwRecipBuffer += (iRecipHigh - iRecipLow) + 1 + RECIPIENT_PREALLOCATION_FACTOR* (cDest-(j+1)); pdwTemp = (DWORD *) pvRealloc((void *) pdwRecipBuffer, cdwRecipBuffer*sizeof(DWORD)); if (!pdwTemp) { hr = E_OUTOFMEMORY; goto Exit; } // Allocation succeeded, set the ptr. pdwRecipBuffer = pdwTemp; } //Make sure we have the usage count to call SetNextDomain if (!fAddedUsage) { hr = InternalAddUsage(); if (FAILED(hr)) goto Exit; fAddedUsage = TRUE; } //Check to see if this message is marked for deletion if (fMailMsgMarkedForDeletion()) { hr = AQUEUE_E_MESSAGE_HANDLED; goto Exit; } if (!iCurrentIndex) //first domain { dwStartDomain = i; } else { hr = m_pIMailMsgRecipients->SetNextDomain(dwLastDomain, i, FLAG_OVERWRITE_EXISTING_LINKS); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: SetNextDomain Failed - hr 0x%08X", hr); goto Exit; } } //save last valid domain dwLastDomain = i; DebugTrace((LPARAM) this, "INFO: Sending recipients %d thru %d for domain %d:%d", iRecipLow, iRecipHigh, j, i); for (k = iRecipLow; k <= iRecipHigh; k++) { pdwRecipBuffer[iCurrentIndex] = k; iCurrentIndex++; } _ASSERT(iCurrentIndex <= cdwRecipBuffer); } } } if (pmbmapTmp->FAllClear(m_cDomains)) { //there is nothing to do for this message if (fRecipsPending) hr = AQUEUE_E_MESSAGE_PENDING; else hr = AQUEUE_E_MESSAGE_HANDLED; goto Exit; } _ASSERT(fAddedUsage); if (dwStartDomain == dwLastDomain) { //There is only 1 domain... we never called SetNextDomain.. we need to //overwrite any previous domain link information hr = m_pIMailMsgRecipients->SetNextDomain(dwStartDomain, dwStartDomain, FLAG_SET_FIRST_DOMAIN); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: SetNextDomain for first domain Failed - hr 0x%08X", hr); goto Exit; } } //Initialize delivery context pdcntxt->Init(this, pmbmapTmp, iCurrentIndex, pdwRecipBuffer, dwStartDomain, pdmrq); //Write other OUT parameters *pcRecips = iCurrentIndex; *prgdwRecips = pdwRecipBuffer; //set pointers to NULL so we don't delete them pdwRecipBuffer = NULL; pmbmapTmp = NULL; Exit: if (FAILED(hr) && pmbmapTmp && !pmbmapTmp->FAllClear(m_cDomains)) { //We need to unset the DNS and pending bits we have set pmbmapTmp->HrFilterUnset(m_cDomains, pmbmapGetPending()); if (fDelayDSN) pmbmapTmp->HrFilterUnset(m_cDomains, pmbmapGetDSN()); } if (pmbmapTmp) delete pmbmapTmp; if (pdwRecipBuffer) FreePv(pdwRecipBuffer); //Make sure we don't leave an extra usage count on failure if (fAddedUsage && FAILED(hr)) InternalReleaseUsage(); TraceFunctLeave(); return hr; } //---[ CMsgRef::HrAckMessage ]------------------------------------------------- // // // Description: // Acknowledges (un)delivery attempts for a message. Will look at the // IMsg and determine which queues the msgref needs to be requeued to. // Parameters: // IN pdcntxt Delivery context of message // IN pMsgAck Delivery status of message // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrAckMessage(IN CDeliveryContext *pdcntxt, IN MessageAck *pMsgAck) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrAckMessage"); _ASSERT(pdcntxt); _ASSERT(pdcntxt->m_pmbmap); _ASSERT(pMsgAck); HRESULT hr = S_OK; CDSNParams dsnparams; dsnparams.dwStartDomain = pdcntxt->m_dwStartDomain; //Do we need to send DSN's? if (MESSAGE_STATUS_CHECK_RECIPS & pMsgAck->dwMsgStatus) { //recipients need to be handled by the DSN code dsnparams.dwDSNActions = DSN_ACTION_FAILURE; //we may need to send relay DSN's as well dsnparams.dwDSNActions |= DSN_ACTION_RELAYED; SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); dsnparams.pIMailMsgProperties = m_pIMailMsgProperties; hr = m_paqinst->HrTriggerDSNGenerationEvent(&dsnparams, FALSE); if (FAILED(hr)) goto Exit; } else if (((MESSAGE_STATUS_DROP_DIRECTORY | MESSAGE_STATUS_LOCAL_DELIVERY) & pMsgAck->dwMsgStatus) && !(MESSAGE_STATUS_RETRY & pMsgAck->dwMsgStatus)) { //This was a successful local (or drop) delivery // - NDR all undelivered // - Generate any success DSNs if necessary dsnparams.dwDSNActions = DSN_ACTION_FAILURE_ALL | DSN_ACTION_DELIVERED | DSN_ACTION_RELAYED; SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); dsnparams.pIMailMsgProperties = m_pIMailMsgProperties; hr = m_paqinst->HrTriggerDSNGenerationEvent(&dsnparams, FALSE); if (FAILED(hr)) goto Exit; } else if (MESSAGE_STATUS_NDR_ALL & pMsgAck->dwMsgStatus) { // // NDR all undelivered recipients // // // Use specific extended status codes if there is no // detailed per-recipient information // hr = HrPromoteMessageStatusToMailMsg(pdcntxt, pMsgAck); if (FAILED(hr)) { // // This is a non-fatal error, we should still attempt // DSN generation... otherwise the sender will get // no indication of the failure. // ErrorTrace((LPARAM) this, "HrPromoteMessageStatusToMailMsg failed with 0x%08X", hr); hr = S_OK; } dsnparams.dwDSNActions = DSN_ACTION_FAILURE_ALL; if (MESSAGE_STATUS_DSN_NOT_SUPPORTED & pMsgAck->dwMsgStatus) dsnparams.dwDSNActions |= DSN_ACTION_RELAYED; dsnparams.pIMailMsgProperties = m_pIMailMsgProperties; SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); hr = m_paqinst->HrTriggerDSNGenerationEvent(&dsnparams, FALSE); if (FAILED(hr)) goto Exit; } else if (((MESSAGE_STATUS_DROP_DIRECTORY | MESSAGE_STATUS_LOCAL_DELIVERY) & pMsgAck->dwMsgStatus) && (MESSAGE_STATUS_RETRY & pMsgAck->dwMsgStatus)) { //This was a retryable local (or drop) delivery failure // - NDR all hard failures // - Generate any success DSNs if necessary dsnparams.dwDSNActions = DSN_ACTION_FAILURE | DSN_ACTION_DELIVERED | DSN_ACTION_RELAYED; SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); dsnparams.pIMailMsgProperties = m_pIMailMsgProperties; hr = m_paqinst->HrTriggerDSNGenerationEvent(&dsnparams, FALSE); if (FAILED(hr)) goto Exit; } //See if we also need to retry this message if (MESSAGE_STATUS_RETRY & pMsgAck->dwMsgStatus) { _ASSERT(!((MESSAGE_STATUS_ALL_DELIVERED | MESSAGE_STATUS_NDR_ALL) & pMsgAck->dwMsgStatus)); hr = HrPrvRetryMessage(pdcntxt, pMsgAck->dwMsgStatus); if (FAILED(hr)) goto Exit; } else { //If retry is not specifically requested, we set the handled bits hr = pdcntxt->m_pmbmap->HrFilterSet(m_cDomains, pmbmapGetHandled()); if (FAILED(hr)) goto Exit; //If this message has a supersedable ID... remove it from list if //we are done if (m_pmgle && pmbmapGetHandled()->FAllSet(m_cDomains)) { m_pmgle->RemoveFromList(); } //There should be something that was not set in the handled bitmap _ASSERT(! pdcntxt->m_pmbmap->FAllClear(m_cDomains)); } //Unset Pending bitmap hr = pdcntxt->m_pmbmap->HrFilterUnset(m_cDomains, pmbmapGetPending()); if (FAILED(hr)) goto Exit; Exit: ReleaseAndBounceUsageOnMsgAck(pMsgAck->dwMsgStatus); TraceFunctLeave(); return hr; } //---[ CMsgRef::HrSendDelayOrNDR ]--------------------------------------------- // // // Description: // Determines if a message has expired (for Delay or NDR) and generates // a DSN if neccessary. // Parameters: // IN dwDSNOptions Flags describing DSN generation // MSGREF_DSN_LOCAL_QUEUE This is for a local queue // MSGREF_DSN_SEND_DELAY Allow Delay DSNs // MSGREF_DSN_HAS_ROUTING_LOCK This thread holds the routing lock // IN pqlstQueues List of DestMsgQueues to DSN // IN hrStatus Status to Pass to DSN generation // (if Status is AQUEUE_E_NDR_ALL... then message will // be NDR'd regardless of timeout). // OUT pdwDSNFlags Reports disposition of message // MSGREF_DSN_SENT_NDR Message NDR-expired and NDR was sent // MSGREF_DSN_SENT_DELAY Message Delay-expired and Delay DSN was sent // MSGREF_HANDLED Message has been completely handled // MSGREF_HAS_NOT_EXPIRED Message younger than it's exipiration dates // Returns: // S_OK on success // AQUEUE_E_SHUTDOWN if shutdown is in progress // History: // 7/13/98 - MikeSwa Created // 8/14/98 - MikeSwa Modified to add support for local expire // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrSendDelayOrNDR( IN DWORD dwDSNOptions, IN CQuickList *pqlstQueues, IN HRESULT hrStatus, OUT DWORD *pdwDSNFlags) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrSendDelayOrNDR"); HRESULT hr = S_OK; BOOL fSendDSN = FALSE; BOOL fSendNDR = FALSE; DWORD dwTimeContext = 0; DWORD dwFlags = 0; FILETIME ftExpireTime; CDSNParams dsnparams; CDeliveryContext dcntxt; BOOL fPendingSet = FALSE; //pending bitmap has been set BOOL fReleaseUsageNeeded = FALSE; //has an extra usage count been added by Prepare Delivery? BOOL fShouldRetryMsg = TRUE; //any allocations done in PrepareDelivery will be freed by ~CDeliveryContext DWORD cRecips = 0; DWORD *rgdwRecips = NULL; _ASSERT(pdwDSNFlags); _ASSERT(MSGREF_SIG == m_dwSignature); *pdwDSNFlags = 0; //Shutdown lock should already be aquired... but lets make sure that the //service shutdown thread is not waiting for it. if (!m_paqinst->fTryShutdownLock()) { hr = AQUEUE_E_SHUTDOWN; goto Exit; } m_paqinst->ShutdownUnlock(); // // We need to open up the file and see what is going on. This will // force a RFC822 rendering of the message content and open // the content file. // if (dwDSNOptions & MSGREF_DSN_CHECK_IF_STALE) { // // If we cannot retry the message, it is stale and we shold // drop it. This will open the handles for a message, but // we will always call BounceUsageCount at the end of this call // InternalAddUsage(); fShouldRetryMsg = fShouldRetry(); InternalReleaseUsage(); if (!fShouldRetryMsg) { *pdwDSNFlags = MSGREF_HANDLED; goto Exit; } } //if this message has been superseded... remove it from the running //Also if the message has already been marked for deletion if ((m_dwDataFlags & MSGREF_SUPERSEDED) || fMailMsgMarkedForDeletion()) { *pdwDSNFlags = MSGREF_HANDLED; goto Exit; } //Make sure we check the correct local/remote expire time if (MSGREF_DSN_LOCAL_QUEUE & dwDSNOptions) memcpy(&ftExpireTime, &m_ftLocalExpireNDR, sizeof(FILETIME)); else memcpy(&ftExpireTime, &m_ftRemoteExpireNDR, sizeof(FILETIME)); // If this wasn't set we need to calculate it if (!((ULARGE_INTEGER*)&ftExpireTime)->QuadPart) { m_paqinst->CalcExpireTimeNDR(m_ftQueueEntry, MSGREF_DSN_LOCAL_QUEUE & dwDSNOptions, &ftExpireTime); } // // Default to status passed in // dsnparams.hrStatus = hrStatus; if (fIsFatalError(hrStatus)) { SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); *pdwDSNFlags |= MSGREF_DSN_SENT_NDR; fSendDSN = TRUE; fSendNDR = TRUE; dsnparams.dwDSNActions |= DSN_ACTION_FAILURE_ALL; } else if (m_paqinst->fInPast(&ftExpireTime, &dwTimeContext)) { SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); *pdwDSNFlags |= MSGREF_DSN_SENT_NDR; fSendDSN = TRUE; fSendNDR = TRUE; dsnparams.dwDSNActions |= DSN_ACTION_FAILURE_ALL; dsnparams.hrStatus = AQUEUE_E_MSG_EXPIRED; } else if (MSGREF_DSN_SEND_DELAY & dwDSNOptions) { //NDR is not needed, but perhaps delay is //Make sure we check the correct local/remote expire time if (MSGREF_DSN_LOCAL_QUEUE & dwDSNOptions) memcpy(&ftExpireTime, &m_ftLocalExpireDelay, sizeof(FILETIME)); else memcpy(&ftExpireTime, &m_ftRemoteExpireDelay, sizeof(FILETIME)); // If this wasn't set we need to calculate it if (!((ULARGE_INTEGER*)&ftExpireTime)->QuadPart) { m_paqinst->CalcExpireTimeDelay(m_ftQueueEntry, MSGREF_DSN_LOCAL_QUEUE & dwDSNOptions, &ftExpireTime); } if (m_paqinst->fInPast(&ftExpireTime, &dwTimeContext)) { SET_DEBUG_DSN_CONTEXT(dsnparams, __LINE__); *pdwDSNFlags |= MSGREF_DSN_SENT_DELAY; fSendDSN = TRUE; dsnparams.dwDSNActions |= DSN_ACTION_DELAYED; dsnparams.hrStatus = AQUEUE_E_MSG_EXPIRED; } } if (!fSendDSN) { //message has not expired for Delay or NDR *pdwDSNFlags |= MSGREF_HAS_NOT_EXPIRED; goto Exit; } //Prepare delivery for NDR/DSN's (setting DSN bitmap for delays) hr = HrPrepareDelivery(MSGREF_DSN_LOCAL_QUEUE & dwDSNOptions, ((*pdwDSNFlags) & MSGREF_DSN_SENT_DELAY) ? TRUE : FALSE, pqlstQueues, NULL, &dcntxt, &cRecips, &rgdwRecips); //make sure we don't delete ourselves when stack context goes away dcntxt.m_pmsgref = NULL; if (AQUEUE_E_MESSAGE_HANDLED == hr) { //nothing to do for the message //if *not* filtering against Delay... //then there are no undelivered recips. Return this info if (!((*pdwDSNFlags) & MSGREF_DSN_SENT_DELAY)) *pdwDSNFlags = MSGREF_HANDLED; else *pdwDSNFlags = MSGREF_HAS_NOT_EXPIRED; //delay wasn't actually sent hr = S_OK; goto Exit; } else if (AQUEUE_E_MESSAGE_PENDING == hr) { //This message is currently being processed by other threads //It has not been handled (so it should not be removed from the queue), //but it is not an error condition *pdwDSNFlags = 0; hr = S_OK; goto Exit; } else if (FAILED(hr)) { *pdwDSNFlags = 0; goto Exit; } fPendingSet = TRUE; //we will need to unset the pending bitmap fReleaseUsageNeeded = TRUE; dsnparams.dwStartDomain = dcntxt.m_dwStartDomain; dsnparams.pIMailMsgProperties = m_pIMailMsgProperties; hr = m_paqinst->HrTriggerDSNGenerationEvent(&dsnparams, MSGREF_DSN_HAS_ROUTING_LOCK & dwDSNOptions); if (FAILED(hr)) { *pdwDSNFlags = 0; goto Exit; } if (S_FALSE == hr) //no DSN generated { if (fSendNDR) //Expire has passed...remove message from queue *pdwDSNFlags = MSGREF_HANDLED; else *pdwDSNFlags = 0; hr = S_OK; //continue through function... still remove from queue even if NOTIFY=NEVER } //if NDR was generated, set handled bits if (fSendNDR) { //Set Handled bitmap hr = dcntxt.m_pmbmap->HrFilterSet(m_cDomains, pmbmapGetHandled()); if (FAILED(hr)) goto Exit; //If this message has a supersedable ID... remove it from list if //we are done if (m_pmgle && pmbmapGetHandled()->FAllSet(m_cDomains)) { m_pmgle->RemoveFromList(); } //There should be something that was not set in the handled bitmap _ASSERT(! dcntxt.m_pmbmap->FAllClear(m_cDomains)); } Exit: //Unset Pending bitmap if (fPendingSet) { dcntxt.m_pmbmap->HrFilterUnset(m_cDomains, pmbmapGetPending()); } _ASSERT(m_pIMailMsgQM); if (fReleaseUsageNeeded) { //we called HrPrepareDelivery which added an extra usage count InternalReleaseUsage(); } //While we are walking queues... bounce usage count - this will conserve handles BounceUsageCount(); TraceFunctLeave(); return hr; } //---[ HrPrvRetryMessage ]----------------------------------------------------- // // // Description: // Queues Message for retry // Parameters: // pdcntxt Delivery context set by HrPrepareDelivery // dwMsgStatus Status flags passed back with Ack // Returns: // S_OK on success // History: // 7/13/98 - MikeSwa Created (separated from HrAckMessage()) // 5/25/99 - MikeSwa Modified - Now we requeue only to DMQ that message // was originally dequeued from. This is for X5:105384, so // that we do not double-count messages that have been // ack'd retry // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrPrvRetryMessage(CDeliveryContext *pdcntxt, DWORD dwMsgStatus) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrPrvRetryMessage"); _ASSERT(pdcntxt); HRESULT hr = S_OK; DWORD i = 0; DWORD j = 0; BOOL fLocked = FALSE; BOOL fLocal = (MESSAGE_STATUS_LOCAL_DELIVERY & dwMsgStatus) ? TRUE : FALSE; DWORD dwNewFlags = fLocal ? MSGREF_MSG_LOCAL_RETRY : MSGREF_MSG_REMOTE_RETRY; CDestMsgRetryQueue *pdmrq = NULL; //make sure retry flags are set for this message DWORD dwOldFlags = dwInterlockedSetBits(&m_dwDataFlags, dwNewFlags); DebugTrace((LPARAM) this, "INFO: Message queued for retry"); InterlockedIncrement((PLONG) &m_cTimesRetried); //update global counters if first retry attempt if (!(dwNewFlags & dwOldFlags)) //this is the first time for this type of retry m_paqinst->IncRetryCount(fLocal); if (fLocal) goto Exit; // // Check and see if we should even bother requeuing the message. // if (!fShouldRetry()) { DebugTrace((LPARAM) this, "Message is no longer valid... dropping"); // // Since we have found a stale message on this queue, we should // tell it to scan on its next DSN generation pass // pdmrq = pdcntxt->pdmrqGetDMRQ(); pdmrq->CheckForStaleMsgsNextDSNGenerationPass(); goto Exit; } //Requeue Message for remote delivery if (m_paqinst->fTryShutdownLock()) { //Don't retry messages if shutdown is happening fLocked = TRUE; pdmrq = pdcntxt->pdmrqGetDMRQ(); _ASSERT(pdmrq && "Delivery Context not initialized correctly"); if (pdmrq) { hr = pdmrq->HrRetryMsg(this); if (FAILED(hr)) goto Exit; } } else { hr = S_OK; //shutdown is happening, not a real failure case } Exit: if (fLocked) m_paqinst->ShutdownUnlock(); TraceFunctLeave(); return hr; } //---[ CMsgRef::pmbmapGetDomainBitmap]----------------------------------------- // // // Description: // Protected method to get the Domain Bitmap for a given domain // Parameters: // iDomain index to the domain wanted // Returns: // ptr to the domain bitmap for the given domain // //----------------------------------------------------------------------------- CMsgBitMap *CMsgRef::pmbmapGetDomainBitmap(DWORD iDomain) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::pmbmapGetDomainBitmap"); CMsgBitMap *pmbmap = NULL; _ASSERT(iDomain < m_cDomains); //recall structure of memory, responsibility maps start 3 after end of //domain mappings pmbmap = (CMsgBitMap *) (m_rgpdmqDomains + m_cDomains); pmbmap = (CMsgBitMap *) ((DWORD_PTR) pmbmap + (3+iDomain)*CMsgBitMap::size(m_cDomains)); TraceFunctLeave(); return pmbmap; } //---[ CMsgRef::pmbmapGetHandled]------------------------------------------------------------ // // // Description: // Protected method to get the Handled Bitmap // Parameters: // - // Returns: // ptr to the handled bitmap // //----------------------------------------------------------------------------- CMsgBitMap *CMsgRef::pmbmapGetHandled() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::pmbmapGetHandled"); CMsgBitMap *pmbmap = NULL; //recall structure of memory, responsibility maps start after end of //domain mappings pmbmap = (CMsgBitMap *) (m_rgpdmqDomains + m_cDomains); TraceFunctLeave(); return pmbmap; } //---[ CMsgRef::pmbmapGetPending]------------------------------------------------------------ // // // Description: // Protected method to get the pending Bitmap // Parameters: // - // Returns: // ptr to the pending bitmap // //----------------------------------------------------------------------------- CMsgBitMap *CMsgRef::pmbmapGetPending() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::pmbmapGetPending"); CMsgBitMap *pmbmap = NULL; //recall structure of memory, responsibility maps start 1 after end of //domain mappings pmbmap = (CMsgBitMap *) (m_rgpdmqDomains + m_cDomains); pmbmap = (CMsgBitMap *) ((DWORD_PTR) pmbmap + CMsgBitMap::size(m_cDomains)); TraceFunctLeave(); return pmbmap; } //---[ CMsgRef::pmbmapGetDSN]------------------------------------------------------------ // // // Description: // Protected method to get the Handled Bitmap // Parameters: // - // Returns: // ptr to the DSN bitmap // //----------------------------------------------------------------------------- CMsgBitMap *CMsgRef::pmbmapGetDSN() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::pmbmapGetDSN"); CMsgBitMap *pmbmap = NULL; //recall structure of memory, responsibility maps start 3 after end of //domain mappings pmbmap = (CMsgBitMap *) (m_rgpdmqDomains + m_cDomains); pmbmap = (CMsgBitMap *) ((DWORD_PTR) pmbmap + 2*CMsgBitMap::size(m_cDomains)); TraceFunctLeave(); return pmbmap; } //---[ CMsgRef::pdwGetRecipIndexStart ]---------------------------------------- // // // Description: // Returns the start index of the Array of recipient indexes // Parameters: // - // Returns: // See above // //----------------------------------------------------------------------------- DWORD *CMsgRef::pdwGetRecipIndexStart() { DWORD_PTR dwTmp = 0; _ASSERT(m_cDomains); //Array of Recipient Indexes start after last CMsgBitmap dwTmp = (DWORD_PTR) pmbmapGetDomainBitmap(m_cDomains-1); //will assert if arg larger dwTmp += CMsgBitMap::size(m_cDomains); _ASSERT(((DWORD_PTR) this + size(m_cDomains)) > dwTmp); _ASSERT(dwTmp); return (DWORD *) dwTmp; } //---[ CMsgRef::SetRecipIndex ]----------------------------------------------- // // // Description: // Set the starting and stoping recipient index for a given domain. Each // Domain in the message has an inclusive range of recipients associated // with it. When the message is prepared for delivery, these ranges are // expanded into a list of recipient indexes. // Parameters: // IN iDomain Index of Domain to set range for // IN iLowRecip Index of lower recipient // IN iHighRecip Index of higher recipient // Returns: // - // //----------------------------------------------------------------------------- void CMsgRef::SetRecipIndex(DWORD iDomain, DWORD iLowRecip, DWORD iHighRecip) { _ASSERT(iDomain < m_cDomains); _ASSERT(iLowRecip <= iHighRecip); DWORD *rgdwRecipIndex = pdwGetRecipIndexStart(); rgdwRecipIndex[2*iDomain] = iLowRecip; rgdwRecipIndex[2*iDomain+1] = iHighRecip; } //---[ CMsgRef::GetRecipIndex ]------------------------------------------------ // // // Description: // Get the recipient index for a given Domain index. Used when generating // a list of recipient indexes to delivery for. // Parameters: // IN iDomain Index of Domain to get range for // OUT piLowRecip Returned lower index of recipient range // OUT piHighRecip Returned higher index of recipient range // Returns: // - // //----------------------------------------------------------------------------- void CMsgRef::GetRecipIndex(DWORD iDomain, DWORD *piLowRecip, DWORD *piHighRecip) { _ASSERT(iDomain < m_cDomains); _ASSERT(piLowRecip && piHighRecip); DWORD *rgdwRecipIndex = pdwGetRecipIndexStart(); *piLowRecip = rgdwRecipIndex[2*iDomain]; *piHighRecip = rgdwRecipIndex[2*iDomain+1]; _ASSERT(*piLowRecip <= *piHighRecip); } //---[ CMsgRef::SupersedeMsg ]------------------------------------------------- // // // Description: // Used to flag this message as superseded by a newer msg // Parameters: // - // Returns: // - // History: // 10/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::SupersedeMsg() { dwInterlockedSetBits(&m_dwDataFlags, MSGREF_SUPERSEDED); } //---[ CMsgRef::fMatchesQueueAdminFilter ]-------------------------------------- // // // Description: // Checks a message against a queue admin message filter to see if it // is a match // Parameters: // IN paqmf Message Filter to check against // Returns: // TRUE if it matches // FALSE if it does not // History: // 12/7/98 - MikeSwa Created // 2/17/99 - MikeSwa updated with better recipient checking // //----------------------------------------------------------------------------- BOOL CMsgRef::fMatchesQueueAdminFilter(CAQAdminMessageFilter *paqmf) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::fMatchesQueueAdminFilter"); _ASSERT(paqmf); HRESULT hr = S_OK; BOOL fMatch = TRUE; BOOL fUsageAdded = FALSE; DWORD dwFilterFlags = paqmf->dwGetMsgFilterFlags(); LPSTR szMsgId = NULL; //Check to see if this message is marked for deletion if (fMailMsgMarkedForDeletion()) { fMatch = FALSE; goto Exit; } if (!dwFilterFlags) { fMatch = FALSE; goto Exit; } if (AQ_MSG_FILTER_ALL & dwFilterFlags) { fMatch = TRUE; goto Exit; } //Check MsgRef props first if (AQ_MSG_FILTER_LARGER_THAN & dwFilterFlags) { fMatch = paqmf->fMatchesSize(m_cbMsgSize); if (!fMatch) goto Exit; } if (AQ_MSG_FILTER_OLDER_THAN & dwFilterFlags) { fMatch = paqmf->fMatchesTime(&m_ftQueueEntry); if (!fMatch) goto Exit; } if (AQ_MSG_FILTER_FROZEN & dwFilterFlags) { fMatch = fIsMsgFrozen(); if (AQ_MSG_FILTER_INVERTSENSE & dwFilterFlags) fMatch = !fMatch; if (!fMatch) goto Exit; } if (AQ_MSG_FILTER_FAILED & dwFilterFlags) { if (m_cTimesRetried) fMatch = TRUE; else fMatch = FALSE; if (AQ_MSG_FILTER_INVERTSENSE & dwFilterFlags) fMatch = !fMatch; if (!fMatch) goto Exit; } //If we haven't failed by this point, we may need to AddUsage and read //props from the mailmsg. Double-check to make sure that we need to //add usage. if (!((AQ_MSG_FILTER_MESSAGEID | AQ_MSG_FILTER_SENDER | AQ_MSG_FILTER_RECIPIENT) & dwFilterFlags)) goto Exit; //don't have to check props //If we are only interested in MSGID... check hash first if (AQ_MSG_FILTER_MESSAGEID & dwFilterFlags) { //If we are interested in ID & hash doesn't match then leave if (!paqmf->fMatchesIdHash(m_dwMsgIdHash)) { fMatch = FALSE; goto Exit; } } hr = InternalAddUsage(); if (FAILED(hr)) goto Exit; fUsageAdded = TRUE; //Check to see if this message is marked for deletion if (fMailMsgMarkedForDeletion()) { fMatch = FALSE; goto Exit; } if (AQ_MSG_FILTER_MESSAGEID & dwFilterFlags) { hr = HrQueueAdminGetStringProp(m_pIMailMsgProperties, IMMPID_MP_RFC822_MSG_ID, &szMsgId); if (FAILED(hr)) szMsgId = NULL; fMatch = paqmf->fMatchesId(szMsgId); if (!fMatch) { DEBUG_DO_IT(g_cDbgMsgIdHashFailures++); goto Exit; } } if (AQ_MSG_FILTER_SENDER & dwFilterFlags) { fMatch = paqmf->fMatchesMailMsgSender(m_pIMailMsgProperties); if (!fMatch) goto Exit; } if (AQ_MSG_FILTER_RECIPIENT & dwFilterFlags) { fMatch = paqmf->fMatchesMailMsgRecipient(m_pIMailMsgProperties); if (!fMatch) goto Exit; } Exit: if (szMsgId) QueueAdminFree(szMsgId); if (fUsageAdded) { InternalReleaseUsage(); BounceUsageCount(); } TraceFunctLeave(); return fMatch; } //---[ CMsgRef::HrGetQueueAdminMsgInfo ]---------------------------------------- // // // Description: // Fills out a queue admin MESSAGE_INFO structure. All allocations are // done with pvQueueAdminAlloc to be freed by the RPC code // Parameters: // IN OUT pMsgInfo MESSAGE_INFO struct to dump data to // IN pIQueueAdminAction - Action interface that is requesting info // used to determine if local or remote info should be // reported. // Returns: // S_OK on success // AQUEUE_E_MESSAGE_HANDLED if the underlying message has been deleted // E_OUTOFMEMORY if an allocation failu // History: // 12/7/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrGetQueueAdminMsgInfo(MESSAGE_INFO *pMsgInfo, IQueueAdminAction *pIQueueAdminAction) { HRESULT hr = S_OK; BOOL fUsageAdded = FALSE; FILETIME ftSubmitted; //Origination time property buffer FILETIME ftExpire; // Expiry time property buffer DWORD cbProp = 0; if (fNormalPri(m_dwDataFlags & MSGREF_PRI_MASK)) pMsgInfo->fMsgFlags = MP_NORMAL; else if (fHighPri(m_dwDataFlags & MSGREF_PRI_MASK)) pMsgInfo->fMsgFlags = MP_HIGH; else pMsgInfo->fMsgFlags = MP_LOW; if (MSGREF_MSG_FROZEN & m_dwDataFlags) pMsgInfo->fMsgFlags |= MP_MSG_FROZEN; //Report the number of failures pMsgInfo->cFailures = m_cTimesRetried; if (pMsgInfo->cFailures) pMsgInfo->fMsgFlags |= MP_MSG_RETRY; hr = InternalAddUsage(); if (FAILED(hr)) goto Exit; fUsageAdded = TRUE; //Check to see if this message is marked for deletion if (fMailMsgMarkedForDeletion()) { hr = AQUEUE_E_MESSAGE_HANDLED; goto Exit; } // // Extract properties that are stored only on mailmsg (this is shared // with all QAPI code). // hr = HrGetMsgInfoFromIMailMsgProperty(m_pIMailMsgProperties, pMsgInfo); if (FAILED(hr)) goto Exit; pMsgInfo->cbMessageSize = m_cbMsgSize; //Get submission and expiration times QueueAdminFileTimeToSystemTime(&m_ftQueueEntry, &pMsgInfo->stReceived); // // Report local expire time if we are on the local queue // if (m_paqinst && m_paqinst->fIsLocalQueueAdminAction(pIQueueAdminAction)) { if (((ULARGE_INTEGER*)&m_ftLocalExpireNDR)->QuadPart) { memcpy(&ftExpire, &m_ftLocalExpireNDR, sizeof(FILETIME)); } else { m_paqinst->CalcExpireTimeNDR(m_ftQueueEntry, TRUE, &ftExpire); } } else { if (((ULARGE_INTEGER*)&m_ftRemoteExpireNDR)->QuadPart) { memcpy(&ftExpire, &m_ftRemoteExpireNDR, sizeof(FILETIME)); } else { m_paqinst->CalcExpireTimeNDR(m_ftQueueEntry, FALSE, &ftExpire); } } QueueAdminFileTimeToSystemTime(&ftExpire, &pMsgInfo->stExpiry); //Get the time the message entered the org hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_ORIGINAL_ARRIVAL_TIME, sizeof(FILETIME), &cbProp, (BYTE *) &ftSubmitted); if (FAILED(hr)) { //Time was not written... use entry time. hr = S_OK; memcpy(&ftSubmitted, &m_ftQueueEntry, sizeof(FILETIME)); } QueueAdminFileTimeToSystemTime(&ftSubmitted, &pMsgInfo->stSubmission); Exit: if (fUsageAdded) { InternalReleaseUsage(); BounceUsageCount(); } return hr; } //---[ CMsgRef::BounceUsageCount ]--------------------------------------------- // // // Description: // Bounces usage count off of 0 to close handles. // Parameters: // - // Returns: // - // History: // 12/7/98 - MikeSwa Created // 7/6/99 - MikeSwa Modified - made async // 10/27/1999 - MikeSwa - spun-pff SyncBounceUsageCount() // //----------------------------------------------------------------------------- void CMsgRef::BounceUsageCount() { DWORD dwFlags = 0; //Bounce usage count - this will conserve handles dwFlags = dwInterlockedSetBits(&m_dwDataFlags, MSGREF_ASYNC_BOUNCE_PENDING); //If the bit was *not* already set, then it is safe to call release usage if (!(MSGREF_ASYNC_BOUNCE_PENDING & dwFlags)) { if (m_paqinst) { if (g_cMaxIMsgHandlesAsyncThreshold > (DWORD) InterlockedIncrement((PLONG)&s_cMsgsPendingBounceUsage)) { AddRef(); m_paqinst->HrQueueWorkItem(this, CMsgRef::fBounceUsageCountCompletion); //Completion function is still called on failure } else { //There are too many messages pending async commit. We //should force a synchronous bounce SyncBounceUsageCount(); } } } } //---[ CMsgRef::SyncBounceUsageCount ]----------------------------------------- // // // Description: // Forces synchronous bounce usage. Can be used when there are too many // messages pending async bounce usage or if you want to bounce usage // immediately (to force deletion of a message marked for deletion). // Parameters: // - // Returns: // - // History: // 10/27/1999 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::SyncBounceUsageCount() { BOOL fCompletionRet = FALSE; AddRef(); //Call the async bounce usage function directly fCompletionRet = fBounceUsageCountCompletion(this, ASYNC_WORK_QUEUE_NORMAL); _ASSERT(fCompletionRet); //This is hardcoded to return TRUE if (!fCompletionRet) Release(); } //---[ CMsgRef::fBounceUsageCountCompletion ]---------------------------------- // // // Description: // Async completion for BounceUsageCount // Parameters: // IN pvContext CMsgRef to bounce usage count on // Returns: // TRUE always // History: // 7/6/99 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CMsgRef::fBounceUsageCountCompletion(PVOID pvContext, DWORD dwStatus) { CMsgRef *pmsgref = (CMsgRef *) pvContext; _ASSERT(pmsgref); _ASSERT(MSGREF_SIG == pmsgref->m_dwSignature); //Don't bounce usage while where are trying to shutdown if (ASYNC_WORK_QUEUE_SHUTDOWN != dwStatus) { //NOTE - Make sure 2 threads don't call ReleaseUsage before AddUsage //(which might cause the usage count to drop below 0). if (fTrySpinLock(&(pmsgref->m_dwDataFlags), MSGREF_USAGE_COUNT_IN_USE)) { //Make sure another thread has not released the mailmsg //for shutdown. if ( pmsgref->m_pIMailMsgQM && !(MSGREF_MAILMSG_RELEASED & pmsgref->m_dwDataFlags)) { pmsgref->InternalReleaseUsage(); pmsgref->InternalAddUsage(); } //Unset the lock usage count bit (since this thread set it) ReleaseSpinLock(&(pmsgref->m_dwDataFlags), MSGREF_USAGE_COUNT_IN_USE); } } //Unset the bit set in BounceUsageCount dwInterlockedUnsetBits(&(pmsgref->m_dwDataFlags), MSGREF_ASYNC_BOUNCE_PENDING); InterlockedDecrement((PLONG)&s_cMsgsPendingBounceUsage); pmsgref->Release(); return TRUE; //This function never retries } //---[ CMsgRef::HrRemoveMessageFromQueue ]------------------------------------- // // // Description: // Remove a message from the specified queue // Parameters: // IN pdmq Queue to remove message from // Returns: // S_OK on success // History: // 12/11/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrRemoveMessageFromQueue(CDestMsgQueue *pdmq) { HRESULT hr = S_OK; DWORD i = 0; BOOL fLocal = !pdmq; //If no link is specified... it is local //Search for matching queue for (i = 0; i < m_cDomains; i++) { // // Check and see if this domain is an exact match. This will work // for local as well, since we mark the queue as NULL when we move // it to the local queue. // if (pdmq == m_rgpdmqDomains[i]) { //just check pending bits if (!pmbmapGetPending()->FTest(m_cDomains, pmbmapGetDomainBitmap(i))) { //Set Handled bits so no one else will try to deliver pmbmapGetHandled()->FTestAndSet(m_cDomains, pmbmapGetDomainBitmap(i)); } // // Stop if we aren't searching for local domains (since we can // only have one match in that case). // if (!fLocal) break; } } return hr; } //---[ CMsgRef::HrQueueAdminNDRMessage ]--------------------------------------- // // // Description: // Forcably NDRs a message for a given queue. // Parameters: // IN pdmq Queue to NDR message for // Returns: // S_OK on success // History: // 12/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrQueueAdminNDRMessage(CDestMsgQueue *pdmq) { HRESULT hr = S_OK; CQuickList qlst; CQuickList *pqlst = NULL; BOOL fLocal = TRUE; DWORD iListIndex = 0; DWORD dwDSNFlags = 0; if (pdmq) { hr = qlst.HrAppendItem(pdmq, &iListIndex); if (FAILED(hr)) goto Exit; pqlst = &qlst; fLocal = FALSE; } //Force NDR from this queue hr = HrSendDelayOrNDR(fLocal ? MSGREF_DSN_LOCAL_QUEUE : 0, pqlst, AQUEUE_E_QADMIN_NDR, &dwDSNFlags); if (FAILED(hr)) { // // Not really a failure if the message has already been handled // if (AQUEUE_E_MESSAGE_HANDLED == hr) hr = S_OK; goto Exit; } Exit: return hr; } //---[ CMsgRef::GlobalFreezeMessage ]------------------------------------------ // // // Description: // Freezes a message for all queues that it is on. // Parameters: // - // Returns: // - // History: // 12/12/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::GlobalFreezeMessage() { dwInterlockedSetBits(&m_dwDataFlags, MSGREF_MSG_FROZEN); } //---[ CMsgRef::GlobalFreezeMessage ]------------------------------------------ // // // Description: // Thaws a previously frozen messages. // Parameters: // - // Returns: // - // History: // 12/12/98 - MikeSwa Created // 2/17/2000 - MikeSwa Modified to move the kick of the DMQ up a level. // //----------------------------------------------------------------------------- void CMsgRef::GlobalThawMessage() { dwInterlockedUnsetBits(&m_dwDataFlags, MSGREF_MSG_FROZEN); } //---[ CMsgRef::RetryOnDelete ]------------------------------------------------ // // // Description: // Marks a message to retry on delete. Basically, we hit an error // that requires retrying the message at a later time (i.e. - cannot // allocate a queue page). However, we cannot put the message into the // failed retry queue until all other threads are done accessing it. // // This call will mark the MsgRef so that it will be retried after its // final release. // Parameters: // - // Returns: // - // History: // 1/19/99 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::RetryOnDelete() { if (!(MSGREF_MSG_RETRY_ON_DELETE & dwInterlockedSetBits(&m_dwDataFlags, MSGREF_MSG_RETRY_ON_DELETE))) { DEBUG_DO_IT(InterlockedIncrement((PLONG) &g_cDbgMsgRefsPendingRetryOnDelete)); } } //---[ CMsgRef::QueryInterface ]----------------------------------------------- // // // Description: // QueryInterface for CMsgRef that supports: // - IUnknown // - CMsgRef // // This is designed primarily to allow Queue Admin functionality to run // against a CMsgRef object as well a "true" COM interface like // IMailMsgProperties. // Parameters: // // Returns: // S_OK on success // History: // 2/21/99 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CMsgRef::QueryInterface(REFIID riid, LPVOID *ppvObj) { HRESULT hr = S_OK; if (!ppvObj) { hr = E_POINTER; goto Exit; } if (IID_IUnknown == riid) { *ppvObj = static_cast(this); } else if (IID_CMsgRef == riid) { *ppvObj = static_cast(this); } else { *ppvObj = NULL; hr = E_NOINTERFACE; goto Exit; } static_cast(*ppvObj)->AddRef(); Exit: return hr; } //---[ ReleaseAndBounceUsageOnMsgAck ]----------------------------------------- // // // Description: // Decides if the message should be commited on the message ack. // Parameters: // DWORD dwMsgStatus Status of msg ack // Returns: // - // History: // 7/6/99 - MikeSwa Created // 9/9/99 - Mikeswa Updated to be more aggressive about closing handles // 10/27/1999 - MikeSwa Added code to delete handled messages // //----------------------------------------------------------------------------- void CMsgRef::ReleaseAndBounceUsageOnMsgAck(DWORD dwMsgStatus) { BOOL fBounceUsage = FALSE; if (MESSAGE_STATUS_RETRY & dwMsgStatus) { //If this message was Ack'd retry... we should try an bounce the usage //count and close the associated handle. fBounceUsage = TRUE; } else if (g_cMaxIMsgHandlesThreshold < m_paqinst->cCountMsgsForHandleThrottling(m_pIMailMsgProperties)) { //If we are over our alloted number of messages in the system, we should //close the message even if was ACK'd OK. The only time we don't want //to is if all domains have been handled. In this case, we are about //to delete the message and usually do not want to commit it first. if (!pmbmapGetHandled()->FAllSet(m_cDomains)) fBounceUsage = TRUE; } // // Release the usage count added by HrPrepareDelivery // NOTE: This must come before the call to Bounce... or the // call to bounce usage will have no effect. It must also // occur after the above call to cCountMsgsForHandleThrottling, // because it may request a property from the mailmsg. // InternalReleaseUsage(); if (pmbmapGetHandled()->FAllSet(m_cDomains)) { //There is are 2 known cases were the above could leave //the message handles open for a while. In both cases, this message //must be queued to multiple DMQs on the same link, and be completely //delivered. In one case, the message is dequeued during delivery and //sits on the retry queue until it is remerged. In another case, the //message sits on the delivery queue until a thead pulls it off and //discovers that it is already handled. //To avoid this, we will mark the message for deletion when the //intneral usage count drops to 0. MarkMailMsgForDeletion(); } if (fMailMsgMarkedForDeletion()) { //Force synchronous bounce usgage since delete should be much //quicker than commit SyncBounceUsageCount(); } else if (fBounceUsage) { BounceUsageCount(); } } //---[ CMsgRef::MarkMailMsgForDeletion ]--------------------------------------- // // // Description: // Sets the bit saying the that we are done with the mailmsg, and it // should delete it the next time the usage count drops to 0. // // To force deletion, the caller should bounce the usage count after // calling. // Parameters: // - // Returns: // - // History: // 10/26/1999 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::MarkMailMsgForDeletion() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::MarkMailMsgForDeletion"); DWORD dwOrigFlags = 0; DebugTrace((LPARAM) this, "Marking message with ID hash of 0x%x for deletion", m_dwMsgIdHash); dwOrigFlags = dwInterlockedSetBits(&m_dwDataFlags, MSGREF_MAILMSG_DELETE_PENDING); //If we were the first thread to set this, then update the counters if (!(dwOrigFlags & MSGREF_MAILMSG_DELETE_PENDING)) { InterlockedIncrement((PLONG) &s_cCurrentMsgsPendingDelete); InterlockedIncrement((PLONG) &s_cTotalMsgsPendingDelete); } _ASSERT(fMailMsgMarkedForDeletion()); TraceFunctLeave(); } //---[ CMsgRef::ThreadSafeMailMsgDelete ]-------------------------------------- // // // Description: // Used to make sure that calling thread is the only one that will call // Delete() on the MailMsg. Will set the MSGREF_MAILMSG_DELETED and call // Delete(). Only called in ReleaseMailMsg() and InternalReleaseUsage(). // // The caller is responsible for making sure that other threads are not // reading the mailmsg or have a usage count. // Parameters: // - // Returns: // - // History: // 10/27/1999 - MikeSwa Created // //----------------------------------------------------------------------------- VOID CMsgRef::ThreadSafeMailMsgDelete() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::ThreadSafeMailMsgDelete"); DWORD dwOrigFlags = 0; HRESULT hr = S_OK; //Try to set the MSGREF_MAILMSG_DELETED. dwOrigFlags = dwInterlockedSetBits(&m_dwDataFlags, MSGREF_MAILMSG_DELETED); //If we are the first thread to set it, then we can Delete() if (!(dwOrigFlags & MSGREF_MAILMSG_DELETED)) { hr = m_pIMailMsgQM->Delete(NULL); if (FAILED(hr)) ErrorTrace((LPARAM) this, "Delete failed with hr 0x%08X", hr); //If this message was marked as pending delete, then update //the appropriate counters if (dwOrigFlags & MSGREF_MAILMSG_DELETE_PENDING) { InterlockedDecrement((PLONG) &s_cCurrentMsgsPendingDelete); InterlockedIncrement((PLONG) &s_cTotalMsgsDeletedAfterPendingDelete); InterlockedIncrement((PLONG) &s_cCurrentMsgsDeletedNotReleased); } } TraceFunctLeave(); } //---[ CMsgRef::InternalAddUsage ]--------------------------------------------- // // // Description: // Wraps the mailmsg call to AddUsage. Allows CMsgRef to call Delete() // on the underlying mailmsg while there are still references to the // CMsgRef. // // Calling InternalAddUsage does not guarantee that there is backing // storage for the MailMsg. Callers must call fMailMsgMarkedForDeletion() // *after* calling InternalAddUsage() // // We do guarantee that if InternalAddUsage() is called and a then // subsequent call to fMailMsgMarkedForDeletion() returns TRUE, then // the mailmsg will not be deleted until after the corresponding // call to InternalReleaseUsage(); // Parameters: // - // Returns: // S_OK if the message is deleted and we did not call into mailmsg // Error/Success code from mailmsg if we called in. // History: // 10/26/1999 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::InternalAddUsage() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::InternalAddUsage"); HRESULT hr = S_OK; InterlockedIncrement((PLONG) &m_cInternalUsageCount); if (!fMailMsgMarkedForDeletion() && m_pIMailMsgQM) { hr = m_pIMailMsgQM->AddUsage(); } //If the call to AddUsage failed, we need to decrement our own count if (FAILED(hr)) { InterlockedDecrement((PLONG) &m_cInternalUsageCount); ErrorTrace((LPARAM) this, "AddUsage failed 0x%0X", hr); } TraceFunctLeave(); return hr; } //---[ CMsgRef::InternalReleaseUsage ]----------------------------------------- // // // Description: // Wraps the mailmsg call to ReleaseUsage. Allows us to call Delete() // on the mailmsg while there are still references to this CMsgRef. // // If MSGREF_MAILMSG_DELETE_PENDING is set but Delete() has not yet been // called, this will call Delete() when the usage count hits 0. // Parameters: // - // Returns: // S_OK if the message is deleted and we did not call into mailmsg // Error/Success code from mailmsg if we called in. // History: // 10/26/1999 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::InternalReleaseUsage() { TraceFunctEnterEx((LPARAM) this, "CMsgRef::InternalReleaseUsage"); HRESULT hr = S_OK; DWORD dwOrigFlags = 0; //We need to call into mailmsg *before* we decrement the usage count. It is //theoretically possible that this would cause an extra commit after we //have decided to delete the message, but since we check right before //calling the timing window is very small. if (m_pIMailMsgQM && !fMailMsgMarkedForDeletion()) hr = m_pIMailMsgQM->ReleaseUsage(); _ASSERT(m_cInternalUsageCount); //Should never go negative if (FAILED(hr)) ErrorTrace((LPARAM) this, "ReleaseUsage failed - 0x%0X", hr); //If we have dropped the usage count to zero, then we need to check //and see if we need to call delete. //The "at rest" value of m_cInternalUsageCount is 1, but BounceUsage //(which calls this function) can cause it to drop to 0. if (0 == InterlockedDecrement((PLONG) &m_cInternalUsageCount)) { //We need to be absolutely thread safe about calling delete. Not only //do we need to ensure that a single thread calls Delete(), but we //also need to ensure that someone has not called InternalAddUsage() //before MSGREF_MAILMSG_DELETE_PENDING was set, but after the //above InterlockedDecrement() was called. // //If we check for the MSGREF_MAILMSG_DELETE_PENDING before the //aboved InterlockedDecrement, we can run into a timing window where //the final InternalReleaseUsage does not detect that we need to delete //the mailmsg. // //To avoid these issues, we will check the m_cInternalUsageCount //again. If it is still zero, then we will proceed, because we know //that the count hit zero after MSGREF_MAILMSG_DELETE_PENDING was set. //If the count is *not* currently zero, we know that a later thread //will release the usage count and hit this code path. if (fMailMsgMarkedForDeletion()) { if (!m_cInternalUsageCount) ThreadSafeMailMsgDelete(); } } TraceFunctLeave(); return hr; } //---[ CMsgRef::ShouldRetry ]-------------------------------------------------- // // // Description: // Determines if we should retry this message if an error occurs. Will // return FALSE if we *know* that the backing store has been deleted. // Parameters: // - // Returns: // TRUE if we should rety the message // FALSE, the backing store for the message is gone... drop it // History: // 1/6/2000 - MikeSwa Created // 4/12/2000 - MikeSwa modified to call CAQSvrInst member // //----------------------------------------------------------------------------- BOOL CMsgRef::fShouldRetry() { IMailMsgProperties *pIMailMsgProperties = m_pIMailMsgProperties; if (pIMailMsgProperties && m_paqinst) return m_paqinst->fShouldRetryMessage(pIMailMsgProperties, FALSE); else return FALSE; } //---[ CMsgRef::GetStatsForMsg ]----------------------------------------------- // // // Description: // Fills in a CAQStats for this message // Parameters: // IN OUT paqstat Stats to fill in // Returns: // NULL // History: // 1/15/2000 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::GetStatsForMsg(IN OUT CAQStats *paqstat) { _ASSERT(paqstat); paqstat->m_cMsgs = 1; paqstat->m_rgcMsgPriorities[PriGetPriority()] = 1; paqstat->m_uliVolume.QuadPart = (ULONGLONG) dwGetMsgSize(); paqstat->m_dwHighestPri = PriGetPriority(); } //---[ CMsgRef::MarkQueueAsLocal ]--------------------------------------------- // // // Description: // Marks a given DMQ as local. This is used in the gateway delivery // path to prevent messages from being lost if a reroute happens after // it has been moved to the local delivery queue. // Parameters: // pdmq Queue to mark as local for this message // Returns: // - // History: // 2/17/2000 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::MarkQueueAsLocal(IN CDestMsgQueue *pdmq) { DWORD i = 0; // //Search for matching queue // for (i = 0; i < m_cDomains; i++) { if (pdmq == m_rgpdmqDomains[i]) { // // Exact match found. Set pointer to NULL and release it. // The caller (CLinkMsgQueue) should still have a reference to // the queue. // if (InterlockedCompareExchangePointer((void **) &m_rgpdmqDomains[i], NULL, pdmq) == (void *) pdmq) pdmq->Release(); return; } } _ASSERT(0 && "Requested DMQ not found!!!!"); } //---[ CountMessageInRemoteTotals ]-------------------------------------------- // // // Description: // Count this message in the totals for remote messages. This means that // we need to decrement the count when this message is released. // // There is no exactly equivalent local count. The counters for local // delivery has based on queue length and can have multiple counts per // message object (as can some of the remote counts as well). // Parameters: // - // Returns: // - // History: // 2/28/2000 - MikeSwa Created // //----------------------------------------------------------------------------- void CMsgRef::CountMessageInRemoteTotals() { dwInterlockedSetBits(&m_dwDataFlags, MSGREF_MSG_COUNTED_AS_REMOTE); } //---[ CMsgRef::HrPromoteMessageStatusToMailMsg ]------------------------------ // // // Description: // Promotes extended status from MessageAck to mailmsg recipient property // if there is not already specific information there. // Parameters: // pdcntxt Delivery context for this message // pMsgAck Ptr to MessageAck structure for this message // Returns: // S_OK on success // History: // 3/20/2000 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrPromoteMessageStatusToMailMsg(CDeliveryContext *pdcntxt, MessageAck *pMsgAck) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrPromoteMessageStatusToMailMsg"); HRESULT hr = S_OK; HRESULT hrRecipStatus = S_OK; HRESULT hrTmp = S_OK; RECIPIENT_FILTER_CONTEXT rpfctxt; BOOL fContextInit = FALSE; DWORD iCurrentRecip = 0; DWORD dwProp = 0; //place holder for recipient status property LPSTR szExtendedStatus = NULL; DWORD dwRecipMask = RP_DSN_HANDLED | // No DSN generated RP_DSN_NOTIFY_NEVER | // NOTIFY!=NEVER (RP_DELIVERED ^ RP_HANDLED); // Not delivered DWORD dwRecipFlags = 0; _ASSERT(pdcntxt); _ASSERT(pMsgAck); _ASSERT(m_pIMailMsgRecipients); // // If we have no extended status, then there is nothing for us to do // if (!(pMsgAck->dwMsgStatus & MESSAGE_STATUS_EXTENDED_STATUS_CODES) || !pMsgAck->cbExtendedStatus || !pMsgAck->szExtendedStatus) { DebugTrace((LPARAM) this, "No extended status codes in MessageAck"); goto Exit; } // // If this was actually not a protocol error, SMTP may not have put // the full status codes on it. Instead of "250 2.0.0 OK" it may // have just put "OK". We should not write this to mailmsg as is, but // should instead fix up string so it is what the DSN sink expects. // hr = HrUpdateExtendedStatus(pMsgAck->cbExtendedStatus, pMsgAck->szExtendedStatus, &szExtendedStatus); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to get extended status from %s - hr 0x%08X", pMsgAck->szExtendedStatus, hr); hr = S_OK; //non-fatal error... eat it goto Exit; } // // Initialize recipient filter context so we can iterate over the // recipients // hr = m_pIMailMsgRecipients->InitializeRecipientFilterContext(&rpfctxt, pdcntxt->m_dwStartDomain, dwRecipFlags, dwRecipMask); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "InitializeRecipientFilterContext failed with 0x%08X", hr); goto Exit; } fContextInit = TRUE; DebugTrace((LPARAM) this, "Init recip filter context with mask 0x%08X and flags 0x%08X and domain %d", dwRecipMask, dwRecipFlags, pdcntxt->m_dwStartDomain); // // Loop over each recipient and update their properties if needed // for (hr = m_pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); SUCCEEDED(hr); hr = m_pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip)) { DebugTrace((LPARAM) this, "Looking at recipient %d", iCurrentRecip); // // See if we have an HRESULT... if it *is* there and is a failure // then continue on to the next recipient // hrRecipStatus = S_OK; hr = m_pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_ERROR_CODE, (DWORD *) &hrRecipStatus); if (SUCCEEDED(hr) && FAILED(hrRecipStatus)) { DebugTrace((LPARAM) this, "Recipient %d already has a status of 0x%08X", iCurrentRecip, hrRecipStatus); continue; } // // Check for existing status property // hr = m_pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_SMTP_STATUS_STRING, &dwProp); if (MAILMSG_E_PROPNOTFOUND != hr) { DebugTrace((LPARAM) this, "Recipient %d has a status string (hr 0x%08X)", iCurrentRecip, hr); continue; } // // There is no detailed information on the recipient. We should // promote the extended status from the message ack to the recipient // hr = m_pIMailMsgRecipients->PutStringA(iCurrentRecip, IMMPID_RP_SMTP_STATUS_STRING, szExtendedStatus); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to write %s to recip %d - hr 0x%08X", szExtendedStatus, iCurrentRecip, hr); goto Exit; } DebugTrace((LPARAM) this, "Wrote extended status %s to recip %d", szExtendedStatus, iCurrentRecip); } if (HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) == hr) hr = S_OK; //we just reached the end of the context Exit: // // Terminate context if init was called // if (fContextInit) { hrTmp = m_pIMailMsgRecipients->TerminateRecipientFilterContext(&rpfctxt); if (FAILED(hrTmp)) { ErrorTrace((LPARAM) this, "TerminateRecipientFilterContext failed 0x%08X", hr); } } TraceFunctLeave(); return hr; } // // These are strings defined in smtpcli.hxx that are used in smtpout.cxx // in calls to m_ResponseContext.m_cabResponse.Append(). These strings // are not in the normal protocol format of "xxx x.x.x Error string" // // g_cNumExtendedStatusStrings Number of hard-coded strings // g_rgszSMTPExtendedStatus SMTP extended status // g_rgszSMTPUpdatedExtendedStatus Full protocol strings. // const DWORD g_cNumExtendedStatusStrings = 4; const CHAR g_rgszSMTPExtendedStatus[g_cNumExtendedStatusStrings][200] = { "Msg Size greater than allowed by Remote Host", "Body type not supported by Remote Host", "Failed to authenticate with Remote Host", "Failed to negotiate secure channel with Remote Host", }; const CHAR g_rgszSMTPUpdatedExtendedStatus[g_cNumExtendedStatusStrings][200] = { "450 5.2.3 Msg Size greater than allowed by Remote Host", "554 5.6.1 Body type not supported by Remote Host", "505 5.7.3 Failed to authenticate with Remote Host", "505 5.7.3 Failed to negotiate secure channel with Remote Host", }; // // Extended status strings for some known cases where we reach HrUpdateExtendedStatus // without having a valid extended status string // const CHAR g_szSMTPStatus_BadSenderAddress [] = "500 5.1.7 Invalid or missing sender SMTP address"; const CHAR g_szSMTPStatus_OtherError [] = "500 5.5.6 Unknown SMTP delivery error"; //---[ CMsgRef::HrUpdateExtendedStatus ]--------------------------------------- // // // Description: // Sometime SMTP is lazy about returning actual protocol-complient // strings for internal errors. If this is not a valid status string, // then we should check against the strings that we know SMTP generates // and make it a protocol string that can be used in DSN generation. // // Parameters: // IN szCurrentStatus current status // OUT pszNewStatus New protcol friendly status string. This // is a const that does not need to be freed // Returns: // S_OK on succcess // E_FAIL when we cannot extract a protocol string from the // extended status string. // History: // 3/20/2000 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CMsgRef::HrUpdateExtendedStatus(DWORD cbCurrentStatus, LPSTR szCurrentStatus, LPSTR *pszNewStatus) { TraceFunctEnterEx((LPARAM) this, "CMsgRef::HrUpdateExtendedStatus"); HRESULT hr = S_OK; DWORD iCurrentExtendedStatus = 0; _ASSERT(szCurrentStatus); _ASSERT(pszNewStatus); *pszNewStatus = szCurrentStatus; // // Check for known condition where szCurrentStatus is set to an // unterminated '5' // if (szCurrentStatus[0] == '5' && cbCurrentStatus == 1) { DWORD cbProp = 0; CHAR szBuffer[100]; // Check to see if the sender is invalid hr = m_pIMailMsgProperties->GetProperty(IMMPID_MP_SENDER_ADDRESS_SMTP, sizeof(szBuffer), &cbProp,(BYTE *) szBuffer); if(FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { // Failed to read sender - set status accordingly *pszNewStatus = (CHAR*) g_szSMTPStatus_BadSenderAddress; } else { // Succeeded reading sender or failed due to insufficient // buffer - which is fine, we only care if the prop exists *pszNewStatus = (CHAR*) g_szSMTPStatus_OtherError; } DebugTrace((LPARAM) this, "Updating char '5' to status \"%s\"", *pszNewStatus); // This isn't a failure even if we didn't get the prop. hr = S_OK; goto Exit; } // // If it starts with a 2 4 or 5, then we know it is not one of the // non-protocol strings // if (('2' == *szCurrentStatus) || ('4' == *szCurrentStatus) || ('5' == *szCurrentStatus)) { DebugTrace((LPARAM) this, "Status %s is already in protocol format", szCurrentStatus); goto Exit; } // // check against all well-known status // for (iCurrentExtendedStatus = 0; iCurrentExtendedStatus < g_cNumExtendedStatusStrings; iCurrentExtendedStatus++) { if (0 == lstrcmpi(szCurrentStatus, g_rgszSMTPExtendedStatus[iCurrentExtendedStatus])) { *pszNewStatus = (CHAR *) g_rgszSMTPUpdatedExtendedStatus[iCurrentExtendedStatus]; DebugTrace((LPARAM) this, "Updating to status \"%s\" from status \"%s\"", szCurrentStatus, *pszNewStatus); // // Our strings should match (modulo the new prefix) // _ASSERT(0 == lstrcmpi(szCurrentStatus, *pszNewStatus + sizeof("xxx x.x.x"))); goto Exit; } } hr = E_FAIL; Exit: TraceFunctLeave(); return hr; }