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.

386 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: A thread pool implementation. You give it CWorkItems,
  4. // it processes them asynchronously, and hands them back to you when they've
  5. // been completed.
  6. //
  7. // To declare a queue, provide the implementation of a CWorkItem subtype,
  8. // the thread name prefix for threads in the pool, and the number of work
  9. // threads you want.
  10. //
  11. // CNet uses this class to offload encryption to a separate thread,
  12. // so that's a good place to start looking for usage examples.
  13. //
  14. //=============================================================================
  15. #ifndef WORKTHREADPOOL_H
  16. #define WORKTHREADPOOL_H
  17. #ifdef _WIN32
  18. #pragma once
  19. #endif
  20. #include <refcount.h>
  21. #include <reliabletimer.h>
  22. #include "jobtime.h"
  23. // forward declaration for CTSQueue which we can't statically allocate as our member
  24. // because of alignment issues on Win64
  25. template <class T, bool bTestOptimizer>
  26. class CTSQueue;
  27. namespace GCSDK {
  28. // forward declarations
  29. class CWorkThread;
  30. class CJobMgr;
  31. // these functions return pointers to fixed string in the code section. We need this for VPROF nodes
  32. #define DECLARE_WORK_ITEM( classname ) \
  33. virtual const char* GetDispatchCompletedName() const { return #classname"::DispatchCompleted"; } \
  34. virtual const char* GetThreadProcessName() const { return #classname"::ThreadProcess"; }
  35. //-----------------------------------------------------------------------------
  36. // Purpose: Work item base class. Derive from this for specific work item types.
  37. // The derived type ideally should be self-contained with all data it
  38. // needs to perform the work.
  39. //-----------------------------------------------------------------------------
  40. class CWorkItem : public CRefCount
  41. {
  42. public:
  43. CWorkItem()
  44. : m_JobID( k_GIDNil ),
  45. m_bRunning( false ),
  46. m_bResubmit( false ),
  47. m_bCanceled( false ),
  48. m_ulSequenceNumber( 0 )
  49. {
  50. m_jobtimeTimeout.SetLTime( 0 );
  51. m_jobtimeQueued.SetToJobTime();
  52. }
  53. CWorkItem( JobID_t jobID )
  54. : m_JobID( jobID ),
  55. m_bRunning( false ),
  56. m_bResubmit( false ),
  57. m_bCanceled( false ),
  58. m_ulSequenceNumber( 0 )
  59. {
  60. m_jobtimeTimeout.SetLTime( 0 );
  61. m_jobtimeQueued.SetToJobTime();
  62. }
  63. CWorkItem( JobID_t jobID, int64 cTimeoutMicroseconds )
  64. : m_JobID( jobID ),
  65. m_bRunning( false ),
  66. m_bResubmit( false ),
  67. m_bCanceled( false ),
  68. m_ulSequenceNumber( 0 )
  69. {
  70. SetPreExecuteTimeout( cTimeoutMicroseconds );
  71. m_jobtimeQueued.SetToJobTime();
  72. }
  73. void SetJobID( JobID_t jobID )
  74. {
  75. Assert(jobID != k_GIDNil) ;
  76. m_JobID = jobID;
  77. }
  78. JobID_t GetJobID() const { return m_JobID; }
  79. bool HasTimedOut() const { return m_jobtimeTimeout.LTime() != 0 && m_jobtimeTimeout.CServerMicroSecsPassed() > 0; }
  80. int64 WaitingTime() const { return m_jobtimeQueued.CServerMicroSecsPassed(); }
  81. void SetPreExecuteTimeout( int64 cMicroSeconds ) { m_jobtimeTimeout.SetFromJobTime( cMicroSeconds ); }
  82. bool BPreExecuteTimeoutSet( ) const { return m_jobtimeTimeout.LTime() != 0; }
  83. void ForceTimeOut() { m_jobtimeTimeout.SetFromJobTime( -1 );}
  84. bool BIsRunning() const { return m_bRunning; } // true if running right now
  85. bool WasCancelled() const { return m_bCanceled; }
  86. void SetCycleCount( CCycleCount& cycleCount ) { m_CycleCount = cycleCount ; }
  87. CCycleCount GetCycleCount() { return m_CycleCount; }
  88. uint64 GetSequenceNumber() { return m_ulSequenceNumber; }
  89. // Work threads can call this to force a work item to be reprocessed (added to the end of the process queue)
  90. void SetResubmit( bool bResubmit ) { m_bResubmit = bResubmit; }
  91. // these functions return pointers to fixed string in the code section.
  92. // We need this for VPROF nodes, you must use the DECLARE_WORK_ITEM macro
  93. virtual const char* GetDispatchCompletedName() const = 0;
  94. virtual const char* GetThreadProcessName() const = 0;
  95. // Return false if your operation failed in some way that you would want to know about
  96. // The CWorkThreadPool will count the failures.
  97. virtual bool ThreadProcess( CWorkThread *pThread ) = 0; // called by the worker thread
  98. virtual bool DispatchCompletedWorkItem( CJobMgr *jobMgr ); // called by main loop after item completed
  99. #ifdef DBGFLAG_VALIDATE
  100. virtual void Validate( CValidator &validator, const char *pchName ) {} // Validate our internal structures
  101. #endif
  102. protected:
  103. // note: destructor is private. This is a ref-counted object, private destructor ensures callers can't accidentally delete
  104. // directly, or declare on stack
  105. virtual ~CWorkItem() { }
  106. friend class CWorkThread;
  107. friend class CWorkThreadPool;
  108. uint64 m_ulSequenceNumber; // Sequence number for the work item, used when enforcing output ordering as matching input order
  109. CCycleCount m_CycleCount; // A record of how long it took to execute this particular work item !
  110. private:
  111. bool m_bResubmit; // true if the item should be resubmitted after last run
  112. volatile bool m_bRunning; // true if the work item is running right now
  113. bool m_bCanceled; // true if the work was canceled due to timeout
  114. CJobTime m_jobtimeTimeout; // time at which this result is no longer valid, so it shouldn't start to be processed
  115. CJobTime m_jobtimeQueued;
  116. JobID_t m_JobID;
  117. };
  118. // forward decl
  119. class CWorkThreadPool;
  120. //-----------------------------------------------------------------------------
  121. // Purpose: Generic work thread implementation, to be specialized if necessary
  122. //-----------------------------------------------------------------------------
  123. class CWorkThread : public CThread
  124. {
  125. public:
  126. CWorkThread( CWorkThreadPool *pThreadPool );
  127. CWorkThread( CWorkThreadPool *pThreadPool, const char *pszName );
  128. virtual ~CWorkThread()
  129. {
  130. }
  131. virtual int Run();
  132. virtual void Cancel()
  133. {
  134. }
  135. protected:
  136. CWorkThreadPool *m_pThreadPool; // parent pool
  137. volatile bool m_bExitThread; // set by CWorkThreadPool::StopWorkerThreads and possibly by subclasses of CWorkThread
  138. volatile bool m_bFinished; // set by CWorkThread::Run [note: must still check IsThreadRunning, and/or call Join]
  139. virtual void OnStart() { }
  140. virtual void OnExit() { }
  141. #ifdef DBGFLAG_VALIDATE
  142. public:
  143. virtual void Validate( CValidator &validator, const char *pchName )
  144. {
  145. VALIDATE_SCOPE();
  146. };
  147. #endif // DBGFLAG_VALIDATE
  148. friend class CWorkThreadPool;
  149. };
  150. //-----------------------------------------------------------------------------
  151. // callback class to create work threads
  152. //-----------------------------------------------------------------------------
  153. class IWorkThreadFactory
  154. {
  155. public:
  156. virtual CWorkThread *CreateWorkerThread( class CWorkThreadPool *pWorkThreadPool ) = 0;
  157. };
  158. //-----------------------------------------------------------------------------
  159. // reusable trivial implementation of IWorkThreadFactory
  160. //-----------------------------------------------------------------------------
  161. template<class T>
  162. class CWorkThreadFactory : public IWorkThreadFactory
  163. {
  164. public:
  165. virtual CWorkThread *CreateWorkerThread( class CWorkThreadPool *pWorkThreadPool )
  166. {
  167. return new T( pWorkThreadPool );
  168. }
  169. };
  170. //-----------------------------------------------------------------------------
  171. // Purpose: interface class for object that the WorkThreadPool can signal when
  172. // there are completed work items to process
  173. //-----------------------------------------------------------------------------
  174. class IWorkThreadPoolSignal
  175. {
  176. public:
  177. virtual void Signal() = 0;
  178. };
  179. //-----------------------------------------------------------------------------
  180. // Purpose: pool of work threads.
  181. //-----------------------------------------------------------------------------
  182. class CWorkThreadPool
  183. {
  184. friend class CWorkThread;
  185. public:
  186. static void SetWorkItemCompletedSignal( IWorkThreadPoolSignal *pObject )
  187. {
  188. sm_pWorkItemsCompletedSignal = pObject;
  189. }
  190. CWorkThreadPool( const char *pszThreadNamePfx );
  191. // eventually it might be nice to be able to resize these pools via console command
  192. // in that case, we'd want a constructor like this, and a PoolSize accessor/mutator pair
  193. // it makes this class much more complicated, however (growing the pool is easy, shrinking it
  194. // is less easy) so we'll punt for now.
  195. /* CWorkThreadPool( const char *pszName = "unnamed thread" ) : CWorkThreadPool( pszName, -1 ); */
  196. virtual ~CWorkThreadPool();
  197. // Setting this will ensure that items of the same priority complete and get dispatched in the same order
  198. // they are added to the threadpool. This has a small additional locking overhead and can increase latency
  199. // as items that are actually completed out-of-order have to queue waiting on earlier items.
  200. void SetEnsureOutputOrdering( bool bEnsureOutputOrdering ) { m_bEnsureOutputOrdering = bEnsureOutputOrdering; }
  201. void AllowTimeouts( bool bMayHaveJobTimeouts ) { m_bMayHaveJobTimeouts = bMayHaveJobTimeouts; }
  202. int AddWorkThread( CWorkThread *pThread );
  203. void StartWorkThreads(); // gentlemen, start your engines
  204. void StopWorkThreads(); // stop work threads
  205. bool HasWorkItemsToProcess() const;
  206. // sets it to use dynamic worker thread construction
  207. // if pWorkThreadControl is NULL, just creates a standard CWorkThread object
  208. void SetWorkThreadAutoConstruct( int cMaxThreads, IWorkThreadFactory *pWorkThreadConstructor );
  209. bool AddWorkItem( CWorkItem *pWorkItem ); // add a work item to the queue to process
  210. CWorkItem *GetNextCompletedWorkItem( ); // get next completed work item and it's priority if needed
  211. const char *GetThreadNamePrefix() const { return m_szThreadNamePfx; }
  212. void SetNeverSetEventOnAdd( bool bNeverSet );
  213. bool BNeverSetEventOnAdd() { return m_bNeverSetOnAdd; }
  214. // get count of completed work items
  215. // can't be inline because of m_TSQueueCompleted type
  216. int GetCompletedWorkItemCount() const;
  217. // get count of work items to process
  218. // can't be inline because of m_TSQueueToProcess type
  219. int GetWorkItemToProcessCount() const;
  220. uint64 GetLastUsedSequenceNumber( ) const
  221. {
  222. return m_ulLastUsedSequenceNumber;
  223. }
  224. uint64 GetLastCompletedSequenceNumber( ) const
  225. {
  226. return m_ulLastCompletedSequenceNumber;
  227. }
  228. uint64 GetLastDispatchedSequenceNumber( ) const
  229. {
  230. return m_ulLastDispatchedSequenceNumber;
  231. }
  232. #if 0
  233. uint64 GetAveExecutionTime() const
  234. {
  235. return m_StatExecutionTime.GetUlAvg();
  236. }
  237. uint64 GetAveWaitTime() const
  238. {
  239. return m_StatWaitTime.GetUlAvg();
  240. }
  241. uint64 GetCurrentBacklogTime() const;
  242. #endif
  243. int CountCompletedSuccess() const { return m_cSuccesses; }
  244. int CountRetries() const { return m_cRetries; }
  245. int CountCompletedFailed() const { return m_cFailures; }
  246. bool BDispatchCompletedWorkItems( const CLimitTimer &limitTimer, CJobMgr *pJobMgr );
  247. bool BExiting() const { return m_bExiting; }
  248. int GetWorkerCount() const { return m_WorkThreads.Count(); }
  249. uint GetActiveThreadCount() const { return m_cActiveThreads; }
  250. // make sure you lock before using this
  251. const CWorkThread *GetWorkThread( int iIndex ) const
  252. {
  253. Assert( iIndex >= 0 && iIndex < m_WorkThreads.Count() );
  254. return m_WorkThreads[iIndex];
  255. }
  256. protected:
  257. // STATICS
  258. static IWorkThreadPoolSignal *sm_pWorkItemsCompletedSignal;
  259. // MEMBERS
  260. CWorkItem *GetNextWorkItemToProcess( );
  261. void StartWorkThread( CWorkThread *pWorkThread, int iName );
  262. // meaningful thread name prefix
  263. char m_szThreadNamePfx[32];
  264. // have we actually initialized the threadpool?
  265. bool m_bThreadsInitialized;
  266. // Incoming queue: queue of all work items to process
  267. // must be dynamically allocated for alignment requirements on Win64
  268. CTSQueue< CWorkItem *, false > *m_pTSQueueToProcess;
  269. // Outgoing queues: queue of all completed work items
  270. // must be dynamically allocated for alignment requirements on Win64
  271. CTSQueue< CWorkItem *, false > *m_pTSQueueCompleted;
  272. // Vectors of completed, but out of order and waiting work items, only used when bEnsureOutputOrdering == true
  273. CThreadMutex m_MutexOnItemCompletedOrdered;
  274. CUtlVector< CWorkItem * > m_vecCompletedAndWaiting;
  275. // Should we emit work items in the same order they are received (on a per priority basis)
  276. bool m_bEnsureOutputOrdering;
  277. // Sequence numbers
  278. uint64 m_ulLastUsedSequenceNumber;
  279. uint64 m_ulLastCompletedSequenceNumber;
  280. uint64 m_ulLastDispatchedSequenceNumber;
  281. bool m_bMayHaveJobTimeouts;
  282. CUtlVector< CWorkThread * > m_WorkThreads;
  283. CThreadMutex m_WorkThreadMutex;
  284. CInterlockedUInt m_cThreadsRunning; // how many threads are running
  285. volatile bool m_bExiting; // are we exiting
  286. CThreadEvent m_EventNewWorkItem; // event set when a new work item is available to process
  287. CInterlockedInt m_cActiveThreads;
  288. volatile bool m_bNeverSetOnAdd;
  289. bool m_bAutoCreateThreads;
  290. int m_cMaxThreads;
  291. IWorkThreadFactory *m_pWorkThreadConstructor;
  292. // override this method if you want to do any special handling of completed work items. Default implementation puts
  293. // work items in our completed item queue.
  294. virtual void OnWorkItemCompleted( CWorkItem *pWorkItem );
  295. bool BTryDeleteExitedWorkerThreads();
  296. int m_cSuccesses;
  297. int m_cFailures;
  298. int m_cRetries;
  299. #if 0
  300. CStat m_StatExecutionTime;
  301. CStat m_StatWaitTime;
  302. #endif
  303. CLimitTimer m_LimitTimerCreateNewThreads;
  304. #ifdef DBGFLAG_VALIDATE
  305. public:
  306. void Validate( CValidator &validator, const char *pchName );
  307. #endif
  308. };
  309. } // namespace GCSDK
  310. #endif // WORKTHREAD_H