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