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
1451 lines
45 KiB
//========= 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
|
|
|