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.

701 lines
19 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. /////////////////////////////////////////////////////////////
  242. //
  243. // mando + 09.21.01
  244. // PREFIX BUG #: 467364
  245. // PREFIX BUG TITLE: PREFIX:enduser: \nt\enduser\troubleshoot\tshoot\threadpool.cpp: CThreadPool::CThreadControl::ProcessRequest(): dereferencing NULL pointer '(this->m_pContext)'
  246. //
  247. /////////////////////////////////////////////////////////////
  248. if( NULL == m_pContext ) throw bad_alloc();
  249. /////////////////////////////////////////////////////////////
  250. m_pContext->ProcessQuery();
  251. // Release the context and set the point to null.
  252. Lock();
  253. delete m_pContext;
  254. m_pContext= NULL;
  255. Unlock();
  256. // Clear the browser and IP address as this request is over.
  257. m_strBrowser.Set( _T("") );
  258. m_strClientIP.Set( _T("") );
  259. }
  260. catch (bad_alloc&)
  261. {
  262. // A memory allocation failure occurred during processing of query, log it.
  263. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  264. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  265. SrcLoc.GetSrcFileLineStr(),
  266. _T(""), _T(""), EV_GTS_CANT_ALLOC );
  267. }
  268. catch (...)
  269. {
  270. // Catch any other exception thrown.
  271. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  272. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  273. SrcLoc.GetSrcFileLineStr(),
  274. _T(""), _T(""),
  275. EV_GTS_GEN_EXCEPTION );
  276. }
  277. ::RevertToSelf();
  278. // Terminate HTTP request
  279. pwqi->pECB->ServerSupportFunction( HSE_REQ_DONE_WITH_SESSION,
  280. NULL,
  281. NULL,
  282. NULL );
  283. ::CloseHandle( pwqi->hImpersonationToken );
  284. }
  285. if (pwqi->pECB)
  286. delete pwqi->pECB;
  287. else
  288. // exit thread if null (we're shutting down)
  289. bShutdown = true;
  290. delete pwqi;
  291. return bShutdown;
  292. }
  293. // To be called on PoolTask thread
  294. bool CThreadPool::CThreadControl::Exit()
  295. {
  296. Lock();
  297. bool bExit = m_bExit;
  298. Unlock();
  299. return bExit;
  300. }
  301. // To be called on PoolTask thread
  302. // Main loop of a worker thread.
  303. void CThreadPool::CThreadControl::PoolTaskLoop()
  304. {
  305. DWORD res;
  306. bool bBad = false;
  307. while ( !Exit() )
  308. {
  309. res = m_pPoolQueue->WaitForWork();
  310. if ( res == WAIT_OBJECT_0 )
  311. {
  312. bBad = false;
  313. Lock();
  314. m_bWorking = true;
  315. time(&m_time);
  316. Unlock();
  317. bool bExit = ProcessRequest();
  318. Lock();
  319. m_bExit = bExit;
  320. Unlock();
  321. m_pPoolQueue->DecrementWorkItems();
  322. Lock();
  323. m_bWorking = false;
  324. time(&m_time);
  325. Unlock();
  326. }
  327. else
  328. {
  329. // utterly unexpected event, like a WAIT_FAILED.
  330. // There's no obvious way to recover from this sort of thing. Fortunately,
  331. // we've never seen it happen. Obviously we want to log to the event log.
  332. // Our variable bBad is a way of deciding that if this happens twice
  333. // in a row, this thread will just exit and give up totally. ,
  334. // If we ever see this in a real live system, it's
  335. // time to give this issue some thought.
  336. CString str;
  337. str.Format(_T("%d/%d"), res, GetLastError());
  338. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  339. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  340. SrcLoc.GetSrcFileLineStr(),
  341. str,
  342. _T(""),
  343. EV_GTS_ERROR_UNEXPECTED_WT );
  344. if (bBad)
  345. {
  346. m_bFailed = true;
  347. break; // out of while loop & implicitly out of thread.
  348. }
  349. else
  350. bBad = true;
  351. }
  352. }
  353. // signal shutdown code that we are finished
  354. ::SetEvent(m_hevDone);
  355. }
  356. // Main routine of a worker thread.
  357. // INPUT lpParams
  358. // Always returns 0.
  359. /* static */ UINT WINAPI CThreadPool::CThreadControl::PoolTask( LPVOID lpParams )
  360. {
  361. CThreadControl * pThreadControl;
  362. #ifdef LOCAL_TROUBLESHOOTER
  363. if (RUNNING_FREE_THREADED())
  364. ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
  365. if (RUNNING_APARTMENT_THREADED())
  366. ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  367. #endif
  368. pThreadControl = (CThreadControl *)lpParams;
  369. pThreadControl->PoolTaskLoop();
  370. #ifdef LOCAL_TROUBLESHOOTER
  371. if (RUNNING_FREE_THREADED() || RUNNING_APARTMENT_THREADED())
  372. ::CoUninitialize();
  373. #endif
  374. return 0;
  375. }
  376. //////////////////////////////////////////////////////////////////////
  377. // CThreadPool
  378. //////////////////////////////////////////////////////////////////////
  379. CThreadPool::CThreadPool(CPoolQueue * pPoolQueue, CSniffConnector* pSniffConnector) :
  380. m_dwErr(0),
  381. m_ppThreadCtl(NULL),
  382. m_dwWorkingThreadCount(0),
  383. m_pPoolQueue(pPoolQueue),
  384. m_pSniffConnector(pSniffConnector)
  385. {
  386. }
  387. CThreadPool::~CThreadPool()
  388. {
  389. DestroyThreads();
  390. if (m_ppThreadCtl)
  391. {
  392. for ( DWORD i = 0; i < m_dwWorkingThreadCount; i++ )
  393. if (m_ppThreadCtl[i])
  394. delete m_ppThreadCtl[i];
  395. delete [] m_ppThreadCtl;
  396. }
  397. }
  398. // get any error during construction
  399. DWORD CThreadPool::GetStatus() const
  400. {
  401. return m_dwErr;
  402. }
  403. DWORD CThreadPool::GetWorkingThreadCount() const
  404. {
  405. return m_dwWorkingThreadCount;
  406. }
  407. //
  408. // Call only from destructor
  409. void CThreadPool::DestroyThreads()
  410. {
  411. int BadTerm = 0;
  412. bool bFirst = true;
  413. DWORD i;
  414. // APGTSExtension should have already signaled the threads to quit.
  415. // >>>(ignore for V3.0) Doing that in APGTSExtension is lousy encapsulation, but
  416. // so far we don't see a clean way to do this.
  417. // Wait for them all to terminate unless we had a problem.
  418. // Because this is called from the dll's process detach, we can't
  419. // signal on thread termination, just when threads have exited their
  420. // infinite while loops
  421. if (m_dwWorkingThreadCount && m_ppThreadCtl)
  422. {
  423. // We will wait longer for the first thread: 10 seconds for processing to finish.
  424. // After that, we clip right along, since this has also been time for all the
  425. // other threads to finish.
  426. for ( i = 0; i < m_dwWorkingThreadCount; i++ )
  427. {
  428. if ( m_ppThreadCtl[i] )
  429. {
  430. if ( ! m_ppThreadCtl[i]->WaitForThreadToFinish((bFirst) ? 20000 : 100) )
  431. ++BadTerm;
  432. bFirst = false;
  433. }
  434. }
  435. if (BadTerm != 0)
  436. {
  437. CString str;
  438. str.Format(_T("%d"), BadTerm);
  439. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  440. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  441. SrcLoc.GetSrcFileLineStr(),
  442. str,
  443. _T(""),
  444. EV_GTS_USER_THRD_KILL );
  445. }
  446. }
  447. }
  448. // create the "pool" threads which handle user requests, one request at a time per thread
  449. // if there are less than dwDesiredThreadCount existing threads, expand the thread pool
  450. // to that size.
  451. // (We cannot shrink the thread pool while we are running).
  452. void CThreadPool::ExpandPool(DWORD dwDesiredThreadCount)
  453. {
  454. CString strErr;
  455. if (dwDesiredThreadCount > m_dwWorkingThreadCount)
  456. {
  457. CThreadControl **ppThreadCtl = NULL;
  458. const DWORD dwOldCount = m_dwWorkingThreadCount;
  459. bool bExceptionThrown = false; // Flag used in cleanup.
  460. // Attempt to allocate additional threads.
  461. try
  462. {
  463. // Allocate new thread block.
  464. ppThreadCtl = new CThreadControl* [dwDesiredThreadCount];
  465. //[BC-03022001] - added check for NULL ptr to satisfy MS code analysis tool.
  466. if(!ppThreadCtl)
  467. {
  468. throw bad_alloc();
  469. }
  470. DWORD i;
  471. // Initialize before adding threads
  472. for (i = 0; i < dwDesiredThreadCount; i++)
  473. ppThreadCtl[i] = NULL;
  474. // Transfer any existing threads.
  475. for (i = 0; i < dwOldCount; i++)
  476. ppThreadCtl[i] = m_ppThreadCtl[i];
  477. // Allocate additional threads.
  478. for (i = dwOldCount; i < dwDesiredThreadCount; i++)
  479. {
  480. ppThreadCtl[i] = new CThreadControl(m_pSniffConnector);
  481. //[BC-03022001] - added check for NULL ptr to satisfy MS code analysis tool.
  482. if(!ppThreadCtl[i])
  483. {
  484. throw bad_alloc();
  485. }
  486. // This function may throw exceptions of type CGeneralException.
  487. m_dwErr = ppThreadCtl[i]->Initialize(m_pPoolQueue);
  488. m_dwWorkingThreadCount++;
  489. }
  490. }
  491. catch (CGeneralException& x)
  492. {
  493. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  494. CEvent::ReportWFEvent( x.GetSrcFileLineStr(),
  495. SrcLoc.GetSrcFileLineStr(),
  496. x.GetErrorMsg(), _T("General exception"),
  497. x.GetErrorCode() );
  498. bExceptionThrown= true;
  499. }
  500. catch (bad_alloc&)
  501. {
  502. // Note memory failure in event log.
  503. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  504. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  505. SrcLoc.GetSrcFileLineStr(),
  506. _T(""), _T(""), EV_GTS_CANT_ALLOC );
  507. bExceptionThrown= true;
  508. }
  509. if ((bExceptionThrown) && (dwOldCount))
  510. {
  511. // Restore previous settings.
  512. // Clean up any allocated memory and reset the working thread count.
  513. for (DWORD i = dwOldCount; i < dwDesiredThreadCount; i++)
  514. {
  515. if (ppThreadCtl[i])
  516. delete ppThreadCtl[i];
  517. }
  518. if (ppThreadCtl)
  519. delete [] ppThreadCtl;
  520. m_dwWorkingThreadCount= dwOldCount;
  521. }
  522. else if (ppThreadCtl)
  523. {
  524. // Move thread block to member variable.
  525. CThreadControl **pp = m_ppThreadCtl;
  526. m_ppThreadCtl = ppThreadCtl;
  527. // Release any previous thread block.
  528. if (pp)
  529. delete[] pp;
  530. }
  531. else
  532. {
  533. // this is a very unlikely situation, but it would mean we have no pool
  534. // threads. We don't want to terminate the program (it's possible that
  535. // we want to run in support of status queries).
  536. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  537. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  538. SrcLoc.GetSrcFileLineStr(),
  539. _T(""), _T(""), EV_GTS_ERROR_NOPOOLTHREADS );
  540. }
  541. }
  542. }
  543. // input i is thread index.
  544. bool CThreadPool::ReinitializeThread(DWORD i)
  545. {
  546. if (i <m_dwWorkingThreadCount && m_ppThreadCtl && m_ppThreadCtl[i])
  547. {
  548. m_ppThreadCtl[i]->Kill(2000L); // 2 seconds to exit normally
  549. try
  550. {
  551. delete m_ppThreadCtl[i];
  552. m_ppThreadCtl[i] = new CThreadControl(m_pSniffConnector);
  553. // This function may throw exceptions of type CGeneralException.
  554. m_dwErr = m_ppThreadCtl[i]->Initialize(m_pPoolQueue);
  555. }
  556. catch (CGeneralException& x)
  557. {
  558. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  559. CEvent::ReportWFEvent( x.GetSrcFileLineStr(),
  560. SrcLoc.GetSrcFileLineStr(),
  561. x.GetErrorMsg(), _T("General exception"),
  562. x.GetErrorCode() );
  563. // Initialization has failed, delete the newly allocated thread.
  564. if (m_ppThreadCtl[i])
  565. delete m_ppThreadCtl[i];
  566. }
  567. catch (bad_alloc&)
  568. {
  569. // A memory allocation failure occurred during processing of query, log it.
  570. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
  571. CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
  572. SrcLoc.GetSrcFileLineStr(),
  573. _T(""), _T(""), EV_GTS_CANT_ALLOC );
  574. // Set the thread to a known state.
  575. m_ppThreadCtl[i]= NULL;
  576. }
  577. return true;
  578. }
  579. else
  580. return false;
  581. }
  582. // Reinitialize any threads that have been "working" more than 10 seconds on a single request
  583. void CThreadPool::ReinitializeStuckThreads()
  584. {
  585. if (!m_ppThreadCtl)
  586. return;
  587. for (DWORD i=0; i<m_dwWorkingThreadCount;i++)
  588. {
  589. if (m_ppThreadCtl[i])
  590. {
  591. CPoolThreadStatus status;
  592. m_ppThreadCtl[i]->WorkingStatus(status);
  593. if ( status.m_bFailed || (status.m_bWorking && status.m_seconds > 10) )
  594. ReinitializeThread(i);
  595. }
  596. }
  597. }
  598. // input i is thread index.
  599. bool CThreadPool::ThreadStatus(DWORD i, CPoolThreadStatus &status)
  600. {
  601. if (i <m_dwWorkingThreadCount && m_ppThreadCtl && m_ppThreadCtl[i])
  602. {
  603. m_ppThreadCtl[i]->WorkingStatus(status);
  604. return true;
  605. }
  606. else
  607. return false;
  608. }