Source code of Windows XP (NT5)
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.

690 lines
17 KiB

  1. //
  2. // MODULE: ThreadPool.CPP
  3. //
  4. // PURPOSE: Fully implement classes for high level of pool thread activity
  5. //
  6. // PROJECT: Generic Troubleshooter DLL for Microsoft AnswerPoint
  7. //
  8. // COMPANY: Saltmine Creative, Inc. (206)-284-7511 [email protected]
  9. //
  10. // AUTHOR: Joe Mabel, based on earlier (8-2-96) work by Roman Mach
  11. //
  12. // ORIGINAL DATE: 9/23/98
  13. //
  14. // NOTES:
  15. // 1.
  16. //
  17. // Version Date By Comments
  18. //--------------------------------------------------------------------
  19. // V0.1 - RM Original
  20. // V3.0 9/23/98 JM better encapsulation & some chages to algorithm
  21. //
  22. #pragma warning(disable:4786)
  23. #include "stdafx.h"
  24. #include "ThreadPool.h"
  25. #include "event.h"
  26. #include "apgtscls.h"
  27. #include "baseexception.h"
  28. #include "CharConv.h"
  29. #include "apgtsMFC.h"
  30. //////////////////////////////////////////////////////////////////////
  31. // CThreadPool::CThreadControl
  32. // POOL/WORKING THREAD
  33. //////////////////////////////////////////////////////////////////////
  34. CThreadPool::CThreadControl::CThreadControl(CSniffConnector* pSniffConnector) :
  35. m_hThread (NULL),
  36. m_hevDone (NULL),
  37. m_hMutex (NULL),
  38. m_bExit (false),
  39. m_pPoolQueue (NULL),
  40. m_timeCreated(0),
  41. m_time (0),
  42. m_bWorking (false),
  43. m_bFailed (false),
  44. m_strBrowser( _T("") ),
  45. m_strClientIP( _T("") ),
  46. m_pContext( NULL ),
  47. m_pSniffConnector(pSniffConnector)
  48. {
  49. time(&m_timeCreated);
  50. }
  51. CThreadPool::CThreadControl::~CThreadControl()
  52. {
  53. if (m_hThread)
  54. ::CloseHandle(m_hThread);
  55. if (m_hevDone)
  56. ::CloseHandle(m_hevDone);
  57. if (m_hMutex)
  58. ::CloseHandle(m_hMutex);
  59. }
  60. // create a "pool" thread to handle user requests, one request at a time per thread
  61. // returns error code; 0 if OK
  62. // NOTE: This function throws exceptions so the caller should be catching exceptions
  63. // rather than checking return values.
  64. DWORD CThreadPool::CThreadControl::Initialize(CPoolQueue * pPoolQueue)
  65. {
  66. DWORD dwThreadID;
  67. CString strErr;
  68. CString strErrNum;
  69. m_pPoolQueue = pPoolQueue;
  70. m_hevDone= NULL;
  71. m_hMutex= NULL;
  72. m_hThread= NULL;
  73. try
  74. {
  75. m_hevDone = ::CreateEvent(NULL, true /* manual reset*/, false /* init non-signaled*/, NULL);
  76. if (!m_hevDone)
  77. {
  78. strErrNum.Format(_T("%d"), ::GetLastError());
  79. strErr = _T( "Failure creating hevDone(GetLastError=" );
  80. strErr += strErrNum;
  81. strErr += _T( ")" );
  82. throw CGeneralException( __FILE__, __LINE__,
  83. strErr ,
  84. EV_GTS_ERROR_THREAD );
  85. }
  86. m_hMutex = ::CreateMutex(NULL, false, NULL);
  87. if (!m_hMutex)
  88. {
  89. strErrNum.Format(_T("%d"), ::GetLastError());
  90. strErr = _T( "Failure creating hMutex (GetLastError=" );
  91. strErr += strErrNum;
  92. strErr += _T( ")" );
  93. throw CGeneralException( __FILE__, __LINE__,
  94. strErr ,
  95. EV_GTS_ERROR_THREAD );
  96. }
  97. // create the thread
  98. // Note although the destructor has a corresponding ::CloseHandle(m_hThread),
  99. // it's probably not needed. However, it should be harmless: we don't tear down
  100. // this object until after the thread has exited.
  101. // That is because the thread goes out of existence on the implicit
  102. // ::ExitThread() when PoolTask returns. See documentation of
  103. // ::CreateThread for further details JM 10/22/98
  104. m_hThread = ::CreateThread( NULL,
  105. 0,
  106. (LPTHREAD_START_ROUTINE)PoolTask,
  107. this,
  108. 0,
  109. &dwThreadID);
  110. if (!m_hThread)
  111. {
  112. strErrNum.Format(_T("%d"), ::GetLastError());
  113. strErr = _T( "Failure creating hThread (GetLastError=" );
  114. strErr += strErrNum;
  115. strErr += _T( ")" );
  116. throw CGeneralException( __FILE__, __LINE__,
  117. strErr ,
  118. EV_GTS_ERROR_THREAD );
  119. }
  120. }
  121. catch (CGeneralException&)
  122. {
  123. // Clean up any open handles.
  124. if (m_hevDone)
  125. {
  126. ::CloseHandle(m_hevDone);
  127. m_hevDone = NULL;
  128. }
  129. if (m_hMutex)
  130. {
  131. ::CloseHandle(m_hMutex);
  132. m_hMutex = NULL;
  133. }
  134. // Rethrow the exception.
  135. throw;
  136. }
  137. return 0;
  138. }
  139. void CThreadPool::CThreadControl::Lock()
  140. {
  141. ::WaitForSingleObject(m_hMutex, INFINITE);
  142. }
  143. void CThreadPool::CThreadControl::Unlock()
  144. {
  145. ::ReleaseMutex(m_hMutex);
  146. }
  147. time_t CThreadPool::CThreadControl::GetTimeCreated() const
  148. {
  149. return m_timeCreated;
  150. }
  151. // OUTPUT status
  152. void CThreadPool::CThreadControl::WorkingStatus(CPoolThreadStatus & status)
  153. {
  154. Lock();
  155. status.m_timeCreated = m_timeCreated;
  156. time_t timeNow;
  157. status.m_bWorking = m_bWorking;
  158. status.m_bFailed = m_bFailed;
  159. time(&timeNow);
  160. status.m_seconds = timeNow - (m_time ? m_time : m_timeCreated);
  161. if (m_pContext)
  162. status.m_strTopic = m_pContext->RetCurrentTopic();
  163. status.m_strBrowser= m_strBrowser.Get();
  164. status.m_strClientIP= m_strClientIP.Get();
  165. Unlock();
  166. }
  167. // This should only be called as a result of an operator request to kill the thread.
  168. // This is not the normal way to stop a thread.
  169. // INPUT milliseconds - how long to wait for normal exit before a TerminateThread
  170. // NOTE: Because this Kill function gets a lock, it is very important that no function
  171. // ever hold this lock more than briefly.
  172. void CThreadPool::CThreadControl::Kill(DWORD milliseconds)
  173. {
  174. Lock();
  175. m_bExit = true;
  176. Unlock();
  177. WaitForThreadToFinish(milliseconds);
  178. }
  179. // After a pool task thread has been signaled to finish, this is how main thread waits for it
  180. // to finish.
  181. // returns true if terminates OK.
  182. bool CThreadPool::CThreadControl::WaitForThreadToFinish(DWORD milliseconds)
  183. {
  184. bool bTermOK = true;
  185. if (m_hevDone != NULL)
  186. {
  187. DWORD dwStatus = ::WaitForSingleObject(m_hevDone, milliseconds);
  188. // terminate thread as last resort if it didn't exit properly
  189. // this may cause memory leak, but shouldn't normally happen
  190. // then close thread handle
  191. if (dwStatus != WAIT_OBJECT_0)
  192. {
  193. // We ignore the return of ::TerminateThread(). If we got here at all, there
  194. // was a problem witht th thread terminating. We don't care about distinguishing
  195. // how severe a problem.
  196. ::TerminateThread(m_hThread,0);
  197. bTermOK = false;
  198. }
  199. }
  200. return bTermOK;
  201. }
  202. // To be called on PoolTask thread
  203. // Return true if this initiates shutdown, false otherwise.
  204. // This is what handles healthy HTTP requests (many errors already filtered out before we
  205. // get here.)
  206. bool CThreadPool::CThreadControl::ProcessRequest()
  207. {
  208. WORK_QUEUE_ITEM * pwqi;
  209. bool bShutdown = false;
  210. pwqi = m_pPoolQueue->GetWorkItem();
  211. if ( !pwqi )
  212. {
  213. // no task. We shouldn't have been awakened.
  214. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  215. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  216. SrcLoc.GetSrcFileLineStr(),
  217. _T(""),
  218. _T(""),
  219. EV_GTS_ERROR_NO_QUEUE_ITEM );
  220. }
  221. if (pwqi->pECB != NULL)
  222. {
  223. // a normal user request
  224. // set privileges, etc. to those of a particular user
  225. if (pwqi->hImpersonationToken)
  226. ::ImpersonateLoggedOnUser( pwqi->hImpersonationToken );
  227. try
  228. {
  229. CString strBrowser;
  230. CString strClientIP;
  231. // Acquire the browser and IP address for status pages.
  232. APGTS_nmspace::GetServerVariable( pwqi->pECB, "HTTP_USER_AGENT", strBrowser );
  233. APGTS_nmspace::GetServerVariable( pwqi->pECB, "REMOTE_ADDR", strClientIP );
  234. m_strBrowser.Set( strBrowser );
  235. m_strClientIP.Set( strClientIP );
  236. m_pContext = new APGTSContext( pwqi->pECB,
  237. pwqi->pConf,
  238. pwqi->pLog,
  239. &pwqi->GTSStat,
  240. m_pSniffConnector);
  241. m_pContext->ProcessQuery();
  242. // Release the context and set the point to null.
  243. Lock();
  244. delete m_pContext;
  245. m_pContext= NULL;
  246. Unlock();
  247. // Clear the browser and IP address as this request is over.
  248. m_strBrowser.Set( _T("") );
  249. m_strClientIP.Set( _T("") );
  250. }
  251. catch (bad_alloc&)
  252. {
  253. // A memory allocation failure occurred during processing of query, log it.
  254. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  255. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  256. SrcLoc.GetSrcFileLineStr(),
  257. _T(""), _T(""), EV_GTS_CANT_ALLOC );
  258. }
  259. catch (...)
  260. {
  261. // Catch any other exception thrown.
  262. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  263. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  264. SrcLoc.GetSrcFileLineStr(),
  265. _T(""), _T(""),
  266. EV_GTS_GEN_EXCEPTION );
  267. }
  268. ::RevertToSelf();
  269. // Terminate HTTP request
  270. pwqi->pECB->ServerSupportFunction( HSE_REQ_DONE_WITH_SESSION,
  271. NULL,
  272. NULL,
  273. NULL );
  274. ::CloseHandle( pwqi->hImpersonationToken );
  275. }
  276. if (pwqi->pECB)
  277. delete pwqi->pECB;
  278. else
  279. // exit thread if null (we're shutting down)
  280. bShutdown = true;
  281. delete pwqi;
  282. return bShutdown;
  283. }
  284. // To be called on PoolTask thread
  285. bool CThreadPool::CThreadControl::Exit()
  286. {
  287. Lock();
  288. bool bExit = m_bExit;
  289. Unlock();
  290. return bExit;
  291. }
  292. // To be called on PoolTask thread
  293. // Main loop of a worker thread.
  294. void CThreadPool::CThreadControl::PoolTaskLoop()
  295. {
  296. DWORD res;
  297. bool bBad = false;
  298. while ( !Exit() )
  299. {
  300. res = m_pPoolQueue->WaitForWork();
  301. if ( res == WAIT_OBJECT_0 )
  302. {
  303. bBad = false;
  304. Lock();
  305. m_bWorking = true;
  306. time(&m_time);
  307. Unlock();
  308. bool bExit = ProcessRequest();
  309. Lock();
  310. m_bExit = bExit;
  311. Unlock();
  312. m_pPoolQueue->DecrementWorkItems();
  313. Lock();
  314. m_bWorking = false;
  315. time(&m_time);
  316. Unlock();
  317. }
  318. else
  319. {
  320. // utterly unexpected event, like a WAIT_FAILED.
  321. // There's no obvious way to recover from this sort of thing. Fortunately,
  322. // we've never seen it happen. Obviously we want to log to the event log.
  323. // Our variable bBad is a way of deciding that if this happens twice
  324. // in a row, this thread will just exit and give up totally. ,
  325. // If we ever see this in a real live system, it's
  326. // time to give this issue some thought.
  327. CString str;
  328. str.Format(_T("%d/%d"), res, GetLastError());
  329. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  330. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  331. SrcLoc.GetSrcFileLineStr(),
  332. str,
  333. _T(""),
  334. EV_GTS_ERROR_UNEXPECTED_WT );
  335. if (bBad)
  336. {
  337. m_bFailed = true;
  338. break; // out of while loop & implicitly out of thread.
  339. }
  340. else
  341. bBad = true;
  342. }
  343. }
  344. // signal shutdown code that we are finished
  345. ::SetEvent(m_hevDone);
  346. }
  347. // Main routine of a worker thread.
  348. // INPUT lpParams
  349. // Always returns 0.
  350. /* static */ UINT WINAPI CThreadPool::CThreadControl::PoolTask( LPVOID lpParams )
  351. {
  352. CThreadControl * pThreadControl;
  353. #ifdef LOCAL_TROUBLESHOOTER
  354. if (RUNNING_FREE_THREADED())
  355. ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
  356. if (RUNNING_APARTMENT_THREADED())
  357. ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  358. #endif
  359. pThreadControl = (CThreadControl *)lpParams;
  360. pThreadControl->PoolTaskLoop();
  361. #ifdef LOCAL_TROUBLESHOOTER
  362. if (RUNNING_FREE_THREADED() || RUNNING_APARTMENT_THREADED())
  363. ::CoUninitialize();
  364. #endif
  365. return 0;
  366. }
  367. //////////////////////////////////////////////////////////////////////
  368. // CThreadPool
  369. //////////////////////////////////////////////////////////////////////
  370. CThreadPool::CThreadPool(CPoolQueue * pPoolQueue, CSniffConnector* pSniffConnector) :
  371. m_dwErr(0),
  372. m_ppThreadCtl(NULL),
  373. m_dwWorkingThreadCount(0),
  374. m_pPoolQueue(pPoolQueue),
  375. m_pSniffConnector(pSniffConnector)
  376. {
  377. }
  378. CThreadPool::~CThreadPool()
  379. {
  380. DestroyThreads();
  381. if (m_ppThreadCtl)
  382. {
  383. for ( DWORD i = 0; i < m_dwWorkingThreadCount; i++ )
  384. if (m_ppThreadCtl[i])
  385. delete m_ppThreadCtl[i];
  386. delete [] m_ppThreadCtl;
  387. }
  388. }
  389. // get any error during construction
  390. DWORD CThreadPool::GetStatus() const
  391. {
  392. return m_dwErr;
  393. }
  394. DWORD CThreadPool::GetWorkingThreadCount() const
  395. {
  396. return m_dwWorkingThreadCount;
  397. }
  398. //
  399. // Call only from destructor
  400. void CThreadPool::DestroyThreads()
  401. {
  402. int BadTerm = 0;
  403. bool bFirst = true;
  404. DWORD i;
  405. // APGTSExtension should have already signaled the threads to quit.
  406. // >>>(ignore for V3.0) Doing that in APGTSExtension is lousy encapsulation, but
  407. // so far we don't see a clean way to do this.
  408. // Wait for them all to terminate unless we had a problem.
  409. // Because this is called from the dll's process detach, we can't
  410. // signal on thread termination, just when threads have exited their
  411. // infinite while loops
  412. if (m_dwWorkingThreadCount && m_ppThreadCtl)
  413. {
  414. // We will wait longer for the first thread: 10 seconds for processing to finish.
  415. // After that, we clip right along, since this has also been time for all the
  416. // other threads to finish.
  417. for ( i = 0; i < m_dwWorkingThreadCount; i++ )
  418. {
  419. if ( m_ppThreadCtl[i] )
  420. {
  421. if ( ! m_ppThreadCtl[i]->WaitForThreadToFinish((bFirst) ? 20000 : 100) )
  422. ++BadTerm;
  423. bFirst = false;
  424. }
  425. }
  426. if (BadTerm != 0)
  427. {
  428. CString str;
  429. str.Format(_T("%d"), BadTerm);
  430. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  431. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  432. SrcLoc.GetSrcFileLineStr(),
  433. str,
  434. _T(""),
  435. EV_GTS_USER_THRD_KILL );
  436. }
  437. }
  438. }
  439. // create the "pool" threads which handle user requests, one request at a time per thread
  440. // if there are less than dwDesiredThreadCount existing threads, expand the thread pool
  441. // to that size.
  442. // (We cannot shrink the thread pool while we are running).
  443. void CThreadPool::ExpandPool(DWORD dwDesiredThreadCount)
  444. {
  445. CString strErr;
  446. if (dwDesiredThreadCount > m_dwWorkingThreadCount)
  447. {
  448. CThreadControl **ppThreadCtl = NULL;
  449. const DWORD dwOldCount = m_dwWorkingThreadCount;
  450. bool bExceptionThrown = false; // Flag used in cleanup.
  451. // Attempt to allocate additional threads.
  452. try
  453. {
  454. // Allocate new thread block.
  455. ppThreadCtl = new CThreadControl* [dwDesiredThreadCount];
  456. //[BC-03022001] - added check for NULL ptr to satisfy MS code analysis tool.
  457. if(!ppThreadCtl)
  458. {
  459. throw bad_alloc();
  460. }
  461. DWORD i;
  462. // Initialize before adding threads
  463. for (i = 0; i < dwDesiredThreadCount; i++)
  464. ppThreadCtl[i] = NULL;
  465. // Transfer any existing threads.
  466. for (i = 0; i < dwOldCount; i++)
  467. ppThreadCtl[i] = m_ppThreadCtl[i];
  468. // Allocate additional threads.
  469. for (i = dwOldCount; i < dwDesiredThreadCount; i++)
  470. {
  471. ppThreadCtl[i] = new CThreadControl(m_pSniffConnector);
  472. //[BC-03022001] - added check for NULL ptr to satisfy MS code analysis tool.
  473. if(!ppThreadCtl[i])
  474. {
  475. throw bad_alloc();
  476. }
  477. // This function may throw exceptions of type CGeneralException.
  478. m_dwErr = ppThreadCtl[i]->Initialize(m_pPoolQueue);
  479. m_dwWorkingThreadCount++;
  480. }
  481. }
  482. catch (CGeneralException& x)
  483. {
  484. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  485. CEvent::ReportWFEvent( x.GetSrcFileLineStr(),
  486. SrcLoc.GetSrcFileLineStr(),
  487. x.GetErrorMsg(), _T("General exception"),
  488. x.GetErrorCode() );
  489. bExceptionThrown= true;
  490. }
  491. catch (bad_alloc&)
  492. {
  493. // Note memory failure in event log.
  494. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  495. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  496. SrcLoc.GetSrcFileLineStr(),
  497. _T(""), _T(""), EV_GTS_CANT_ALLOC );
  498. bExceptionThrown= true;
  499. }
  500. if ((bExceptionThrown) && (dwOldCount))
  501. {
  502. // Restore previous settings.
  503. // Clean up any allocated memory and reset the working thread count.
  504. for (DWORD i = dwOldCount; i < dwDesiredThreadCount; i++)
  505. {
  506. if (ppThreadCtl[i])
  507. delete ppThreadCtl[i];
  508. }
  509. if (ppThreadCtl)
  510. delete [] ppThreadCtl;
  511. m_dwWorkingThreadCount= dwOldCount;
  512. }
  513. else if (ppThreadCtl)
  514. {
  515. // Move thread block to member variable.
  516. CThreadControl **pp = m_ppThreadCtl;
  517. m_ppThreadCtl = ppThreadCtl;
  518. // Release any previous thread block.
  519. if (pp)
  520. delete[] pp;
  521. }
  522. else
  523. {
  524. // this is a very unlikely situation, but it would mean we have no pool
  525. // threads. We don't want to terminate the program (it's possible that
  526. // we want to run in support of status queries).
  527. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  528. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  529. SrcLoc.GetSrcFileLineStr(),
  530. _T(""), _T(""), EV_GTS_ERROR_NOPOOLTHREADS );
  531. }
  532. }
  533. }
  534. // input i is thread index.
  535. bool CThreadPool::ReinitializeThread(DWORD i)
  536. {
  537. if (i <m_dwWorkingThreadCount && m_ppThreadCtl && m_ppThreadCtl[i])
  538. {
  539. m_ppThreadCtl[i]->Kill(2000L); // 2 seconds to exit normally
  540. try
  541. {
  542. delete m_ppThreadCtl[i];
  543. m_ppThreadCtl[i] = new CThreadControl(m_pSniffConnector);
  544. // This function may throw exceptions of type CGeneralException.
  545. m_dwErr = m_ppThreadCtl[i]->Initialize(m_pPoolQueue);
  546. }
  547. catch (CGeneralException& x)
  548. {
  549. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  550. CEvent::ReportWFEvent( x.GetSrcFileLineStr(),
  551. SrcLoc.GetSrcFileLineStr(),
  552. x.GetErrorMsg(), _T("General exception"),
  553. x.GetErrorCode() );
  554. // Initialization has failed, delete the newly allocated thread.
  555. if (m_ppThreadCtl[i])
  556. delete m_ppThreadCtl[i];
  557. }
  558. catch (bad_alloc&)
  559. {
  560. // A memory allocation failure occurred during processing of query, log it.
  561. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  562. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  563. SrcLoc.GetSrcFileLineStr(),
  564. _T(""), _T(""), EV_GTS_CANT_ALLOC );
  565. // Set the thread to a known state.
  566. m_ppThreadCtl[i]= NULL;
  567. }
  568. return true;
  569. }
  570. else
  571. return false;
  572. }
  573. // Reinitialize any threads that have been "working" more than 10 seconds on a single request
  574. void CThreadPool::ReinitializeStuckThreads()
  575. {
  576. if (!m_ppThreadCtl)
  577. return;
  578. for (DWORD i=0; i<m_dwWorkingThreadCount;i++)
  579. {
  580. if (m_ppThreadCtl[i])
  581. {
  582. CPoolThreadStatus status;
  583. m_ppThreadCtl[i]->WorkingStatus(status);
  584. if ( status.m_bFailed || (status.m_bWorking && status.m_seconds > 10) )
  585. ReinitializeThread(i);
  586. }
  587. }
  588. }
  589. // input i is thread index.
  590. bool CThreadPool::ThreadStatus(DWORD i, CPoolThreadStatus &status)
  591. {
  592. if (i <m_dwWorkingThreadCount && m_ppThreadCtl && m_ppThreadCtl[i])
  593. {
  594. m_ppThreadCtl[i]->WorkingStatus(status);
  595. return true;
  596. }
  597. else
  598. return false;
  599. }