Team Fortress 2 Source Code as on 22/4/2020
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.

751 lines
23 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "stdafx.h"
  8. #include "tslist.h"
  9. #include <workthreadpool.h>
  10. #include <gclogger.h>
  11. #include "tier0/memdbgon.h"
  12. namespace GCSDK {
  13. IWorkThreadPoolSignal *CWorkThreadPool::sm_pWorkItemsCompletedSignal = NULL;
  14. //-----------------------------------------------------------------------------
  15. // Purpose: CWorkThread constructors
  16. //-----------------------------------------------------------------------------
  17. CWorkThread::CWorkThread( CWorkThreadPool *pThreadPool )
  18. : m_pThreadPool( pThreadPool ),
  19. m_bExitThread( false ),
  20. m_bFinished( false )
  21. {
  22. }
  23. CWorkThread::CWorkThread( CWorkThreadPool *pThreadPool, const char *pszName )
  24. : m_pThreadPool( pThreadPool ),
  25. m_bExitThread( false ),
  26. m_bFinished( false )
  27. {
  28. SetName( pszName );
  29. }
  30. //-----------------------------------------------------------------------------
  31. // Purpose: Tell work thread pool not to set event on every item added (SetEvent is very expensive)
  32. //-----------------------------------------------------------------------------
  33. void CWorkThreadPool::SetNeverSetEventOnAdd( bool bNeverSet )
  34. {
  35. bool bWasSet = m_bNeverSetOnAdd;
  36. m_bNeverSetOnAdd = bNeverSet;
  37. // In case of disabling set right away to make sure if we have pending work we execute it now with no latency
  38. if ( bWasSet && !m_bNeverSetOnAdd )
  39. m_EventNewWorkItem.Set();
  40. }
  41. //-----------------------------------------------------------------------------
  42. // Purpose: performs the work loop for the thread, waits for work,
  43. // notifies the owner (the pool) as it completes work and before it exits
  44. //-----------------------------------------------------------------------------
  45. int CWorkThread::Run()
  46. {
  47. // manage our thread pool's statistics
  48. ++m_pThreadPool->m_cThreadsRunning;
  49. #ifdef _SERVER
  50. g_CompletionPortManager.AssociateCallingThreadWithIOCP();
  51. #endif
  52. OnStart();
  53. #if 0 // need to port over new vprof code
  54. #if defined( VPROF_ENABLED )
  55. CVProfile *pProfile = GetVProfProfileForCurrentThread();
  56. #endif
  57. #endif
  58. CWorkThreadPool *pPool = m_pThreadPool;
  59. int nIterations = 0;
  60. const int nMaxFastIterations = 4;
  61. while ( !m_bExitThread )
  62. {
  63. #if 0 // game vprof doesn't yet support TLS'd vprof instances, until new vprof code is ported
  64. #if defined( VPROF_ENABLED )
  65. if ( pProfile )
  66. pProfile->MarkFrame( GetName() );
  67. #endif
  68. #endif
  69. pPool->m_cActiveThreads++;
  70. nIterations = 0;
  71. while ( (pPool->BNeverSetEventOnAdd() && nIterations < nMaxFastIterations) || nIterations == 0 )
  72. {
  73. // process any items which have arrived
  74. CWorkItem *pWorkItem = pPool->GetNextWorkItemToProcess( );
  75. while ( pWorkItem )
  76. {
  77. #if 0
  78. pPool->m_StatWaitTime.Update( pWorkItem->WaitingTime() );
  79. #endif
  80. if ( pWorkItem->HasTimedOut() )
  81. {
  82. pWorkItem->m_bCanceled = true;
  83. }
  84. else
  85. {
  86. // call the work item to do its work
  87. pWorkItem->m_bCanceled = false;
  88. CFastTimer fastTimer;
  89. fastTimer.Start();
  90. pWorkItem->m_bRunning = true;
  91. bool bSuccess = pWorkItem->ThreadProcess( this );
  92. pWorkItem->m_bRunning = false;
  93. fastTimer.End();
  94. CCycleCount cycleCount = fastTimer.GetDuration();
  95. pWorkItem->SetCycleCount(cycleCount);
  96. #if 0
  97. pPool->m_StatExecutionTime.Update( cycleCount.GetUlMicroseconds() );
  98. #endif
  99. if ( bSuccess )
  100. pPool->m_cSuccesses ++;
  101. else
  102. pWorkItem->m_bResubmit ? pPool->m_cRetries++ : pPool->m_cFailures++;
  103. }
  104. // do we need to resubmit this item?
  105. if ( pWorkItem->m_bResubmit )
  106. {
  107. pWorkItem->m_bResubmit = false;
  108. pWorkItem->m_bCanceled = false;
  109. // put it at the tail of the incoming queue
  110. pPool->AddWorkItem( pWorkItem );
  111. pWorkItem->Release(); // dec since AddWorkItem added 1 more again
  112. }
  113. else
  114. {
  115. // put it in the outgoing queue
  116. pPool->OnWorkItemCompleted( pWorkItem );
  117. }
  118. // If we are flagged as exiting don't try to get more work, we need to exit right away and orphan the work
  119. // to avoid blocking shutdown.
  120. if ( !m_bExitThread )
  121. {
  122. // get the next work item (if any)
  123. pWorkItem = pPool->GetNextWorkItemToProcess( );
  124. }
  125. else
  126. {
  127. pWorkItem = NULL;
  128. }
  129. #if 0 // game vprof doesn't yet support TLS'd vprof instances, until new vprof code is ported
  130. #if defined( VPROF_ENABLED )
  131. if ( pProfile && pWorkItem )
  132. pProfile->MarkFrame( GetName() );
  133. #endif
  134. #endif
  135. }
  136. if ( m_bExitThread )
  137. break;
  138. ++nIterations;
  139. if ( pPool->BNeverSetEventOnAdd() && nIterations < nMaxFastIterations )
  140. {
  141. VPROF_BUDGET( "CWorkThread -- Sleep", VPROF_BUDGETGROUP_SLEEPING );
  142. ThreadSleep( 2 );
  143. }
  144. }
  145. pPool->m_cActiveThreads--;
  146. // wait for a new work item to arrive in the queue, check the counts first just to be sure
  147. {
  148. VPROF_BUDGET( "CWorkThread -- Sleep", VPROF_BUDGETGROUP_SLEEPING );
  149. #ifdef _SERVER
  150. if ( pPool->BNeverSetEventOnAdd() )
  151. pPool->m_EventNewWorkItem.Wait( 15 );
  152. else
  153. pPool->m_EventNewWorkItem.Wait( 50 );
  154. #else
  155. pPool->m_EventNewWorkItem.Wait( 50 );
  156. #endif
  157. }
  158. }
  159. // Since we are exiting, we must have been signaled to shutdown, and we should signal any remaining threads
  160. // since each signal wakes only one thread.
  161. pPool->m_EventNewWorkItem.Set();
  162. m_bFinished = true;
  163. // updates stats
  164. --m_pThreadPool->m_cThreadsRunning;
  165. return EXIT_SUCCESS;
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Purpose: Construct a new CWorkThreadPool object
  169. //-----------------------------------------------------------------------------
  170. CWorkThreadPool::CWorkThreadPool( const char *pszThreadName )
  171. :
  172. #if 0
  173. m_StatWaitTime( 100 ),
  174. m_StatExecutionTime( 100 ),
  175. #endif
  176. m_bThreadsInitialized( false ),
  177. m_cThreadsRunning( 0 ),
  178. m_cActiveThreads( 0 ),
  179. m_bMayHaveJobTimeouts( false ),
  180. m_bExiting( false ),
  181. m_bAutoCreateThreads( false ),
  182. m_cMaxThreads( 0 ),
  183. m_cFailures( 0 ),
  184. m_cSuccesses( 0 ),
  185. m_pWorkThreadConstructor( NULL ),
  186. m_ulLastCompletedSequenceNumber( 0 ),
  187. m_ulLastUsedSequenceNumber( 0 ),
  188. m_ulLastDispatchedSequenceNumber( 0 ),
  189. m_bEnsureOutputOrdering( false ),
  190. m_bNeverSetOnAdd( false )
  191. {
  192. Assert( pszThreadName != NULL );
  193. Q_strncpy( m_szThreadNamePfx, pszThreadName, sizeof( m_szThreadNamePfx ) );
  194. m_LimitTimerCreateNewThreads.SetLimit( 1 );
  195. m_pTSQueueToProcess = new CTSQueue< CWorkItem* >;
  196. m_pTSQueueCompleted = new CTSQueue< CWorkItem* >;
  197. }
  198. //-----------------------------------------------------------------------------
  199. // Purpose: destructor; does assertion checks to make sure we weere shut down cleanly
  200. // cleans up even if we weren't cleanly stopped
  201. //-----------------------------------------------------------------------------
  202. CWorkThreadPool::~CWorkThreadPool()
  203. {
  204. // If you hit this you probably didn't call StopWorkThreads() first
  205. AssertMsg1( ( !m_bThreadsInitialized || m_bExiting ) && 0 == m_cThreadsRunning,
  206. "CWorkThreadPool::~CWorkThreadPool(): Thread pool %s shutdown incorrectly.\n",
  207. m_szThreadNamePfx );
  208. if ( m_WorkThreads.Count() )
  209. {
  210. StopWorkThreads();
  211. Assert( 0 == m_WorkThreads.Count() );
  212. }
  213. Assert( 0 == m_cThreadsRunning );
  214. // WARNING: We need to release any items left in the queues
  215. CWorkItem *pWorkItem = NULL;
  216. if ( m_pTSQueueCompleted->Count() > 0 )
  217. {
  218. EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::~CWorkThreadPool: work complete queue not empty, %d items discarded.\n", m_pTSQueueCompleted->Count() );
  219. pWorkItem = NULL;
  220. while ( m_pTSQueueCompleted->PopItem( &pWorkItem ) )
  221. {
  222. while( pWorkItem->Release() )
  223. {
  224. /* nothing */
  225. }
  226. }
  227. }
  228. if ( m_pTSQueueToProcess->Count() > 0 )
  229. {
  230. EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::~CWorkThreadPool: work processing queue not empty: %d items discarded.\n", m_pTSQueueToProcess->Count() );
  231. while ( m_pTSQueueToProcess->PopItem( &pWorkItem ) )
  232. {
  233. while( pWorkItem->Release() )
  234. {
  235. /* nothing */
  236. }
  237. }
  238. }
  239. delete m_pTSQueueToProcess;
  240. delete m_pTSQueueCompleted;
  241. }
  242. #if 0
  243. //-----------------------------------------------------------------------------
  244. // Purpose: estimate the current backlog time using previous execution time,
  245. // the number of outstanding items, and the number of running threads
  246. //-----------------------------------------------------------------------------
  247. uint64 CWorkThreadPool::GetCurrentBacklogTime() const
  248. {
  249. if ( m_WorkThreads.Count() == 0 )
  250. return 0;
  251. return ( m_pTSQueueToProcess->Count() * m_StatExecutionTime.GetUlAvg() ) / m_WorkThreads.Count();
  252. }
  253. #endif
  254. int CWorkThreadPool::AddWorkThread( CWorkThread *pThread )
  255. {
  256. AUTO_LOCK( m_WorkThreadMutex );
  257. Assert( pThread );
  258. return m_WorkThreads.AddToTail( pThread );
  259. }
  260. void CWorkThreadPool::StartWorkThread( CWorkThread *pWorkThread, int iName )
  261. {
  262. char rgchThreadName[32];
  263. Q_snprintf( rgchThreadName, sizeof( rgchThreadName ), "%s:%d", m_szThreadNamePfx, iName );
  264. pWorkThread->SetName( rgchThreadName );
  265. if ( !pWorkThread->Start() )
  266. EmitError( SPEW_THREADS, "CWorkThreadPool::StartWorkThread: Thread creation failed.\n" );
  267. }
  268. void CWorkThreadPool::StartWorkThreads()
  269. {
  270. m_bThreadsInitialized = true;
  271. if ( 0 == m_WorkThreads.Count() )
  272. {
  273. EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::StartWorkThreads: called with no threads in the pool, this is probably a bug.\n" );
  274. return;
  275. }
  276. m_bExiting = false;
  277. m_cThreadsRunning = 0;
  278. AUTO_LOCK( m_WorkThreadMutex );
  279. FOR_EACH_VEC( m_WorkThreads, i )
  280. {
  281. StartWorkThread( m_WorkThreads[i], i );
  282. }
  283. // XXX why?
  284. while ( m_cThreadsRunning == (uint) 0 )
  285. {
  286. ThreadSleep( 1 );
  287. }
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose: stops whatever work threads we're running
  291. // this must be called before the thread pool object is destroyed
  292. //-----------------------------------------------------------------------------
  293. void CWorkThreadPool::StopWorkThreads()
  294. {
  295. // indicate that we're shutting down;
  296. // don't accept more work in this thread
  297. m_bExiting = true;
  298. AUTO_LOCK( m_WorkThreadMutex );
  299. FOR_EACH_VEC( m_WorkThreads, i )
  300. {
  301. m_WorkThreads[i]->m_bExitThread = true;
  302. m_WorkThreads[i]->Cancel();
  303. }
  304. // loop until all threads are dead
  305. while ( true )
  306. {
  307. // This thread already holds the mutex; recursive try-lock should always succeed
  308. DbgVerify( BTryDeleteExitedWorkerThreads() );
  309. if ( m_WorkThreads.Count() == 0 )
  310. break;
  311. // Keep waking up threads until they're all dead.
  312. m_EventNewWorkItem.Set();
  313. #ifdef _PS3
  314. // call to abort any running call to gethostbyname().
  315. // this is called over all the remaining work threads, while
  316. // waiting for the rest of the work threads to finish so that they won't
  317. // spuriously block on new calls to gethostbyname() as the
  318. // sys_net_abort_resolver call only stops the next call to the
  319. // network API, not any future calls.
  320. FOR_EACH_VEC( m_WorkThreads, iPS3 )
  321. {
  322. // PS3 hack to abort gethostbyname() calls that may be blocking...
  323. sys_net_abort_resolver( m_WorkThreads[ iPS3 ]->GetThreadID(), SYS_NET_ABORT_STRICT_CHECK );
  324. }
  325. #endif
  326. const uint k_uJoinTimeoutMillisec = 10000; // 10 seconds seems pretty arbitrary.
  327. CWorkThread *pWorkThread = m_WorkThreads[0];
  328. bool bJoined = pWorkThread->Join( k_uJoinTimeoutMillisec );
  329. if ( !bJoined )
  330. {
  331. // Print thread id as a pointer for cross-platform compatibility
  332. EmitWarning( SPEW_THREADS, 2, "Thread \"%s\" (ID %p) failed to shut down", pWorkThread->GetName(), (void*)pWorkThread->GetThreadID() );
  333. }
  334. else
  335. {
  336. // Succesful join means that the thread has terminated.
  337. if ( !pWorkThread->m_bFinished )
  338. {
  339. // This would be a logic error in the thread proc if it ever tripped.
  340. AssertMsg( false, "pWorkThread->m_bFinished is false but thread is not running" );
  341. // Recover by flagging the thread as potentially eligable for deletion, since it's dead.
  342. pWorkThread->m_bFinished = true;
  343. }
  344. }
  345. }
  346. Assert( m_WorkThreads.Count() == 0 && m_cThreadsRunning == (uint32) 0 );
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose: sees if we have a non-zero number of work threads,
  350. // or a non-zero number of active threads
  351. //-----------------------------------------------------------------------------
  352. bool CWorkThreadPool::HasWorkItemsToProcess() const
  353. {
  354. return ( m_pTSQueueToProcess->Count() > 0 )
  355. || ( m_cActiveThreads > 0 );
  356. }
  357. //-----------------------------------------------------------------------------
  358. // Purpose: sets dynamic thread construction
  359. //-----------------------------------------------------------------------------
  360. void CWorkThreadPool::SetWorkThreadAutoConstruct( int cMaxThreads, IWorkThreadFactory *pWorkThreadConstructor )
  361. {
  362. AUTO_LOCK( m_WorkThreadMutex );
  363. m_bThreadsInitialized = true;
  364. m_bAutoCreateThreads = true;
  365. m_cMaxThreads = MAX( 1, cMaxThreads );
  366. m_pWorkThreadConstructor = pWorkThreadConstructor;
  367. // If we have too many threads now, mark some to exit next time they loop.
  368. for ( int i = m_cMaxThreads; i < m_WorkThreads.Count(); i++ )
  369. {
  370. m_WorkThreads[i]->m_bExitThread = true;
  371. }
  372. }
  373. //-----------------------------------------------------------------------------
  374. // Purpose: Adds a work item
  375. // Output: true if successful,
  376. // false if a low priority work item is not added due to a busy system
  377. // false if this work pool is shutting down and work isn't being accepted
  378. // NOTE: Adding normal priority items should always succeed
  379. //-----------------------------------------------------------------------------
  380. bool CWorkThreadPool::AddWorkItem( CWorkItem *pWorkItem )
  381. {
  382. Assert( !m_bExiting );
  383. if ( m_bExiting )
  384. return false;
  385. if ( m_bEnsureOutputOrdering )
  386. {
  387. AssertMsg( pWorkItem->m_bResubmit == false, "CWorkThreadPool can't support item auto resubmission when ensuring output ordering" );
  388. }
  389. // if we're in auto-create mode, make sure we have enough threads running
  390. if ( m_bAutoCreateThreads && m_WorkThreads.Count() < m_cMaxThreads )
  391. {
  392. int cPendingItems = m_pTSQueueToProcess->Count();
  393. // we shouldn't get more than 12 items queued per already existing thread, otherwise we
  394. // want to create a new thread to help us keep up.
  395. if ( m_WorkThreads.Count() < 1 || m_WorkThreads.Count() * 12 < ( cPendingItems + 1 ) )
  396. {
  397. if ( m_WorkThreads.Count() >= 2 && !m_LimitTimerCreateNewThreads.BLimitReached() )
  398. {
  399. // Don't create more yet, we don't want to create them too fast
  400. }
  401. else
  402. {
  403. // create another thread
  404. CWorkThread *pWorkThread = NULL;
  405. if ( m_pWorkThreadConstructor )
  406. {
  407. pWorkThread = m_pWorkThreadConstructor->CreateWorkerThread( this );
  408. }
  409. else
  410. {
  411. pWorkThread = new CWorkThread( this );
  412. }
  413. if( pWorkThread != NULL )
  414. {
  415. int iName = AddWorkThread( pWorkThread );
  416. StartWorkThread( pWorkThread, iName );
  417. }
  418. m_LimitTimerCreateNewThreads.SetLimit( 250*k_nThousand );
  419. }
  420. }
  421. }
  422. //
  423. // Do we actually have any threads ? If creating threads can fail, then maybe we don't !
  424. // In that case, this WorkItem is not going to run !
  425. //
  426. if ( m_WorkThreads.Count() == 0 )
  427. {
  428. Assert(false);
  429. return false ;
  430. }
  431. // WARNING: We need to call pWorkItem AddRef() and Release() at all entry/exit points for the thread pool system.
  432. pWorkItem->AddRef();
  433. pWorkItem->m_ulSequenceNumber = (++m_ulLastUsedSequenceNumber);
  434. m_pTSQueueToProcess->PushItem( pWorkItem );
  435. if ( !BNeverSetEventOnAdd() && m_cActiveThreads == 0 )
  436. {
  437. VPROF_BUDGET( "SetEvent()", VPROF_BUDGETGROUP_THREADINGMAIN );
  438. m_EventNewWorkItem.Set();
  439. }
  440. return true;
  441. }
  442. CWorkItem *CWorkThreadPool::GetNextCompletedWorkItem( )
  443. {
  444. CWorkItem *pWorkItem = NULL;
  445. // Use a while loop just in case ref counts get screwed up and an item gets deleted when we release our reference to it
  446. while ( m_pTSQueueCompleted->PopItem( &pWorkItem ) )
  447. {
  448. // WARNING: We need to call workitem AddRef() and Release() at all entry/exit points for the thread pool system.
  449. // Release() returns the current refcount of the object (after decrementing it by one) and should be non-zero unless the
  450. // the caller has released it already.
  451. if ( pWorkItem != NULL && pWorkItem->Release() > 0 )
  452. {
  453. return pWorkItem;
  454. }
  455. }
  456. return NULL;
  457. }
  458. //-----------------------------------------------------------------------------
  459. // Purpose: gets the next work item to process. This non-blocking function
  460. // returns NULL immediately if there's nothing left in the queue.
  461. // otherwise, a pointer to the next CWorkItem.
  462. //-----------------------------------------------------------------------------
  463. CWorkItem *CWorkThreadPool::GetNextWorkItemToProcess( )
  464. {
  465. CWorkItem *pWorkItem = NULL;
  466. if ( m_pTSQueueToProcess->Count() && m_pTSQueueToProcess->PopItem( &pWorkItem ) )
  467. {
  468. return pWorkItem;
  469. }
  470. return NULL;
  471. }
  472. bool CWorkThreadPool::BDispatchCompletedWorkItems( const CLimitTimer &limitTimer, CJobMgr *pJobMgr )
  473. {
  474. BTryDeleteExitedWorkerThreads();
  475. CWorkItem *pWorkItem = GetNextCompletedWorkItem( );
  476. while ( pWorkItem != NULL )
  477. {
  478. uint64 ulSequenceNumber = pWorkItem->m_ulSequenceNumber;
  479. // NOTE: despite its name, this YIELDS - the target job
  480. // is resumed, and we resume here.
  481. if ( !pWorkItem->DispatchCompletedWorkItem( pJobMgr ) )
  482. {
  483. EmitWarning( SPEW_THREADS, 2, "Work Item for Work Pool %s completed but job no longer existed to notify\n", m_szThreadNamePfx == NULL ? "UNKNOWN" :m_szThreadNamePfx );
  484. AssertMsg1( m_bMayHaveJobTimeouts, "Work Item for Work Pool %s completed but job no longer existed to notify", m_szThreadNamePfx == NULL ? "UNKNOWN" :m_szThreadNamePfx );
  485. }
  486. // pWorkItem was released by DispatchCompletedWorkItem
  487. m_ulLastDispatchedSequenceNumber = ulSequenceNumber;
  488. if ( limitTimer.BLimitReached() )
  489. break;
  490. pWorkItem = GetNextCompletedWorkItem( );
  491. }
  492. return ( GetCompletedWorkItemCount() > 0 );
  493. }
  494. //-----------------------------------------------------------------------------
  495. // Purpose: delete any thread objects that have exited
  496. // we'll make sure the thread has actually ended;
  497. // if they haven't, they'll remain in the threads to delete list
  498. //-----------------------------------------------------------------------------
  499. bool CWorkThreadPool::BTryDeleteExitedWorkerThreads()
  500. {
  501. if ( m_WorkThreadMutex.TryLock() )
  502. {
  503. if ( m_cThreadsRunning < (uint) m_WorkThreads.Count() )
  504. {
  505. FOR_EACH_VEC_BACK( m_WorkThreads, i )
  506. {
  507. CWorkThread *pWorkThread = m_WorkThreads[i];
  508. if ( pWorkThread->m_bFinished && !pWorkThread->IsThreadRunning() )
  509. {
  510. m_WorkThreads.FastRemove( i );
  511. delete pWorkThread;
  512. }
  513. }
  514. }
  515. m_WorkThreadMutex.Unlock();
  516. return true;
  517. }
  518. return false;
  519. }
  520. bool CWorkItem::DispatchCompletedWorkItem( CJobMgr *pJobMgr )
  521. {
  522. // Check if this work item needs to signal a job
  523. if ( pJobMgr && k_GIDNil != m_JobID )
  524. {
  525. if ( !pJobMgr->BRouteWorkItemCompletedIfExists( m_JobID, m_bCanceled ) )
  526. return false;
  527. }
  528. else if ( k_GIDNil != m_JobID )
  529. {
  530. // This should never happen since we have already released our reference to the work item
  531. // and the calling job should have released its ref when it exited
  532. AssertMsg( false, "CWorkItem::DispatchCompletedWorkItem: got a work item with no job ID" );
  533. }
  534. return true;
  535. }
  536. //-----------------------------------------------------------------------------
  537. // Purpose: Called by the worker thread when it finishes an individual work item
  538. // This function will see if our work is meant to be well-ordred; if so,
  539. // it will do the necessary work to ensure ordering.
  540. //
  541. // It adds the item to the completed work item list so
  542. // the pool owner can retrieve it and checks to see if any threads
  543. // deserve to be shut down.
  544. //-----------------------------------------------------------------------------
  545. void CWorkThreadPool::OnWorkItemCompleted( CWorkItem *pWorkItem )
  546. {
  547. if ( sm_pWorkItemsCompletedSignal != NULL )
  548. sm_pWorkItemsCompletedSignal->Signal();
  549. if ( !m_bEnsureOutputOrdering )
  550. {
  551. // Since we aren't locking this sequence number could get screwed up a bit, but it's
  552. // pretty meaningless if ensure output ordering if off anyway...
  553. m_ulLastCompletedSequenceNumber = pWorkItem->m_ulSequenceNumber;
  554. m_pTSQueueCompleted->PushItem( pWorkItem );
  555. }
  556. else
  557. {
  558. // In the ordered case we need to lock completely here since we'll be moving around between
  559. // various data structures and also need to ensure the ordering of items in the TS queue
  560. m_MutexOnItemCompletedOrdered.Lock();
  561. if ( m_ulLastCompletedSequenceNumber + 1 == pWorkItem->m_ulSequenceNumber )
  562. {
  563. m_ulLastCompletedSequenceNumber = pWorkItem->m_ulSequenceNumber;
  564. m_pTSQueueCompleted->PushItem( pWorkItem );
  565. // We walk the vector multiple times, but it should be very short as items are likely to come in
  566. // close to in order, just mixed up a little if we have lots of threads or one item is much more
  567. // costly than others.
  568. bool bFoundNext = false;
  569. do
  570. {
  571. bFoundNext = false;
  572. FOR_EACH_VEC( m_vecCompletedAndWaiting, i )
  573. {
  574. CWorkItem *pWaiting = m_vecCompletedAndWaiting[i];
  575. if ( m_ulLastCompletedSequenceNumber + 1 == pWaiting->m_ulSequenceNumber )
  576. {
  577. m_ulLastCompletedSequenceNumber = pWaiting->m_ulSequenceNumber;
  578. m_pTSQueueCompleted->PushItem( pWaiting );
  579. m_vecCompletedAndWaiting.FastRemove( i );
  580. bFoundNext = true;
  581. break;
  582. }
  583. }
  584. } while ( bFoundNext == true );
  585. }
  586. else
  587. {
  588. m_vecCompletedAndWaiting.AddToTail( pWorkItem );
  589. }
  590. m_MutexOnItemCompletedOrdered.Unlock();
  591. }
  592. }
  593. //-----------------------------------------------------------------------------
  594. // Purpose: return the count of items we've queued to process
  595. //-----------------------------------------------------------------------------
  596. int CWorkThreadPool::GetWorkItemToProcessCount() const
  597. {
  598. return m_pTSQueueToProcess->Count();
  599. }
  600. //-----------------------------------------------------------------------------
  601. // Purpose: return the count of items we've completed but not notified the consumer about
  602. //-----------------------------------------------------------------------------
  603. int CWorkThreadPool::GetCompletedWorkItemCount() const
  604. {
  605. int nCount = m_pTSQueueCompleted->Count();
  606. return nCount;
  607. }
  608. #ifdef DBGFLAG_VALIDATE
  609. //-----------------------------------------------------------------------------
  610. // Purpose: Validates memory
  611. //-----------------------------------------------------------------------------
  612. void CWorkThreadPool::Validate( CValidator &validator, const char *pchName )
  613. {
  614. VALIDATE_SCOPE();
  615. AUTO_LOCK( m_WorkThreadMutex );
  616. ValidateObj( m_WorkThreads );
  617. FOR_EACH_VEC( m_WorkThreads, iWorkThread )
  618. {
  619. m_WorkThreads[ iWorkThread ]->Suspend();
  620. ValidatePtr( m_WorkThreads[ iWorkThread ] );
  621. }
  622. ValidateAlignedPtr( m_pTSQueueCompleted );
  623. ValidateAlignedPtr( m_pTSQueueToProcess );
  624. ValidateObj( m_vecCompletedAndWaiting );
  625. FOR_EACH_VEC( m_vecCompletedAndWaiting, j )
  626. {
  627. ValidatePtr( m_vecCompletedAndWaiting.Element( j ) );
  628. }
  629. FOR_EACH_VEC( m_WorkThreads, iWorkThread )
  630. {
  631. m_WorkThreads[ iWorkThread ]->Resume();
  632. }
  633. #if 0
  634. ValidateObj( m_StatExecutionTime );
  635. ValidateObj( m_StatWaitTime );
  636. #endif
  637. }
  638. #endif // DBGFLAG_VALIDATE
  639. } // namespace GCSDK