summaryrefslogtreecommitdiff
path: root/gcsdk/job.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /gcsdk/job.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'gcsdk/job.cpp')
-rw-r--r--gcsdk/job.cpp1451
1 files changed, 1451 insertions, 0 deletions
diff --git a/gcsdk/job.cpp b/gcsdk/job.cpp
new file mode 100644
index 0000000..0eef357
--- /dev/null
+++ b/gcsdk/job.cpp
@@ -0,0 +1,1451 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+
+#include "stdafx.h"
+
+#ifdef WIN32
+#include "typeinfo.h"
+#else
+#include <typeinfo>
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+//-----------------------------------------------------------------------------
+// Purpose: EJobPauseReason descriptions
+//-----------------------------------------------------------------------------
+static const char * const k_prgchJobPauseReason[] =
+{
+ "active",
+ "not started",
+ "netmsg",
+ "sleep for time",
+ "waiting for lock",
+ "yielding",
+ "SQL",
+ "work item",
+};
+
+COMPILE_TIME_ASSERT( ARRAYSIZE( k_prgchJobPauseReason ) == k_EJobPauseReasonCount );
+
+CJob *g_pJobCur = NULL;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Delete a Job
+// Input: pJob - The Job to delete
+//-----------------------------------------------------------------------------
+void CJob::DeleteJob( CJob *pJob )
+{
+ // we can't delete the if we still have a pending work item
+ pJob->WaitForThreadFuncWorkItemBlocking();
+ delete pJob;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input: pServerParent - The server we belong to
+//-----------------------------------------------------------------------------
+CJob::CJob( CJobMgr &jobMgr, const char *pchJobName ) : m_JobMgr( jobMgr ), m_pchJobName( pchJobName )
+{
+ m_ePauseReason = k_EJobPauseReasonNotStarted;
+
+ m_JobID = jobMgr.GetNewJobID();
+ m_pJobType = NULL;
+ m_bWorkItemCanceled = false;
+ m_hCoroutine = Coroutine_Create( &BRunProxy, this );
+ m_pvStartParam = NULL;
+ m_bRunFromMsg = false;
+ m_pJobPrev = NULL;
+ m_pWaitingOnLock = NULL;
+ m_pJobToNotifyOnLockRelease = NULL;
+ m_pWaitingOnWorkItem = NULL;
+ m_STimeStarted.SetToJobTime();
+ m_STimeSwitched.SetToJobTime();
+ m_STimeNextHeartbeat.SetFromJobTime( k_cMicroSecJobHeartbeat );
+ m_bIsLongRunning = false;
+ m_cLocksAttempted = 0;
+ m_cLocksWaitedFor = 0;
+ m_flags.m_uFlags = 0;
+ m_cyclecountTotal = 0;
+ m_unWaitMsgType = 0;
+
+ GetJobMgr().InsertJob( *this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CJob::~CJob()
+{
+ // don't want SendMsgToConnection to call back into us, we *know*
+ // we are replying to these other jobs now
+ g_pJobCur = NULL;
+
+ // reset the job pointer
+ g_pJobCur = m_pJobPrev;
+
+ // remove from the job tracking list
+ GetJobMgr().RemoveJob( *this );
+
+ // Forcefully release any locks
+ ReleaseLocks();
+
+ // free any network messages we've allocated
+ FOR_EACH_VEC( m_vecNetPackets, i )
+ {
+ m_vecNetPackets[i]->Release();
+ }
+ m_vecNetPackets.RemoveAll();
+
+ AssertMsg2( 0 == GetDoNotYieldDepth(), "Job ending with %d open Do Not Yields. Are we missing a END_DO_NOT_YIELD()? Innermost delared at %s",
+ GetDoNotYieldDepth(), m_stackDoNotYieldGuards[m_stackDoNotYieldGuards.Head()] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: if necessary wait for the pending work item to finish
+//-----------------------------------------------------------------------------
+void CJob::WaitForThreadFuncWorkItemBlocking()
+{
+ if ( m_pWaitingOnWorkItem )
+ {
+ switch ( GetPauseReason() )
+ {
+ case k_EJobPauseReasonWorkItem:
+ // force the workitem to be canceled in case it's still in the in-queue
+ m_pWaitingOnWorkItem->ForceTimeOut();
+
+ // we can't shutdown the job while it's work item is currently running
+ // alot of work items refernce back into the job object
+ while ( m_pWaitingOnWorkItem->BIsRunning() )
+ ThreadSleep( 25 );
+
+ m_pWaitingOnWorkItem = NULL;
+ break;
+
+#if 0 // not used in gcsdk
+ case k_EJobPauseReasonGeneric:
+ AssertMsg1( ( !m_pWaitingForGeneric || ( m_pWaitingForGeneric == ( void * ) 1 ) ), "CJob::WaitForThreadFuncWorkItemBlocking job %s will leak generic heap object", GetName() );
+ // Let another assert fire later, don't null-out: m_pWaitingForGeneric = NULL;
+ break;
+#endif
+
+ default:
+ AssertMsg1( false, "CJob::WaitForThreadFuncWorkItemBlocking job %s has unexpected work item state", GetName() );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the name of the job
+//-----------------------------------------------------------------------------
+const char *CJob::GetName() const
+{
+ if ( m_pchJobName )
+ return m_pchJobName;
+ else if ( m_pJobType )
+ return m_pJobType->m_pchName;
+ else
+ return "";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: string description of why we're paused
+//-----------------------------------------------------------------------------
+const char *CJob::GetPauseReasonDescription() const
+{
+ static char srgchPauseReason[k_cSmallBuff];
+ if ( GetPauseReason() < Q_ARRAYSIZE( k_prgchJobPauseReason ) )
+ {
+ switch( GetPauseReason() )
+ {
+ case k_EJobPauseReasonWaitingForLock:
+ {
+ Q_snprintf( srgchPauseReason, k_cSmallBuff, "WOL: 0x%x (%s)", (unsigned int)m_pWaitingOnLock, m_pWaitingOnLock ? m_pWaitingOnLock->GetName() : "null" );
+ return srgchPauseReason;
+ }
+
+ case k_EJobPauseReasonNetworkMsg:
+ {
+ const char *pchMsgType;
+ if( g_theMessageList.GetMessage( m_unWaitMsgType, &pchMsgType, 0xFF ) )
+ {
+ Q_snprintf( srgchPauseReason, k_cSmallBuff, "NetMsg: %s", pchMsgType );
+ return srgchPauseReason;
+ }
+ else
+ {
+ Q_snprintf( srgchPauseReason, k_cSmallBuff, "NetMsg: Unknown %d", m_unWaitMsgType );
+ return srgchPauseReason;
+ }
+ }
+
+ default:
+ return k_prgchJobPauseReason[ GetPauseReason() ];
+ }
+ }
+
+ return "undefined";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: accessor to get access to the JobMgr from the server we belong to
+//-----------------------------------------------------------------------------
+CJobMgr &CJob::GetJobMgr()
+{
+ return m_JobMgr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Starts the job, based on the current network msg
+// Input : hConnection - Connection that the message was received form
+// pubPkt - The raw message packet
+// cubPkt - The size of the message packet
+//-----------------------------------------------------------------------------
+void CJob::StartJobFromNetworkMsg( IMsgNetPacket *pNetPacket, const JobID_t &gidJobIDSrc )
+{
+ // hang on to the packet with the message that started this job
+ AddPacketToList( pNetPacket, gidJobIDSrc );
+ SetFromFromMsg( true );
+ // start running this job
+ InitCoroutine();
+ //and start executing the job
+ Continue();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Starts the job
+//-----------------------------------------------------------------------------
+void CJob::StartJob( void * pvStartParam )
+{
+ // job must not be started
+ AssertMsg1( m_ePauseReason == k_EJobPauseReasonNotStarted, "CJob::StartJob() called twice on job %s\n", GetName() );
+ // save the start params for this job
+ SetStartParam( pvStartParam );
+ m_JobMgr.CheckThreadID();
+
+ // start running this job
+ InitCoroutine();
+ Continue();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Starts the job in a suspended state
+//-----------------------------------------------------------------------------
+
+void CJob::StartJobDelayed( void * pvStartParam )
+{
+ // job must not be started
+ AssertMsg1( m_ePauseReason == k_EJobPauseReasonNotStarted, "CJob::StartJob() called twice on job %s\n", GetName() );
+ // save the start params for this job
+ SetStartParam( pvStartParam );
+ m_JobMgr.CheckThreadID();
+
+ //init the job, but don't start it
+ InitCoroutine();
+
+ //set our job as suspended (a yield)
+ m_ePauseReason = k_EJobPauseReasonYield;
+ m_JobMgr.AddDelayedJobToYieldList( *this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: setup the debug memory and job name before running a job the first time
+//-----------------------------------------------------------------------------
+void CJob::InitCoroutine()
+{
+ // make sure we have an appropriate chunk of memory to store
+ // our debug alloc info
+ if( MemAlloc_GetDebugInfoSize() )
+ {
+ m_memAllocStack.EnsureCapacity( MemAlloc_GetDebugInfoSize() );
+
+ // Set the job name as the root
+ MemAlloc_InitDebugInfo( m_memAllocStack.Base(), GetName(), 0 );
+ }
+
+ // Set the job name
+ if ( !m_pJobType && !m_pchJobName )
+ {
+#ifdef _WIN32
+ m_pchJobName = typeid( *this ).raw_name();
+ if ( m_pchJobName[0] == '.' && m_pchJobName[1] == '?' && m_pchJobName[2] == 'A')
+ m_pchJobName += 4;
+ if ( m_pchJobName[0] == '?' && m_pchJobName[1] == '$' )
+ m_pchJobName += 2;
+#else
+ m_pchJobName = typeid( *this ).name();
+#endif
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: proxy function for starting the job in the coroutine
+//-----------------------------------------------------------------------------
+void CJob::BRunProxy( void *pvThis )
+{
+ CJob *pJob = (CJob *)pvThis;
+
+ // run the job
+ bool bJobReturn = false;
+ if ( pJob->m_bRunFromMsg )
+ {
+ Assert( pJob->m_vecNetPackets.Count() > 0 );
+ bJobReturn = pJob->BYieldingRunJobFromMsg( pJob->m_vecNetPackets.Head() );
+ }
+ else
+ {
+ bJobReturn = pJob->BYieldingRunJob( pJob->m_pvStartParam );
+ }
+
+ pJob->m_flags.m_bits.m_bJobFailed = ( true != bJobReturn );
+
+ // kill it
+ DeleteJob( pJob );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds this packet to the linked list of packets for this job
+//-----------------------------------------------------------------------------
+void CJob::AddPacketToList( IMsgNetPacket *pNetPacket, const GID_t gidJobIDSrc )
+{
+ Assert( pNetPacket );
+ pNetPacket->AddRef();
+
+ m_vecNetPackets.AddToTail( pNetPacket );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: marks a net packet as being finished with, releases the packet and frees the memory
+//-----------------------------------------------------------------------------
+void CJob::ReleaseNetPacket( IMsgNetPacket *pNetPacket )
+{
+ int iVec = m_vecNetPackets.Find( pNetPacket );
+ if ( iVec != m_vecNetPackets.InvalidIndex() )
+ {
+ pNetPacket->Release();
+ m_vecNetPackets.Remove( iVec );
+ }
+ else
+ {
+ AssertMsg( false, "Job failed trying to release a IMsgNetPacket it doesn't own" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: continues the current job
+//-----------------------------------------------------------------------------
+void CJob::Continue()
+{
+ AssertNotRunningThisJob();
+
+ m_pJobPrev = g_pJobCur;
+ g_pJobCur = this;
+
+ // Record frame we're starting in and start a timer to track how long we work for within the frame
+ // Also, add in how much time has passed since we were last paused to track heartbeat requirements
+ m_FastTimerDelta.Start();
+
+ m_STimeSwitched.SetToJobTime();
+
+ // Check if we need to heartbeat
+ if ( BJobNeedsToHeartbeat() )
+ {
+ Heartbeat();
+ }
+
+ m_JobMgr.GetJobStats().m_cTimeslices++;
+
+ m_ePauseReason = k_EJobPauseReasonNone;
+#if defined(_WIN32) && defined(COROUTINE_TRACE)
+ const char *pchRawName = typeid( *this ).raw_name();
+ if ( pchRawName[0] == '.' && pchRawName[1] == '?' && pchRawName[2] == 'A')
+ pchRawName += 4;
+ if ( pchRawName[0] == '?' && pchRawName[1] == '$' )
+ pchRawName += 2;
+#else
+ const char *pchRawName = "";
+#endif
+
+ // Save debug credit "call stack"
+ void *pvSaveDebugInfo = GetJobMgr().GetMainMemoryDebugInfo();
+ MemAlloc_SaveDebugInfo( pvSaveDebugInfo );
+ MemAlloc_RestoreDebugInfo( m_memAllocStack.Base() );
+
+ // continue the coroutine, with the profiling if necessary
+ bool bJobStillActive;
+#if defined( VPROF_ENABLED )
+ if ( g_VProfCurrentProfile.IsEnabled() )
+ {
+ VPROF_BUDGET( GetName(), VPROF_BUDGETGROUP_JOBS_COROUTINES );
+ bJobStillActive = Coroutine_Continue( m_hCoroutine, pchRawName );
+ }
+ else
+#endif
+ {
+ bJobStillActive = Coroutine_Continue( m_hCoroutine, pchRawName );
+ }
+
+ // WARNING: MEMBER VARIABLES ARE NOW UNSAFE TO ACCESS - this CJob may be deleted
+
+ // Restore debug credit call stack
+ if( bJobStillActive )
+ {
+ // only save off debug info for jobs that are still running
+ MemAlloc_SaveDebugInfo( m_memAllocStack.Base() );
+ }
+ MemAlloc_RestoreDebugInfo( pvSaveDebugInfo );
+
+}
+
+void CJob::Debug()
+{
+ AssertNotRunningThisJob();
+
+ // This function will 'load' this coroutine then immediately
+ // break into the debugger. When execution is continued, it
+ // will pop back out to this context
+
+ // So, we don't set m_pJobPrev or g_pJobCur because nobody
+ // would ever have the chance to see them anyway.
+ Coroutine_DebugBreak( m_hCoroutine );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the current job
+//-----------------------------------------------------------------------------
+void CJob::Pause( EJobPauseReason eReason )
+{
+ AssertRunningThisJob();
+ AssertMsg1( 0 == m_stackDoNotYieldGuards.Count(), "Yielding while in a BEGIN_DO_NOT_YIELD() block declared at %s", m_stackDoNotYieldGuards[m_stackDoNotYieldGuards.Head()] );
+
+ g_pJobCur = m_pJobPrev;
+
+ // End our timer so we know how much time we've spent
+ m_FastTimerDelta.End();
+ m_cyclecountTotal += m_FastTimerDelta.GetDuration();
+
+ if ( m_FastTimerDelta.GetDuration().GetMicroseconds() > k_cMicroSecTaskGranularity * 10 )
+ {
+ m_flags.m_bits.m_bLongInterYield = true;
+ }
+ // pause this job, remembering which frame and why
+ m_ePauseReason = eReason;
+ // We shouldn't have to set the frame -- it should be the same one
+ Assert( m_STimeSwitched.LTime() == CJobTime::LJobTimeCur() );
+ Coroutine_YieldToMain();
+}
+
+void CJob::GenerateAssert( const char *pchMsg )
+{
+ // Default message if they didn't provide a custom one
+ if ( !pchMsg )
+ {
+ pchMsg = "Forced assert failure";
+ }
+
+ // Just for grins, allow this function to be called whether
+ // we are the current job or not
+ if ( this == g_pJobCur )
+ {
+ AssertMsg1( !"Job assertion requested", "%s", pchMsg );
+ }
+ else
+ {
+ Coroutine_DebugAssert( m_hCoroutine, pchMsg );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a network msg for the job arrives
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitForMsg( IMsgNetPacket **ppNetPacket )
+{
+ AssertRunningThisJob();
+ *ppNetPacket = NULL;
+
+ // await and retrieve the network message
+ if ( GetJobMgr().BYieldingWaitForMsg( *this ) )
+ {
+ Assert( m_vecNetPackets.Count() > 0 );
+ *ppNetPacket = m_vecNetPackets.Tail();
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a network msg for the job arrives
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg )
+{
+ IMsgNetPacket *pNetPacket = NULL;
+
+ // Check if we already waited for a message of this type
+ // but timed out. If so, then we currently don't have a way
+ // to tell if the message we might receive is the reply
+ // to the old mesage, of the one we're about to send.
+ // So let's just disallow this entirely.
+ if ( BHasFailedToReceivedMsgType( eMsg ) )
+ {
+ AssertMsg2( false, "Job %s cannot wait for msg %u, it has already failed to wait for that msg type.", GetName(), eMsg );
+ return false;
+ }
+
+ m_unWaitMsgType = eMsg;
+ if ( !BYieldingWaitForMsg( &pNetPacket) )
+ {
+ // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
+ MarkFailedToReceivedMsgType( eMsg );
+ return false;
+ }
+
+ pMsg->SetPacket( pNetPacket );
+
+ if ( pMsg->Hdr().m_eMsg != eMsg )
+ {
+ // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
+ MarkFailedToReceivedMsgType( eMsg );
+
+ AssertMsg2( false, "CJob::BYieldingWaitForMsg expected msg %u but received %u", eMsg, pMsg->Hdr().m_eMsg );
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+bool CJob::BHasFailedToReceivedMsgType( MsgType_t m ) const
+{
+ FOR_EACH_VEC( m_vecMsgTypesFailedToReceive, i )
+ {
+ if ( m_vecMsgTypesFailedToReceive[i] == m )
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void CJob::MarkFailedToReceivedMsgType( MsgType_t m )
+{
+ if ( !BHasFailedToReceivedMsgType( m ) )
+ {
+ m_vecMsgTypesFailedToReceive.AddToTail( m );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CJob::ClearFailedToReceivedMsgType( MsgType_t m )
+{
+ m_vecMsgTypesFailedToReceive.FindAndFastRemove( m );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a network msg for the job arrives
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg )
+{
+ IMsgNetPacket *pNetPacket = NULL;
+
+ // Check if we already waited for a message of this type
+ // but timed out. If so, then we currently don't have a way
+ // to tell if the message we might receive is the reply
+ // to the old mesage, of the one we're about to send.
+ // So let's just disallow this entirely.
+ if ( BHasFailedToReceivedMsgType( eMsg ) )
+ {
+ AssertMsg2( false, "Job %s cannot wait for msg %u, it has already failed to wait for that msg type.", GetName(), eMsg );
+ return false;
+ }
+
+ m_unWaitMsgType = eMsg;
+ if ( !BYieldingWaitForMsg( &pNetPacket) )
+ {
+ // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
+ MarkFailedToReceivedMsgType( eMsg );
+ return false;
+ }
+
+ pMsg->InitFromPacket( pNetPacket );
+
+ if ( pMsg->GetEMsg() != eMsg )
+ {
+ // Remember this event, so we can at least detect if a reply comes late, we don't get confused.
+ MarkFailedToReceivedMsgType( eMsg );
+
+ EmitError( SPEW_GC, "CJob::BYieldingWaitForMsg expected msg %u but received %u", eMsg, pMsg->GetEMsg() );
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef GC
+
+bool CJob::BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID )
+{
+ if( !BYieldingWaitForMsg( pMsg, eMsg ) )
+ return false;
+
+ if( pMsg->Hdr().m_ulSteamID != expectedID.ConvertToUint64() )
+ {
+ 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 );
+ return false;
+ }
+
+ return true;
+}
+
+
+bool CJob::BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID )
+{
+ if( !BYieldingWaitForMsg( pMsg, eMsg ) )
+ return false;
+
+ if( pMsg->GetClientSteamID() != expectedID )
+ {
+ 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 );
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a network msg for the job arrives
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingRunQuery( CGCSQLQueryGroup *pQueryGroup, ESchemaCatalog eSchemaCatalog )
+{
+ AssertRunningThisJob();
+
+ // await and retrieve the network message
+ return GetJobMgr().BYieldingRunQuery( *this, pQueryGroup, eSchemaCatalog );
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a work item callback occurs
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitForWorkItem( const char *pszWorkItemName )
+{
+ AssertRunningThisJob();
+
+ // await the work item completion
+ return GetJobMgr().BYieldingWaitForWorkItem( *this, pszWorkItemName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: CWorkItem for processing functions in CJob-derived classes on another thread
+//-----------------------------------------------------------------------------
+class CJobThreadFuncWorkItem : public CWorkItem
+{
+public:
+ DECLARE_WORK_ITEM( CJobThreadFuncWorkItem );
+ CJobThreadFuncWorkItem( CJob *pJob, JobThreadFunc_t jobThreadFunc, CFunctor *pFunctor ) : CWorkItem( pJob->GetJobID() ),
+ m_pJob( pJob ),
+ m_pJobThreadFunc( jobThreadFunc ),
+ m_pFunctor( pFunctor )
+ {
+ }
+
+ virtual bool ThreadProcess( CWorkThread *pThread )
+ {
+ if ( m_pJobThreadFunc )
+ (m_pJob->*m_pJobThreadFunc)();
+ if ( m_pFunctor )
+ (*m_pFunctor)();
+ return true;
+ }
+
+private:
+ CJob *m_pJob;
+ JobThreadFunc_t m_pJobThreadFunc;
+ CFunctor *m_pFunctor;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a work item callback occurs
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitForThreadFuncWorkItem( CWorkItem *pItem )
+{
+ AssertRunningThisJob();
+ Assert( m_pWaitingOnWorkItem == NULL );
+ Assert( pItem->GetJobID() == GetJobID() );
+
+ m_pWaitingOnWorkItem = pItem;
+
+ // add it to a central thread pool
+ GetJobMgr().AddThreadedJobWorkItem( pItem );
+
+ // await the work item completion
+ bool bSuccess = GetJobMgr().BYieldingWaitForWorkItem( *this );
+
+ m_pWaitingOnWorkItem = NULL;
+
+ return bSuccess;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until a work item callback occurs
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitForThreadFunc( CFunctor *jobFunctor )
+{
+ // store off which function to launch when we're done
+ CJobThreadFuncWorkItem *pJobThreadFuncWorkItem = new CJobThreadFuncWorkItem( this, NULL, jobFunctor );
+
+ bool bSuccess = BYieldingWaitForThreadFuncWorkItem( pJobThreadFuncWorkItem );
+
+ // free the thread func
+ SafeRelease( jobFunctor );
+ SAFE_RELEASE( pJobThreadFuncWorkItem );
+
+ return bSuccess;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a job that was paused for a specific reason to resume
+//-----------------------------------------------------------------------------
+void CJob::EndPause( EJobPauseReason eExpectedState )
+{
+ Assert( m_ePauseReason == eExpectedState );
+ if( m_ePauseReason == eExpectedState )
+ {
+ m_ePauseReason = k_EJobPauseReasonYield;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of heartbeats to wait before timing out this job
+//-----------------------------------------------------------------------------
+uint32 CJob::CHeartbeatsBeforeTimeout()
+{
+ return k_cJobHeartbeatsBeforeTimeoutDefault;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Send heartbeat messages to our listeners during long operations
+// to let them know we're still alive and avoid timeouts
+// This should be called by the CJobMgr
+//-----------------------------------------------------------------------------
+void CJob::Heartbeat()
+{
+ // Reset our counter
+ m_STimeNextHeartbeat.SetFromJobTime( k_cMicroSecJobHeartbeat );
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: waits for specified time and checks for timeout. Useful when you
+// need to repeatedly sleep while waiting for something to happen.
+// This function uses STime (server "pseudo" time) to determine
+// timeout conditions.
+//
+// Input: cMicrosecondsToSleep - duration to sleep this call
+// stimeStarted - the time to calculate timeout from. (Typically,
+// the time you start calling this in a loop, passing the same
+// start time each time you call this method.)
+// nMicroSecLimit - duration from stimeStarted to consider timed out
+// Output : Returns true if not timed out yet, false if timed out
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitTimeWithLimit( uint32 cMicrosecondsToSleep, CJobTime &stimeStarted, int64 nMicroSecLimit )
+{
+ if ( stimeStarted.CServerMicroSecsPassed() > nMicroSecLimit )
+ return false;
+
+ return BYieldingWaitTime( cMicrosecondsToSleep );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: waits for specified time and checks for timeout. Useful when you
+// need to repeatedly sleep while waiting for something to happen.
+// This function uses RTime (wall-clock "real" time) to determine
+// timeout conditions.
+//
+// Input: cMicrosecondsToSleep - duration to sleep this call
+// nSecLimit - duration from stimeStarted to consider timed out
+// Output : Returns true if not timed out yet, false if timed out
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitTimeWithLimitRealTime( uint32 cMicrosecondsToSleep, int nSecondsLimit )
+{
+ return BYieldingWaitTime( cMicrosecondsToSleep );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job for the specified amount of time
+// Input : m_cMicrosecondsToSleep - microseconds to wait for
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitTime( uint32 cMicrosecondsToSleep )
+{
+ AssertRunningThisJob();
+ return GetJobMgr().BYieldingWaitTime( *this, cMicrosecondsToSleep );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: pauses the job until the next time the JobMgr Run() is called
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYield()
+{
+ AssertRunningThisJob();
+ return GetJobMgr().BYield( *this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pauses the job ONLY IF JobMgr decides it needs to based on time run and priority
+// If pausing, pauses until the next time the JobMgr Run() is called
+// Input: pbYielded - Set to true if we did yield
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYieldIfNeeded( bool *pbYielded )
+{
+ AssertRunningThisJob();
+
+ if ( pbYielded )
+ *pbYielded = false;
+
+ // Assume only low priority jobs need to yield
+ // Automatically bail out here if the job is not low priority
+
+ return GetJobMgr().BYieldIfNeeded( *this, pbYielded );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Pauses the job for a single frame
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::BYieldingWaitOneFrame()
+{
+ return BYieldingWaitTime( 1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Blocks until we acquire the lock on the specified object
+// Input : *pLock - object to lock
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::_BYieldingAcquireLock( CLock *pLock, const char *filename, int line )
+{
+ AssertRunningThisJob();
+
+ // Skip the path info from the filename. It just maks the debug messages excessively long.
+ filename = V_GetFileName( filename );
+
+ // Is the lock locked by this job? If so, inc the ref count.
+ if ( pLock->GetJobLocking() == this )
+ {
+ pLock->IncrementReference();
+ return true;
+ }
+
+ // jobs can have multiple locks as long as they are in priority order
+ FOR_EACH_VEC( m_vecLocks, i )
+ {
+ if( m_vecLocks[i]->GetLockType() == pLock->GetLockType() )
+ {
+ if( m_vecLocks[i]->GetLockSubType() <= pLock->GetLockSubType() )
+ {
+ AssertMsg7( false, "Job %s Locking %s at %s:(%d) with yielding; holds lock %s from %s(%d)\n",
+ GetName(),
+ pLock->GetName(),
+ filename, line,
+ m_vecLocks[i]->GetName(),
+ m_vecLocks[i]->m_pFilename, m_vecLocks[i]->m_line );
+ return false;
+ }
+ }
+ else if ( m_vecLocks[i]->GetLockType() < pLock->GetLockType() )
+ {
+ AssertMsg7( false, "Job %s Locking %s at %s:(%d) with yielding; holds lock %s from %s(%d)\n",
+ GetName(),
+ pLock->GetName(),
+ filename, line,
+ m_vecLocks[i]->GetName(),
+ m_vecLocks[i]->m_pFilename, m_vecLocks[i]->m_line );
+ return false;
+ }
+ }
+
+ if( m_pWaitingOnLock != NULL )
+ {
+ AssertMsg7( false, "Job (%s) locking %s at %s(%d); already waiting on %s at %s(%d).\n",
+ GetName(),
+ pLock->GetName(),
+ filename, line,
+ m_pWaitingOnLock->GetName(),
+ m_pWaitingOnLock->m_pFilename, m_pWaitingOnLock->m_line );
+ return false;
+ }
+
+ m_cLocksAttempted++;
+ if ( pLock->BIsLocked() )
+ {
+ // tell the job we want the lock next
+ // But walking the entire linked list is slow so
+ // skip to the tail pointer
+ pLock->AddToWaitingQueue( this );
+
+ // We should be the tail of the list
+ Assert( NULL == m_pJobToNotifyOnLockRelease );
+
+ // yield until we get the lock
+ m_pWaitingOnLock = pLock;
+ m_pWaitingOnLockFilename = filename;
+ m_waitingOnLockLine = line;
+ m_cLocksWaitedFor++;
+ Pause( k_EJobPauseReasonWaitingForLock );
+ m_pWaitingOnLock = NULL;
+
+ // make sure we actually got it, instead of timing out
+ int index = m_vecLocks.Find( pLock );
+ if ( index != m_vecLocks.InvalidIndex() && this == pLock->GetJobLocking() )
+ {
+ pLock->IncrementReference();
+ return true;
+ }
+ else
+ {
+ m_flags.m_bits.m_bLocksFailed = true;
+ EmitWarning( SPEW_JOB, LOG_ALWAYS, "Failed to get lock %s at %s(%d) after waiting in %s\n", pLock->GetName(), filename, line, GetName() );
+ if ( m_vecLocks.Count() == 0 )
+ {
+ EmitWarning( SPEW_JOB, LOG_ALWAYS, "m_vecLocks.Count(): %d, this: 0x%p, pLock->GetJobLocking(): %s (0x%p)\n",
+ m_vecLocks.Count(), this, pLock->GetJobLocking() ? pLock->GetJobLocking()->GetName() : "(null)", pLock->GetJobLocking() );
+ }
+ else
+ {
+ EmitWarning( SPEW_JOB, LOG_ALWAYS, "m_vecLocks.Count(): %d this: 0x%p, pLock: 0x%p pLock->GetJobLocking(): %s (0x%p)\n",
+ m_vecLocks.Count(), this, pLock, pLock->GetJobLocking() ? pLock->GetJobLocking()->GetName() : "(null)", pLock->GetJobLocking() );
+ FOR_EACH_VEC( m_vecLocks, i )
+ {
+ EmitWarning( SPEW_JOB, LOG_ALWAYS, "m_vecLocks[%d]: %s (0x%p) %s(%d)\n",
+ i, m_vecLocks[i] ? m_vecLocks[i]->GetName() : "(null)", m_vecLocks[i], m_vecLocks[i]->m_pFilename, m_vecLocks[i]->m_line );
+ }
+ }
+ return false;
+ }
+ }
+ else
+ {
+ // unused, take it for ourself
+ pLock->IncrementReference();
+ _SetLock( pLock, filename, line );
+ return true;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Either locks on the specified object immediately or returns failure
+// Input : *pLock - object to lock
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CJob::_BAcquireLockImmediate( CLock *pLock, const char *filename, int line )
+{
+ AssertRunningThisJob();
+
+ 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 );
+
+ m_cLocksAttempted++;
+
+ // Is the lock locked by this job? If so, inc the ref count.
+ if ( pLock->GetJobLocking() == this )
+ {
+ pLock->IncrementReference();
+ return true;
+ }
+
+ if ( !pLock->BIsLocked() )
+ {
+ // unused, take it for ourself
+ pLock->IncrementReference();
+ _SetLock( pLock, filename, line );
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Releases the specified lock, passing it on to the next job if necessary
+//-----------------------------------------------------------------------------
+void CJob::_ReleaseLock( CLock *pLock, bool bForce, const char *filename, int line )
+{
+ Assert( pLock );
+ if ( !pLock )
+ return;
+
+ Assert( m_vecLocks.HasElement( pLock ) );
+ if ( !m_vecLocks.HasElement( pLock ) )
+ {
+ EmitError( SPEW_JOB, "Job %s trying to release lock %s at %s(%d) it's not holding\n", GetName(), pLock->GetName(), filename, line );
+ return;
+ }
+
+ if ( pLock->GetJobLocking() != this )
+ {
+ 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() );
+ return;
+ }
+
+ if ( bForce )
+ {
+ // Force clear reference count
+ pLock->ClearReference();
+ }
+ else
+ {
+ // Dec the reference count. If it is not yet zero, don't fully unlock
+ if ( pLock->DecrementReference() > 0 )
+ {
+ return;
+ }
+ }
+
+ if ( pLock->m_pJobToNotifyOnLockRelease )
+ {
+ // post a message to the main system to wakeup the next lock
+ PassLockToJob( pLock->m_pJobToNotifyOnLockRelease, pLock );
+ m_pJobToNotifyOnLockRelease = NULL;
+
+ Assert( this != pLock->m_pJobWaitingQueueTail );
+ }
+ else
+ {
+ // just release
+ UnsetLock( pLock );
+ Assert( NULL == pLock->m_pJobWaitingQueueTail || this == pLock->m_pJobWaitingQueueTail );
+ pLock->m_pJobWaitingQueueTail = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Release all locks this job holds. This is only to be used by long lived
+// jobs that don't destruct.
+//-----------------------------------------------------------------------------
+void CJob::ReleaseLocks()
+{
+ // release any locks - do this in reverse order because they're being removed from the vector in the loop
+ FOR_EACH_VEC_BACK( m_vecLocks, nLock )
+ {
+ _ReleaseLock( m_vecLocks[nLock], true, __FILE__, __LINE__ );
+ }
+ m_vecLocks.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Assert that we don't hold any locks, and if we hold them, release them
+//-----------------------------------------------------------------------------
+void CJob::ShouldNotHoldAnyLocks()
+{
+ if ( m_vecLocks.Count() == 0 )
+ return;
+
+ CUtlString sErrMsg;
+ sErrMsg.Format( "Job %s detected and cleaned up leak of %d lock(s):\n", GetName(), m_vecLocks.Count() );
+ FOR_EACH_VEC_BACK( m_vecLocks, nLock )
+ {
+ CLock *pLock = m_vecLocks[nLock];
+ sErrMsg.Append( CFmtStr( " Lock %s, acquired %s(%d)\n", pLock->GetName(), pLock->m_pFilename, pLock->m_line ).Access() );
+ }
+
+ AssertMsg1( false, "%s", sErrMsg.String() );
+
+ // Now release them
+ ReleaseLocks();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: sets up the job to notify when we've release our locks
+//-----------------------------------------------------------------------------
+void CJob::AddJobToNotifyOnLockRelease( CJob *pJob )
+{
+ // if we already are going to be notifying someone, then have them notify the new requester
+ if ( m_pJobToNotifyOnLockRelease )
+ {
+ AssertMsg( false, "AddJobToNotifyOnLockRelease attempting to walk the linked list. We've optimized this out." );
+ m_pJobToNotifyOnLockRelease->AddJobToNotifyOnLockRelease( pJob );
+ }
+ else
+ {
+ m_pJobToNotifyOnLockRelease = pJob;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the lock
+//-----------------------------------------------------------------------------
+void CJob::_SetLock( CLock *pLock, const char *filename, int line )
+{
+ Assert( !m_vecLocks.HasElement( pLock ) );
+ Assert( !pLock->BIsLocked() );
+
+ pLock->m_pJob = this;
+ pLock->m_sTimeAcquired.SetToJobTime();
+ pLock->m_pFilename = filename;
+ pLock->m_line = line;
+ m_vecLocks.AddToTail( pLock );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes the lock
+//-----------------------------------------------------------------------------
+void CJob::UnsetLock( CLock *pLock )
+{
+ Assert( pLock->GetJobLocking() == this );
+
+ pLock->m_pJob = NULL;
+ // if we've held the lock for more than a few seconds, make noise.
+ if ( /*!BIsTest() &&*/ pLock->m_sTimeAcquired.CServerMicroSecsPassed() >= 10 * k_nMillion )
+ {
+ m_flags.m_bits.m_bLocksLongHeld = true;
+ if ( pLock->m_pJobToNotifyOnLockRelease )
+ {
+ pLock->m_pJobToNotifyOnLockRelease->m_flags.m_bits.m_bLocksLongWait = true;
+ 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() );
+ }
+ else
+ EmitWarning( SPEW_JOB, 4, "Job of type %s held lock for %.2f seconds\n", GetName(), (double) pLock->m_sTimeAcquired.CServerMicroSecsPassed() / k_nMillion );
+ }
+ m_vecLocks.FindAndRemove( pLock );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Releases the lock from the old job, and immediately passes it on to the waiting job
+//-----------------------------------------------------------------------------
+void CJob::PassLockToJob( CJob *pNewJob, CLock *pLock )
+{
+ Assert( pNewJob->GetPauseReason() == k_EJobPauseReasonWaitingForLock );
+ Assert( pNewJob->m_pWaitingOnLock == pLock );
+
+ pLock->m_pJobToNotifyOnLockRelease = pNewJob->m_pJobToNotifyOnLockRelease;
+ if ( NULL == pLock->m_pJobToNotifyOnLockRelease )
+ {
+ pLock->m_pJobWaitingQueueTail = NULL;
+ }
+
+ pNewJob->m_pJobToNotifyOnLockRelease = NULL;
+ Assert( pLock->m_nWaitingCount > 0 );
+ pLock->m_nWaitingCount--;
+
+ // release the lock
+ UnsetLock( pLock );
+
+ // If the other job isn't waiting on a lock, then we certainly don't
+ // want to call SetLock() on it
+ if ( pNewJob->GetPauseReason() == k_EJobPauseReasonWaitingForLock && pNewJob->m_pWaitingOnLock == pLock )
+ {
+ // give the job the lock
+ pNewJob->_SetLock( pLock, pNewJob->m_pWaitingOnLockFilename, m_waitingOnLockLine );
+
+ // set the job with the newly acquired lock to wakeup
+ pNewJob->GetJobMgr().WakeupLockedJob( *pNewJob );
+ }
+ else
+ {
+ EmitError( SPEW_JOB, "Job passed lock it wasn't waiting for. Job: %s, Lock: %s %s(%d), Paused for %s, Waiting on %s\n",
+ pNewJob->GetName(), pLock->GetName(), pLock->m_pFilename, pLock->m_line, pNewJob->GetPauseReasonDescription(), m_pWaitingOnLock ? m_pWaitingOnLock->GetName() : "none" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: a lock is letting us know it's been deleted
+// fail all jobs trying to get the lock
+//-----------------------------------------------------------------------------
+void CJob::OnLockDeleted( CLock *pLock )
+{
+ //EmitWarning( SPEW_JOB, SPEW_ALWAYS, "Deleting lock %s\n", GetName() );
+
+ Assert( pLock->BIsLocked() );
+ Assert( pLock->m_pJob == this );
+
+ // fail all the jobs waiting on the lock
+ CJob *pJob = pLock->m_pJobToNotifyOnLockRelease;
+ while ( pJob )
+ {
+ // insert the job into the sleep list with 0 time, so it wakes up immediately
+ // it will see it doesn't have the desired lock and suicide
+ pJob->GetJobMgr().WakeupLockedJob( *pJob );
+
+ // move to the next job
+ CJob *pJobT = pJob;
+ pJob = pJob->m_pJobToNotifyOnLockRelease;
+ pJobT->m_pJobToNotifyOnLockRelease = NULL;
+ }
+
+ m_pJobToNotifyOnLockRelease = NULL;
+ pLock->m_pJobToNotifyOnLockRelease = NULL;
+ pLock->m_pJobWaitingQueueTail = NULL;
+
+ // remove the lock
+ UnsetLock( pLock );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reports how many Do Not Yield guards the job currently has
+//-----------------------------------------------------------------------------
+int32 CJob::GetDoNotYieldDepth() const
+{
+ return m_stackDoNotYieldGuards.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a Do Not Yield guard to the job
+//-----------------------------------------------------------------------------
+void CJob::PushDoNotYield( const char *pchFileAndLine )
+{
+ m_stackDoNotYieldGuards.AddToHead( pchFileAndLine );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes the last-added Do Not Yield guard from the job
+//-----------------------------------------------------------------------------
+void CJob::PopDoNotYield()
+{
+ AssertMsg( m_stackDoNotYieldGuards.Count() > 0, "Could not pop a Do Not Yield guard when the job's stack is empty" );
+ if ( m_stackDoNotYieldGuards.Count() > 0 )
+ {
+ m_stackDoNotYieldGuards.Remove( m_stackDoNotYieldGuards.Head() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Implementation of the stack-scope Do Not Yield guard
+//-----------------------------------------------------------------------------
+CDoNotYieldScope::CDoNotYieldScope( const char *pchFileAndLine )
+{
+ AssertRunningJob();
+
+ GJobCur().PushDoNotYield( pchFileAndLine );
+}
+
+CDoNotYieldScope::~CDoNotYieldScope()
+{
+ AssertRunningJob();
+
+ GJobCur().PopDoNotYield();
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+//-----------------------------------------------------------------------------
+// Purpose: Run a global validation pass on all of our data structures and memory
+// allocations.
+// Input: validator - Our global validator object
+// pchName - Our name (typically a member var in our container)
+//-----------------------------------------------------------------------------
+void CJob::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+
+ ValidateObj( m_stackDoNotYieldGuards );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Run a global validation pass on all of our static data structures and memory
+// allocations.
+// Input: validator - Our global validator object
+// pchName - Our name (typically a member var in our container)
+//-----------------------------------------------------------------------------
+void CJob::ValidateStatics( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE_STATIC( "CJob class statics" );
+}
+#endif // DBGFLAG_VALIDATE
+
+
+CLock::CLock( )
+: m_pJob( NULL ),
+m_pJobToNotifyOnLockRelease( NULL ),
+m_pJobWaitingQueueTail( NULL ),
+m_nWaitingCount(0),
+m_nsLockType(0),
+m_nsNameType( k_ENameTypeNone ),
+m_ulID( 0 ),
+m_pchConstStr( NULL ),
+m_unLockSubType ( 0 ),
+m_nRefCount( 0 ),
+m_pFilename( "unknown" ),
+m_line( 0 )
+{
+}
+
+
+CLock::~CLock()
+{
+ if ( m_pJob )
+ {
+ m_pJob->OnLockDeleted( this );
+ }
+}
+
+
+void CLock::AddToWaitingQueue( CJob *pJob )
+{
+ if ( m_pJobWaitingQueueTail )
+ {
+ Assert( NULL == m_pJobWaitingQueueTail->m_pJobToNotifyOnLockRelease );
+ m_pJobWaitingQueueTail->AddJobToNotifyOnLockRelease( pJob );
+ }
+ else
+ {
+ Assert( NULL == m_pJobToNotifyOnLockRelease );
+ m_pJobToNotifyOnLockRelease = pJob;
+ }
+
+ m_pJobWaitingQueueTail = pJob;
+ m_nWaitingCount++;
+}
+
+
+void CLock::SetName( const char *pchName )
+{
+ m_nsNameType = k_ENameTypeConstStr;
+ m_pchConstStr = pchName;
+}
+
+
+void CLock::SetName( const char *pchPrefix, uint64 ulID )
+{
+ m_nsNameType = k_ENameTypeConcat;
+ m_pchConstStr = pchPrefix;
+ m_ulID = ulID;
+}
+
+
+void CLock::SetName( const CSteamID &steamID )
+{
+ m_nsNameType = k_ENameTypeSteamID;
+ m_ulID = steamID.ConvertToUint64();
+}
+
+
+const char *CLock::GetName() const
+{
+ switch ( m_nsNameType )
+ {
+ case k_ENameTypeNone:
+ return "None";
+ case k_ENameTypeSteamID:
+ return CSteamID::Render( m_ulID );
+ case k_ENameTypeConstStr:
+ return m_pchConstStr;
+ case k_ENameTypeConcat:
+ if ( !m_strName.Length() )
+ m_strName.Format( "%s %llu", m_pchConstStr, m_ulID );
+ return m_strName.Get();
+ default:
+ AssertMsg1( false, "Invalid lock name type %d", m_nsNameType );
+ return "(Unknown)";
+ }
+}
+
+#define REF_COUNT_ASSERT 1000
+
+void CLock::IncrementReference()
+{
+ m_nRefCount++;
+ Assert( m_nRefCount != REF_COUNT_ASSERT );
+}
+
+int CLock::DecrementReference()
+{
+ Assert( m_nRefCount > 0 );
+ if ( m_nRefCount > 0 )
+ {
+ m_nRefCount--;
+ }
+ return m_nRefCount;
+}
+
+void CLock::Dump( const char *pszPrefix, int nPrintMax, bool bPrintWaiting ) const
+{
+ if ( m_pJob != NULL )
+ {
+ 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 );
+ EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%sLock acquired: %s:%d\n", pszPrefix, m_pFilename, m_line );
+ }
+ else
+ {
+ EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s%s: Lock owner: None, Type: %d, %d Waiting\n", pszPrefix, GetName(), (int32)m_nsLockType, m_nWaitingCount );
+ }
+
+ CJob *pCurrWaiting = m_pJobToNotifyOnLockRelease;
+ int nPrinted = 0;
+ int nTotal = 0;
+ while( pCurrWaiting != NULL && nPrinted < nPrintMax )
+ {
+ bool bPrint = false;
+ if ( nPrinted < nPrintMax )
+ {
+ bPrint = true;
+ }
+ if ( pCurrWaiting->GetPauseReason() == k_EJobPauseReasonWaitingForLock && pCurrWaiting->m_pWaitingOnLock == this )
+ {
+ bPrint = true;
+ }
+
+ if ( bPrint && bPrintWaiting )
+ {
+ EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s\tOther jobs waiting for this lock: %s (%llu)\n", pszPrefix, pCurrWaiting->GetName(), pCurrWaiting->GetJobID() );
+ if ( pCurrWaiting->GetPauseReason() == k_EJobPauseReasonWaitingForLock && pCurrWaiting->m_pWaitingOnLock == this )
+ {
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, 3, "%s\tAt: %s:%d\n", pszPrefix, pCurrWaiting->m_pWaitingOnLockFilename, pCurrWaiting->m_waitingOnLockLine );
+ }
+ nPrinted++;
+ }
+ pCurrWaiting = pCurrWaiting->m_pJobToNotifyOnLockRelease;
+ nTotal++;
+ }
+ if ( bPrintWaiting || nTotal != 0 )
+ {
+ EmitInfo( SPEW_JOB, SPEW_ALWAYS, LOG_ALWAYS, "%s%d out of %d waiting jobs printed.\n", pszPrefix, nPrinted, nTotal );
+ }
+}
+
+} // namespace GCSDK