source-engine/public/gcsdk/job.h

466 lines
17 KiB
C
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#ifndef GC_JOB_H
#define GC_JOB_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/memdbgon.h"
#include "tier1/functors.h"
#include "workthreadpool.h"
namespace GCSDK
{
class CJobMgr;
class CLock;
class CJob;
class IMsgNetPacket;
//-----------------------------------------------------------------------------
// Purpose: Use these macros to declare blocks where it is unsafe to yield.
// The job will assert if it pauses within the block
//-----------------------------------------------------------------------------
#define DO_NOT_YIELD_THIS_SCOPE() GCSDK::CDoNotYieldScope doNotYieldScope_##line( FILE_AND_LINE )
#define BEGIN_DO_NOT_YIELD() GJobCur().PushDoNotYield( FILE_AND_LINE )
#define END_DO_NOT_YIELD() GJobCur().PopDoNotYield()
class CDoNotYieldScope
{
public:
CDoNotYieldScope( const char *pchLocation );
~CDoNotYieldScope();
private:
// Disallow these constructors and operators
CDoNotYieldScope();
CDoNotYieldScope( const CDoNotYieldScope &that );
CDoNotYieldScope &operator=( const CDoNotYieldScope &that );
};
//-----------------------------------------------------------------------------
// job creation function
typedef CJob *(*JobCreationFunc_t)( void *pvServerParent, void * pvStartParam );
//-----------------------------------------------------------------------------
// Purpose: static job information
// contains information relevant to one type of CJob
//-----------------------------------------------------------------------------
struct JobType_t
{
const char *m_pchName; // name of this type of job
MsgType_t m_eCreationMsg; // network message that creates this job
EServerType m_eServerType; // the server type that responds to this message
JobCreationFunc_t m_pJobFactory; // virtual constructor
};
//-----------------------------------------------------------------------------
// Purpose: reason as to why the current job has yielded to the main thread (paused)
// if this is updated, k_prgchJobPauseReason[] in job.cpp also needs to be updated
//-----------------------------------------------------------------------------
enum EJobPauseReason
{
k_EJobPauseReasonNone,
k_EJobPauseReasonNotStarted,
k_EJobPauseReasonNetworkMsg,
k_EJobPauseReasonSleepForTime,
k_EJobPauseReasonWaitingForLock,
k_EJobPauseReasonYield,
k_EJobPauseReasonSQL,
k_EJobPauseReasonWorkItem,
k_EJobPauseReasonCount
};
//-----------------------------------------------------------------------------
// Purpose: contains information used to route a message to a job, or to
// create a new job from that message type
//-----------------------------------------------------------------------------
struct JobMsgInfo_t
{
MsgType_t m_eMsg;
JobID_t m_JobIDSource;
JobID_t m_JobIDTarget;
EServerType m_eServerType;
JobMsgInfo_t()
{
m_eMsg = (MsgType_t)0;
m_JobIDSource = k_GIDNil;
m_JobIDTarget = k_GIDNil;
m_eServerType = k_EServerTypeInvalid;
}
JobMsgInfo_t( MsgType_t eMsg, JobID_t jobIDSource, JobID_t jobIDTarget, EServerType eServerType )
{
m_eMsg = eMsg;
m_JobIDSource = jobIDSource;
m_JobIDTarget = jobIDTarget;
m_eServerType = eServerType;
}
};
typedef void (CJob::*JobThreadFunc_t)();
#define BYieldingAcquireLock( lock ) _BYieldingAcquireLock( lock, __FILE__, __LINE__ )
#define BAcquireLockImmediate( lock ) _BAcquireLockImmediate( lock, __FILE__, __LINE__ )
#define ReleaseLock( lock ) _ReleaseLock( lock, false, __FILE__, __LINE__ )
//-----------------------------------------------------------------------------
// Purpose: A job is any server operation that requires state. Typically, we use jobs for
// operations that need to pause waiting for responses from other servers. The
// job object persists the state of the operation while it waits, and the reply
// from the remote server re-activates the job.
//-----------------------------------------------------------------------------
class CJob
{
public:
// Constructors & destructors, when overriding job name a static string pointer must be used
explicit CJob( CJobMgr &jobMgr, char const *pchJobName = NULL );
virtual ~CJob();
// starts the job, storing off the network msg and calling it's Run() function
void StartJobFromNetworkMsg( IMsgNetPacket *pNetPacket, const JobID_t &gidJobIDSrc );
// accessors
JobID_t GetJobID() const { return m_JobID; }
// start job immediately
// mostly for CMD jobs, which should immediately Yield() once
// so that they don't do their work until the enclosing JobMbr.Run()
// is called
void StartJob( void * pvStartParam );
// schedules the job for execution, but does not interrup the currently running job. Effectively starts the job on the yielding list as if it had immediately yielded
// although is more efficient than actually doing so
void StartJobDelayed( void * pvStartParam );
// string name of the job
const char *GetName() const;
// return reason why we're paused
EJobPauseReason GetPauseReason() const { return m_ePauseReason; }
// string description of why we're paused
const char *GetPauseReasonDescription() const;
// return time at which this job was last paused or continued
const CJobTime& GetTimeSwitched() const { return m_STimeSwitched; }
// return microseconds run since we were last continued
uint64 GetMicrosecondsRun() const { return m_FastTimerDelta.GetDurationInProgress().GetUlMicroseconds(); }
bool BJobNeedsToHeartbeat() const { return ( m_STimeNextHeartbeat.CServerMicroSecsPassed() >= 0 ); }
// --- locking pointers
bool _BYieldingAcquireLock( CLock *pLock, const char *filename = "unknown", int line = 0 );
bool _BAcquireLockImmediate( CLock *pLock, const char *filename = "unknown", int line = 0 );
void _ReleaseLock( CLock *pLock, bool bForce = false, const char *filename = "unknown", int line = 0 );
bool BHoldsAnyLocks() const { return m_vecLocks.Count() > 0; }
void ReleaseLocks();
/// If we hold any locks, spew about them and release them.
/// This is useful for long running jobs, to make sure they don't leak
/// locks that never get cleaned up
void ShouldNotHoldAnyLocks();
// --- general methods for waiting for events
// Simple yield to other jobs until Run() called again
bool BYield();
// Yield IF JobMgr thinks we need to based on how long we've run and our priority
bool BYieldIfNeeded( bool *pbYielded = NULL );
// waits for a set amount of time
bool BYieldingWaitTime( uint32 m_cMicrosecondsToSleep );
bool BYieldingWaitOneFrame();
// waits for another network msg, returning false if none returns
bool BYieldingWaitForMsg( IMsgNetPacket **ppNetPacket );
bool BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg );
bool BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg );
#ifdef GC
bool BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID );
bool BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID );
bool BYieldingRunQuery( CGCSQLQueryGroup *pQueryGroup, ESchemaCatalog eSchemaCatalog );
#endif
bool BYieldingWaitTimeWithLimit( uint32 cMicrosecondsToSleep, CJobTime &stimeStarted, int64 nMicroSecLimit );
bool BYieldingWaitTimeWithLimitRealTime( uint32 cMicrosecondsToSleep, int nSecLimit );
void RecordWaitTimeout() { m_flags.m_bits.m_bWaitTimeout = true; }
// wait for pending work items before deleting job
void WaitForThreadFuncWorkItemBlocking();
// waits for a work item completion callback
// You can pass a string that describes what sort of work item you are waiting on.
// WARNING: This function saves the pointer to the string, it doesn't copy the string
bool BYieldingWaitForWorkItem( const char *pszWorkItemName = NULL );
// adds this work item to threaded work pool and waits for it
bool BYieldingWaitForThreadFuncWorkItem( CWorkItem * );
// calls a local function in a thread, and yields until it's done
bool BYieldingWaitForThreadFunc( CFunctor *jobFunctor );
// Used by the DO_NOT_YIELD() macros
int32 GetDoNotYieldDepth() const;
void PushDoNotYield( const char *pchFileAndLine );
void PopDoNotYield();
#ifdef DBGFLAG_VALIDATE
virtual void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures
static void ValidateStatics( CValidator &validator, const char *pchName );
#endif
// creates a job
template <typename JOB_TYPE, typename PARAM_TYPE>
static JOB_TYPE *AllocateJob( PARAM_TYPE *pParam )
{
return new JOB_TYPE( pParam );
}
// delete a job (the job knows what allocator to use)
static void DeleteJob( CJob *pJob );
void SetStartParam( void * pvStartParam ) { Assert( NULL == m_pvStartParam ); m_pvStartParam = pvStartParam; }
void SetFromFromMsg( bool bRunFromMsg ) { m_bRunFromMsg = true; }
void AddPacketToList( IMsgNetPacket *pNetPacket, const JobID_t gidJobIDSrc );
// marks a packet as being finished with, releases the packet and frees the memory
void ReleaseNetPacket( IMsgNetPacket *pNetPacket );
void EndPause( EJobPauseReason eExpectedState );
// Generate an assertion in the coroutine of this job
// (creating a minidump). Useful for inspecting stuck jobs
void GenerateAssert( const char *pchMsg = NULL );
/// Return true if we tried to waited on a message of this type,
/// and failed to receive it. (If so, then this means we could
/// conceivably still receive a reply of that type at any moment.)
bool BHasFailedToReceivedMsgType( MsgType_t m ) const;
/// Mark that we awaited a message of the specified type, but timed out
void MarkFailedToReceivedMsgType( MsgType_t m );
/// Clear flag that we timed out waiting on a message of the specified type.
/// This is used to allow you to wait ont he same message again, even if
/// you know the reply might be a late reply. It's up to you to deal with
/// mismatched replies!
void ClearFailedToReceivedMsgType( MsgType_t m );
protected:
// main job implementation, in the coroutine. Every job must implement at least one of these methods.
virtual bool BYieldingRunJob( void * pvStartParam ) { Assert( false ); return true; } // implement this if your job can be started directly
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket * pNetPacket ) { Assert( false ); return true; } // implement this if your job can be started by a network message
// Can be overridden to return a different timeout per job class
virtual uint32 CHeartbeatsBeforeTimeout();
// Called by CJobMgr to send heartbeat message to our listeners during long operations
void Heartbeat();
// accessor to get access to the JobMgr from the server we belong to
CJobMgr &GetJobMgr();
uint32 m_bRunFromMsg:1,
m_bWorkItemCanceled:1, // true if the work item we were waiting on was canceled
m_bIsTest:1,
m_bIsLongRunning:1;
private:
// starts the coroutine that activates the job
void InitCoroutine();
// continues the current job
void Continue();
// break into this coroutine - can only be called from OUTSIDE this coroutine
void Debug();
// pauses the current job - can only be called from inside a coroutine
void Pause( EJobPauseReason eReason );
static void BRunProxy( void *pvThis );
JobID_t m_JobID; // Our unique identifier.
HCoroutine m_hCoroutine;
void * m_pvStartParam; // Start params for our job, if any
// all these flags indicate some kind of failure and we will want to report them
union {
struct {
uint32
m_bJobFailed:1, // true if BYieldingRunJob returned false
m_bLocksFailed:1,
m_bLocksLongHeld:1,
m_bLocksLongWait:1,
m_bWaitTimeout:1,
m_bLongInterYield:1,
m_bTimeoutNetMsg:1,
m_bTimeoutOther:1,
m_uUnused:24;
} m_bits;
uint32 m_uFlags;
} m_flags;
int m_cLocksAttempted;
int m_cLocksWaitedFor;
EJobPauseReason m_ePauseReason;
MsgType_t m_unWaitMsgType;
CJobTime m_STimeStarted; // time (frame) at which this job started
CJobTime m_STimeSwitched; // time (frame) at which we were last paused or continued
CJobTime m_STimeNextHeartbeat; // Time at which next heartbeat should be performed
CFastTimer m_FastTimerDelta; // How much time we've been running for without yielding
CCycleCount m_cyclecountTotal; // Total runtime
CJob *m_pJobPrev; // the job that launched us
// lock manipulation
void _SetLock( CLock *pLock, const char *filename, int line );
void UnsetLock( CLock *pLock );
void PassLockToJob( CJob *pNewJob, CLock *pLock );
void OnLockDeleted( CLock *pLock );
void AddJobToNotifyOnLockRelease( CJob *pJob );
CUtlVectorFixedGrowable< CLock *, 2 > m_vecLocks;
CLock *m_pWaitingOnLock; // lock we're waiting on, if any
const char *m_pWaitingOnLockFilename;
int m_waitingOnLockLine;
CJob *m_pJobToNotifyOnLockRelease; // other job that wants this lock
CWorkItem *m_pWaitingOnWorkItem; // set if job is waiting for this work item
CJobMgr &m_JobMgr; // our job manager
CUtlVectorFixedGrowable< IMsgNetPacket *, 1 > m_vecNetPackets; // list of tcp packets currently held by this job (ie, needing release on job exit)
/// List of message types that we have waited for, but timed out.
/// once this happens, we currently do not have a good mechanism
/// to recover gracefully. But we at least can detect the situation
/// and avoid getting totally hosed or processing the wrong reply
CUtlVector<MsgType_t> m_vecMsgTypesFailedToReceive;
// pointer to our own static job info
struct JobType_t const *m_pJobType;
// Name of the job for when it's not registered
const char *m_pchJobName;
// A stack of do not yield guards so we can print the right warning if they're nested
CUtlLinkedList<const char *> m_stackDoNotYieldGuards;
// setting the job info
friend void Job_SetJobType( CJob &job, const JobType_t *pJobType );
friend class CJobMgr;
friend class CLock;
// used to store the memory allocation stack
CUtlMemory< unsigned char > m_memAllocStack;
};
// Only one job can be running at a time. We keep a global accessor to it.
extern CJob *g_pJobCur;
inline CJob &GJobCur() { Assert( g_pJobCur != NULL ); return *g_pJobCur; }
#define AssertRunningJob() { Assert( NULL != g_pJobCur ); }
#define AssertRunningThisJob() { Assert( this == g_pJobCur ); }
#define AssertNotRunningThisJob() { Assert( this != g_pJobCur ); }
#define AssertNotRunningJob() { Assert( NULL == g_pJobCur ); }
//-----------------------------------------------------------------------------
// Purpose: simple locking class
// add this object to any classes you want jobs to be able to lock
//-----------------------------------------------------------------------------
class CLock
{
public:
CLock( );
~CLock();
bool BIsLocked() { return m_pJob != NULL; }
CJob *GetJobLocking() { return m_pJob; }
CJob *GetJobWaitingQueueHead() { return m_pJobToNotifyOnLockRelease; }
CJob *GetJobWaitingQueueTail() { return m_pJobWaitingQueueTail; }
void AddToWaitingQueue( CJob *pJob );
const char *GetName() const;
void SetName( const char *pchName );
void SetName( const char *pchPrefix, uint64 ulID );
void SetName( const CSteamID &steamID );
int16 GetLockType() const { return m_nsLockType; }
void SetLockType( int16 nsLockType ) { m_nsLockType = nsLockType; }
uint64 GetLockSubType() const { return m_unLockSubType; }
void SetLockSubType( uint64 unLockSubType ) { m_unLockSubType = unLockSubType; }
int32 GetWaitingCount() const { return m_nWaitingCount; }
int64 GetMicroSecondsSinceLock() const { return m_sTimeAcquired.CServerMicroSecsPassed(); }
void IncrementReference();
int DecrementReference();
void ClearReference() { m_nRefCount = 0; }
int32 GetReferenceCount() const { return m_nRefCount; }
void Dump( const char *pszPrefix = "\t\t", int nPrintMax = 1, bool bPrintWaiting = true ) const;
private:
enum ENameType
{
k_ENameTypeNone = 0,
k_ENameTypeSteamID = 1,
k_ENameTypeConstStr = 2,
k_ENameTypeConcat = 3,
};
CJob *m_pJob; // the job that's currently locking us
CJob *m_pJobToNotifyOnLockRelease; // Pointer to the first job waiting on us
CJob *m_pJobWaitingQueueTail; // Pointer to the last job waiting on us
int16 m_nsLockType; // Lock priority for safely waiting on multiple locks
int16 m_nsNameType; // Enum that controls how this lock is named
uint64 m_ulID; // ID part of the lock's name
const char *m_pchConstStr; // Prefix part of the lock's name
mutable CUtlString m_strName; // Cached name
int32 m_nRefCount; // # of times locked
int32 m_nWaitingCount; // Count of jobs waiting on the lock
CJobTime m_sTimeAcquired; // Time the lock was last locked
uint64 m_unLockSubType;
const char *m_pFilename; // Filename of the source file who aquired this lock
int m_line; // Line number of the filename
friend class CJob;
};
//-----------------------------------------------------------------------------
// Purpose: automatic locking class
//-----------------------------------------------------------------------------
class CAutoCLock
{
public:
CAutoCLock( CLock &refLock )
: m_pLock ( &refLock)
{
DbgVerify( GJobCur().BYieldingAcquireLock( m_pLock ) );
}
// explicitly unlock before destruction
void Unlock()
{
if ( m_pLock != NULL )
GJobCur().ReleaseLock( m_pLock );
m_pLock = NULL;
}
~CAutoCLock( )
{
Unlock();
}
private:
CLock *m_pLock;
CJob *m_pJob;
};
} // namespace GCSDK
#include "tier0/memdbgoff.h"
#endif // GC_JOB_H