//--------------------------------------------------------------------------- // // // File: main.cpp // // Description: Main file for SMTP retry sink // // Author: NimishK // // History: // 7/15/99 - MikeSwa Moved to Platinum // // Copyright (C) 1999 Microsoft Corporation // //--------------------------------------------------------------------------- #include "precomp.h" //constants // #define MAX_RETRY_OBJECTS 15000 #define DEFAULT_GLITCH_FAILURE_THRESHOLD 3 #define DEFAULT_FIRST_TIER_RETRY_THRESHOLD 6 #define DEFAULT_GLITCH_FAILURE_RETRY_SECONDS (1 * 60) // retry a glitch intwo minutes #define DEFAULT_FIRST_TIER_RETRY_SECONDS (15 * 60) // retry a failure in 15 minutes #define DEFAULT_SECOND_TIER_RETRY_SECONDS (60 * 60) // retry a failure in 60 minutes // provide memory for static declared in RETRYHASH_ENTRY // CPool CRETRY_HASH_ENTRY::PoolForHashEntries(RETRY_ENTRY_SIGNATURE_VALID); DWORD CSMTP_RETRY_HANDLER::dwInstanceCount = 0; //Forward declarations // BOOL ShouldHoldForRetry(DWORD dwConnectionStatus, DWORD cFailedMsgCount, DWORD cTriedMsgCount); //Debugging related // #define LOGGING_DIRECTORY "c:\\temp\\" enum DEBUGTYPE { INSERT, UPDATE }; #ifdef DEBUG void WriteDebugInfo(CRETRY_HASH_ENTRY* pRHEntry, DWORD DebugType, DWORD dwConnectionStatus, DWORD cTriedMessages, DWORD cFailedMessages); #endif //-------------------------------------------------------------------------------- // Logic : // In a normal state every hashentry is added to a retry hash and // a retry queue strcture. // An entry is considered deleted when removed from both structures. // The deletion could happen in two ways depending on the sequence in // which the entry is removed from the two structres. // For eg : when an entry is to be released from retry, we start by // dequeing it from RETRYQ and then remove it from hash table. // On the other hand when we get successful ConnectionReleased( ) for // a domain that we are holding fro retry, we remove it from the hash // table first based on the name. // The following is the logic for deletion that has least contention and // guards against race conditions. // Every hash entry has normally two ref counts - one for hash table and // the other for the retry queue. // If a thread gets into ProcessEntry(), that means it dequed a // hash entry from RETRYQ. Obviously no other thread is going to // succeed in dequeing this same entry. // Some other thread could possibily remove it from the table, but // will not succeed in dequeing it from RETRYQ. // The deletion logic is that only the thread succeeding in dequing // the hash entry from RETRYQ frees it up. // The conflicting thread that removed the entry from hashtable via a // call to RemoveDomain() will fail on deque and simply carry on. // The thread that succeeded in dequeing may fail to remove it from hash // table becasue somebody has already removed it, but still goes ahead // and frees up the hash entry //-------------------------------------------------------------------------------- //------------------------------------------------------------------------------ // CSMTP_RETRY_HANDLER::HrInitialize // // //------------------------------------------------------------------------------ // HRESULT CSMTP_RETRY_HANDLER::HrInitialize(IN IConnectionRetryManager *pIConnectionRetryManager) { TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::HrInitialize"); //Decide if we need to copy over data from earlier sink _ASSERT(pIConnectionRetryManager != NULL); if(!pIConnectionRetryManager) { ErrorTrace((LPARAM)this, "Bad Init params"); return E_FAIL; } m_pIRetryManager = pIConnectionRetryManager; m_ThreadsInRetry = 0; if(InterlockedIncrement((LONG*)&CSMTP_RETRY_HANDLER::dwInstanceCount) == 1) { //First instance to come in reserves the memory for the retry entries if (!CRETRY_HASH_ENTRY::PoolForHashEntries.ReserveMemory( MAX_RETRY_OBJECTS, sizeof(CRETRY_HASH_ENTRY))) { DWORD err = GetLastError(); ErrorTrace((LPARAM)NULL, "ReserveMemory failed for CRETRY_HASH_ENTRY. err: %u", err); _ASSERT(err != NO_ERROR); if(err == NO_ERROR) err = ERROR_NOT_ENOUGH_MEMORY; TraceFunctLeaveEx((LPARAM)NULL); return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } } //Initialize Hash Table m_pRetryHash = new CRETRY_HASH_TABLE(); if(!m_pRetryHash || !m_pRetryHash->IsHashTableValid()) { ErrorTrace((LPARAM)this, "Failed to initialize the hash table "); _ASSERT(0); TraceFunctLeaveEx((LPARAM)this); return E_FAIL; } //Create the retry queue m_pRetryQueue = CRETRY_Q::CreateQueue(); if(!m_pRetryQueue) { ErrorTrace((LPARAM)this, "Failed to initialize the retry queue "); _ASSERT(0); TraceFunctLeaveEx((LPARAM)this); return E_FAIL; } //create the Retry queue event. Others will set this event //when something is placed at the top of the queue or when //Sink needs to shutdown // m_RetryEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_RetryEvent == NULL) { TraceFunctLeaveEx((LPARAM)this); return FALSE; } //create the Shutdown event. The last of the ConnectionReleased //threads will set this event when the Shutting down flag is set. // m_ShutdownEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_ShutdownEvent == NULL) { TraceFunctLeaveEx((LPARAM)this); return FALSE; } //create the thread that processes things out of the //the queue DWORD ThreadId; m_ThreadHandle = CreateThread (NULL, 0, CSMTP_RETRY_HANDLER::RetryThreadRoutine, this, 0, &ThreadId); if (m_ThreadHandle == NULL) { TraceFunctLeaveEx((LPARAM)this); return FALSE; } //Initialize RetryQ return S_OK; TraceFunctLeaveEx((LPARAM)this); } //------------------------------------------------------------------------------- // CSMTP_RETRY_HANDLER::HrDeInitialize // // //-------------------------------------------------------------------------------- HRESULT CSMTP_RETRY_HANDLER::HrDeInitialize(void) { TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::HrDeInitialize"); //Set the flag that the Handler is shutting down SetShuttingDown(); //Release the Retry thread by setting the retry event SetQueueEvent(); //Wait for the thread to exit //NK** - right now this is infinite wait - but that needs to //change and we will have to comeout and keep giving hints WaitForQThread(); //At this point we just need to wait for all the threads that are in there //to go out and we can then shutdown //Obviously ConnectionManager has to to stop sending threads this way //NK** - right now this is infinite wait - but that needs to //change and we will have to comeout and keep giving hints if(m_ThreadsInRetry) WaitForShutdown(); //Close the shutdown Event handle if(m_ShutdownEvent != NULL) CloseHandle(m_ShutdownEvent); //Close the Retry Event handle if(m_RetryEvent != NULL) CloseHandle(m_RetryEvent); //Close the Retry Thread handle if(m_ThreadHandle != NULL) CloseHandle(m_ThreadHandle); //Once all threads are gone //we can deinit the hash table and the queue m_pRetryQueue->DeInitialize(); m_pRetryHash->DeInitialize(); //Release the shedule manager m_pIRetryManager->Release(); if(InterlockedDecrement((LONG*)&CSMTP_RETRY_HANDLER::dwInstanceCount) == 0) { //finally, release all our memory CRETRY_HASH_ENTRY::PoolForHashEntries.ReleaseMemory(); } TraceFunctLeaveEx((LPARAM)this); delete this; return S_OK; } //---[ CSMTP_RETRY_HANDLER::ConnectionReleased ]------------------------------- // // // Description: // Default sink for ConnectionReleased event // Parameters: // - see aqintrnl.idl for a description of parameters // Returns: // S_OK on success // History: // 9/24/98 - MikeSwa updated from original ConnectionReleased // //----------------------------------------------------------------------------- STDMETHODIMP CSMTP_RETRY_HANDLER::ConnectionReleased( IN DWORD cbDomainName, IN CHAR szDomainName[], IN DWORD dwDomainInfoFlags, IN DWORD dwScheduleID, IN GUID guidRouting, IN DWORD dwConnectionStatus, IN DWORD cFailedMessages, IN DWORD cTriedMessages, IN DWORD cConsecutiveConnectionFailures, OUT BOOL* pfAllowImmediateRetry, OUT FILETIME *pftNextRetryTime) { TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::ConnectionReleased"); HRESULT hr; DWORD dwError; GUID guid = GUID_NULL; LPSTR szRouteHashedDomain = NULL; //Keep a track of threads that are inside //This will be needed in shutdown InterlockedIncrement(&m_ThreadsInRetry); //By default, we will allow the domain to retry _ASSERT(pfAllowImmediateRetry); *pfAllowImmediateRetry = TRUE; _ASSERT(pftNextRetryTime); if(TRUE) { // Check what we want to do // **If we need to disable the connection - disable it // **Check if there are any outstanding connections // If no connections, calculate the retry time and add in the queue and return if(ShouldHoldForRetry(dwConnectionStatus, cFailedMessages, cTriedMessages)) { //Do not hold TURN/ETRN domains for retry (except for "glitch" retry) if((!(dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY))) || (cConsecutiveConnectionFailures < m_dwRetryThreshold)) { //Insert it - we could fail to insert it if an entry already exists //That is OK - we will return success if(!InsertDomain(szDomainName, cbDomainName, dwConnectionStatus, dwScheduleID, &guidRouting, cConsecutiveConnectionFailures, cTriedMessages, cFailedMessages, pftNextRetryTime )) { dwError = GetLastError(); DebugTrace((LPARAM)this, "Failed to insert %s entry into retry hash table : Err : %d ", szDomainName, dwError); if(dwError == ERROR_FILE_EXISTS ) { //We did not insert because the entry was already there *pfAllowImmediateRetry = FALSE; hr = S_OK; goto Exit; } else { if(dwError == ERROR_NOT_ENOUGH_MEMORY ) { hr = E_OUTOFMEMORY; } else { _ASSERT(0); hr = E_FAIL; } goto Exit; } } //Normal retry domain *pfAllowImmediateRetry = FALSE; DebugTrace((LPARAM)this, "Holding domain %s for retry",szDomainName); } } else { // Some connection succeeded for this domain. //If we have it marked for retry - it needs to be freed up //Looks like the incident which caused retry has cleared up. CHAR szHashedDomain[MAX_RETRY_DOMAIN_NAME_LEN]; //Hash schedule ID and router guid to domain name CreateRouteHash(cbDomainName, szDomainName, ROUTE_HASH_SCHEDULE_ID, &guidRouting, dwScheduleID, szHashedDomain, sizeof(szHashedDomain)); RemoveDomain(szHashedDomain); hr = S_OK; goto Exit; } } hr = S_OK; Exit : //Keep a track of threads that are inside //This will be needed in shutdown if(InterlockedDecrement(&m_ThreadsInRetry) == 0 && IsShuttingDown()) { //we signal the shutdown event to indicate that //no more threads are in the system _ASSERT(m_ShutdownEvent != NULL); SetEvent(m_ShutdownEvent); } TraceFunctLeaveEx((LPARAM)this); return hr; } ///////////////////////////////////////////////////////////////////////////////// // CSMTP_RETRY_HANDLER::InsertDomain // // ///////////////////////////////////////////////////////////////////////////////// BOOL CSMTP_RETRY_HANDLER::InsertDomain(char * szDomainName, IN DWORD cbDomainName, IN DWORD dwConnectionStatus, //eConnectionStatus IN DWORD dwScheduleID, IN GUID *pguidRouting, IN DWORD cConnectionFailureCount, IN DWORD cTriedMessages, //# of untried messages in queue IN DWORD cFailedMessages, //# of failed message for *this* connection OUT FILETIME *pftNextRetry) { DWORD dwError; FILETIME TimeNow; FILETIME RetryTime; CRETRY_HASH_ENTRY* pRHEntry = NULL; TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::InsertDomain"); //Get the insertion time for the entry GetSystemTimeAsFileTime(&TimeNow); //Cpool based allocations for hash entries pRHEntry = new CRETRY_HASH_ENTRY (szDomainName, cbDomainName, dwScheduleID, pguidRouting, &TimeNow); if(!pRHEntry) { //_ASSERT(0); dwError = GetLastError(); DebugTrace((LPARAM)this, "failed to Create a new hash entry : %s err: %d", szDomainName, dwError); SetLastError(ERROR_NOT_ENOUGH_MEMORY); TraceFunctLeaveEx((LPARAM)this); return FALSE; } //Based on the current time and the number of connections failures calculate the //time of release for retry RetryTime = CalculateRetryTime(cConnectionFailureCount, &TimeNow); pRHEntry->SetRetryReleaseTime(&RetryTime); pRHEntry->SetFailureCount(cConnectionFailureCount); //The hash entry has been initialized //Insert it - we could fail to insert it if an entry already exists //That is OK - we will return success if(!m_pRetryHash->InsertIntoTable (pRHEntry)) { //Free up the entry _ASSERT(pRHEntry); delete pRHEntry; TraceFunctLeaveEx((LPARAM)this); return FALSE; } else { //Report next retry time if (pftNextRetry) memcpy(pftNextRetry, &RetryTime, sizeof(FILETIME)); //Insert into the retry queue. BOOL fTopOfQueue = FALSE; //Lock the queue m_pRetryQueue->LockQ(); m_pRetryQueue->InsertSortedIntoQueue(pRHEntry, &fTopOfQueue); #ifdef DEBUG //Add ref count for logging before releasing the lock //Do rtacing afterwards so as to reduce lock time pRHEntry->IncRefCount(); #endif m_pRetryQueue->UnLockQ(); //If the insertion was at the top of the queue //wake up the retry thread to evaluate the new //sleep time if(fTopOfQueue) { SetEvent(m_RetryEvent); } #ifdef DEBUG //Write out the insert and release time to a file // WriteDebugInfo(pRHEntry, INSERT, dwConnectionStatus, cTriedMessages, cFailedMessages); //Decrement the ref count obtained for the tracing pRHEntry->DecRefCount(); #endif } TraceFunctLeaveEx((LPARAM)this); return TRUE; } //--------------------------------------------------------------------------------- // CSMTP_RETRY_HANDLER::RemoveDomain // // //--------------------------------------------------------------------------------- // BOOL CSMTP_RETRY_HANDLER::RemoveDomain(char * szDomainName) { PRETRY_HASH_ENTRY pRHEntry; TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::RemoveDomain"); if(!m_pRetryHash->RemoveFromTable(szDomainName, &pRHEntry)) { if(GetLastError() == ERROR_PATH_NOT_FOUND) return TRUE; else { _ASSERT(0); TraceFunctLeaveEx((LPARAM)this); return FALSE; } } _ASSERT(pRHEntry != NULL); //Remove it from the queue m_pRetryQueue->LockQ(); if(!m_pRetryQueue->RemoveFromQueue(pRHEntry)) { m_pRetryQueue->UnLockQ(); if(GetLastError() == ERROR_PATH_NOT_FOUND) return TRUE; else { _ASSERT(0); TraceFunctLeaveEx((LPARAM)this); return FALSE; } } m_pRetryQueue->UnLockQ(); //If successful in removing from the queue then we are not competing with //the Retry thread //decrement hash table ref count as well as the ref count for the queue pRHEntry->DecRefCount(); pRHEntry->DecRefCount(); //Release this entry by setting the right flags //This should always succeed DebugTrace((LPARAM)this, "Releasing domain %s because another connection succeeded", szDomainName); if(!ReleaseForRetry(szDomainName)) { ErrorTrace((LPARAM)this, "Failed to release the entry"); TraceFunctLeaveEx((LPARAM)this); //_ASSERT(0); } return TRUE; } //--------------------------------------------------------------------------------- // // CSMTP_RETRY_HANDLER::CalculateRetryTime // // Logic to decide based on the number of failed connection how long to hld this // domain for retry // //--------------------------------------------------------------------------------- FILETIME CSMTP_RETRY_HANDLER::CalculateRetryTime(DWORD cFailedConnections, FILETIME* InsertedTime) { FILETIME ftTemp; LONGLONG Temptime; DWORD dwRetryMilliSec = 0; //Does this look like a glitch //A glitch is defined as less than x consecutive failures if(cFailedConnections < m_dwRetryThreshold) dwRetryMilliSec = m_dwGlitchRetrySeconds * 1000; else { switch(cFailedConnections - m_dwRetryThreshold) { case 0: dwRetryMilliSec = m_dwFirstRetrySeconds * 1000; break; case 1: dwRetryMilliSec = m_dwSecondRetrySeconds * 1000; break; case 2: dwRetryMilliSec = m_dwThirdRetrySeconds * 1000; break; case 3: dwRetryMilliSec = m_dwFourthRetrySeconds * 1000; break; default: dwRetryMilliSec = m_dwFourthRetrySeconds * 1000; break; } } _ASSERT(dwRetryMilliSec); Temptime = INT64_FROM_FILETIME(*InsertedTime) + HnsFromMs((__int64)dwRetryMilliSec); // HnsFromMin(m_RetryMinutes) ftTemp = FILETIME_FROM_INT64(Temptime); return ftTemp; } //--------------------------------------------------------------------------------- // // CSMTP_RETRY_HANDLER::ProcessEntry // // Description : // Process the hash entry removed from the queue because it is // time to release the corresponding domain. // We mark the domain active for retry and then take the hash // entry out of the hash table and delete the hash entry. // //--------------------------------------------------------------------------------- void CSMTP_RETRY_HANDLER::ProcessEntry(PRETRY_HASH_ENTRY pRHEntry) { TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::ProcessEntry"); PRETRY_HASH_ENTRY pTempEntry; _ASSERT(pRHEntry != NULL); if (pRHEntry->IsCallback()) { //call callback function pRHEntry->ExecCallback(); } else { //Remove the entry from the hash table if(!m_pRetryHash->RemoveFromTable(pRHEntry->GetHashKey(), &pTempEntry)) { _ASSERT(GetLastError() == ERROR_PATH_NOT_FOUND); } //Check to see if this is //Release this entry by setting the right flags //This shoudl alway suceed DebugTrace((LPARAM)this, "Releasing domain %s for retry", pRHEntry->GetHashKey()); if(!ReleaseForRetry(pRHEntry->GetHashKey())) { ErrorTrace((LPARAM)this, "Failed to release the entry %s", pRHEntry->GetHashKey()); // _ASSERT(0); } //Irrespective of fail or success while removing the hash entry, //we decrement the refcount for both the hash table pRHEntry->DecRefCount(); } pRHEntry->DecRefCount(); TraceFunctLeaveEx((LPARAM)this); } //--------------------------------------------------------------------------------- // // CSMTP_RETRY_HANDLER::UpdateAllEntries // // Whenever the config data changes we update the release time for the queues // based on it. // // //--------------------------------------------------------------------------------- // BOOL CSMTP_RETRY_HANDLER::UpdateAllEntries(void) { CRETRY_HASH_ENTRY * pHashEntry = NULL; CRETRY_Q * pTempRetryQueue = NULL; FILETIME ftInsertTime, ftRetryTime; DWORD cConnectionFailureCount = 0; BOOL fTopOfQueue; BOOL fInserted = FALSE; TraceFunctEnterEx((LPARAM)this, "CRETRY_Q::UpdateAllEntries"); //Create the temporary retry queue pTempRetryQueue = CRETRY_Q::CreateQueue(); if(!pTempRetryQueue) { ErrorTrace((LPARAM)this, "Failed to initialize the temp retry queue "); _ASSERT(0); TraceFunctLeaveEx((LPARAM)this); return FALSE; } m_pRetryQueue->LockQ(); //Create a new queue and load everything into it while(1) { //Get the top entry from first queue //Do not release the ref count on it - we need the entry to be around //so as to reinsert it at the right place in the updated queue pHashEntry = m_pRetryQueue->RemoveFromTop(); //If we get hash entry if(pHashEntry) { if (!pHashEntry->IsCallback()) //don't update times of callbacks { ftInsertTime = pHashEntry->GetInsertTime(); cConnectionFailureCount = pHashEntry->GetFailureCount(); ftRetryTime = CalculateRetryTime(cConnectionFailureCount, &ftInsertTime); pHashEntry->SetRetryReleaseTime(&ftRetryTime); #ifdef DEBUG WriteDebugInfo(pHashEntry,UPDATE,0,0,0); #endif } //Insert the entry into the new queue using the new Release time //This will bump up the ref count. pTempRetryQueue->InsertSortedIntoQueue(pHashEntry, &fTopOfQueue); //Decrement the ref count to correspond to remove from Old queue now pHashEntry->DecRefCount(); fInserted = TRUE; } else break; } //Update the old queue head with the Flink/Blink ptrs from the new queue if(fInserted) { m_pRetryQueue->StealQueueEntries(pTempRetryQueue); } pTempRetryQueue->DeInitialize(); m_pRetryQueue->UnLockQ(); SetEvent(m_RetryEvent); TraceFunctLeaveEx((LPARAM)this); return TRUE; } //-------------------------------------------------------------------------------------- // // // Name : // CSMTP_RETRY_HANDLER::RetryThreadRoutine // // Description: // This function is the static member // function that gets passed to CreateThread // during the initialization. It is the main // thread that does the work of releasing the // domain that are being held for retry. // // Arguments: // A pointer to a RETRYQ // // Returns: //-------------------------------------------------------------------------------------- // DWORD WINAPI CSMTP_RETRY_HANDLER::RetryThreadRoutine(void * ThisPtr) { CSMTP_RETRY_HANDLER* RetryHandler = (CSMTP_RETRY_HANDLER*)ThisPtr; CRETRY_Q* QueuePtr = (CRETRY_Q*) RetryHandler->GetQueuePtr(); PRETRY_HASH_ENTRY pRHEntry; DWORD dwDelay; //Delay in seconds to sleep for // HANDLE WaitTable[2]; // HRESULT hr = S_OK; TraceFunctEnterEx((LPARAM)QueuePtr, "CSMTP_RETRY_HANDLER::RetryThreadRoutine"); //This thread will permanently loop on the retry queue. //If we find something at the top of the queue that can be retried, it gets // while(TRUE) { //if we are shutting down, break out of the loop if (RetryHandler->IsShuttingDown()) { goto Out; } //if we find the top entry to be ready for a retry //we remove it from the queue and do the needful // if( QueuePtr->CanRETRYHeadEntry(&pRHEntry, &dwDelay)) { //We got an entry to process //Processing should be simply enabling a link if(pRHEntry) { RetryHandler->ProcessEntry(pRHEntry); } else { DebugTrace((LPARAM)QueuePtr, "Error getting a domain entry off the retry queue"); } } else { DebugTrace((LPARAM)QueuePtr,"Sleeping for %d seconds", dwDelay); //Goto Sleep WaitForSingleObject(RetryHandler->m_RetryEvent,dwDelay); } } //end while Out: DebugTrace((LPARAM)QueuePtr,"Queue thread exiting"); TraceFunctLeaveEx((LPARAM)QueuePtr); return 1; } //-------------------------------------------------------------------------------------- // // Logic to decide based on the failure condition if the connection needs to be // disabled and added to retry queue // If we fail we hold it for retry // Otherwise if we tried more than one messages and every one of them failed we // hold for retry // In all other cases we keep the link active // // 2/5/99 - MikeSwa Modified to kick all non-success acks into retry //-------------------------------------------------------------------------------------- BOOL ShouldHoldForRetry(DWORD dwConnectionStatus, DWORD cFailedMsgCount, DWORD cTriedMsgCount) { //If connection failed or all messages on this connection failed TRUE if(dwConnectionStatus != CONNECTION_STATUS_OK) { return TRUE; } else if( cTriedMsgCount > 0 && !(cTriedMsgCount - cFailedMsgCount)) { return TRUE; } else { return FALSE; } } //---[ CSMTP_RETRY_HANDLER::SetCallbackTime ]---------------------------------- // // // Description: // Puts an entry in the retry queue to provide a callback at a specified // later time. // Parameters: // IN pCallbackFn Pointer to retry function // IN pvContext Context passed to retry function // IN dwCallbackMinutes Minutes to wait before calling back // Returns: // S_OK on success // E_OUTOFMEMORY if a hash entry cannot be allocated // E_INVALIDARG of pCallbackFn is NULL // History: // 8/17/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CSMTP_RETRY_HANDLER::SetCallbackTime( IN RETRFN pCallbackFn, IN PVOID pvContext, IN DWORD dwCallbackMinutes) { TraceFunctEnterEx((LPARAM) this, "CSMTP_RETRY_HANDLER::SetCallbackTime"); HRESULT hr = S_OK; CRETRY_HASH_ENTRY* pRHEntry = NULL; BOOL fTopOfQueue = FALSE; FILETIME TimeNow; FILETIME RetryTime; LONGLONG Temptime; GUID guidFakeRoutingGUID = GUID_NULL; //$$REVIEW //This (and all other occurences of this in retrsink) is not really thread //safe... but since the code calling the retrsink *is* thread safe, //this is not too much of a problem. Still, this should get fixed for M3 //though - MikeSwa 8/17/98 InterlockedIncrement(&m_ThreadsInRetry); if (!pCallbackFn) { hr = E_INVALIDARG; goto Exit; } //Get the insertion time for the entry GetSystemTimeAsFileTime(&TimeNow); pRHEntry = new CRETRY_HASH_ENTRY (CALLBACK_DOMAIN, sizeof(CALLBACK_DOMAIN), 0, &guidFakeRoutingGUID, &TimeNow); if (!pRHEntry) { ErrorTrace((LPARAM) this, "ERROR: Unable to allocate retry hash entry"); hr = E_OUTOFMEMORY; goto Exit; } //Calculate retry time Temptime = INT64_FROM_FILETIME(TimeNow) + HnsFromMs((__int64)dwCallbackMinutes*60*1000); RetryTime = FILETIME_FROM_INT64(Temptime); //set callback time pRHEntry->SetRetryReleaseTime(&RetryTime); pRHEntry->SetCallbackContext(pCallbackFn, pvContext); //Lock the queue m_pRetryQueue->LockQ(); m_pRetryQueue->InsertSortedIntoQueue(pRHEntry, &fTopOfQueue); #ifdef DEBUG //Add ref count for logging before releasing the lock //Do rtacing afterwards so as to reduce lock time pRHEntry->IncRefCount(); #endif //DEBUG m_pRetryQueue->UnLockQ(); //If the insertion was at the top of the queue //wake up the retry thread to evaluate the new //sleep time if(fTopOfQueue) { SetEvent(m_RetryEvent); } #ifdef DEBUG //Write out the insert and release time to a file WriteDebugInfo(pRHEntry, INSERT, 0xFFFFFFFF, 0,0); //Decrement the ref count obtained for the tracing pRHEntry->DecRefCount(); #endif //DEBUG Exit: InterlockedDecrement(&m_ThreadsInRetry); TraceFunctLeave(); return hr; } //---[ ReleaseForRetry ]------------------------------------------------------- // // // Description: // Releases given domain for retry by setting link state flags // Parameters: // IN szHashedDomainName Route-hashed domain name to release // Returns: // TRUE on success // FALSE on failure // History: // 9/25/98 - MikeSwa Created (adapted from inline function) // //----------------------------------------------------------------------------- BOOL CSMTP_RETRY_HANDLER::ReleaseForRetry(IN char * szHashedDomainName) { _ASSERT(szHashedDomainName); HRESULT hr = S_OK; DWORD dwScheduleID = dwGetIDFromRouteHash(szHashedDomainName); GUID guidRouting = GUID_NULL; LPSTR szUnHashedDomain = szGetDomainFromRouteHash(szHashedDomainName); GetGUIDFromRouteHash(szHashedDomainName, &guidRouting); hr = m_pIRetryManager->RetryLink(lstrlen(szUnHashedDomain), szUnHashedDomain, dwScheduleID, guidRouting); return (SUCCEEDED(hr)); } //-------------------------------------------------------------------------------------- // // Debugging functions // // //-------------------------------------------------------------------------------------- #ifdef DEBUG void CSMTP_RETRY_HANDLER::DumpAll(void) { m_pRetryQueue->PrintAllEntries(); } void WriteDebugInfo(CRETRY_HASH_ENTRY* pRHEntry, DWORD DebugType, DWORD dwConnectionStatus, DWORD cTriedMessages, DWORD cFailedMessages) { //open a transcript file and put the insert and release times in it // SYSTEMTIME stRetryTime, stInsertTime, stLocalInsertTime, stLocalRetryTime; char szScratch[MAX_PATH]; char sztmp[20]; DWORD cbWritten; TIME_ZONE_INFORMATION tz; FileTimeToSystemTime(&pRHEntry->GetRetryTime(), &stRetryTime); FileTimeToSystemTime(&pRHEntry->GetInsertTime(), &stInsertTime); GetTimeZoneInformation(&tz); SystemTimeToTzSpecificLocalTime(&tz, &stInsertTime, &stLocalInsertTime); SystemTimeToTzSpecificLocalTime(&tz, &stRetryTime, &stLocalRetryTime); if(DebugType == INSERT) { //Get rid of annoying routing information if (lstrcmp(pRHEntry->GetHashKey(), CALLBACK_DOMAIN)) { sprintf(pRHEntry->m_szTranscriptFile, "%s%.200s.%p.rtr", LOGGING_DIRECTORY, szGetDomainFromRouteHash(pRHEntry->GetHashKey()), pRHEntry); } else { //callback function sprintf(pRHEntry->m_szTranscriptFile, "%s%.200s.rtr", LOGGING_DIRECTORY, pRHEntry->GetHashKey()); } _ASSERT(strlen(pRHEntry->m_szTranscriptFile) < MAX_PATH); pRHEntry->m_hTranscriptHandle = INVALID_HANDLE_VALUE; pRHEntry->m_hTranscriptHandle = CreateFile(pRHEntry->m_szTranscriptFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); switch(dwConnectionStatus) { case 0: lstrcpy( sztmp, "OK"); break; case 1: lstrcpy( sztmp, "FAILED"); break; case 2: lstrcpy( sztmp, "DROPPED"); break; default:lstrcpy( sztmp, "UNKNOWN"); break; } sprintf(szScratch,"InsertTime:%d:%d:%d Retrytime:%d:%d:%d\nConnection status:%s Consecutive failures:%d\nMessages Tried:%d Failed:%d\n\n", stLocalInsertTime.wHour, stLocalInsertTime. wMinute,stLocalInsertTime.wSecond, stLocalRetryTime.wHour, stLocalRetryTime.wMinute, stLocalRetryTime.wSecond, sztmp, pRHEntry->GetFailureCount(), cTriedMessages, cFailedMessages); if( pRHEntry->m_hTranscriptHandle != INVALID_HANDLE_VALUE) { SetFilePointer(pRHEntry->m_hTranscriptHandle, 0, NULL, FILE_END); ::WriteFile(pRHEntry->m_hTranscriptHandle, szScratch, strlen(szScratch), &cbWritten, NULL); } } else if (DebugType == UPDATE) { sprintf(szScratch,"Updated : InsertedTime:%d:%d:%d Retrytime:%d:%d:%d\n\n", stLocalInsertTime.wHour, stLocalInsertTime.wMinute, stLocalInsertTime.wSecond, stLocalRetryTime.wHour, stLocalRetryTime.wMinute, stLocalRetryTime.wSecond); if( pRHEntry->m_hTranscriptHandle != INVALID_HANDLE_VALUE) { SetFilePointer(pRHEntry->m_hTranscriptHandle, 0, NULL, FILE_END); ::WriteFile(pRHEntry->m_hTranscriptHandle, szScratch, strlen(szScratch), &cbWritten, NULL); } } } #endif