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.

1451 lines
45 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "stdafx.h"
  8. #ifdef WIN32
  9. #include "typeinfo.h"
  10. #else
  11. #include <typeinfo>
  12. #endif
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. namespace GCSDK
  16. {
  17. //-----------------------------------------------------------------------------
  18. // Purpose: EJobPauseReason descriptions
  19. //-----------------------------------------------------------------------------
  20. static const char * const k_prgchJobPauseReason[] =
  21. {
  22. "active",
  23. "not started",
  24. "netmsg",
  25. "sleep for time",
  26. "waiting for lock",
  27. "yielding",
  28. "SQL",
  29. "work item",
  30. };
  31. COMPILE_TIME_ASSERT( ARRAYSIZE( k_prgchJobPauseReason ) == k_EJobPauseReasonCount );
  32. CJob *g_pJobCur = NULL;
  33. //-----------------------------------------------------------------------------
  34. // Purpose: Delete a Job
  35. // Input: pJob - The Job to delete
  36. //-----------------------------------------------------------------------------
  37. void CJob::DeleteJob( CJob *pJob )
  38. {
  39. // we can't delete the if we still have a pending work item
  40. pJob->WaitForThreadFuncWorkItemBlocking();
  41. delete pJob;
  42. }
  43. //-----------------------------------------------------------------------------
  44. // Purpose: Constructor
  45. // Input: pServerParent - The server we belong to
  46. //-----------------------------------------------------------------------------
  47. CJob::CJob( CJobMgr &jobMgr, const char *pchJobName ) : m_JobMgr( jobMgr ), m_pchJobName( pchJobName )
  48. {
  49. m_ePauseReason = k_EJobPauseReasonNotStarted;
  50. m_JobID = jobMgr.GetNewJobID();
  51. m_pJobType = NULL;
  52. m_bWorkItemCanceled = false;
  53. m_hCoroutine = Coroutine_Create( &BRunProxy, this );
  54. m_pvStartParam = NULL;
  55. m_bRunFromMsg = false;
  56. m_pJobPrev = NULL;
  57. m_pWaitingOnLock = NULL;
  58. m_pJobToNotifyOnLockRelease = NULL;
  59. m_pWaitingOnWorkItem = NULL;
  60. m_STimeStarted.SetToJobTime();
  61. m_STimeSwitched.SetToJobTime();
  62. m_STimeNextHeartbeat.SetFromJobTime( k_cMicroSecJobHeartbeat );
  63. m_bIsLongRunning = false;
  64. m_cLocksAttempted = 0;
  65. m_cLocksWaitedFor = 0;
  66. m_flags.m_uFlags = 0;
  67. m_cyclecountTotal = 0;
  68. m_unWaitMsgType = 0;
  69. GetJobMgr().InsertJob( *this );
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Purpose: Destructor
  73. //-----------------------------------------------------------------------------
  74. CJob::~CJob()
  75. {
  76. // don't want SendMsgToConnection to call back into us, we *know*
  77. // we are replying to these other jobs now
  78. g_pJobCur = NULL;
  79. // reset the job pointer
  80. g_pJobCur = m_pJobPrev;
  81. // remove from the job tracking list
  82. GetJobMgr().RemoveJob( *this );
  83. // Forcefully release any locks
  84. ReleaseLocks();
  85. // free any network messages we've allocated
  86. FOR_EACH_VEC( m_vecNetPackets, i )
  87. {
  88. m_vecNetPackets[i]->Release();
  89. }
  90. m_vecNetPackets.RemoveAll();
  91. AssertMsg2( 0 == GetDoNotYieldDepth(), "Job ending with %d open Do Not Yields. Are we missing a END_DO_NOT_YIELD()? Innermost delared at %s",
  92. GetDoNotYieldDepth(), m_stackDoNotYieldGuards[m_stackDoNotYieldGuards.Head()] );
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Purpose: if necessary wait for the pending work item to finish
  96. //-----------------------------------------------------------------------------
  97. void CJob::WaitForThreadFuncWorkItemBlocking()
  98. {
  99. if ( m_pWaitingOnWorkItem )
  100. {
  101. switch ( GetPauseReason() )
  102. {
  103. case k_EJobPauseReasonWorkItem:
  104. // force the workitem to be canceled in case it's still in the in-queue
  105. m_pWaitingOnWorkItem->ForceTimeOut();
  106. // we can't shutdown the job while it's work item is currently running
  107. // alot of work items refernce back into the job object
  108. while ( m_pWaitingOnWorkItem->BIsRunning() )
  109. ThreadSleep( 25 );
  110. m_pWaitingOnWorkItem = NULL;
  111. break;
  112. #if 0 // not used in gcsdk
  113. case k_EJobPauseReasonGeneric:
  114. AssertMsg1( ( !m_pWaitingForGeneric || ( m_pWaitingForGeneric == ( void * ) 1 ) ), "CJob::WaitForThreadFuncWorkItemBlocking job %s will leak generic heap object", GetName() );
  115. // Let another assert fire later, don't null-out: m_pWaitingForGeneric = NULL;
  116. break;
  117. #endif
  118. default:
  119. AssertMsg1( false, "CJob::WaitForThreadFuncWorkItemBlocking job %s has unexpected work item state", GetName() );
  120. }
  121. }
  122. }
  123. //-----------------------------------------------------------------------------
  124. // Purpose: returns the name of the job
  125. //-----------------------------------------------------------------------------
  126. const char *CJob::GetName() const
  127. {
  128. if ( m_pchJobName )
  129. return m_pchJobName;
  130. else if ( m_pJobType )
  131. return m_pJobType->m_pchName;
  132. else
  133. return "";
  134. }
  135. //-----------------------------------------------------------------------------
  136. // Purpose: string description of why we're paused
  137. //-----------------------------------------------------------------------------
  138. const char *CJob::GetPauseReasonDescription() const
  139. {
  140. static char srgchPauseReason[k_cSmallBuff];
  141. if ( GetPauseReason() < Q_ARRAYSIZE( k_prgchJobPauseReason ) )
  142. {
  143. switch( GetPauseReason() )
  144. {
  145. case k_EJobPauseReasonWaitingForLock:
  146. {
  147. Q_snprintf( srgchPauseReason, k_cSmallBuff, "WOL: 0x%x (%s)", (unsigned int)m_pWaitingOnLock, m_pWaitingOnLock ? m_pWaitingOnLock->GetName() : "null" );
  148. return srgchPauseReason;
  149. }
  150. case k_EJobPauseReasonNetworkMsg:
  151. {
  152. const char *pchMsgType;
  153. if( g_theMessageList.GetMessage( m_unWaitMsgType, &pchMsgType, 0xFF ) )
  154. {
  155. Q_snprintf( srgchPauseReason, k_cSmallBuff, "NetMsg: %s", pchMsgType );
  156. return srgchPauseReason;
  157. }
  158. else
  159. {
  160. Q_snprintf( srgchPauseReason, k_cSmallBuff, "NetMsg: Unknown %d", m_unWaitMsgType );
  161. return srgchPauseReason;
  162. }
  163. }
  164. default:
  165. return k_prgchJobPauseReason[ GetPauseReason() ];
  166. }
  167. }
  168. return "undefined";
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose: accessor to get access to the JobMgr from the server we belong to
  172. //-----------------------------------------------------------------------------
  173. CJobMgr &CJob::GetJobMgr()
  174. {
  175. return m_JobMgr;
  176. }
  177. //-----------------------------------------------------------------------------
  178. // Purpose: Starts the job, based on the current network msg
  179. // Input : hConnection - Connection that the message was received form
  180. // pubPkt - The raw message packet
  181. // cubPkt - The size of the message packet
  182. //-----------------------------------------------------------------------------
  183. void CJob::StartJobFromNetworkMsg( IMsgNetPacket *pNetPacket, const JobID_t &gidJobIDSrc )
  184. {
  185. // hang on to the packet with the message that started this job
  186. AddPacketToList( pNetPacket, gidJobIDSrc );
  187. SetFromFromMsg( true );
  188. // start running this job
  189. InitCoroutine();
  190. //and start executing the job
  191. Continue();
  192. }
  193. //-----------------------------------------------------------------------------
  194. // Purpose: Starts the job
  195. //-----------------------------------------------------------------------------
  196. void CJob::StartJob( void * pvStartParam )
  197. {
  198. // job must not be started
  199. AssertMsg1( m_ePauseReason == k_EJobPauseReasonNotStarted, "CJob::StartJob() called twice on job %s\n", GetName() );
  200. // save the start params for this job
  201. SetStartParam( pvStartParam );
  202. m_JobMgr.CheckThreadID();
  203. // start running this job
  204. InitCoroutine();
  205. Continue();
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose: Starts the job in a suspended state
  209. //-----------------------------------------------------------------------------
  210. void CJob::StartJobDelayed( void * pvStartParam )
  211. {
  212. // job must not be started
  213. AssertMsg1( m_ePauseReason == k_EJobPauseReasonNotStarted, "CJob::StartJob() called twice on job %s\n", GetName() );
  214. // save the start params for this job
  215. SetStartParam( pvStartParam );
  216. m_JobMgr.CheckThreadID();
  217. //init the job, but don't start it
  218. InitCoroutine();
  219. //set our job as suspended (a yield)
  220. m_ePauseReason = k_EJobPauseReasonYield;
  221. m_JobMgr.AddDelayedJobToYieldList( *this );
  222. }
  223. //-----------------------------------------------------------------------------
  224. // Purpose: setup the debug memory and job name before running a job the first time
  225. //-----------------------------------------------------------------------------
  226. void CJob::InitCoroutine()
  227. {
  228. // make sure we have an appropriate chunk of memory to store
  229. // our debug alloc info
  230. if( MemAlloc_GetDebugInfoSize() )
  231. {
  232. m_memAllocStack.EnsureCapacity( MemAlloc_GetDebugInfoSize() );
  233. // Set the job name as the root
  234. MemAlloc_InitDebugInfo( m_memAllocStack.Base(), GetName(), 0 );
  235. }
  236. // Set the job name
  237. if ( !m_pJobType && !m_pchJobName )
  238. {
  239. #ifdef _WIN32
  240. m_pchJobName = typeid( *this ).raw_name();
  241. if ( m_pchJobName[0] == '.' && m_pchJobName[1] == '?' && m_pchJobName[2] == 'A')
  242. m_pchJobName += 4;
  243. if ( m_pchJobName[0] == '?' && m_pchJobName[1] == '$' )
  244. m_pchJobName += 2;
  245. #else
  246. m_pchJobName = typeid( *this ).name();
  247. #endif
  248. }
  249. }
  250. //-----------------------------------------------------------------------------
  251. // Purpose: proxy function for starting the job in the coroutine
  252. //-----------------------------------------------------------------------------
  253. void CJob::BRunProxy( void *pvThis )
  254. {
  255. CJob *pJob = (CJob *)pvThis;
  256. // run the job
  257. bool bJobReturn = false;
  258. if ( pJob->m_bRunFromMsg )
  259. {
  260. Assert( pJob->m_vecNetPackets.Count() > 0 );
  261. bJobReturn = pJob->BYieldingRunJobFromMsg( pJob->m_vecNetPackets.Head() );
  262. }
  263. else
  264. {
  265. bJobReturn = pJob->BYieldingRunJob( pJob->m_pvStartParam );
  266. }
  267. pJob->m_flags.m_bits.m_bJobFailed = ( true != bJobReturn );
  268. // kill it
  269. DeleteJob( pJob );
  270. }
  271. //-----------------------------------------------------------------------------
  272. // Purpose: Adds this packet to the linked list of packets for this job
  273. //-----------------------------------------------------------------------------
  274. void CJob::AddPacketToList( IMsgNetPacket *pNetPacket, const GID_t gidJobIDSrc )
  275. {
  276. Assert( pNetPacket );
  277. pNetPacket->AddRef();
  278. m_vecNetPackets.AddToTail( pNetPacket );
  279. }
  280. //-----------------------------------------------------------------------------
  281. // Purpose: marks a net packet as being finished with, releases the packet and frees the memory
  282. //-----------------------------------------------------------------------------
  283. void CJob::ReleaseNetPacket( IMsgNetPacket *pNetPacket )
  284. {
  285. int iVec = m_vecNetPackets.Find( pNetPacket );
  286. if ( iVec != m_vecNetPackets.InvalidIndex() )
  287. {
  288. pNetPacket->Release();
  289. m_vecNetPackets.Remove( iVec );
  290. }
  291. else
  292. {
  293. AssertMsg( false, "Job failed trying to release a IMsgNetPacket it doesn't own" );
  294. }
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Purpose: continues the current job
  298. //-----------------------------------------------------------------------------
  299. void CJob::Continue()
  300. {
  301. AssertNotRunningThisJob();
  302. m_pJobPrev = g_pJobCur;
  303. g_pJobCur = this;
  304. // Record frame we're starting in and start a timer to track how long we work for within the frame
  305. // Also, add in how much time has passed since we were last paused to track heartbeat requirements
  306. m_FastTimerDelta.Start();
  307. m_STimeSwitched.SetToJobTime();
  308. // Check if we need to heartbeat
  309. if ( BJobNeedsToHeartbeat() )
  310. {
  311. Heartbeat();
  312. }
  313. m_JobMgr.GetJobStats().m_cTimeslices++;
  314. m_ePauseReason = k_EJobPauseReasonNone;
  315. #if defined(_WIN32) && defined(COROUTINE_TRACE)
  316. const char *pchRawName = typeid( *this ).raw_name();
  317. if ( pchRawName[0] == '.' && pchRawName[1] == '?' && pchRawName[2] == 'A')
  318. pchRawName += 4;
  319. if ( pchRawName[0] == '?' && pchRawName[1] == '$' )
  320. pchRawName += 2;
  321. #else
  322. const char *pchRawName = "";
  323. #endif
  324. // Save debug credit "call stack"
  325. void *pvSaveDebugInfo = GetJobMgr().GetMainMemoryDebugInfo();
  326. MemAlloc_SaveDebugInfo( pvSaveDebugInfo );
  327. MemAlloc_RestoreDebugInfo( m_memAllocStack.Base() );
  328. // continue the coroutine, with the profiling if necessary
  329. bool bJobStillActive;
  330. #if defined( VPROF_ENABLED )
  331. if ( g_VProfCurrentProfile.IsEnabled() )
  332. {
  333. VPROF_BUDGET( GetName(), VPROF_BUDGETGROUP_JOBS_COROUTINES );
  334. bJobStillActive = Coroutine_Continue( m_hCoroutine, pchRawName );
  335. }
  336. else
  337. #endif
  338. {
  339. bJobStillActive = Coroutine_Continue( m_hCoroutine, pchRawName );
  340. }
  341. // WARNING: MEMBER VARIABLES ARE NOW UNSAFE TO ACCESS - this CJob may be deleted
  342. // Restore debug credit call stack
  343. if( bJobStillActive )
  344. {
  345. // only save off debug info for jobs that are still running
  346. MemAlloc_SaveDebugInfo( m_memAllocStack.Base() );
  347. }
  348. MemAlloc_RestoreDebugInfo( pvSaveDebugInfo );
  349. }
  350. void CJob::Debug()
  351. {
  352. AssertNotRunningThisJob();
  353. // This function will 'load' this coroutine then immediately
  354. // break into the debugger. When execution is continued, it
  355. // will pop back out to this context
  356. // So, we don't set m_pJobPrev or g_pJobCur because nobody
  357. // would ever have the chance to see them anyway.
  358. Coroutine_DebugBreak( m_hCoroutine );
  359. }
  360. //-----------------------------------------------------------------------------
  361. // Purpose: pauses the current job
  362. //-----------------------------------------------------------------------------
  363. void CJob::Pause( EJobPauseReason eReason )
  364. {
  365. AssertRunningThisJob();
  366. AssertMsg1( 0 == m_stackDoNotYieldGuards.Count(), "Yielding while in a BEGIN_DO_NOT_YIELD() block declared at %s", m_stackDoNotYieldGuards[m_stackDoNotYieldGuards.Head()] );
  367. g_pJobCur = m_pJobPrev;
  368. // End our timer so we know how much time we've spent
  369. m_FastTimerDelta.End();
  370. m_cyclecountTotal += m_FastTimerDelta.GetDuration();
  371. if ( m_FastTimerDelta.GetDuration().GetMicroseconds() > k_cMicroSecTaskGranularity * 10 )
  372. {
  373. m_flags.m_bits.m_bLongInterYield = true;
  374. }
  375. // pause this job, remembering which frame and why
  376. m_ePauseReason = eReason;
  377. // We shouldn't have to set the frame -- it should be the same one
  378. Assert( m_STimeSwitched.LTime() == CJobTime::LJobTimeCur() );
  379. Coroutine_YieldToMain();
  380. }
  381. void CJob::GenerateAssert( const char *pchMsg )
  382. {
  383. // Default message if they didn't provide a custom one
  384. if ( !pchMsg )
  385. {
  386. pchMsg = "Forced assert failure";
  387. }
  388. // Just for grins, allow this function to be called whether
  389. // we are the current job or not
  390. if ( this == g_pJobCur )
  391. {
  392. AssertMsg1( !"Job assertion requested", "%s", pchMsg );
  393. }
  394. else
  395. {
  396. Coroutine_DebugAssert( m_hCoroutine, pchMsg );
  397. }
  398. }
  399. //-----------------------------------------------------------------------------
  400. // Purpose: pauses the job until a network msg for the job arrives
  401. //-----------------------------------------------------------------------------
  402. bool CJob::BYieldingWaitForMsg( IMsgNetPacket **ppNetPacket )
  403. {
  404. AssertRunningThisJob();
  405. *ppNetPacket = NULL;
  406. // await and retrieve the network message
  407. if ( GetJobMgr().BYieldingWaitForMsg( *this ) )
  408. {
  409. Assert( m_vecNetPackets.Count() > 0 );
  410. *ppNetPacket = m_vecNetPackets.Tail();
  411. return true;
  412. }
  413. return false;
  414. }
  415. //-----------------------------------------------------------------------------
  416. // Purpose: pauses the job until a network msg for the job arrives
  417. //-----------------------------------------------------------------------------
  418. bool CJob::BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg )
  419. {
  420. IMsgNetPacket *pNetPacket = NULL;
  421. // Check if we already waited for a message of this type
  422. // but timed out. If so, then we currently don't have a way
  423. // to tell if the message we might receive is the reply
  424. // to the old mesage, of the one we're about to send.
  425. // So let's just disallow this entirely.
  426. if ( BHasFailedToReceivedMsgType( eMsg ) )
  427. {
  428. AssertMsg2( false, "Job %s cannot wait for msg %u, it has already failed to wait for that msg type.", GetName(), eMsg );
  429. return false;
  430. }
  431. m_unWaitMsgType = eMsg;
  432. if ( !BYieldingWaitForMsg( &pNetPacket) )
  433. {
  434. // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
  435. MarkFailedToReceivedMsgType( eMsg );
  436. return false;
  437. }
  438. pMsg->SetPacket( pNetPacket );
  439. if ( pMsg->Hdr().m_eMsg != eMsg )
  440. {
  441. // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
  442. MarkFailedToReceivedMsgType( eMsg );
  443. AssertMsg2( false, "CJob::BYieldingWaitForMsg expected msg %u but received %u", eMsg, pMsg->Hdr().m_eMsg );
  444. return false;
  445. }
  446. return true;
  447. }
  448. //-----------------------------------------------------------------------------
  449. bool CJob::BHasFailedToReceivedMsgType( MsgType_t m ) const
  450. {
  451. FOR_EACH_VEC( m_vecMsgTypesFailedToReceive, i )
  452. {
  453. if ( m_vecMsgTypesFailedToReceive[i] == m )
  454. return true;
  455. }
  456. return false;
  457. }
  458. //-----------------------------------------------------------------------------
  459. void CJob::MarkFailedToReceivedMsgType( MsgType_t m )
  460. {
  461. if ( !BHasFailedToReceivedMsgType( m ) )
  462. {
  463. m_vecMsgTypesFailedToReceive.AddToTail( m );
  464. }
  465. }
  466. //-----------------------------------------------------------------------------
  467. void CJob::ClearFailedToReceivedMsgType( MsgType_t m )
  468. {
  469. m_vecMsgTypesFailedToReceive.FindAndFastRemove( m );
  470. }
  471. //-----------------------------------------------------------------------------
  472. // Purpose: pauses the job until a network msg for the job arrives
  473. //-----------------------------------------------------------------------------
  474. bool CJob::BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg )
  475. {
  476. IMsgNetPacket *pNetPacket = NULL;
  477. // Check if we already waited for a message of this type
  478. // but timed out. If so, then we currently don't have a way
  479. // to tell if the message we might receive is the reply
  480. // to the old mesage, of the one we're about to send.
  481. // So let's just disallow this entirely.
  482. if ( BHasFailedToReceivedMsgType( eMsg ) )
  483. {
  484. AssertMsg2( false, "Job %s cannot wait for msg %u, it has already failed to wait for that msg type.", GetName(), eMsg );
  485. return false;
  486. }
  487. m_unWaitMsgType = eMsg;
  488. if ( !BYieldingWaitForMsg( &pNetPacket) )
  489. {
  490. // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
  491. MarkFailedToReceivedMsgType( eMsg );
  492. return false;
  493. }
  494. pMsg->InitFromPacket( pNetPacket );
  495. if ( pMsg->GetEMsg() != eMsg )
  496. {
  497. // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
  498. MarkFailedToReceivedMsgType( eMsg );
  499. EmitError( SPEW_GC, "CJob::BYieldingWaitForMsg expected msg %u but received %u", eMsg, pMsg->GetEMsg() );
  500. return false;
  501. }
  502. return true;
  503. }
  504. #ifdef GC
  505. bool CJob::BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID )
  506. {
  507. if( !BYieldingWaitForMsg( pMsg, eMsg ) )
  508. return false;
  509. if( pMsg->Hdr().m_ulSteamID != expectedID.ConvertToUint64() )
  510. {
  511. EmitError( SPEW_GC, "CJob::BYieldingWaitForMsg expected reply from steam ID %s, but instead got a response from %s for message %d\n", expectedID.Render(), CSteamID( pMsg->Hdr().m_ulSteamID ).Render(), eMsg );
  512. return false;
  513. }
  514. return true;
  515. }
  516. bool CJob::BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID )
  517. {
  518. if( !BYieldingWaitForMsg( pMsg, eMsg ) )
  519. return false;
  520. if( pMsg->GetClientSteamID() != expectedID )
  521. {
  522. EmitError( SPEW_GC, "CJob::BYieldingWaitForMsg expected reply from steam ID %s, but instead got a response from %s for message %d\n", expectedID.Render(), pMsg->GetClientSteamID().Render(), eMsg );
  523. return false;
  524. }
  525. return true;
  526. }
  527. //-----------------------------------------------------------------------------
  528. // Purpose: pauses the job until a network msg for the job arrives
  529. //-----------------------------------------------------------------------------
  530. bool CJob::BYieldingRunQuery( CGCSQLQueryGroup *pQueryGroup, ESchemaCatalog eSchemaCatalog )
  531. {
  532. AssertRunningThisJob();
  533. // await and retrieve the network message
  534. return GetJobMgr().BYieldingRunQuery( *this, pQueryGroup, eSchemaCatalog );
  535. }
  536. #endif
  537. //-----------------------------------------------------------------------------
  538. // Purpose: pauses the job until a work item callback occurs
  539. // Output : Returns true on success, false on failure.
  540. //-----------------------------------------------------------------------------
  541. bool CJob::BYieldingWaitForWorkItem( const char *pszWorkItemName )
  542. {
  543. AssertRunningThisJob();
  544. // await the work item completion
  545. return GetJobMgr().BYieldingWaitForWorkItem( *this, pszWorkItemName );
  546. }
  547. //-----------------------------------------------------------------------------
  548. // Purpose: CWorkItem for processing functions in CJob-derived classes on another thread
  549. //-----------------------------------------------------------------------------
  550. class CJobThreadFuncWorkItem : public CWorkItem
  551. {
  552. public:
  553. DECLARE_WORK_ITEM( CJobThreadFuncWorkItem );
  554. CJobThreadFuncWorkItem( CJob *pJob, JobThreadFunc_t jobThreadFunc, CFunctor *pFunctor ) : CWorkItem( pJob->GetJobID() ),
  555. m_pJob( pJob ),
  556. m_pJobThreadFunc( jobThreadFunc ),
  557. m_pFunctor( pFunctor )
  558. {
  559. }
  560. virtual bool ThreadProcess( CWorkThread *pThread )
  561. {
  562. if ( m_pJobThreadFunc )
  563. (m_pJob->*m_pJobThreadFunc)();
  564. if ( m_pFunctor )
  565. (*m_pFunctor)();
  566. return true;
  567. }
  568. private:
  569. CJob *m_pJob;
  570. JobThreadFunc_t m_pJobThreadFunc;
  571. CFunctor *m_pFunctor;
  572. };
  573. //-----------------------------------------------------------------------------
  574. // Purpose: pauses the job until a work item callback occurs
  575. // Output : Returns true on success, false on failure.
  576. //-----------------------------------------------------------------------------
  577. bool CJob::BYieldingWaitForThreadFuncWorkItem( CWorkItem *pItem )
  578. {
  579. AssertRunningThisJob();
  580. Assert( m_pWaitingOnWorkItem == NULL );
  581. Assert( pItem->GetJobID() == GetJobID() );
  582. m_pWaitingOnWorkItem = pItem;
  583. // add it to a central thread pool
  584. GetJobMgr().AddThreadedJobWorkItem( pItem );
  585. // await the work item completion
  586. bool bSuccess = GetJobMgr().BYieldingWaitForWorkItem( *this );
  587. m_pWaitingOnWorkItem = NULL;
  588. return bSuccess;
  589. }
  590. //-----------------------------------------------------------------------------
  591. // Purpose: pauses the job until a work item callback occurs
  592. // Output : Returns true on success, false on failure.
  593. //-----------------------------------------------------------------------------
  594. bool CJob::BYieldingWaitForThreadFunc( CFunctor *jobFunctor )
  595. {
  596. // store off which function to launch when we're done
  597. CJobThreadFuncWorkItem *pJobThreadFuncWorkItem = new CJobThreadFuncWorkItem( this, NULL, jobFunctor );
  598. bool bSuccess = BYieldingWaitForThreadFuncWorkItem( pJobThreadFuncWorkItem );
  599. // free the thread func
  600. SafeRelease( jobFunctor );
  601. SAFE_RELEASE( pJobThreadFuncWorkItem );
  602. return bSuccess;
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose: Allows a job that was paused for a specific reason to resume
  606. //-----------------------------------------------------------------------------
  607. void CJob::EndPause( EJobPauseReason eExpectedState )
  608. {
  609. Assert( m_ePauseReason == eExpectedState );
  610. if( m_ePauseReason == eExpectedState )
  611. {
  612. m_ePauseReason = k_EJobPauseReasonYield;
  613. }
  614. }
  615. //-----------------------------------------------------------------------------
  616. // Purpose: Returns the number of heartbeats to wait before timing out this job
  617. //-----------------------------------------------------------------------------
  618. uint32 CJob::CHeartbeatsBeforeTimeout()
  619. {
  620. return k_cJobHeartbeatsBeforeTimeoutDefault;
  621. }
  622. //-----------------------------------------------------------------------------
  623. // Purpose: Send heartbeat messages to our listeners during long operations
  624. // to let them know we're still alive and avoid timeouts
  625. // This should be called by the CJobMgr
  626. //-----------------------------------------------------------------------------
  627. void CJob::Heartbeat()
  628. {
  629. // Reset our counter
  630. m_STimeNextHeartbeat.SetFromJobTime( k_cMicroSecJobHeartbeat );
  631. }
  632. //-----------------------------------------------------------------------------
  633. // Purpose: waits for specified time and checks for timeout. Useful when you
  634. // need to repeatedly sleep while waiting for something to happen.
  635. // This function uses STime (server "pseudo" time) to determine
  636. // timeout conditions.
  637. //
  638. // Input: cMicrosecondsToSleep - duration to sleep this call
  639. // stimeStarted - the time to calculate timeout from. (Typically,
  640. // the time you start calling this in a loop, passing the same
  641. // start time each time you call this method.)
  642. // nMicroSecLimit - duration from stimeStarted to consider timed out
  643. // Output : Returns true if not timed out yet, false if timed out
  644. //-----------------------------------------------------------------------------
  645. bool CJob::BYieldingWaitTimeWithLimit( uint32 cMicrosecondsToSleep, CJobTime &stimeStarted, int64 nMicroSecLimit )
  646. {
  647. if ( stimeStarted.CServerMicroSecsPassed() > nMicroSecLimit )
  648. return false;
  649. return BYieldingWaitTime( cMicrosecondsToSleep );
  650. }
  651. //-----------------------------------------------------------------------------
  652. // Purpose: waits for specified time and checks for timeout. Useful when you
  653. // need to repeatedly sleep while waiting for something to happen.
  654. // This function uses RTime (wall-clock "real" time) to determine
  655. // timeout conditions.
  656. //
  657. // Input: cMicrosecondsToSleep - duration to sleep this call
  658. // nSecLimit - duration from stimeStarted to consider timed out
  659. // Output : Returns true if not timed out yet, false if timed out
  660. //-----------------------------------------------------------------------------
  661. bool CJob::BYieldingWaitTimeWithLimitRealTime( uint32 cMicrosecondsToSleep, int nSecondsLimit )
  662. {
  663. return BYieldingWaitTime( cMicrosecondsToSleep );
  664. }
  665. //-----------------------------------------------------------------------------
  666. // Purpose: pauses the job for the specified amount of time
  667. // Input : m_cMicrosecondsToSleep - microseconds to wait for
  668. // Output : Returns true on success, false on failure.
  669. //-----------------------------------------------------------------------------
  670. bool CJob::BYieldingWaitTime( uint32 cMicrosecondsToSleep )
  671. {
  672. AssertRunningThisJob();
  673. return GetJobMgr().BYieldingWaitTime( *this, cMicrosecondsToSleep );
  674. }
  675. //-----------------------------------------------------------------------------
  676. // Purpose: pauses the job until the next time the JobMgr Run() is called
  677. // Output : Returns true on success, false on failure.
  678. //-----------------------------------------------------------------------------
  679. bool CJob::BYield()
  680. {
  681. AssertRunningThisJob();
  682. return GetJobMgr().BYield( *this );
  683. }
  684. //-----------------------------------------------------------------------------
  685. // Purpose: Pauses the job ONLY IF JobMgr decides it needs to based on time run and priority
  686. // If pausing, pauses until the next time the JobMgr Run() is called
  687. // Input: pbYielded - Set to true if we did yield
  688. // Output : Returns true on success, false on failure.
  689. //-----------------------------------------------------------------------------
  690. bool CJob::BYieldIfNeeded( bool *pbYielded )
  691. {
  692. AssertRunningThisJob();
  693. if ( pbYielded )
  694. *pbYielded = false;
  695. // Assume only low priority jobs need to yield
  696. // Automatically bail out here if the job is not low priority
  697. return GetJobMgr().BYieldIfNeeded( *this, pbYielded );
  698. }
  699. //-----------------------------------------------------------------------------
  700. // Purpose: Pauses the job for a single frame
  701. // Output : Returns true on success, false on failure.
  702. //-----------------------------------------------------------------------------
  703. bool CJob::BYieldingWaitOneFrame()
  704. {
  705. return BYieldingWaitTime( 1 );
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose: Blocks until we acquire the lock on the specified object
  709. // Input : *pLock - object to lock
  710. // Output : Returns true on success, false on failure.
  711. //-----------------------------------------------------------------------------
  712. bool CJob::_BYieldingAcquireLock( CLock *pLock, const char *filename, int line )
  713. {
  714. AssertRunningThisJob();
  715. // Skip the path info from the filename. It just maks the debug messages excessively long.
  716. filename = V_GetFileName( filename );
  717. // Is the lock locked by this job? If so, inc the ref count.
  718. if ( pLock->GetJobLocking() == this )
  719. {
  720. pLock->IncrementReference();
  721. return true;
  722. }
  723. // jobs can have multiple locks as long as they are in priority order
  724. FOR_EACH_VEC( m_vecLocks, i )
  725. {
  726. if( m_vecLocks[i]->GetLockType() == pLock->GetLockType() )
  727. {
  728. if( m_vecLocks[i]->GetLockSubType() <= pLock->GetLockSubType() )
  729. {
  730. AssertMsg7( false, "Job %s Locking %s at %s:(%d) with yielding; holds lock %s from %s(%d)\n",
  731. GetName(),
  732. pLock->GetName(),
  733. filename, line,
  734. m_vecLocks[i]->GetName(),
  735. m_vecLocks[i]->m_pFilename, m_vecLocks[i]->m_line );
  736. return false;
  737. }
  738. }
  739. else if ( m_vecLocks[i]->GetLockType() < pLock->GetLockType() )
  740. {
  741. AssertMsg7( false, "Job %s Locking %s at %s:(%d) with yielding; holds lock %s from %s(%d)\n",
  742. GetName(),
  743. pLock->GetName(),
  744. filename, line,
  745. m_vecLocks[i]->GetName(),
  746. m_vecLocks[i]->m_pFilename, m_vecLocks[i]->m_line );
  747. return false;
  748. }
  749. }
  750. if( m_pWaitingOnLock != NULL )
  751. {
  752. AssertMsg7( false, "Job (%s) locking %s at %s(%d); already waiting on %s at %s(%d).\n",
  753. GetName(),
  754. pLock->GetName(),
  755. filename, line,
  756. m_pWaitingOnLock->GetName(),
  757. m_pWaitingOnLock->m_pFilename, m_pWaitingOnLock->m_line );
  758. return false;
  759. }
  760. m_cLocksAttempted++;
  761. if ( pLock->BIsLocked() )
  762. {
  763. // tell the job we want the lock next
  764. // But walking the entire linked list is slow so
  765. // skip to the tail pointer
  766. pLock->AddToWaitingQueue( this );
  767. // We should be the tail of the list
  768. Assert( NULL == m_pJobToNotifyOnLockRelease );
  769. // yield until we get the lock
  770. m_pWaitingOnLock = pLock;
  771. m_pWaitingOnLockFilename = filename;
  772. m_waitingOnLockLine = line;
  773. m_cLocksWaitedFor++;
  774. Pause( k_EJobPauseReasonWaitingForLock );
  775. m_pWaitingOnLock = NULL;
  776. // make sure we actually got it, instead of timing out
  777. int index = m_vecLocks.Find( pLock );
  778. if ( index != m_vecLocks.InvalidIndex() && this == pLock->GetJobLocking() )
  779. {
  780. pLock->IncrementReference();
  781. return true;
  782. }
  783. else
  784. {
  785. m_flags.m_bits.m_bLocksFailed = true;
  786. EmitWarning( SPEW_JOB, LOG_ALWAYS, "Failed to get lock %s at %s(%d) after waiting in %s\n", pLock->GetName(), filename, line, GetName() );
  787. if ( m_vecLocks.Count() == 0 )
  788. {
  789. EmitWarning( SPEW_JOB, LOG_ALWAYS, "m_vecLocks.Count(): %d, this: 0x%p, pLock->GetJobLocking(): %s (0x%p)\n",
  790. m_vecLocks.Count(), this, pLock->GetJobLocking() ? pLock->GetJobLocking()->GetName() : "(null)", pLock->GetJobLocking() );
  791. }
  792. else
  793. {
  794. EmitWarning( SPEW_JOB, LOG_ALWAYS, "m_vecLocks.Count(): %d this: 0x%p, pLock: 0x%p pLock->GetJobLocking(): %s (0x%p)\n",
  795. m_vecLocks.Count(), this, pLock, pLock->GetJobLocking() ? pLock->GetJobLocking()->GetName() : "(null)", pLock->GetJobLocking() );
  796. FOR_EACH_VEC( m_vecLocks, i )
  797. {
  798. EmitWarning( SPEW_JOB, LOG_ALWAYS, "m_vecLocks[%d]: %s (0x%p) %s(%d)\n",
  799. i, m_vecLocks[i] ? m_vecLocks[i]->GetName() : "(null)", m_vecLocks[i], m_vecLocks[i]->m_pFilename, m_vecLocks[i]->m_line );
  800. }
  801. }
  802. return false;
  803. }
  804. }
  805. else
  806. {
  807. // unused, take it for ourself
  808. pLock->IncrementReference();
  809. _SetLock( pLock, filename, line );
  810. return true;
  811. }
  812. }
  813. //-----------------------------------------------------------------------------
  814. // Purpose: Either locks on the specified object immediately or returns failure
  815. // Input : *pLock - object to lock
  816. // Output : Returns true on success, false on failure.
  817. //-----------------------------------------------------------------------------
  818. bool CJob::_BAcquireLockImmediate( CLock *pLock, const char *filename, int line )
  819. {
  820. AssertRunningThisJob();
  821. AssertMsg5( m_pWaitingOnLock == NULL, "Job (%s) at %s(%d) trying to take a lock while it was already waiting for the first one at %s(%d)", GetName(), filename, line, m_pWaitingOnLockFilename, m_waitingOnLockLine );
  822. m_cLocksAttempted++;
  823. // Is the lock locked by this job? If so, inc the ref count.
  824. if ( pLock->GetJobLocking() == this )
  825. {
  826. pLock->IncrementReference();
  827. return true;
  828. }
  829. if ( !pLock->BIsLocked() )
  830. {
  831. // unused, take it for ourself
  832. pLock->IncrementReference();
  833. _SetLock( pLock, filename, line );
  834. return true;
  835. }
  836. return false;
  837. }
  838. //-----------------------------------------------------------------------------
  839. // Purpose: Releases the specified lock, passing it on to the next job if necessary
  840. //-----------------------------------------------------------------------------
  841. void CJob::_ReleaseLock( CLock *pLock, bool bForce, const char *filename, int line )
  842. {
  843. Assert( pLock );
  844. if ( !pLock )
  845. return;
  846. Assert( m_vecLocks.HasElement( pLock ) );
  847. if ( !m_vecLocks.HasElement( pLock ) )
  848. {
  849. EmitError( SPEW_JOB, "Job %s trying to release lock %s at %s(%d) it's not holding\n", GetName(), pLock->GetName(), filename, line );
  850. return;
  851. }
  852. if ( pLock->GetJobLocking() != this )
  853. {
  854. EmitError( SPEW_JOB, "Job %s trying to release lock %s at %s(%d) though the lock is held by %s\n", GetName(), pLock->GetName(), filename, line, pLock->GetJobLocking()->GetName() );
  855. return;
  856. }
  857. if ( bForce )
  858. {
  859. // Force clear reference count
  860. pLock->ClearReference();
  861. }
  862. else
  863. {
  864. // Dec the reference count. If it is not yet zero, don't fully unlock
  865. if ( pLock->DecrementReference() > 0 )
  866. {
  867. return;
  868. }
  869. }
  870. if ( pLock->m_pJobToNotifyOnLockRelease )
  871. {
  872. // post a message to the main system to wakeup the next lock
  873. PassLockToJob( pLock->m_pJobToNotifyOnLockRelease, pLock );
  874. m_pJobToNotifyOnLockRelease = NULL;
  875. Assert( this != pLock->m_pJobWaitingQueueTail );
  876. }
  877. else
  878. {
  879. // just release
  880. UnsetLock( pLock );
  881. Assert( NULL == pLock->m_pJobWaitingQueueTail || this == pLock->m_pJobWaitingQueueTail );
  882. pLock->m_pJobWaitingQueueTail = NULL;
  883. }
  884. }
  885. //-----------------------------------------------------------------------------
  886. // Purpose: Release all locks this job holds. This is only to be used by long lived
  887. // jobs that don't destruct.
  888. //-----------------------------------------------------------------------------
  889. void CJob::ReleaseLocks()
  890. {
  891. // release any locks - do this in reverse order because they're being removed from the vector in the loop
  892. FOR_EACH_VEC_BACK( m_vecLocks, nLock )
  893. {
  894. _ReleaseLock( m_vecLocks[nLock], true, __FILE__, __LINE__ );
  895. }
  896. m_vecLocks.RemoveAll();
  897. }
  898. //-----------------------------------------------------------------------------
  899. // Purpose: Assert that we don't hold any locks, and if we hold them, release them
  900. //-----------------------------------------------------------------------------
  901. void CJob::ShouldNotHoldAnyLocks()
  902. {
  903. if ( m_vecLocks.Count() == 0 )
  904. return;
  905. CUtlString sErrMsg;
  906. sErrMsg.Format( "Job %s detected and cleaned up leak of %d lock(s):\n", GetName(), m_vecLocks.Count() );
  907. FOR_EACH_VEC_BACK( m_vecLocks, nLock )
  908. {
  909. CLock *pLock = m_vecLocks[nLock];
  910. sErrMsg.Append( CFmtStr( " Lock %s, acquired %s(%d)\n", pLock->GetName(), pLock->m_pFilename, pLock->m_line ).Access() );
  911. }
  912. AssertMsg1( false, "%s", sErrMsg.String() );
  913. // Now release them
  914. ReleaseLocks();
  915. }
  916. //-----------------------------------------------------------------------------
  917. // Purpose: sets up the job to notify when we've release our locks
  918. //-----------------------------------------------------------------------------
  919. void CJob::AddJobToNotifyOnLockRelease( CJob *pJob )
  920. {
  921. // if we already are going to be notifying someone, then have them notify the new requester
  922. if ( m_pJobToNotifyOnLockRelease )
  923. {
  924. AssertMsg( false, "AddJobToNotifyOnLockRelease attempting to walk the linked list. We've optimized this out." );
  925. m_pJobToNotifyOnLockRelease->AddJobToNotifyOnLockRelease( pJob );
  926. }
  927. else
  928. {
  929. m_pJobToNotifyOnLockRelease = pJob;
  930. }
  931. }
  932. //-----------------------------------------------------------------------------
  933. // Purpose: Sets the lock
  934. //-----------------------------------------------------------------------------
  935. void CJob::_SetLock( CLock *pLock, const char *filename, int line )
  936. {
  937. Assert( !m_vecLocks.HasElement( pLock ) );
  938. Assert( !pLock->BIsLocked() );
  939. pLock->m_pJob = this;
  940. pLock->m_sTimeAcquired.SetToJobTime();
  941. pLock->m_pFilename = filename;
  942. pLock->m_line = line;
  943. m_vecLocks.AddToTail( pLock );
  944. }
  945. //-----------------------------------------------------------------------------
  946. // Purpose: Removes the lock
  947. //-----------------------------------------------------------------------------
  948. void CJob::UnsetLock( CLock *pLock )
  949. {
  950. Assert( pLock->GetJobLocking() == this );
  951. pLock->m_pJob = NULL;
  952. // if we've held the lock for more than a few seconds, make noise.
  953. if ( /*!BIsTest() &&*/ pLock->m_sTimeAcquired.CServerMicroSecsPassed() >= 10 * k_nMillion )
  954. {
  955. m_flags.m_bits.m_bLocksLongHeld = true;
  956. if ( pLock->m_pJobToNotifyOnLockRelease )
  957. {
  958. pLock->m_pJobToNotifyOnLockRelease->m_flags.m_bits.m_bLocksLongWait = true;
  959. EmitWarning( SPEW_JOB, 4, "Job of type %s held lock for %.2f seconds while job of type %s was waiting\n", GetName(), (double) pLock->m_sTimeAcquired.CServerMicroSecsPassed() / k_nMillion, pLock->m_pJobToNotifyOnLockRelease->GetName() );
  960. }
  961. else
  962. EmitWarning( SPEW_JOB, 4, "Job of type %s held lock for %.2f seconds\n", GetName(), (double) pLock->m_sTimeAcquired.CServerMicroSecsPassed() / k_nMillion );
  963. }
  964. m_vecLocks.FindAndRemove( pLock );
  965. }
  966. //-----------------------------------------------------------------------------
  967. // Purpose: Releases the lock from the old job, and immediately passes it on to the waiting job
  968. //-----------------------------------------------------------------------------
  969. void CJob::PassLockToJob( CJob *pNewJob, CLock *pLock )
  970. {
  971. Assert( pNewJob->GetPauseReason() == k_EJobPauseReasonWaitingForLock );
  972. Assert( pNewJob->m_pWaitingOnLock == pLock );
  973. pLock->m_pJobToNotifyOnLockRelease = pNewJob->m_pJobToNotifyOnLockRelease;
  974. if ( NULL == pLock->m_pJobToNotifyOnLockRelease )
  975. {
  976. pLock->m_pJobWaitingQueueTail = NULL;
  977. }
  978. pNewJob->m_pJobToNotifyOnLockRelease = NULL;
  979. Assert( pLock->m_nWaitingCount > 0 );
  980. pLock->m_nWaitingCount--;
  981. // release the lock
  982. UnsetLock( pLock );
  983. // If the other job isn't waiting on a lock, then we certainly don't
  984. // want to call SetLock() on it
  985. if ( pNewJob->GetPauseReason() == k_EJobPauseReasonWaitingForLock && pNewJob->m_pWaitingOnLock == pLock )
  986. {
  987. // give the job the lock
  988. pNewJob->_SetLock( pLock, pNewJob->m_pWaitingOnLockFilename, m_waitingOnLockLine );
  989. // set the job with the newly acquired lock to wakeup
  990. pNewJob->GetJobMgr().WakeupLockedJob( *pNewJob );
  991. }
  992. else
  993. {
  994. EmitError( SPEW_JOB, "Job passed lock it wasn't waiting for. Job: %s, Lock: %s %s(%d), Paused for %s, Waiting on %s\n",
  995. pNewJob->GetName(), pLock->GetName(), pLock->m_pFilename, pLock->m_line, pNewJob->GetPauseReasonDescription(), m_pWaitingOnLock ? m_pWaitingOnLock->GetName() : "none" );
  996. }
  997. }
  998. //-----------------------------------------------------------------------------
  999. // Purpose: a lock is letting us know it's been deleted
  1000. // fail all jobs trying to get the lock
  1001. //-----------------------------------------------------------------------------
  1002. void CJob::OnLockDeleted( CLock *pLock )
  1003. {
  1004. //EmitWarning( SPEW_JOB, SPEW_ALWAYS, "Deleting lock %s\n", GetName() );
  1005. Assert( pLock->BIsLocked() );
  1006. Assert( pLock->m_pJob == this );
  1007. // fail all the jobs waiting on the lock
  1008. CJob *pJob = pLock->m_pJobToNotifyOnLockRelease;
  1009. while ( pJob )
  1010. {
  1011. // insert the job into the sleep list with 0 time, so it wakes up immediately
  1012. // it will see it doesn't have the desired lock and suicide
  1013. pJob->GetJobMgr().WakeupLockedJob( *pJob );
  1014. // move to the next job
  1015. CJob *pJobT = pJob;
  1016. pJob = pJob->m_pJobToNotifyOnLockRelease;
  1017. pJobT->m_pJobToNotifyOnLockRelease = NULL;
  1018. }
  1019. m_pJobToNotifyOnLockRelease = NULL;
  1020. pLock->m_pJobToNotifyOnLockRelease = NULL;
  1021. pLock->m_pJobWaitingQueueTail = NULL;
  1022. // remove the lock
  1023. UnsetLock( pLock );
  1024. }
  1025. //-----------------------------------------------------------------------------
  1026. // Purpose: Reports how many Do Not Yield guards the job currently has
  1027. //-----------------------------------------------------------------------------
  1028. int32 CJob::GetDoNotYieldDepth() const
  1029. {
  1030. return m_stackDoNotYieldGuards.Count();
  1031. }
  1032. //-----------------------------------------------------------------------------
  1033. // Purpose: Adds a Do Not Yield guard to the job
  1034. //-----------------------------------------------------------------------------
  1035. void CJob::PushDoNotYield( const char *pchFileAndLine )
  1036. {
  1037. m_stackDoNotYieldGuards.AddToHead( pchFileAndLine );
  1038. }
  1039. //-----------------------------------------------------------------------------
  1040. // Purpose: Removes the last-added Do Not Yield guard from the job
  1041. //-----------------------------------------------------------------------------
  1042. void CJob::PopDoNotYield()
  1043. {
  1044. AssertMsg( m_stackDoNotYieldGuards.Count() > 0, "Could not pop a Do Not Yield guard when the job's stack is empty" );
  1045. if ( m_stackDoNotYieldGuards.Count() > 0 )
  1046. {
  1047. m_stackDoNotYieldGuards.Remove( m_stackDoNotYieldGuards.Head() );
  1048. }
  1049. }
  1050. //-----------------------------------------------------------------------------
  1051. // Purpose: Implementation of the stack-scope Do Not Yield guard
  1052. //-----------------------------------------------------------------------------
  1053. CDoNotYieldScope::CDoNotYieldScope( const char *pchFileAndLine )
  1054. {
  1055. AssertRunningJob();
  1056. GJobCur().PushDoNotYield( pchFileAndLine );
  1057. }
  1058. CDoNotYieldScope::~CDoNotYieldScope()
  1059. {
  1060. AssertRunningJob();
  1061. GJobCur().PopDoNotYield();
  1062. }
  1063. #ifdef DBGFLAG_VALIDATE
  1064. //-----------------------------------------------------------------------------
  1065. // Purpose: Run a global validation pass on all of our data structures and memory
  1066. // allocations.
  1067. // Input: validator - Our global validator object
  1068. // pchName - Our name (typically a member var in our container)
  1069. //-----------------------------------------------------------------------------
  1070. void CJob::Validate( CValidator &validator, const char *pchName )
  1071. {
  1072. VALIDATE_SCOPE();
  1073. ValidateObj( m_stackDoNotYieldGuards );
  1074. }
  1075. //-----------------------------------------------------------------------------
  1076. // Purpose: Run a global validation pass on all of our static data structures and memory
  1077. // allocations.
  1078. // Input: validator - Our global validator object
  1079. // pchName - Our name (typically a member var in our container)
  1080. //-----------------------------------------------------------------------------
  1081. void CJob::ValidateStatics( CValidator &validator, const char *pchName )
  1082. {
  1083. VALIDATE_SCOPE_STATIC( "CJob class statics" );
  1084. }
  1085. #endif // DBGFLAG_VALIDATE
  1086. CLock::CLock( )
  1087. : m_pJob( NULL ),
  1088. m_pJobToNotifyOnLockRelease( NULL ),
  1089. m_pJobWaitingQueueTail( NULL ),
  1090. m_nWaitingCount(0),
  1091. m_nsLockType(0),
  1092. m_nsNameType( k_ENameTypeNone ),
  1093. m_ulID( 0 ),
  1094. m_pchConstStr( NULL ),
  1095. m_unLockSubType ( 0 ),
  1096. m_nRefCount( 0 ),
  1097. m_pFilename( "unknown" ),
  1098. m_line( 0 )
  1099. {
  1100. }
  1101. CLock::~CLock()
  1102. {
  1103. if ( m_pJob )
  1104. {
  1105. m_pJob->OnLockDeleted( this );
  1106. }
  1107. }
  1108. void CLock::AddToWaitingQueue( CJob *pJob )
  1109. {
  1110. if ( m_pJobWaitingQueueTail )
  1111. {
  1112. Assert( NULL == m_pJobWaitingQueueTail->m_pJobToNotifyOnLockRelease );
  1113. m_pJobWaitingQueueTail->AddJobToNotifyOnLockRelease( pJob );
  1114. }
  1115. else
  1116. {
  1117. Assert( NULL == m_pJobToNotifyOnLockRelease );
  1118. m_pJobToNotifyOnLockRelease = pJob;
  1119. }
  1120. m_pJobWaitingQueueTail = pJob;
  1121. m_nWaitingCount++;
  1122. }
  1123. void CLock::SetName( const char *pchName )
  1124. {
  1125. m_nsNameType = k_ENameTypeConstStr;
  1126. m_pchConstStr = pchName;
  1127. }
  1128. void CLock::SetName( const char *pchPrefix, uint64 ulID )
  1129. {
  1130. m_nsNameType = k_ENameTypeConcat;
  1131. m_pchConstStr = pchPrefix;
  1132. m_ulID = ulID;
  1133. }
  1134. void CLock::SetName( const CSteamID &steamID )
  1135. {
  1136. m_nsNameType = k_ENameTypeSteamID;
  1137. m_ulID = steamID.ConvertToUint64();
  1138. }
  1139. const char *CLock::GetName() const
  1140. {
  1141. switch ( m_nsNameType )
  1142. {
  1143. case k_ENameTypeNone:
  1144. return "None";
  1145. case k_ENameTypeSteamID:
  1146. return CSteamID::Render( m_ulID );
  1147. case k_ENameTypeConstStr:
  1148. return m_pchConstStr;
  1149. case k_ENameTypeConcat:
  1150. if ( !m_strName.Length() )
  1151. m_strName.Format( "%s %llu", m_pchConstStr, m_ulID );
  1152. return m_strName.Get();
  1153. default:
  1154. AssertMsg1( false, "Invalid lock name type %d", m_nsNameType );
  1155. return "(Unknown)";
  1156. }
  1157. }
  1158. #define REF_COUNT_ASSERT 1000
  1159. void CLock::IncrementReference()
  1160. {
  1161. m_nRefCount++;
  1162. Assert( m_nRefCount != REF_COUNT_ASSERT );
  1163. }
  1164. int CLock::DecrementReference()
  1165. {
  1166. Assert( m_nRefCount > 0 );
  1167. if ( m_nRefCount > 0 )
  1168. {
  1169. m_nRefCount--;
  1170. }
  1171. return m_nRefCount;
  1172. }
  1173. void CLock::Dump( const char *pszPrefix, int nPrintMax, bool bPrintWaiting ) const
  1174. {
  1175. if ( m_pJob != NULL )
  1176. {
  1177. EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s%s: Lock owner: %s, Type: %d, %d Waiting\n", pszPrefix, GetName(), CFmtStr( "%s (%llu), Reason: %s", m_pJob->GetName(), m_pJob->GetJobID(), m_pJob->GetPauseReasonDescription() ).Access(), (int32)m_nsLockType, m_nWaitingCount );
  1178. EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%sLock acquired: %s:%d\n", pszPrefix, m_pFilename, m_line );
  1179. }
  1180. else
  1181. {
  1182. EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s%s: Lock owner: None, Type: %d, %d Waiting\n", pszPrefix, GetName(), (int32)m_nsLockType, m_nWaitingCount );
  1183. }
  1184. CJob *pCurrWaiting = m_pJobToNotifyOnLockRelease;
  1185. int nPrinted = 0;
  1186. int nTotal = 0;
  1187. while( pCurrWaiting != NULL && nPrinted < nPrintMax )
  1188. {
  1189. bool bPrint = false;
  1190. if ( nPrinted < nPrintMax )
  1191. {
  1192. bPrint = true;
  1193. }
  1194. if ( pCurrWaiting->GetPauseReason() == k_EJobPauseReasonWaitingForLock && pCurrWaiting->m_pWaitingOnLock == this )
  1195. {
  1196. bPrint = true;
  1197. }
  1198. if ( bPrint && bPrintWaiting )
  1199. {
  1200. EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s\tOther jobs waiting for this lock: %s (%llu)\n", pszPrefix, pCurrWaiting->GetName(), pCurrWaiting->GetJobID() );
  1201. if ( pCurrWaiting->GetPauseReason() == k_EJobPauseReasonWaitingForLock && pCurrWaiting->m_pWaitingOnLock == this )
  1202. {
  1203. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, 3, "%s\tAt: %s:%d\n", pszPrefix, pCurrWaiting->m_pWaitingOnLockFilename, pCurrWaiting->m_waitingOnLockLine );
  1204. }
  1205. nPrinted++;
  1206. }
  1207. pCurrWaiting = pCurrWaiting->m_pJobToNotifyOnLockRelease;
  1208. nTotal++;
  1209. }
  1210. if ( bPrintWaiting || nTotal != 0 )
  1211. {
  1212. EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s%d out of %d waiting jobs printed.\n", pszPrefix, nPrinted, nTotal );
  1213. }
  1214. }
  1215. } // namespace GCSDK