Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1100 lines
37 KiB

  1. //---------------------------------------------------------------------------
  2. //
  3. //
  4. // File: main.cpp
  5. //
  6. // Description: Main file for SMTP retry sink
  7. //
  8. // Author: NimishK
  9. //
  10. // History:
  11. // 7/15/99 - MikeSwa Moved to Platinum
  12. //
  13. // Copyright (C) 1999 Microsoft Corporation
  14. //
  15. //---------------------------------------------------------------------------
  16. #include "precomp.h"
  17. //constants
  18. //
  19. #define MAX_RETRY_OBJECTS 15000
  20. #define DEFAULT_GLITCH_FAILURE_THRESHOLD 3
  21. #define DEFAULT_FIRST_TIER_RETRY_THRESHOLD 6
  22. #define DEFAULT_GLITCH_FAILURE_RETRY_SECONDS (1 * 60) // retry a glitch intwo minutes
  23. #define DEFAULT_FIRST_TIER_RETRY_SECONDS (15 * 60) // retry a failure in 15 minutes
  24. #define DEFAULT_SECOND_TIER_RETRY_SECONDS (60 * 60) // retry a failure in 60 minutes
  25. // provide memory for static declared in RETRYHASH_ENTRY
  26. //
  27. CPool CRETRY_HASH_ENTRY::PoolForHashEntries(RETRY_ENTRY_SIGNATURE_VALID);
  28. DWORD CSMTP_RETRY_HANDLER::dwInstanceCount = 0;
  29. //Forward declarations
  30. //
  31. BOOL ShouldHoldForRetry(DWORD dwConnectionStatus,
  32. DWORD cFailedMsgCount,
  33. DWORD cTriedMsgCount);
  34. //Debugging related
  35. //
  36. #define LOGGING_DIRECTORY "c:\\temp\\"
  37. enum DEBUGTYPE
  38. {
  39. INSERT,
  40. UPDATE
  41. };
  42. #ifdef DEBUG
  43. void WriteDebugInfo(CRETRY_HASH_ENTRY* pRHEntry,
  44. DWORD DebugType,
  45. DWORD dwConnectionStatus,
  46. DWORD cTriedMessages,
  47. DWORD cFailedMessages);
  48. #endif
  49. //--------------------------------------------------------------------------------
  50. // Logic :
  51. // In a normal state every hashentry is added to a retry hash and
  52. // a retry queue strcture.
  53. // An entry is considered deleted when removed from both structures.
  54. // The deletion could happen in two ways depending on the sequence in
  55. // which the entry is removed from the two structres.
  56. // For eg : when an entry is to be released from retry, we start by
  57. // dequeing it from RETRYQ and then remove it from hash table.
  58. // On the other hand when we get successful ConnectionReleased( ) for
  59. // a domain that we are holding fro retry, we remove it from the hash
  60. // table first based on the name.
  61. // The following is the logic for deletion that has least contention and
  62. // guards against race conditions.
  63. // Every hash entry has normally two ref counts - one for hash table and
  64. // the other for the retry queue.
  65. // If a thread gets into ProcessEntry(), that means it dequed a
  66. // hash entry from RETRYQ. Obviously no other thread is going to
  67. // succeed in dequeing this same entry.
  68. // Some other thread could possibily remove it from the table, but
  69. // will not succeed in dequeing it from RETRYQ.
  70. // The deletion logic is that only the thread succeeding in dequing
  71. // the hash entry from RETRYQ frees it up.
  72. // The conflicting thread that removed the entry from hashtable via a
  73. // call to RemoveDomain() will fail on deque and simply carry on.
  74. // The thread that succeeded in dequeing may fail to remove it from hash
  75. // table becasue somebody has already removed it, but still goes ahead
  76. // and frees up the hash entry
  77. //--------------------------------------------------------------------------------
  78. //------------------------------------------------------------------------------
  79. // CSMTP_RETRY_HANDLER::HrInitialize
  80. //
  81. //
  82. //------------------------------------------------------------------------------
  83. //
  84. HRESULT CSMTP_RETRY_HANDLER::HrInitialize(IN IConnectionRetryManager *pIConnectionRetryManager)
  85. {
  86. TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::HrInitialize");
  87. //Decide if we need to copy over data from earlier sink
  88. _ASSERT(pIConnectionRetryManager != NULL);
  89. if(!pIConnectionRetryManager)
  90. {
  91. ErrorTrace((LPARAM)this, "Bad Init params");
  92. return E_FAIL;
  93. }
  94. m_pIRetryManager = pIConnectionRetryManager;
  95. m_ThreadsInRetry = 0;
  96. if(InterlockedIncrement((LONG*)&CSMTP_RETRY_HANDLER::dwInstanceCount) == 1)
  97. {
  98. //First instance to come in reserves the memory for the retry entries
  99. if (!CRETRY_HASH_ENTRY::PoolForHashEntries.ReserveMemory( MAX_RETRY_OBJECTS,
  100. sizeof(CRETRY_HASH_ENTRY)))
  101. {
  102. DWORD err = GetLastError();
  103. ErrorTrace((LPARAM)NULL,
  104. "ReserveMemory failed for CRETRY_HASH_ENTRY. err: %u", err);
  105. _ASSERT(err != NO_ERROR);
  106. if(err == NO_ERROR)
  107. err = ERROR_NOT_ENOUGH_MEMORY;
  108. TraceFunctLeaveEx((LPARAM)NULL);
  109. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  110. }
  111. }
  112. //Initialize Hash Table
  113. m_pRetryHash = new CRETRY_HASH_TABLE();
  114. if(!m_pRetryHash || !m_pRetryHash->IsHashTableValid())
  115. {
  116. ErrorTrace((LPARAM)this, "Failed to initialize the hash table ");
  117. _ASSERT(0);
  118. TraceFunctLeaveEx((LPARAM)this);
  119. return E_FAIL;
  120. }
  121. //Create the retry queue
  122. m_pRetryQueue = CRETRY_Q::CreateQueue();
  123. if(!m_pRetryQueue)
  124. {
  125. ErrorTrace((LPARAM)this, "Failed to initialize the retry queue ");
  126. _ASSERT(0);
  127. TraceFunctLeaveEx((LPARAM)this);
  128. return E_FAIL;
  129. }
  130. //create the Retry queue event. Others will set this event
  131. //when something is placed at the top of the queue or when
  132. //Sink needs to shutdown
  133. //
  134. m_RetryEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  135. if (m_RetryEvent == NULL)
  136. {
  137. TraceFunctLeaveEx((LPARAM)this);
  138. return FALSE;
  139. }
  140. //create the Shutdown event. The last of the ConnectionReleased
  141. //threads will set this event when the Shutting down flag is set.
  142. //
  143. m_ShutdownEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  144. if (m_ShutdownEvent == NULL)
  145. {
  146. TraceFunctLeaveEx((LPARAM)this);
  147. return FALSE;
  148. }
  149. //create the thread that processes things out of the
  150. //the queue
  151. DWORD ThreadId;
  152. m_ThreadHandle = CreateThread (NULL,
  153. 0,
  154. CSMTP_RETRY_HANDLER::RetryThreadRoutine,
  155. this,
  156. 0,
  157. &ThreadId);
  158. if (m_ThreadHandle == NULL)
  159. {
  160. TraceFunctLeaveEx((LPARAM)this);
  161. return FALSE;
  162. }
  163. //Initialize RetryQ
  164. return S_OK;
  165. TraceFunctLeaveEx((LPARAM)this);
  166. }
  167. //-------------------------------------------------------------------------------
  168. // CSMTP_RETRY_HANDLER::HrDeInitialize
  169. //
  170. //
  171. //--------------------------------------------------------------------------------
  172. HRESULT CSMTP_RETRY_HANDLER::HrDeInitialize(void)
  173. {
  174. TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::HrDeInitialize");
  175. //Set the flag that the Handler is shutting down
  176. SetShuttingDown();
  177. //Release the Retry thread by setting the retry event
  178. SetQueueEvent();
  179. //Wait for the thread to exit
  180. //NK** - right now this is infinite wait - but that needs to
  181. //change and we will have to comeout and keep giving hints
  182. WaitForQThread();
  183. //At this point we just need to wait for all the threads that are in there
  184. //to go out and we can then shutdown
  185. //Obviously ConnectionManager has to to stop sending threads this way
  186. //NK** - right now this is infinite wait - but that needs to
  187. //change and we will have to comeout and keep giving hints
  188. if(m_ThreadsInRetry)
  189. WaitForShutdown();
  190. //Close the shutdown Event handle
  191. if(m_ShutdownEvent != NULL)
  192. CloseHandle(m_ShutdownEvent);
  193. //Close the Retry Event handle
  194. if(m_RetryEvent != NULL)
  195. CloseHandle(m_RetryEvent);
  196. //Close the Retry Thread handle
  197. if(m_ThreadHandle != NULL)
  198. CloseHandle(m_ThreadHandle);
  199. //Once all threads are gone
  200. //we can deinit the hash table and the queue
  201. m_pRetryQueue->DeInitialize();
  202. m_pRetryHash->DeInitialize();
  203. //Release the shedule manager
  204. m_pIRetryManager->Release();
  205. if(InterlockedDecrement((LONG*)&CSMTP_RETRY_HANDLER::dwInstanceCount) == 0)
  206. {
  207. //finally, release all our memory
  208. CRETRY_HASH_ENTRY::PoolForHashEntries.ReleaseMemory();
  209. }
  210. TraceFunctLeaveEx((LPARAM)this);
  211. delete this;
  212. return S_OK;
  213. }
  214. //---[ CSMTP_RETRY_HANDLER::ConnectionReleased ]-------------------------------
  215. //
  216. //
  217. // Description:
  218. // Default sink for ConnectionReleased event
  219. // Parameters:
  220. // - see aqintrnl.idl for a description of parameters
  221. // Returns:
  222. // S_OK on success
  223. // History:
  224. // 9/24/98 - MikeSwa updated from original ConnectionReleased
  225. //
  226. //-----------------------------------------------------------------------------
  227. STDMETHODIMP CSMTP_RETRY_HANDLER::ConnectionReleased(
  228. IN DWORD cbDomainName,
  229. IN CHAR szDomainName[],
  230. IN DWORD dwDomainInfoFlags,
  231. IN DWORD dwScheduleID,
  232. IN GUID guidRouting,
  233. IN DWORD dwConnectionStatus,
  234. IN DWORD cFailedMessages,
  235. IN DWORD cTriedMessages,
  236. IN DWORD cConsecutiveConnectionFailures,
  237. OUT BOOL* pfAllowImmediateRetry,
  238. OUT FILETIME *pftNextRetryTime)
  239. {
  240. TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::ConnectionReleased");
  241. HRESULT hr;
  242. DWORD dwError;
  243. GUID guid = GUID_NULL;
  244. LPSTR szRouteHashedDomain = NULL;
  245. //Keep a track of threads that are inside
  246. //This will be needed in shutdown
  247. InterlockedIncrement(&m_ThreadsInRetry);
  248. //By default, we will allow the domain to retry
  249. _ASSERT(pfAllowImmediateRetry);
  250. *pfAllowImmediateRetry = TRUE;
  251. _ASSERT(pftNextRetryTime);
  252. if(TRUE)
  253. {
  254. // Check what we want to do
  255. // **If we need to disable the connection - disable it
  256. // **Check if there are any outstanding connections
  257. // If no connections, calculate the retry time and add in the queue and return
  258. if(ShouldHoldForRetry(dwConnectionStatus,
  259. cFailedMessages,
  260. cTriedMessages))
  261. {
  262. //Do not hold TURN/ETRN domains for retry (except for "glitch" retry)
  263. if((!(dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY))) ||
  264. (cConsecutiveConnectionFailures < m_dwRetryThreshold))
  265. {
  266. //Insert it - we could fail to insert it if an entry already exists
  267. //That is OK - we will return success
  268. if(!InsertDomain(szDomainName,
  269. cbDomainName,
  270. dwConnectionStatus,
  271. dwScheduleID,
  272. &guidRouting,
  273. cConsecutiveConnectionFailures,
  274. cTriedMessages,
  275. cFailedMessages, pftNextRetryTime ))
  276. {
  277. dwError = GetLastError();
  278. DebugTrace((LPARAM)this,
  279. "Failed to insert %s entry into retry hash table : Err : %d ",
  280. szDomainName, dwError);
  281. if(dwError == ERROR_FILE_EXISTS )
  282. {
  283. //We did not insert because the entry was already there
  284. *pfAllowImmediateRetry = FALSE;
  285. hr = S_OK;
  286. goto Exit;
  287. }
  288. else
  289. {
  290. if(dwError == ERROR_NOT_ENOUGH_MEMORY )
  291. {
  292. hr = E_OUTOFMEMORY;
  293. }
  294. else
  295. {
  296. _ASSERT(0);
  297. hr = E_FAIL;
  298. }
  299. goto Exit;
  300. }
  301. }
  302. //Normal retry domain
  303. *pfAllowImmediateRetry = FALSE;
  304. DebugTrace((LPARAM)this,
  305. "Holding domain %s for retry",szDomainName);
  306. }
  307. }
  308. else
  309. {
  310. // Some connection succeeded for this domain.
  311. //If we have it marked for retry - it needs to be freed up
  312. //Looks like the incident which caused retry has cleared up.
  313. CHAR szHashedDomain[MAX_RETRY_DOMAIN_NAME_LEN];
  314. //Hash schedule ID and router guid to domain name
  315. CreateRouteHash(cbDomainName, szDomainName, ROUTE_HASH_SCHEDULE_ID,
  316. &guidRouting, dwScheduleID, szHashedDomain, sizeof(szHashedDomain));
  317. RemoveDomain(szHashedDomain);
  318. hr = S_OK;
  319. goto Exit;
  320. }
  321. }
  322. hr = S_OK;
  323. Exit :
  324. //Keep a track of threads that are inside
  325. //This will be needed in shutdown
  326. if(InterlockedDecrement(&m_ThreadsInRetry) == 0 && IsShuttingDown())
  327. {
  328. //we signal the shutdown event to indicate that
  329. //no more threads are in the system
  330. _ASSERT(m_ShutdownEvent != NULL);
  331. SetEvent(m_ShutdownEvent);
  332. }
  333. TraceFunctLeaveEx((LPARAM)this);
  334. return hr;
  335. }
  336. /////////////////////////////////////////////////////////////////////////////////
  337. // CSMTP_RETRY_HANDLER::InsertDomain
  338. //
  339. //
  340. /////////////////////////////////////////////////////////////////////////////////
  341. BOOL CSMTP_RETRY_HANDLER::InsertDomain(char * szDomainName,
  342. IN DWORD cbDomainName,
  343. IN DWORD dwConnectionStatus, //eConnectionStatus
  344. IN DWORD dwScheduleID,
  345. IN GUID *pguidRouting,
  346. IN DWORD cConnectionFailureCount,
  347. IN DWORD cTriedMessages, //# of untried messages in queue
  348. IN DWORD cFailedMessages, //# of failed message for *this* connection
  349. OUT FILETIME *pftNextRetry)
  350. {
  351. DWORD dwError;
  352. FILETIME TimeNow;
  353. FILETIME RetryTime;
  354. CRETRY_HASH_ENTRY* pRHEntry = NULL;
  355. TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::InsertDomain");
  356. //Get the insertion time for the entry
  357. GetSystemTimeAsFileTime(&TimeNow);
  358. //Cpool based allocations for hash entries
  359. pRHEntry = new CRETRY_HASH_ENTRY (szDomainName, cbDomainName,
  360. dwScheduleID, pguidRouting, &TimeNow);
  361. if(!pRHEntry)
  362. {
  363. //_ASSERT(0);
  364. dwError = GetLastError();
  365. DebugTrace((LPARAM)this,
  366. "failed to Create a new hash entry : %s err: %d",
  367. szDomainName,
  368. dwError);
  369. SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  370. TraceFunctLeaveEx((LPARAM)this);
  371. return FALSE;
  372. }
  373. //Based on the current time and the number of connections failures calculate the
  374. //time of release for retry
  375. RetryTime = CalculateRetryTime(cConnectionFailureCount, &TimeNow);
  376. pRHEntry->SetRetryReleaseTime(&RetryTime);
  377. pRHEntry->SetFailureCount(cConnectionFailureCount);
  378. //The hash entry has been initialized
  379. //Insert it - we could fail to insert it if an entry already exists
  380. //That is OK - we will return success
  381. if(!m_pRetryHash->InsertIntoTable (pRHEntry))
  382. {
  383. //Free up the entry
  384. _ASSERT(pRHEntry);
  385. delete pRHEntry;
  386. TraceFunctLeaveEx((LPARAM)this);
  387. return FALSE;
  388. }
  389. else
  390. {
  391. //Report next retry time
  392. if (pftNextRetry)
  393. memcpy(pftNextRetry, &RetryTime, sizeof(FILETIME));
  394. //Insert into the retry queue.
  395. BOOL fTopOfQueue = FALSE;
  396. //Lock the queue
  397. m_pRetryQueue->LockQ();
  398. m_pRetryQueue->InsertSortedIntoQueue(pRHEntry, &fTopOfQueue);
  399. #ifdef DEBUG
  400. //Add ref count for logging before releasing the lock
  401. //Do rtacing afterwards so as to reduce lock time
  402. pRHEntry->IncRefCount();
  403. #endif
  404. m_pRetryQueue->UnLockQ();
  405. //If the insertion was at the top of the queue
  406. //wake up the retry thread to evaluate the new
  407. //sleep time
  408. if(fTopOfQueue)
  409. {
  410. SetEvent(m_RetryEvent);
  411. }
  412. #ifdef DEBUG
  413. //Write out the insert and release time to a file
  414. //
  415. WriteDebugInfo(pRHEntry,
  416. INSERT,
  417. dwConnectionStatus,
  418. cTriedMessages,
  419. cFailedMessages);
  420. //Decrement the ref count obtained for the tracing
  421. pRHEntry->DecRefCount();
  422. #endif
  423. }
  424. TraceFunctLeaveEx((LPARAM)this);
  425. return TRUE;
  426. }
  427. //---------------------------------------------------------------------------------
  428. // CSMTP_RETRY_HANDLER::RemoveDomain
  429. //
  430. //
  431. //---------------------------------------------------------------------------------
  432. //
  433. BOOL CSMTP_RETRY_HANDLER::RemoveDomain(char * szDomainName)
  434. {
  435. PRETRY_HASH_ENTRY pRHEntry;
  436. TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::RemoveDomain");
  437. if(!m_pRetryHash->RemoveFromTable(szDomainName, &pRHEntry))
  438. {
  439. if(GetLastError() == ERROR_PATH_NOT_FOUND)
  440. return TRUE;
  441. else
  442. {
  443. _ASSERT(0);
  444. TraceFunctLeaveEx((LPARAM)this);
  445. return FALSE;
  446. }
  447. }
  448. _ASSERT(pRHEntry != NULL);
  449. //Remove it from the queue
  450. m_pRetryQueue->LockQ();
  451. if(!m_pRetryQueue->RemoveFromQueue(pRHEntry))
  452. {
  453. m_pRetryQueue->UnLockQ();
  454. if(GetLastError() == ERROR_PATH_NOT_FOUND)
  455. return TRUE;
  456. else
  457. {
  458. _ASSERT(0);
  459. TraceFunctLeaveEx((LPARAM)this);
  460. return FALSE;
  461. }
  462. }
  463. m_pRetryQueue->UnLockQ();
  464. //If successful in removing from the queue then we are not competing with
  465. //the Retry thread
  466. //decrement hash table ref count as well as the ref count for the queue
  467. pRHEntry->DecRefCount();
  468. pRHEntry->DecRefCount();
  469. //Release this entry by setting the right flags
  470. //This should always succeed
  471. DebugTrace((LPARAM)this,
  472. "Releasing domain %s because another connection succeeded", szDomainName);
  473. if(!ReleaseForRetry(szDomainName))
  474. {
  475. ErrorTrace((LPARAM)this, "Failed to release the entry");
  476. TraceFunctLeaveEx((LPARAM)this);
  477. //_ASSERT(0);
  478. }
  479. return TRUE;
  480. }
  481. //---------------------------------------------------------------------------------
  482. //
  483. // CSMTP_RETRY_HANDLER::CalculateRetryTime
  484. //
  485. // Logic to decide based on the number of failed connection how long to hld this
  486. // domain for retry
  487. //
  488. //---------------------------------------------------------------------------------
  489. FILETIME CSMTP_RETRY_HANDLER::CalculateRetryTime(DWORD cFailedConnections,
  490. FILETIME* InsertedTime)
  491. {
  492. FILETIME ftTemp;
  493. LONGLONG Temptime;
  494. DWORD dwRetryMilliSec = 0;
  495. //Does this look like a glitch
  496. //A glitch is defined as less than x consecutive failures
  497. if(cFailedConnections < m_dwRetryThreshold)
  498. dwRetryMilliSec = m_dwGlitchRetrySeconds * 1000;
  499. else
  500. {
  501. switch(cFailedConnections - m_dwRetryThreshold)
  502. {
  503. case 0: dwRetryMilliSec = m_dwFirstRetrySeconds * 1000;
  504. break;
  505. case 1: dwRetryMilliSec = m_dwSecondRetrySeconds * 1000;
  506. break;
  507. case 2: dwRetryMilliSec = m_dwThirdRetrySeconds * 1000;
  508. break;
  509. case 3: dwRetryMilliSec = m_dwFourthRetrySeconds * 1000;
  510. break;
  511. default: dwRetryMilliSec = m_dwFourthRetrySeconds * 1000;
  512. break;
  513. }
  514. }
  515. _ASSERT(dwRetryMilliSec);
  516. Temptime = INT64_FROM_FILETIME(*InsertedTime) + HnsFromMs((__int64)dwRetryMilliSec);
  517. // HnsFromMin(m_RetryMinutes)
  518. ftTemp = FILETIME_FROM_INT64(Temptime);
  519. return ftTemp;
  520. }
  521. //---------------------------------------------------------------------------------
  522. //
  523. // CSMTP_RETRY_HANDLER::ProcessEntry
  524. //
  525. // Description :
  526. // Process the hash entry removed from the queue because it is
  527. // time to release the corresponding domain.
  528. // We mark the domain active for retry and then take the hash
  529. // entry out of the hash table and delete the hash entry.
  530. //
  531. //---------------------------------------------------------------------------------
  532. void CSMTP_RETRY_HANDLER::ProcessEntry(PRETRY_HASH_ENTRY pRHEntry)
  533. {
  534. TraceFunctEnterEx((LPARAM)this, "CSMTP_RETRY_HANDLER::ProcessEntry");
  535. PRETRY_HASH_ENTRY pTempEntry;
  536. _ASSERT(pRHEntry != NULL);
  537. if (pRHEntry->IsCallback())
  538. {
  539. //call callback function
  540. pRHEntry->ExecCallback();
  541. }
  542. else
  543. {
  544. //Remove the entry from the hash table
  545. if(!m_pRetryHash->RemoveFromTable(pRHEntry->GetHashKey(), &pTempEntry))
  546. {
  547. _ASSERT(GetLastError() == ERROR_PATH_NOT_FOUND);
  548. }
  549. //Check to see if this is
  550. //Release this entry by setting the right flags
  551. //This shoudl alway suceed
  552. DebugTrace((LPARAM)this,
  553. "Releasing domain %s for retry", pRHEntry->GetHashKey());
  554. if(!ReleaseForRetry(pRHEntry->GetHashKey()))
  555. {
  556. ErrorTrace((LPARAM)this,
  557. "Failed to release the entry %s", pRHEntry->GetHashKey());
  558. // _ASSERT(0);
  559. }
  560. //Irrespective of fail or success while removing the hash entry,
  561. //we decrement the refcount for both the hash table
  562. pRHEntry->DecRefCount();
  563. }
  564. pRHEntry->DecRefCount();
  565. TraceFunctLeaveEx((LPARAM)this);
  566. }
  567. //---------------------------------------------------------------------------------
  568. //
  569. // CSMTP_RETRY_HANDLER::UpdateAllEntries
  570. //
  571. // Whenever the config data changes we update the release time for the queues
  572. // based on it.
  573. //
  574. //
  575. //---------------------------------------------------------------------------------
  576. //
  577. BOOL CSMTP_RETRY_HANDLER::UpdateAllEntries(void)
  578. {
  579. CRETRY_HASH_ENTRY * pHashEntry = NULL;
  580. CRETRY_Q * pTempRetryQueue = NULL;
  581. FILETIME ftInsertTime, ftRetryTime;
  582. DWORD cConnectionFailureCount = 0;
  583. BOOL fTopOfQueue;
  584. BOOL fInserted = FALSE;
  585. TraceFunctEnterEx((LPARAM)this, "CRETRY_Q::UpdateAllEntries");
  586. //Create the temporary retry queue
  587. pTempRetryQueue = CRETRY_Q::CreateQueue();
  588. if(!pTempRetryQueue)
  589. {
  590. ErrorTrace((LPARAM)this,
  591. "Failed to initialize the temp retry queue ");
  592. _ASSERT(0);
  593. TraceFunctLeaveEx((LPARAM)this);
  594. return FALSE;
  595. }
  596. m_pRetryQueue->LockQ();
  597. //Create a new queue and load everything into it
  598. while(1)
  599. {
  600. //Get the top entry from first queue
  601. //Do not release the ref count on it - we need the entry to be around
  602. //so as to reinsert it at the right place in the updated queue
  603. pHashEntry = m_pRetryQueue->RemoveFromTop();
  604. //If we get hash entry
  605. if(pHashEntry)
  606. {
  607. if (!pHashEntry->IsCallback()) //don't update times of callbacks
  608. {
  609. ftInsertTime = pHashEntry->GetInsertTime();
  610. cConnectionFailureCount = pHashEntry->GetFailureCount();
  611. ftRetryTime = CalculateRetryTime(cConnectionFailureCount, &ftInsertTime);
  612. pHashEntry->SetRetryReleaseTime(&ftRetryTime);
  613. #ifdef DEBUG
  614. WriteDebugInfo(pHashEntry,UPDATE,0,0,0);
  615. #endif
  616. }
  617. //Insert the entry into the new queue using the new Release time
  618. //This will bump up the ref count.
  619. pTempRetryQueue->InsertSortedIntoQueue(pHashEntry, &fTopOfQueue);
  620. //Decrement the ref count to correspond to remove from Old queue now
  621. pHashEntry->DecRefCount();
  622. fInserted = TRUE;
  623. }
  624. else
  625. break;
  626. }
  627. //Update the old queue head with the Flink/Blink ptrs from the new queue
  628. if(fInserted)
  629. {
  630. m_pRetryQueue->StealQueueEntries(pTempRetryQueue);
  631. }
  632. pTempRetryQueue->DeInitialize();
  633. m_pRetryQueue->UnLockQ();
  634. SetEvent(m_RetryEvent);
  635. TraceFunctLeaveEx((LPARAM)this);
  636. return TRUE;
  637. }
  638. //--------------------------------------------------------------------------------------
  639. //
  640. //
  641. // Name :
  642. // CSMTP_RETRY_HANDLER::RetryThreadRoutine
  643. //
  644. // Description:
  645. // This function is the static member
  646. // function that gets passed to CreateThread
  647. // during the initialization. It is the main
  648. // thread that does the work of releasing the
  649. // domain that are being held for retry.
  650. //
  651. // Arguments:
  652. // A pointer to a RETRYQ
  653. //
  654. // Returns:
  655. //--------------------------------------------------------------------------------------
  656. //
  657. DWORD WINAPI CSMTP_RETRY_HANDLER::RetryThreadRoutine(void * ThisPtr)
  658. {
  659. CSMTP_RETRY_HANDLER* RetryHandler = (CSMTP_RETRY_HANDLER*)ThisPtr;
  660. CRETRY_Q* QueuePtr = (CRETRY_Q*) RetryHandler->GetQueuePtr();
  661. PRETRY_HASH_ENTRY pRHEntry;
  662. DWORD dwDelay; //Delay in seconds to sleep for
  663. // HANDLE WaitTable[2];
  664. // HRESULT hr = S_OK;
  665. TraceFunctEnterEx((LPARAM)QueuePtr, "CSMTP_RETRY_HANDLER::RetryThreadRoutine");
  666. //This thread will permanently loop on the retry queue.
  667. //If we find something at the top of the queue that can be retried, it gets
  668. //
  669. while(TRUE)
  670. {
  671. //if we are shutting down, break out of the loop
  672. if (RetryHandler->IsShuttingDown())
  673. {
  674. goto Out;
  675. }
  676. //if we find the top entry to be ready for a retry
  677. //we remove it from the queue and do the needful
  678. //
  679. if( QueuePtr->CanRETRYHeadEntry(&pRHEntry, &dwDelay))
  680. {
  681. //We got an entry to process
  682. //Processing should be simply enabling a link
  683. if(pRHEntry)
  684. {
  685. RetryHandler->ProcessEntry(pRHEntry);
  686. }
  687. else
  688. {
  689. DebugTrace((LPARAM)QueuePtr,
  690. "Error getting a domain entry off the retry queue");
  691. }
  692. }
  693. else
  694. {
  695. DebugTrace((LPARAM)QueuePtr,"Sleeping for %d seconds", dwDelay);
  696. //Goto Sleep
  697. WaitForSingleObject(RetryHandler->m_RetryEvent,dwDelay);
  698. }
  699. } //end while
  700. Out:
  701. DebugTrace((LPARAM)QueuePtr,"Queue thread exiting");
  702. TraceFunctLeaveEx((LPARAM)QueuePtr);
  703. return 1;
  704. }
  705. //--------------------------------------------------------------------------------------
  706. //
  707. // Logic to decide based on the failure condition if the connection needs to be
  708. // disabled and added to retry queue
  709. // If we fail we hold it for retry
  710. // Otherwise if we tried more than one messages and every one of them failed we
  711. // hold for retry
  712. // In all other cases we keep the link active
  713. //
  714. // 2/5/99 - MikeSwa Modified to kick all non-success acks into retry
  715. //--------------------------------------------------------------------------------------
  716. BOOL ShouldHoldForRetry(DWORD dwConnectionStatus,
  717. DWORD cFailedMsgCount,
  718. DWORD cTriedMsgCount)
  719. {
  720. //If connection failed or all messages on this connection failed TRUE
  721. if(dwConnectionStatus != CONNECTION_STATUS_OK)
  722. {
  723. return TRUE;
  724. }
  725. else if( cTriedMsgCount > 0 && !(cTriedMsgCount - cFailedMsgCount))
  726. {
  727. return TRUE;
  728. }
  729. else
  730. {
  731. return FALSE;
  732. }
  733. }
  734. //---[ CSMTP_RETRY_HANDLER::SetCallbackTime ]----------------------------------
  735. //
  736. //
  737. // Description:
  738. // Puts an entry in the retry queue to provide a callback at a specified
  739. // later time.
  740. // Parameters:
  741. // IN pCallbackFn Pointer to retry function
  742. // IN pvContext Context passed to retry function
  743. // IN dwCallbackMinutes Minutes to wait before calling back
  744. // Returns:
  745. // S_OK on success
  746. // E_OUTOFMEMORY if a hash entry cannot be allocated
  747. // E_INVALIDARG of pCallbackFn is NULL
  748. // History:
  749. // 8/17/98 - MikeSwa Created
  750. //
  751. //-----------------------------------------------------------------------------
  752. HRESULT CSMTP_RETRY_HANDLER::SetCallbackTime(
  753. IN RETRFN pCallbackFn,
  754. IN PVOID pvContext,
  755. IN DWORD dwCallbackMinutes)
  756. {
  757. TraceFunctEnterEx((LPARAM) this, "CSMTP_RETRY_HANDLER::SetCallbackTime");
  758. HRESULT hr = S_OK;
  759. CRETRY_HASH_ENTRY* pRHEntry = NULL;
  760. BOOL fTopOfQueue = FALSE;
  761. FILETIME TimeNow;
  762. FILETIME RetryTime;
  763. LONGLONG Temptime;
  764. GUID guidFakeRoutingGUID = GUID_NULL;
  765. //$$REVIEW
  766. //This (and all other occurences of this in retrsink) is not really thread
  767. //safe... but since the code calling the retrsink *is* thread safe,
  768. //this is not too much of a problem. Still, this should get fixed for M3
  769. //though - MikeSwa 8/17/98
  770. InterlockedIncrement(&m_ThreadsInRetry);
  771. if (!pCallbackFn)
  772. {
  773. hr = E_INVALIDARG;
  774. goto Exit;
  775. }
  776. //Get the insertion time for the entry
  777. GetSystemTimeAsFileTime(&TimeNow);
  778. pRHEntry = new CRETRY_HASH_ENTRY (CALLBACK_DOMAIN,
  779. sizeof(CALLBACK_DOMAIN),
  780. 0,
  781. &guidFakeRoutingGUID,
  782. &TimeNow);
  783. if (!pRHEntry)
  784. {
  785. ErrorTrace((LPARAM) this, "ERROR: Unable to allocate retry hash entry");
  786. hr = E_OUTOFMEMORY;
  787. goto Exit;
  788. }
  789. //Calculate retry time
  790. Temptime = INT64_FROM_FILETIME(TimeNow) + HnsFromMs((__int64)dwCallbackMinutes*60*1000);
  791. RetryTime = FILETIME_FROM_INT64(Temptime);
  792. //set callback time
  793. pRHEntry->SetRetryReleaseTime(&RetryTime);
  794. pRHEntry->SetCallbackContext(pCallbackFn, pvContext);
  795. //Lock the queue
  796. m_pRetryQueue->LockQ();
  797. m_pRetryQueue->InsertSortedIntoQueue(pRHEntry, &fTopOfQueue);
  798. #ifdef DEBUG
  799. //Add ref count for logging before releasing the lock
  800. //Do rtacing afterwards so as to reduce lock time
  801. pRHEntry->IncRefCount();
  802. #endif //DEBUG
  803. m_pRetryQueue->UnLockQ();
  804. //If the insertion was at the top of the queue
  805. //wake up the retry thread to evaluate the new
  806. //sleep time
  807. if(fTopOfQueue)
  808. {
  809. SetEvent(m_RetryEvent);
  810. }
  811. #ifdef DEBUG
  812. //Write out the insert and release time to a file
  813. WriteDebugInfo(pRHEntry, INSERT, 0xFFFFFFFF, 0,0);
  814. //Decrement the ref count obtained for the tracing
  815. pRHEntry->DecRefCount();
  816. #endif //DEBUG
  817. Exit:
  818. InterlockedDecrement(&m_ThreadsInRetry);
  819. TraceFunctLeave();
  820. return hr;
  821. }
  822. //---[ ReleaseForRetry ]-------------------------------------------------------
  823. //
  824. //
  825. // Description:
  826. // Releases given domain for retry by setting link state flags
  827. // Parameters:
  828. // IN szHashedDomainName Route-hashed domain name to release
  829. // Returns:
  830. // TRUE on success
  831. // FALSE on failure
  832. // History:
  833. // 9/25/98 - MikeSwa Created (adapted from inline function)
  834. //
  835. //-----------------------------------------------------------------------------
  836. BOOL CSMTP_RETRY_HANDLER::ReleaseForRetry(IN char * szHashedDomainName)
  837. {
  838. _ASSERT(szHashedDomainName);
  839. HRESULT hr = S_OK;
  840. DWORD dwScheduleID = dwGetIDFromRouteHash(szHashedDomainName);
  841. GUID guidRouting = GUID_NULL;
  842. LPSTR szUnHashedDomain = szGetDomainFromRouteHash(szHashedDomainName);
  843. GetGUIDFromRouteHash(szHashedDomainName, &guidRouting);
  844. hr = m_pIRetryManager->RetryLink(lstrlen(szUnHashedDomain),
  845. szUnHashedDomain, dwScheduleID, guidRouting);
  846. return (SUCCEEDED(hr));
  847. }
  848. //--------------------------------------------------------------------------------------
  849. //
  850. // Debugging functions
  851. //
  852. //
  853. //--------------------------------------------------------------------------------------
  854. #ifdef DEBUG
  855. void CSMTP_RETRY_HANDLER::DumpAll(void)
  856. {
  857. m_pRetryQueue->PrintAllEntries();
  858. }
  859. void WriteDebugInfo(CRETRY_HASH_ENTRY* pRHEntry,
  860. DWORD DebugType,
  861. DWORD dwConnectionStatus,
  862. DWORD cTriedMessages,
  863. DWORD cFailedMessages)
  864. {
  865. //open a transcript file and put the insert and release times in it
  866. //
  867. SYSTEMTIME stRetryTime, stInsertTime, stLocalInsertTime, stLocalRetryTime;
  868. char szScratch[MAX_PATH];
  869. char sztmp[20];
  870. DWORD cbWritten;
  871. TIME_ZONE_INFORMATION tz;
  872. FileTimeToSystemTime(&pRHEntry->GetRetryTime(), &stRetryTime);
  873. FileTimeToSystemTime(&pRHEntry->GetInsertTime(), &stInsertTime);
  874. GetTimeZoneInformation(&tz);
  875. SystemTimeToTzSpecificLocalTime(&tz, &stInsertTime, &stLocalInsertTime);
  876. SystemTimeToTzSpecificLocalTime(&tz, &stRetryTime, &stLocalRetryTime);
  877. if(DebugType == INSERT)
  878. {
  879. //Get rid of annoying routing information
  880. if (lstrcmp(pRHEntry->GetHashKey(), CALLBACK_DOMAIN))
  881. {
  882. sprintf(pRHEntry->m_szTranscriptFile, "%s%.200s.%p.rtr",
  883. LOGGING_DIRECTORY,
  884. szGetDomainFromRouteHash(pRHEntry->GetHashKey()),
  885. pRHEntry);
  886. }
  887. else
  888. {
  889. //callback function
  890. sprintf(pRHEntry->m_szTranscriptFile, "%s%.200s.rtr",
  891. LOGGING_DIRECTORY,
  892. pRHEntry->GetHashKey());
  893. }
  894. _ASSERT(strlen(pRHEntry->m_szTranscriptFile) < MAX_PATH);
  895. pRHEntry->m_hTranscriptHandle = INVALID_HANDLE_VALUE;
  896. pRHEntry->m_hTranscriptHandle = CreateFile(pRHEntry->m_szTranscriptFile,
  897. GENERIC_READ | GENERIC_WRITE,
  898. FILE_SHARE_READ | FILE_SHARE_WRITE,
  899. NULL,
  900. OPEN_ALWAYS,
  901. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
  902. NULL);
  903. switch(dwConnectionStatus)
  904. {
  905. case 0: lstrcpy( sztmp, "OK");
  906. break;
  907. case 1: lstrcpy( sztmp, "FAILED");
  908. break;
  909. case 2: lstrcpy( sztmp, "DROPPED");
  910. break;
  911. default:lstrcpy( sztmp, "UNKNOWN");
  912. break;
  913. }
  914. sprintf(szScratch,"InsertTime:%d:%d:%d Retrytime:%d:%d:%d\nConnection status:%s Consecutive failures:%d\nMessages Tried:%d Failed:%d\n\n",
  915. stLocalInsertTime.wHour, stLocalInsertTime.
  916. wMinute,stLocalInsertTime.wSecond,
  917. stLocalRetryTime.wHour, stLocalRetryTime.wMinute,
  918. stLocalRetryTime.wSecond,
  919. sztmp,
  920. pRHEntry->GetFailureCount(),
  921. cTriedMessages,
  922. cFailedMessages);
  923. if( pRHEntry->m_hTranscriptHandle != INVALID_HANDLE_VALUE)
  924. {
  925. SetFilePointer(pRHEntry->m_hTranscriptHandle,
  926. 0,
  927. NULL,
  928. FILE_END);
  929. ::WriteFile(pRHEntry->m_hTranscriptHandle,
  930. szScratch,
  931. strlen(szScratch),
  932. &cbWritten,
  933. NULL);
  934. }
  935. }
  936. else if (DebugType == UPDATE)
  937. {
  938. sprintf(szScratch,"Updated : InsertedTime:%d:%d:%d Retrytime:%d:%d:%d\n\n",
  939. stLocalInsertTime.wHour, stLocalInsertTime.wMinute,
  940. stLocalInsertTime.wSecond,
  941. stLocalRetryTime.wHour, stLocalRetryTime.wMinute,
  942. stLocalRetryTime.wSecond);
  943. if( pRHEntry->m_hTranscriptHandle != INVALID_HANDLE_VALUE)
  944. {
  945. SetFilePointer(pRHEntry->m_hTranscriptHandle,
  946. 0,
  947. NULL,
  948. FILE_END);
  949. ::WriteFile(pRHEntry->m_hTranscriptHandle,
  950. szScratch,
  951. strlen(szScratch),
  952. &cbWritten,
  953. NULL);
  954. }
  955. }
  956. }
  957. #endif