Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4432 lines
151 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "stdafx.h"
  8. #include "gcbase.h"
  9. #include "tier1/interface.h"
  10. #include "tier0/minidump.h"
  11. #include "tier0/icommandline.h"
  12. #include "gcjob.h"
  13. #include "sqlaccess/schemaupdate.h"
  14. #include "gcsystemmsgs.h"
  15. #include "rtime.h"
  16. #include "msgprotobuf.h"
  17. #include "gcsdk_gcmessages.pb.h"
  18. #include "gcsdk/gcparalleljobfarm.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. namespace GCSDK
  22. {
  23. //----------------------------------------------------------------------
  24. // Emit groups
  25. //----------------------------------------------------------------------
  26. DECLARE_GC_EMIT_GROUP( g_EGHTTPRequest, http_request );
  27. CGCBase *g_pGCBase = NULL;
  28. // Thread pool size convar
  29. static void OnConVarChangeJobMgrThreadPoolSize( IConVar *pConVar, const char *pOldString, float flOldValue );
  30. GCConVar jobmgr_threadpool_size( "jobmgr_threadpool_size", "-1", 0,
  31. "Maximum threads in the job manager thread pool. Values <= 0 mean number_logical_cpus - this.",
  32. OnConVarChangeJobMgrThreadPoolSize );
  33. static uint32 GetThreadPoolSizeFromConVar()
  34. {
  35. int nVal = jobmgr_threadpool_size.GetInt();
  36. int nRet = ( nVal > 0 ) ? nVal : GetCPUInformation()->m_nLogicalProcessors + nVal;
  37. return (uint32)Clamp( nRet, 1, INT_MAX );
  38. }
  39. static void OnConVarChangeJobMgrThreadPoolSize( IConVar *pConVar, const char *pOldString, float flOldValue )
  40. {
  41. if ( GGCBase()->GetIsShuttingDown() )
  42. return;
  43. GGCBase()->GetJobMgr().SetThreadPoolSize( GetThreadPoolSizeFromConVar() );
  44. }
  45. GCConVar cv_concurrent_start_playing_limit( "concurrent_start_playing_limit", "1000" );
  46. GCConVar cv_logon_surge_start_playing_limit( "logon_surge_start_playing_limit", "2000" );
  47. GCConVar cv_logon_surge_request_session_jobs( "logon_surge_request_session_jobs", "1000" );
  48. GCConVar cv_webapi_throttle_job_threshold( "webapi_throttle_job_threshold", "2000", 0, "If the job count exceeds this threshold, reject low-priority webapi jobs" );
  49. GCConVar enable_startplaying_gameserver_creation_spew( "enable_startplaying_gameserver_creation_spew", "0" );
  50. // Enable the restore-version-from-memcache machinery. Disabled because it assumes reloading an SOCache is
  51. // deterministic, which is no longer true for us, resulting in clients with stale versions believing themselves to be in
  52. // sync.
  53. //
  54. // This probably needs a look -- ideally we'd delineate deterministic objects that can be assumed to remain in sync in
  55. // GC reboots, and dynamic objects that cannot.
  56. //
  57. // Note that we already removed hacks for this in player groups and started using lazy-loaded objects in SOCaches that
  58. // violate the assumptions this was making, so re-enabling it requires work. We probably really want to split type
  59. // caches into deterministic-between-GC-reboots and not, and resend based on said flag.
  60. GCConVar socache_persist_version_via_memcached( "socache_persist_version_via_memcached", "0" );
  61. static GCConVar cv_assert_minidump_window( "assert_minidump_window", "28800", 0, "Size of the minidump window in seconds. Each unique assert will dump at most assert_max_minidumps_in_window times in this many seconds" );
  62. static GCConVar cv_assert_max_minidumps_in_window( "assert_max_minidumps_in_window", "5", 0, "The amount of times each unique assert will write a dump in assert_minidump_window seconds" );
  63. static GCConVar cv_debug_steam_startplaying( "cv_debug_steam_startplaying", "0", 0, "Turn this ON to debug the stream of startplaying messages we get from Steam" );
  64. static GCConVar temp_list_mismatched_replies( "temp_list_mismatched_replies", "0", "When set to 1, this report all replies that fail because the incoming message didn't expect a response. Temporary to help track down some failed state" );
  65. static GCConVar writeback_queue_max_accumulate_time( "writeback_queue_max_accumulate_time", "10", 0, "The maximum amount of time in seconds that the writeback queue will accumulate database writes before performing queries. This is the time *before* the queries are executed, which is unbounded." );
  66. static GCConVar writeback_queue_max_caches( "writeback_queue_max_caches", "0", 0, "The maximum amount of caches to write back in a single transaction. Set to zero to remove this restriction." );
  67. static GCConVar geolocation_spewlevel( "geolocation_spewlevel", "4", 0, "Spewlevel to use for geolocation debug spew" );
  68. static GCConVar geolocation_loglevel( "geolocation_loglevel", "4", 0, "Spewlevel to use for geolocation debug spew" );
  69. extern GCConVar max_user_messages_per_second;
  70. // There is also a GCConVar writeback_delay to control how frequently we do writebacks.
  71. // !KLUDGE! Temp shim. Will get rid of this when we bring over the real gcinterface stuff from DOTA.
  72. CGCInterface g_GCInterface;
  73. CGCInterface *GGCInterface() { return &g_GCInterface; }
  74. CSteamID CGCInterface::ConstructSteamIDForClient( AccountID_t unAccountID ) const
  75. {
  76. return CSteamID( unAccountID, GetUniverse(), k_EAccountTypeIndividual );
  77. }
  78. //-----------------------------------------------------------------------------
  79. // Purpose: Overrides the spew func used by Msg and DMsg to print to the console
  80. //-----------------------------------------------------------------------------
  81. SpewRetval_t ConsoleSpewFunc( SpewType_t type, const tchar *pMsg )
  82. {
  83. const char *fmt = ( sizeof( tchar ) == sizeof( char ) ) ? "%hs" : "%ls";
  84. switch (type )
  85. {
  86. default:
  87. case SPEW_MESSAGE:
  88. case SPEW_LOG:
  89. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, fmt, pMsg );
  90. break;
  91. case SPEW_WARNING:
  92. EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, fmt, pMsg );
  93. break;
  94. case SPEW_ASSERT:
  95. if ( ThreadInMainThread() && ( g_pJobCur != NULL ) )
  96. {
  97. fmt = ( sizeof( tchar ) == sizeof( char ) ) ? "[Job %s] %hs" : "[Job %s] %ls";
  98. EmitError( SPEW_CONSOLE, fmt, g_pJobCur->GetName(), pMsg );
  99. }
  100. else
  101. {
  102. EmitError( SPEW_CONSOLE, fmt, pMsg );
  103. }
  104. break;
  105. case SPEW_ERROR:
  106. EmitError( SPEW_CONSOLE, fmt, pMsg );
  107. break;
  108. }
  109. if ( type == SPEW_ASSERT )
  110. {
  111. #ifndef WIN32
  112. // Non-win32
  113. bool bRaiseOnAssert = getenv( "RAISE_ON_ASSERT" ) || !!CommandLine()->FindParm( "-raiseonassert" );
  114. #elif defined( _DEBUG )
  115. // Win32 debug
  116. bool bRaiseOnAssert = true;
  117. #else
  118. // Win32 release
  119. bool bRaiseOnAssert = !!CommandLine()->FindParm( "-raiseonassert" );
  120. #endif
  121. return bRaiseOnAssert ? SPEW_DEBUGGER : SPEW_CONTINUE;
  122. }
  123. else if ( type == SPEW_ERROR )
  124. return SPEW_ABORT;
  125. else
  126. return SPEW_CONTINUE;
  127. }
  128. class CGCShutdownJob : public CGCJob
  129. {
  130. public:
  131. CGCShutdownJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  132. virtual bool BYieldingRunGCJob()
  133. {
  134. m_pGC->SetIsShuttingDown();
  135. // Log off all of the game servers and users, so that if something
  136. // in the log off dirties caches they can be written back
  137. CUtlVector<CSteamID> vecIDsToStop;
  138. for( CGCGSSession **ppSession = m_pGC->GetFirstGSSession(); ppSession != NULL; ppSession = m_pGC->GetNextGSSession( ppSession ) )
  139. {
  140. vecIDsToStop.AddToTail( (*ppSession)->GetSteamID() );
  141. }
  142. FOR_EACH_VEC( vecIDsToStop, i )
  143. {
  144. m_pGC->YieldingStopGameserver( vecIDsToStop[i] );
  145. ShouldNotHoldAnyLocks();
  146. }
  147. vecIDsToStop.RemoveAll();
  148. for( CGCUserSession **ppSession = m_pGC->GetFirstUserSession(); ppSession != NULL; ppSession = m_pGC->GetNextUserSession( ppSession ) )
  149. {
  150. vecIDsToStop.AddToTail( (*ppSession)->GetSteamID() );
  151. }
  152. FOR_EACH_VEC( vecIDsToStop, i )
  153. {
  154. m_pGC->YieldingStopPlaying( vecIDsToStop[i] );
  155. ShouldNotHoldAnyLocks();
  156. }
  157. // wait for jobs to finish (except this one!)
  158. const int kMaxIterations = 100;
  159. int cIter = 0;
  160. while ( cIter++ < kMaxIterations && m_pGC->GetJobMgr().CountJobs() > 1 )
  161. {
  162. BYieldingWaitOneFrame();
  163. }
  164. m_pGC->YieldingGracefulShutdown();
  165. GGCHost()->ShutdownComplete();
  166. return false;
  167. }
  168. };
  169. class CPreTestSetupJob : public CGCJob
  170. {
  171. public:
  172. CPreTestSetupJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  173. virtual bool BYieldingRunGCJob( GCSDK::CNetPacket *pNetPacket )
  174. {
  175. CGCMsg<MsgGCEmpty_t> msg( pNetPacket );
  176. m_pGC->YieldingPreTestSetup();
  177. return true;
  178. }
  179. };
  180. GC_REG_JOB( CGCBase, CPreTestSetupJob, "CPreTestSetupJob", k_EGCMsgPreTestSetup, k_EServerTypeGC );
  181. static void SpewSerializedKeyValues( const byte *pubVarData, uint32 cubVarData )
  182. {
  183. if ( pubVarData == NULL || cubVarData == 0 )
  184. {
  185. EmitInfo( SPEW_GC, 1, 1, " No KV data\n" );
  186. return;
  187. }
  188. char szLine[512] = "";
  189. for ( uint32 i = 0 ; i < cubVarData ; ++i )
  190. {
  191. char szByteVal[32];
  192. V_sprintf_safe( szByteVal, "%02X", pubVarData[ i ] );
  193. if ( i % 32 )
  194. {
  195. V_strcat_safe( szLine, ", " );
  196. V_strcat_safe( szLine, szByteVal );
  197. }
  198. else
  199. {
  200. if ( szLine[0] )
  201. EmitInfo( SPEW_GC, 1, 1, " %s\n", szLine );
  202. V_strcpy_safe( szLine, szByteVal );
  203. }
  204. }
  205. if ( szLine[0] )
  206. EmitInfo( SPEW_GC, 1, 1, " %s\n", szLine );
  207. KeyValuesAD pkvDetails( "SessionDetails" );
  208. CUtlBuffer buf;
  209. buf.Put( pubVarData, cubVarData );
  210. if( pkvDetails->ReadAsBinary( buf ) )
  211. {
  212. FOR_EACH_VALUE( pkvDetails, v )
  213. {
  214. EmitInfo( SPEW_GC, 1, 1, " %s = %s\n", v->GetName(), v->GetString( NULL, "??" ) );
  215. }
  216. }
  217. else
  218. {
  219. EmitInfo( SPEW_GC, 1, 1, " KV data failed parse\n" );
  220. }
  221. }
  222. class CStartPlayingJob : public CGCJob
  223. {
  224. public:
  225. CStartPlayingJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  226. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  227. {
  228. CGCMsg<MsgGCStartPlaying_t> msg( pNetPacket );
  229. // @note Tom Bui/Joe Ludwig: This can happen for PS3 Steam accounts
  230. if ( !msg.Body().m_steamID.IsValid() )
  231. return true;
  232. if ( cv_debug_steam_startplaying.GetBool() )
  233. {
  234. netadr_t serverAdr( msg.Body().m_unServerAddr, msg.Body().m_usServerPort );
  235. EmitInfo( SPEW_GC, 1, 1, "Received StartPlaying( user = %s, GS = %s @ %s )\n", msg.Body().m_steamID.Render(), msg.Body().m_steamIDGS.Render(), serverAdr.ToString() );
  236. SpewSerializedKeyValues( msg.PubVarData(), msg.CubVarData() );
  237. }
  238. m_pGC->QueueStartPlaying( msg.Body().m_steamID, msg.Body().m_steamIDGS, msg.Body().m_unServerAddr, msg.Body().m_usServerPort, msg.PubVarData(), msg.CubVarData() );
  239. return true;
  240. }
  241. };
  242. GC_REG_JOB(CGCBase, CStartPlayingJob, "CStartPlayingJob", k_EGCMsgStartPlaying, k_EServerTypeGC);
  243. class CExecuteStartPlayingJob : public CGCJob
  244. {
  245. public:
  246. CExecuteStartPlayingJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  247. virtual bool BYieldingRunGCJob( )
  248. {
  249. m_pGC->YieldingExecuteNextStartPlaying();
  250. return true;
  251. }
  252. };
  253. class CStopPlayingJob : public CGCJob
  254. {
  255. public:
  256. CStopPlayingJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  257. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  258. {
  259. CGCMsg<MsgGCStopSession_t> msg( pNetPacket );
  260. // @note Tom Bui/Joe Ludwig: This can happen for PS3 Steam accounts
  261. if ( !msg.Body().m_steamID.IsValid() )
  262. return true;
  263. if ( cv_debug_steam_startplaying.GetBool() )
  264. {
  265. EmitInfo( SPEW_GC, 1, 1, "Received StopPlaying( user = %s )\n", msg.Body().m_steamID.Render() );
  266. }
  267. m_pGC->YieldingStopPlaying( msg.Body().m_steamID );
  268. return true;
  269. }
  270. };
  271. GC_REG_JOB(CGCBase, CStopPlayingJob, "CStopPlayingJob", k_EGCMsgStopPlaying, k_EServerTypeGC);
  272. class CStartGameserverJob : public CGCJob
  273. {
  274. public:
  275. CStartGameserverJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  276. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  277. {
  278. CGCMsg<MsgGCStartGameserver_t> msg( pNetPacket );
  279. m_pGC->QueueStartPlaying( msg.Body().m_steamID, CSteamID(), msg.Body().m_unServerAddr, msg.Body().m_usServerPort, msg.PubVarData(), msg.CubVarData() );
  280. return true;
  281. }
  282. };
  283. GC_REG_JOB(CGCBase, CStartGameserverJob, "CStartGameserverJob", k_EGCMsgStartGameserver, k_EServerTypeGC);
  284. class CStopGameserverJob : public CGCJob
  285. {
  286. public:
  287. CStopGameserverJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  288. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  289. {
  290. CGCMsg<MsgGCStopSession_t> msg( pNetPacket );
  291. m_pGC->YieldingStopGameserver( msg.Body().m_steamID );
  292. return true;
  293. }
  294. };
  295. GC_REG_JOB(CGCBase, CStopGameserverJob, "CStopGameserverJob", k_EGCMsgStopGameserver, k_EServerTypeGC);
  296. class CGetSystemStatsJob : public CGCJob
  297. {
  298. public:
  299. CGetSystemStatsJob( CGCBase *pGC ) : CGCJob( pGC ) {}
  300. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  301. {
  302. CProtoBufMsg<CGCMsgGetSystemStats> msg( pNetPacket );
  303. CProtoBufMsg<CGCMsgGetSystemStatsResponse> msgResponse( k_EGCMsgGetSystemStatsResponse );
  304. msgResponse.Body().set_gc_app_id( m_pGC->GetAppID() );
  305. // @note Tom Bui: we don't support dynamic stats yet, but once we do, we can use the KV stuff
  306. m_pGC->SystemStats_Update( msgResponse.Body() );
  307. // KVPacker packer;
  308. // KeyValuesAD pKVStats( "GCStats" );
  309. // CUtlBuffer buffer;
  310. // if ( packer.WriteAsBinary( pKVStats, buffer ) )
  311. // {
  312. // msgResponse.Body().set_stats_kv( buffer.Base(), buffer.TellPut() );
  313. // }
  314. return m_pGC->BSendSystemMessage( msgResponse );
  315. }
  316. };
  317. GC_REG_JOB(CGCBase, CGetSystemStatsJob, "CGetSystemStatsJob", k_EGCMsgGetSystemStats, k_EServerTypeGC);
  318. //-----------------------------------------------------------------------------
  319. class CGCJobAccountVacStatusChange : public CGCJob
  320. {
  321. public:
  322. CGCJobAccountVacStatusChange( CGCBase *pGC ) : CGCJob( pGC ) {}
  323. bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  324. {
  325. CProtoBufMsg<CMsgGCHAccountVacStatusChange> msg( pNetPacket );
  326. if ( GGCBase()->GetAppID() != msg.Body().app_id() )
  327. return true;
  328. CSteamID steamID( msg.Body().steam_id() );
  329. bool bIsVacBanned = msg.Body().is_banned_now();
  330. // Fetch app details, but force them to be re-loaded
  331. bool bForceReload = true;
  332. const CAccountDetails *pAccountDetails = GGCBase()->YieldingGetAccountDetails( steamID, bForceReload );
  333. // Account details is up to date so just return
  334. if ( pAccountDetails && bIsVacBanned != pAccountDetails->BIsVacBanned() )
  335. {
  336. EmitWarning( SPEW_GC, 2, "VAC status didn't update for %s afetr receiving VacStatusChange and the force reloading the account details\n", steamID.Render() );
  337. }
  338. return true;
  339. }
  340. };
  341. GC_REG_JOB( CGCBase, CGCJobAccountVacStatusChange, "CGCJobAccountVacStatusChange", k_EGCMsgGCAccountVacStatusChange, k_EServerTypeGC );
  342. //-----------------------------------------------------------------------------
  343. class CGCJobAccountPhoneNumberChange : public CGCJob
  344. {
  345. public:
  346. CGCJobAccountPhoneNumberChange( CGCBase *pGC ) : CGCJob( pGC ) {}
  347. bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  348. {
  349. CProtoBufMsg<CMsgGCHAccountPhoneNumberChange> msg( pNetPacket );
  350. if ( GGCBase()->GetAppID() != msg.Body().appid() )
  351. return true;
  352. CSteamID steamID( msg.Body().steamid() );
  353. CScopedSteamIDLock scopedLock( steamID );
  354. if ( !scopedLock.BYieldingPerformLock( __FILE__, __LINE__ ) )
  355. {
  356. EmitError( SPEW_GC, __FUNCTION__ ": Failed to lock steamid %s\n", steamID.Render() );
  357. return true;
  358. }
  359. bool bHasPhoneVerified = msg.Body().is_verified();
  360. bool bIsPhoneIdentifying = msg.Body().is_identifying();
  361. // Fetch app details, but force them to be re-loaded
  362. bool bForceReload = true;
  363. const CAccountDetails *pAccountDetails = GGCBase()->YieldingGetAccountDetails( steamID, bForceReload );
  364. // Account details is up to date so just return
  365. if ( pAccountDetails && ( bHasPhoneVerified != pAccountDetails->BIsPhoneVerified() ||
  366. bIsPhoneIdentifying != pAccountDetails->BIsPhoneIdentifying() ) )
  367. {
  368. EmitWarning( SPEW_GC, 2, "Phone status didn't update for %s afetr receiving PhoneNumberChange and force reloading the account details\n",
  369. steamID.Render() );
  370. }
  371. GGCBase()->YldOnAccountPhoneVerificationChange( steamID );
  372. EmitInfo( SPEW_GC, 5, 5, "AccountPhoneVerificationChange for %s\n", steamID.Render() );
  373. return true;
  374. }
  375. };
  376. GC_REG_JOB( CGCBase, CGCJobAccountPhoneNumberChange, "CGCJobAccountPhoneNumberChange", k_EGCMsgAccountPhoneNumberChange, k_EServerTypeGC );
  377. //-----------------------------------------------------------------------------
  378. class CGCJobAccountTwoFactorChange : public CGCJob
  379. {
  380. public:
  381. CGCJobAccountTwoFactorChange( CGCBase *pGC ) : CGCJob( pGC ) {}
  382. bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
  383. {
  384. CProtoBufMsg<CMsgGCHAccountTwoFactorChange> msg( pNetPacket );
  385. if ( GGCBase()->GetAppID() != msg.Body().appid() )
  386. return true;
  387. CSteamID steamID( msg.Body().steamid() );
  388. bool bHasTwoFactor = msg.Body().twofactor_enabled();
  389. // Fetch app details, but force them to be re-loaded
  390. bool bForceReload = true;
  391. const CAccountDetails *pAccountDetails = GGCBase()->YieldingGetAccountDetails( steamID, bForceReload );
  392. // Account details is up to date so just return
  393. if ( pAccountDetails && bHasTwoFactor != pAccountDetails->BIsTwoFactorAuthEnabled() )
  394. {
  395. EmitWarning( SPEW_GC, 2, "VAC status didn't update for %s afetr receiving VacStatusChange and the force reloading the account details\n", steamID.Render() );
  396. }
  397. GGCBase()->YldOnAccountTwoFactorChange( steamID );
  398. EmitInfo( SPEW_GC, 5, 5, "AccountTwoFactorChange for %s\n", steamID.Render() );
  399. return true;
  400. }
  401. };
  402. GC_REG_JOB( CGCBase, CGCJobAccountTwoFactorChange, "CGCJobAccountTwoFactorChange", k_EGCMsgAccountTwoFactorChange, k_EServerTypeGC );
  403. //-----------------------------------------------------------------------------
  404. // Purpose: Constructor
  405. //-----------------------------------------------------------------------------
  406. CGCBase::CGCBase( )
  407. : m_mapSOCache( ),
  408. m_rbtreeSOCachesBeingLoaded( DefLessFunc( CSteamID ) ),
  409. m_rbtreeSOCachesWithDirtyVersions( DefLessFunc( CSteamID ) ),
  410. m_hashUserSessions( k_nUserSessionRunInterval/ k_cMicroSecPerShellFrame ),
  411. m_hashGSSessions( k_nGSSessionRunInterval/ k_cMicroSecPerShellFrame ),
  412. m_hashSteamIDLocks( k_nLocksRunInterval / k_cMicroSecPerShellFrame ),
  413. m_bStartupComplete( false ),
  414. m_bIsShuttingDown( false ),
  415. m_bStartProfiling( false ),
  416. m_bStopProfiling( false ),
  417. m_bDumpVprofImbalances( false ),
  418. m_nStartPlayingJobCount( 0 ),
  419. m_nRequestSessionJobsActive( 0 ),
  420. m_nLogonSurgeFramesRemaining( k_nMillion * 10 / k_cMicroSecPerShellFrame ), // stay in "logon surge" mode for at least 10 seconds after boot.
  421. m_mapStartPlayingQueueIndexBySteamID( DefLessFunc( CSteamID ) ),
  422. m_MsgRateLimit( max_user_messages_per_second ),
  423. m_nStartupCompleteTime( CRTime::RTime32TimeCur() ),
  424. m_nInitTime( CRTime::RTime32TimeCur() ),
  425. m_jobidFlushInventoryCacheAccounts( k_GIDNil ),
  426. m_numFlushInventoryCacheAccountsLastScheduled( 0 )
  427. {
  428. }
  429. //-----------------------------------------------------------------------------
  430. // Purpose: Destructor
  431. //-----------------------------------------------------------------------------
  432. CGCBase::~CGCBase()
  433. {
  434. }
  435. //-----------------------------------------------------------------------------
  436. // Purpose: Remembers the app ID and host
  437. //-----------------------------------------------------------------------------
  438. bool CGCBase::BInit( AppId_t unAppID, const char *pchAppPath, IGameCoordinatorHost *pHost )
  439. {
  440. VPROF_BUDGET( "CGCBase::BInit", VPROF_BUDGETGROUP_STEAM );
  441. // Make sure we can't deploy debug GCs outside the dev environment
  442. #ifdef _DEBUG
  443. if ( pHost->GetUniverse() != k_EUniverseDev )
  444. {
  445. //pHost->EmitMessage( SPEW_GC, SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS,
  446. // CFmtStr( "The GC for App %u is a debug binary. Shutting down.\n", unAppID ) );
  447. //return false;
  448. pHost->EmitMessage( SPEW_GC.GetName(), SPEW_WARNING, SPEW_ALWAYS, LOG_ALWAYS,
  449. CFmtStr( "The GC for App %u is a debug binary.\n", unAppID ) );
  450. }
  451. #endif
  452. m_JobMgr.SetThreadPoolSize( GetThreadPoolSizeFromConVar() );
  453. MsgRegistrationFromEnumDescriptor( EGCSystemMsg_descriptor(), GCSDK::MT_GC_SYSTEM );
  454. MsgRegistrationFromEnumDescriptor( EGCBaseClientMsg_descriptor(), GCSDK::MT_GC );
  455. MsgRegistrationFromEnumDescriptor( EGCToGCMsg_descriptor(), GCSDK::MT_GC_SYSTEM );
  456. m_unAppID = unAppID;
  457. m_pHost = pHost;
  458. m_sPath = pchAppPath;
  459. SetGCHost( pHost );
  460. g_pGCBase = this;
  461. SetMinidumpFilenamePrefix( CFmtStr("dumps\\gc%d", m_unAppID) );
  462. // Make sure the assert dialog doesn't come up and hang the process in production
  463. //SetAssertDialogDisabled( pHost->GetUniverse() != k_EUniverseDev );
  464. SetAssertFailedNotifyFunc( CGCBase::AssertCallbackFunc );
  465. // init the time very early so CRTime::RTime32TimeCur will return the right thing
  466. CRTime::UpdateRealTime();
  467. m_hashUserSessions.Init( k_cGCUserSessionInit, k_cBucketGCUserSession );
  468. m_hashGSSessions.Init( k_cGCGSSessionInit, k_cBucketGCGSSession );
  469. m_hashSteamIDLocks.Init( k_cGCLocksInit, k_cBucketGCLocks );
  470. m_OutputFuncPrev = GetSpewOutputFunc();
  471. SpewOutputFunc( &ConsoleSpewFunc );
  472. EmitInfo( SPEW_GC, 1, 1, "CGCBase::BInit( AppID=%d, appPath=%s, sPath=%s )\n", unAppID, pchAppPath, m_sPath.String() );
  473. if ( !OnInit() )
  474. return false;
  475. DbgVerify( g_theMessageList.BInit( ) );
  476. /*
  477. // @note Tom Bui: we don't need dynamic stats...yet.
  478. // when we do, we'll need to specify the how the values are aggregated over all the same GCs
  479. // and how the values should be treated
  480. KeyValuesAD pKVStats( "GCStats" );
  481. SystemStats_Update( pKVStats );
  482. CUtlBuffer buffer;
  483. KVPacker packer;
  484. if ( packer.WriteAsBinary( pKVStats, buffer ) )
  485. {
  486. CProtoBufMsg< CGCMsgSystemStatsSchema > msg( GCSDK::k_EGCMsgSystemStatsSchema );
  487. msg.Body().set_gc_app_id( GetAppID() );
  488. msg.Body().set_schema_kv( buffer.Base(), buffer.TellPut() );
  489. BSendSystemMessage( msg );
  490. }
  491. */
  492. return BSendWebApiRegistration();
  493. }
  494. //-----------------------------------------------------------------------------
  495. // Purpose: Report back to the host that startup is complete
  496. //-----------------------------------------------------------------------------
  497. void CGCBase::SetStartupComplete( bool bSuccess )
  498. {
  499. // !KLUDGE! Fatal error messages on startup frequently get lost in the
  500. // mass of messages. Let's spray a big error message box if we fail
  501. // to startup. Ideally, the cause of the failure will be
  502. // spewed just above this box.
  503. if ( !bSuccess )
  504. {
  505. EmitError( SPEW_GC, "^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^\n" );
  506. EmitError( SPEW_GC, "GC failed to startup. Error mesage is probably directly above\n" );
  507. EmitError( SPEW_GC, "**************************************************************\n" );
  508. }
  509. m_nStartupCompleteTime = CRTime::RTime32TimeCur();
  510. m_bStartupComplete = true;
  511. GGCHost()->StartupComplete( bSuccess );
  512. }
  513. uint32 CGCBase::GetGCUpTime() const
  514. {
  515. return CRTime::RTime32TimeCur() - m_nInitTime;
  516. }
  517. //-----------------------------------------------------------------------------
  518. // Purpose: Starts a job to perform graceful shutdown
  519. //-----------------------------------------------------------------------------
  520. void CGCBase::Shutdown()
  521. {
  522. VPROF_BUDGET( "CGCBase::Shutdown", VPROF_BUDGETGROUP_STEAM );
  523. m_DumpHTTPErrorsSchedule.Cancel();
  524. CGCShutdownJob *pJob = new CGCShutdownJob( this );
  525. pJob->StartJob( NULL );
  526. }
  527. //-----------------------------------------------------------------------------
  528. // Purpose: Cleans up the GC to prepare for shutdown
  529. //-----------------------------------------------------------------------------
  530. void CGCBase::Uninit( )
  531. {
  532. VPROF_BUDGET( "CGCBase::Uninit", VPROF_BUDGETGROUP_STEAM );
  533. OnUninit();
  534. // clean up all of the sessions and caches here so we can be sure it happens before the memory pools go away at static destruction time
  535. for( CGCUserSession **ppSession = m_hashUserSessions.PvRecordFirst(); ppSession != NULL; ppSession = m_hashUserSessions.PvRecordNext( ppSession ) )
  536. {
  537. delete (*ppSession);
  538. }
  539. m_hashUserSessions.RemoveAll();
  540. for( CGCGSSession **ppSession = m_hashGSSessions.PvRecordFirst(); ppSession != NULL; ppSession = m_hashGSSessions.PvRecordNext( ppSession ) )
  541. {
  542. delete (*ppSession);
  543. }
  544. m_hashGSSessions.RemoveAll();
  545. FOR_EACH_MAP_FAST( m_mapSOCache, nIndex )
  546. {
  547. // Remove from map before deleting, to prevent some debug
  548. // code from getting tangled up
  549. CGCSharedObjectCache *pCache = m_mapSOCache[nIndex];
  550. m_mapSOCache[nIndex] = NULL;
  551. m_mapSOCache.RemoveAt( nIndex );
  552. delete pCache;
  553. }
  554. m_mapSOCache.RemoveAll();
  555. m_rbtreeSOCachesBeingLoaded.RemoveAll();
  556. m_rbtreeSOCachesWithDirtyVersions.RemoveAll();
  557. m_hashSteamIDLocks.RemoveAll();
  558. GSchemaFull().Uninit();
  559. SpewOutputFunc( m_OutputFuncPrev );
  560. }
  561. GCConVar cv_flush_inventory_cache_jobs( "cv_flush_inventory_cache_jobs", "20", 0, "The maximum number of jobs flushing inventory caches that can be in flight at once, zero to disable flushing" );
  562. GCConVar cv_flush_inventory_cache_contextid( "cv_flush_inventory_cache_contextid", "2" /* k_EEconContextBackpack */, 0, "Which context id we flush for Steam web user-facing inventory" );
  563. GCConVar cv_flush_inventory_cache_spew( "cv_flush_inventory_cache_spew", "0", 0, "Controls spew level for jobs flushing inventory cache (0=off; 1=summary; 2=verbose)" );
  564. class CFlushInventoryCacheAccountsJob : public CGCJob, public IYieldingParallelFarmJobHandler
  565. {
  566. public:
  567. CFlushInventoryCacheAccountsJob( CGCBase *pGC, CUtlRBTree< AccountID_t, int32, CDefLess< AccountID_t > > &rbAccounts ) : CGCJob( pGC )
  568. {
  569. m_rbAccounts.Swap( rbAccounts );
  570. }
  571. virtual bool BYieldingRunGCJob() OVERRIDE
  572. {
  573. if ( !m_rbAccounts.Count() )
  574. return false;
  575. if ( cv_flush_inventory_cache_jobs.GetInt() <= 0 )
  576. return false;
  577. bool bShouldSpew = ( cv_flush_inventory_cache_spew.GetInt() >= 1 );
  578. uint32 msTimeStart = 0;
  579. int numAccountsWorkload = m_rbAccounts.Count();
  580. if ( bShouldSpew )
  581. {
  582. msTimeStart = Plat_MSTime();
  583. }
  584. { // Run parallel processing of the workload
  585. int numJobs = numAccountsWorkload;
  586. numJobs = MIN( cv_flush_inventory_cache_jobs.GetInt(), numJobs );
  587. numJobs = MAX( 1, numJobs );
  588. ( void ) BYieldingExecuteParallel( numJobs, "YieldingFlushInventoryCacheAccountsJob" );
  589. }
  590. if ( bShouldSpew )
  591. {
  592. EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "IEconService/FlushInventoryCache: Batch for %d accounts completed in %u ms\n",
  593. numAccountsWorkload, Plat_MSTime() - msTimeStart );
  594. }
  595. return true;
  596. }
  597. virtual bool BYieldingRunWorkload( int iJobSequenceCounter, bool *pbWorkloadCompleted ) OVERRIDE
  598. {
  599. if ( m_rbAccounts.Count() )
  600. {
  601. int32 idxElement = m_rbAccounts.FirstInorder();
  602. AccountID_t unAccountID = m_rbAccounts.Element( idxElement );
  603. m_rbAccounts.RemoveAt( idxElement );
  604. ( void ) BYieldingFlushRequest( unAccountID );
  605. }
  606. if ( !m_rbAccounts.Count() )
  607. {
  608. *pbWorkloadCompleted = true;
  609. }
  610. return true;
  611. }
  612. bool BYieldingFlushRequest( AccountID_t unAccountID )
  613. {
  614. bool bShouldSpew = ( cv_flush_inventory_cache_spew.GetInt() >= 2 );
  615. uint32 msTimeStart = 0;
  616. if ( bShouldSpew )
  617. {
  618. msTimeStart = Plat_MSTime();
  619. }
  620. CSteamID steamID( GGCInterface()->ConstructSteamIDForClient( unAccountID ) );
  621. CSteamAPIRequest apiRequest( k_EHTTPMethodPOST, "IEconService", "FlushInventoryCache", 1 );
  622. apiRequest.SetPOSTParamUInt32( "appid", GGCBase()->GetAppID() );
  623. apiRequest.SetPOSTParamUInt64( "steamid", steamID.ConvertToUint64() );
  624. apiRequest.SetPOSTParamUInt32( "contextid", 2 );
  625. CHTTPResponse apiResponse;
  626. bool bSucceededQuery = m_pGC->BYieldingSendHTTPRequest( &apiRequest, &apiResponse );
  627. if ( !bSucceededQuery )
  628. {
  629. EmitErrorRatelimited( SPEW_GC, "IEconService/FlushInventoryCache: Web call did not get a response for %s.\n", steamID.Render() );
  630. }
  631. else if ( k_EHTTPStatusCode200OK != apiResponse.GetStatusCode() )
  632. {
  633. EmitErrorRatelimited( SPEW_GC, "IEconService/FlushInventoryCache: Web call got failure code %d for %s\n", apiResponse.GetStatusCode(), steamID.Render() );
  634. bSucceededQuery = false;
  635. }
  636. if ( bSucceededQuery )
  637. {
  638. // Have a valid response
  639. KeyValuesAD pKVResponse( "response" );
  640. pKVResponse->UsesEscapeSequences( true );
  641. if ( !pKVResponse->LoadFromBuffer( "webResponse", *apiResponse.GetBodyBuffer() ) )
  642. {
  643. EmitErrorRatelimited( SPEW_GC, "IEconService/FlushInventoryCache: Web call got code %d for %s, but failed to parse response\n", apiResponse.GetStatusCode(), steamID.Render() );
  644. bSucceededQuery = false;
  645. }
  646. else if ( !pKVResponse->GetBool( "success" ) )
  647. {
  648. // We got a response, and it's not success
  649. EmitErrorRatelimited( SPEW_GC, "IEconService/FlushInventoryCache: Web call got code %d for %s, but not success\n", apiResponse.GetStatusCode(), steamID.Render() );
  650. bSucceededQuery = false;
  651. }
  652. }
  653. if ( bShouldSpew )
  654. {
  655. EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "IEconService/FlushInventoryCache: Web call for %s %s in %u ms\n",
  656. steamID.Render(), bSucceededQuery ? "succeeded" : "failed", Plat_MSTime() - msTimeStart );
  657. }
  658. return bSucceededQuery;
  659. }
  660. public:
  661. CUtlRBTree< AccountID_t, int32, CDefLess< AccountID_t > > m_rbAccounts;
  662. };
  663. //-----------------------------------------------------------------------------
  664. // Purpose: Called every frame. Mostly updates times and pulses the job manager
  665. //-----------------------------------------------------------------------------
  666. bool CGCBase::BMainLoopOncePerFrame( uint64 ulLimitMicroseconds )
  667. {
  668. // if we don't have a GCHost yet, don't do any work per frame
  669. if( !GGCHost() )
  670. return false;
  671. #ifndef STEAM
  672. CRTime::UpdateRealTime();
  673. #endif
  674. #ifdef VPROF_ENABLED
  675. // Make sure we end the frame at the root node
  676. if ( !g_VProfCurrentProfile.AtRoot() && m_bDumpVprofImbalances )
  677. {
  678. EmitWarning( SPEW_GC, SPEW_ALWAYS, "VProf not at root at end of frame. Stack:\n" );
  679. }
  680. for( int i = 0; !g_VProfCurrentProfile.AtRoot() && i < 100; i++ )
  681. {
  682. if ( m_bDumpVprofImbalances )
  683. {
  684. EmitWarning( SPEW_GC, SPEW_ALWAYS, " %s\n", g_VProfCurrentProfile.GetCurrentNode()->GetName() );
  685. }
  686. g_VProfCurrentProfile.ExitScope();
  687. }
  688. g_VProfCurrentProfile.MarkFrame();
  689. if ( m_bStopProfiling || m_bStartProfiling )
  690. {
  691. while ( g_VProfCurrentProfile.IsEnabled() )
  692. {
  693. g_VProfCurrentProfile.Stop();
  694. }
  695. m_bStopProfiling = false;
  696. if ( m_bStartProfiling )
  697. {
  698. g_VProfCurrentProfile.Reset();
  699. g_VProfCurrentProfile.Start();
  700. m_bStartProfiling = false;
  701. }
  702. }
  703. #endif
  704. VPROF_BUDGET( "Main Loop", VPROF_BUDGETGROUP_STEAM );
  705. CLimitTimer limitTimer;
  706. limitTimer.SetLimit( ulLimitMicroseconds );
  707. CJobTime::UpdateJobTime( k_cMicroSecPerShellFrame );
  708. bool bWorkRemaining = m_JobMgr.BFrameFuncRunSleepingJobs( limitTimer );
  709. //run all of our frame functions
  710. GFrameFunctionMgr().RunFrame( limitTimer );
  711. {
  712. VPROF_BUDGET( "Run Sessions", VPROF_BUDGETGROUP_STEAM );
  713. m_AccountDetailsManager.MarkFrame();
  714. m_hashUserSessions.StartFrameSchedule( true );
  715. m_hashGSSessions.StartFrameSchedule( true );
  716. m_hashSteamIDLocks.StartFrameSchedule( true );
  717. bool bUsersFinished = false, bGSFinished = false;
  718. while( !limitTimer.BLimitReached() && ( !bUsersFinished || !bGSFinished ) )
  719. {
  720. if( !bUsersFinished )
  721. {
  722. CGCUserSession **ppSession = m_hashUserSessions.PvRecordRun();
  723. if ( ppSession && *ppSession )
  724. {
  725. (*ppSession)->Run();
  726. }
  727. else
  728. {
  729. bUsersFinished = true;
  730. }
  731. if ( m_hashUserSessions.BCompletedPass() )
  732. {
  733. FinishedMainLoopUserSweep();
  734. }
  735. }
  736. if( !bGSFinished )
  737. {
  738. CGCGSSession **ppSession = m_hashGSSessions.PvRecordRun();
  739. if ( ppSession && *ppSession )
  740. {
  741. (*ppSession)->Run();
  742. }
  743. else
  744. {
  745. bGSFinished = true;
  746. }
  747. }
  748. }
  749. }
  750. {
  751. VPROF_BUDGET( "UpdateSOCacheVersions", VPROF_BUDGETGROUP_STEAM );
  752. UpdateSOCacheVersions();
  753. }
  754. if( m_llStartPlaying.Count() > 0 )
  755. {
  756. VPROF_BUDGET( "StartStartPlayingJobs", VPROF_BUDGETGROUP_STEAM );
  757. int nJobsNeeded = min( m_llStartPlaying.Count(), cv_concurrent_start_playing_limit.GetInt() - m_nStartPlayingJobCount );
  758. while( nJobsNeeded > 0 )
  759. {
  760. nJobsNeeded--;
  761. m_nStartPlayingJobCount++;
  762. CExecuteStartPlayingJob *pJob = new CExecuteStartPlayingJob( this );
  763. pJob->StartJob( NULL );
  764. }
  765. }
  766. // Decide if we should be in logon surge
  767. bool bShouldBeInlogonSurge =
  768. m_llStartPlaying.Count() >= cv_logon_surge_start_playing_limit.GetInt();
  769. // This might be a good idea, but let's see what the real numbers are during logon surge.
  770. //|| m_nRequestSessionJobsActive >= cv_logon_surge_request_session_jobs.GetInt();
  771. // Check if we're already in logon surge, is it time to check if we should leave,
  772. // and should we dump our status periodically?
  773. const int k_nLogonSurgeFrameInterval = k_nMillion * 10 / k_cMicroSecPerShellFrame;
  774. if ( m_nLogonSurgeFramesRemaining > 0 )
  775. {
  776. // Currently in logon surge
  777. --m_nLogonSurgeFramesRemaining;
  778. if ( m_nLogonSurgeFramesRemaining == 0 )
  779. {
  780. // Time to check for leaving logon surge mode.
  781. // Should I flip the flag off?
  782. if ( bShouldBeInlogonSurge )
  783. {
  784. // We're still in logon surge. Schedule another check
  785. // a few frames from now, and dump our status.
  786. m_nLogonSurgeFramesRemaining = k_nLogonSurgeFrameInterval;
  787. Dump();
  788. }
  789. else
  790. {
  791. // We're over the hump!
  792. EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "** LOGON SURGE COMPLETED **\n" );
  793. }
  794. }
  795. }
  796. else if ( bShouldBeInlogonSurge )
  797. {
  798. // We finished logon surge one, but now we are re-entering it.
  799. // This usually doesn't happen. This is suspicious.
  800. EmitWarning( SPEW_GC, SPEW_ALWAYS, "RE-ENTERING logon surge mode!\n" );
  801. m_nLogonSurgeFramesRemaining = k_nLogonSurgeFrameInterval;
  802. }
  803. else
  804. {
  805. // Not in logon surge. make sure flag is slammed to zero
  806. m_nLogonSurgeFramesRemaining = 0;
  807. }
  808. // Flush inventory cache for accounts
  809. if ( m_rbFlushInventoryCacheAccounts.Count() && ( ( m_jobidFlushInventoryCacheAccounts == k_GIDNil ) ||
  810. !GetJobMgr().BJobExists( m_jobidFlushInventoryCacheAccounts ) ) )
  811. {
  812. m_numFlushInventoryCacheAccountsLastScheduled = m_rbFlushInventoryCacheAccounts.Count();
  813. m_jobidFlushInventoryCacheAccounts = StartNewJobDelayed( new CFlushInventoryCacheAccountsJob( this, m_rbFlushInventoryCacheAccounts ) )->GetJobID();
  814. }
  815. bool bSubRet = OnMainLoopOncePerFrame( limitTimer );
  816. return bWorkRemaining || bSubRet;
  817. }
  818. bool CGCBase::BShouldThrottleLowServiceLevelWebAPIJobs() const
  819. {
  820. // Always throttle them during logon surge.
  821. if ( BIsInLogonSurge() )
  822. return true;
  823. // Check threshold
  824. if ( m_JobMgr.CountJobs() > cv_webapi_throttle_job_threshold.GetInt() )
  825. return true;
  826. // We are not too busy, we can service the request
  827. return false;
  828. }
  829. bool CGCBase::BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds )
  830. {
  831. VPROF_BUDGET( "Main Loop", VPROF_BUDGETGROUP_STEAM );
  832. CLimitTimer limitTimer;
  833. limitTimer.SetLimit( ulLimitMicroseconds );
  834. bool bRet = m_JobMgr.BFrameFuncRunYieldingJobs( limitTimer );
  835. bRet |= GSDOCache().BFrameFuncRunJobsUntilCompleted( limitTimer );
  836. bRet |= GSDOCache().BFrameFuncRunMemcachedQueriesUntilCompleted( limitTimer );
  837. bRet |= GSDOCache().BFrameFuncRunSQLQueriesUntilCompleted( limitTimer );
  838. bRet |= m_AccountDetailsManager.BExpireRecords( limitTimer );
  839. bool bSubRet = OnMainLoopUntilFrameCompletion( limitTimer );
  840. bRet |= GFrameFunctionMgr().RunFrameTick( limitTimer );
  841. {
  842. VPROF_BUDGET( "Expire locks", VPROF_BUDGETGROUP_STEAM );
  843. for ( CLock *pLock = m_hashSteamIDLocks.PvRecordRun(); NULL != pLock; pLock = m_hashSteamIDLocks.PvRecordRun() )
  844. {
  845. if ( !pLock->BIsLocked() && pLock->GetMicroSecondsSinceLock() > k_cMicroSecLockLifetime )
  846. {
  847. m_hashSteamIDLocks.Remove( pLock );
  848. }
  849. if ( limitTimer.BLimitReached() )
  850. return true;
  851. }
  852. }
  853. return bRet || bSubRet;
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose: Called when we get to the end of a user session Run() sweep, and
  857. // are about to start over with the first session in the list.
  858. //-----------------------------------------------------------------------------
  859. void CGCBase::FinishedMainLoopUserSweep()
  860. {
  861. // Base class does nothing
  862. }
  863. //-----------------------------------------------------------------------------
  864. // Purpose: Queues up a start playing request that we should process when we
  865. // get a chance.
  866. //-----------------------------------------------------------------------------
  867. void CGCBase::QueueStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData )
  868. {
  869. MEM_ALLOC_CREDIT_( "QueueStartPlaying" );
  870. Assert( steamID.BIndividualAccount() || steamID.BGameServerAccount() );
  871. Assert( steamID.IsValid() );
  872. // Should be one-to-one correspondence in these data structures
  873. Assert( (size_t)m_mapStartPlayingQueueIndexBySteamID.Count() == (size_t)m_llStartPlaying.Count() );
  874. // !FIXME! Here we really should check whether they already have a session.
  875. // if so, we've already gone through all the startplaying work and shouldn't
  876. // repeat it. We might just need to kick the communications or make
  877. // sure they are on the right game server.
  878. // Check if we already have an entry in the queue for this guy.
  879. StartPlayingWork_t *pWork = NULL;
  880. int nMapIndex = m_mapStartPlayingQueueIndexBySteamID.Find( steamID );
  881. if ( nMapIndex != m_mapStartPlayingQueueIndexBySteamID.InvalidIndex() )
  882. {
  883. // We already have an entry for this guy, let's update this one, rather than creating a new one
  884. int nQueueIndex = m_mapStartPlayingQueueIndexBySteamID[ nMapIndex ];
  885. pWork = &m_llStartPlaying[ nQueueIndex ];
  886. // Sanity check data structures. I'd use an assert,
  887. // but this is going live in an environment without
  888. // asserts enabled, so I need to use spew.
  889. if ( pWork->m_steamID == steamID )
  890. {
  891. // Don't leak user data, if we had any
  892. delete pWork->m_pVarData;
  893. pWork->m_pVarData = NULL;
  894. // // This could definitely happen occasionally, but if it happens with massive frequency,
  895. // // something is wrong
  896. // if ( gsSteamID == pWork->m_gsSteamID )
  897. // {
  898. // EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "Got StartPlaying message for %s, who was already in the startplaying queue for the same gameserver %s.\n", steamID.Render(), gsSteamID.Render() );
  899. // }
  900. // else
  901. // {
  902. // EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "Got StartPlaying message for %s, who was already in the startplaying queue; changing gameserver %s -> %s.\n", steamID.Render(), pWork->m_gsSteamID.Render(), gsSteamID.Render() );
  903. // }
  904. }
  905. else
  906. {
  907. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Start playing queue corruption! Map entry points to wrong queue entry!\n" );
  908. pWork = NULL;
  909. m_mapStartPlayingQueueIndexBySteamID.RemoveAt( nMapIndex );
  910. }
  911. }
  912. else
  913. {
  914. // EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "Got StartPlaying message for %s, new queue for gameserver %s.\n", steamID.Render(), gsSteamID.Render() );
  915. }
  916. // Need to create a new entry?
  917. if ( pWork == NULL )
  918. {
  919. // Create a new queue entry
  920. int nQueueIndex = m_llStartPlaying.AddToTail();
  921. pWork = &m_llStartPlaying[ nQueueIndex ];
  922. // Add it to the steam ID map, so we can locate this guy quickly in the future
  923. m_mapStartPlayingQueueIndexBySteamID.Insert( steamID, nQueueIndex );
  924. }
  925. // Fill in the queue entry with the latest details
  926. pWork->m_steamID = steamID;
  927. pWork->m_gsSteamID = gsSteamID;
  928. pWork->m_unServerAddr = unServerAddr;
  929. pWork->m_usServerPort = usServerPort;
  930. if( cubVarData )
  931. {
  932. pWork->m_pVarData = new CUtlBuffer;
  933. pWork->m_pVarData->Put( pubVarData, cubVarData );
  934. }
  935. else
  936. {
  937. pWork->m_pVarData = NULL;
  938. }
  939. // Should be one-to-one correspondence in these data structures
  940. Assert( (size_t)m_mapStartPlayingQueueIndexBySteamID.Count() == (size_t)m_llStartPlaying.Count() );
  941. }
  942. //-----------------------------------------------------------------------------
  943. bool CGCBase::BRemoveStartPlayingQueueEntry( const CSteamID & steamID )
  944. {
  945. int nMapIndex = m_mapStartPlayingQueueIndexBySteamID.Find( steamID );
  946. if ( nMapIndex == m_mapStartPlayingQueueIndexBySteamID.InvalidIndex() )
  947. {
  948. return false;
  949. }
  950. //EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "Removed startplaying queue entry for %s.\n", steamID.Render() );
  951. // Locate queue entry, make sure it matches, and remote it
  952. int nQueueIndex = m_mapStartPlayingQueueIndexBySteamID[ nMapIndex ];
  953. if ( m_llStartPlaying[ nQueueIndex ].m_steamID == steamID )
  954. {
  955. delete m_llStartPlaying[ nQueueIndex ].m_pVarData;
  956. m_llStartPlaying.Remove( nQueueIndex );
  957. }
  958. else
  959. {
  960. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Start playing queue corruption! Map entry doesn't point to matching queue index (found while removing entry in BRemoveStartPlayingQueueEntry)!\n" );
  961. }
  962. // Remove from map
  963. m_mapStartPlayingQueueIndexBySteamID.RemoveAt( nQueueIndex );
  964. // Found and removed
  965. return true;
  966. }
  967. //-----------------------------------------------------------------------------
  968. // Purpose: Pull the next startplaying job off the queue and executes it
  969. //-----------------------------------------------------------------------------
  970. void CGCBase::YieldingExecuteNextStartPlaying()
  971. {
  972. // maybe we have nothing to do!
  973. if( m_llStartPlaying.Count() > 0 )
  974. {
  975. // Execute the entry at the head
  976. YieldingExecuteStartPlayingQueueEntryByIndex( m_llStartPlaying.Head() );
  977. }
  978. m_nStartPlayingJobCount--;
  979. }
  980. //-----------------------------------------------------------------------------
  981. // Purpose: Executes a single entry from the start playing queue, given the linked list handle
  982. //-----------------------------------------------------------------------------
  983. void CGCBase::YieldingExecuteStartPlayingQueueEntryByIndex( int idxStartPlayingQueue )
  984. {
  985. VPROF_BUDGET( "CGCBase::YieldingExecuteStartPlayingQueueEntryByIndex - LinkedList", VPROF_BUDGETGROUP_STEAM );
  986. // Remove the entry from the queue
  987. StartPlayingWork_t work = m_llStartPlaying[ idxStartPlayingQueue ];
  988. m_llStartPlaying.Remove( idxStartPlayingQueue );
  989. VPROF_BUDGET( "CGCBase::YieldingExecuteStartPlayingQueueEntryByIndex", VPROF_BUDGETGROUP_STEAM );
  990. // Remove it from the Steam ID map, too.
  991. int nMapIndex = m_mapStartPlayingQueueIndexBySteamID.Find( work.m_steamID );
  992. if ( nMapIndex == m_mapStartPlayingQueueIndexBySteamID.InvalidIndex() )
  993. {
  994. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Start playing queue corruption! Queue entry is not in map!\n" );
  995. }
  996. else if ( m_mapStartPlayingQueueIndexBySteamID[ nMapIndex ] != idxStartPlayingQueue )
  997. {
  998. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Start playing queue corruption! Map entry doesn't have proper queue index!\n" );
  999. }
  1000. else
  1001. {
  1002. m_mapStartPlayingQueueIndexBySteamID.RemoveAt( nMapIndex );
  1003. }
  1004. // Do the work.
  1005. if ( work.m_steamID.BIndividualAccount() )
  1006. {
  1007. YieldingStartPlaying( work.m_steamID, work.m_gsSteamID, work.m_unServerAddr, work.m_usServerPort, work.m_pVarData );
  1008. }
  1009. else if ( work.m_steamID.BGameServerAccount() )
  1010. {
  1011. const uint8 *pVarData = NULL;
  1012. uint32 cubVarData = 0;
  1013. if ( work.m_pVarData != NULL )
  1014. {
  1015. pVarData = (const uint8 *)work.m_pVarData->Base();
  1016. cubVarData = work.m_pVarData->TellMaxPut();
  1017. }
  1018. YieldingStartGameserver( work.m_steamID, work.m_unServerAddr, work.m_usServerPort, pVarData, cubVarData );
  1019. }
  1020. else
  1021. {
  1022. AssertMsg1( false, "Bogus steam ID %s in start playing queue", work.m_steamID.Render() );
  1023. }
  1024. // Clean up
  1025. delete work.m_pVarData;
  1026. }
  1027. void CGCBase::SetUserSessionDetails( CGCUserSession *pUserSession, KeyValues *pkvDetails )
  1028. {
  1029. if( pkvDetails )
  1030. {
  1031. pUserSession->m_unIPPublic = pkvDetails->GetInt( "ip", 0 );
  1032. pUserSession->m_osType = static_cast<EOSType>( pkvDetails->GetInt( "osType", k_eOSUnknown ) );
  1033. pUserSession->m_bIsTestSession = pkvDetails->GetInt( "isTestSession", 0 ) != 0;
  1034. pUserSession->m_bIsSecure = pkvDetails->GetInt( "secure", 0 ) != 0;
  1035. }
  1036. }
  1037. //-----------------------------------------------------------------------------
  1038. // Purpose: Does the real work when a player starts playing (inside a job)
  1039. //-----------------------------------------------------------------------------
  1040. void CGCBase::YieldingStartPlaying( const CSteamID & steamID, const CSteamID & gsSteamID, uint32 unServerAddr, uint16 usServerPort, CUtlBuffer *pVarData )
  1041. {
  1042. VPROF_BUDGET( "CGCBase::YieldingStartPlaying", VPROF_BUDGETGROUP_STEAM );
  1043. if ( m_bIsShuttingDown )
  1044. return;
  1045. if( !BYieldingLockSteamID( steamID, __FILE__, __LINE__ ) )
  1046. {
  1047. EmitError( SPEW_GC, "Failed to lock steamID %s in YieldingStartPlaying\n", steamID.Render() );
  1048. return;
  1049. }
  1050. // if var data came with this StartPlaying message, parse it into a KV and stick it on the session
  1051. KeyValues *pkvDetails = NULL;
  1052. if( pVarData )
  1053. {
  1054. MEM_ALLOC_CREDIT_("StartPlaying - SessionDetails" );
  1055. pkvDetails = new KeyValues( "SessionDetails" );
  1056. if( !pkvDetails->ReadAsBinary( *pVarData ) )
  1057. {
  1058. EmitError( SPEW_GC, "Unable to parse session details for %s\n", steamID.Render() );
  1059. pkvDetails->deleteThis();
  1060. pkvDetails = NULL;
  1061. }
  1062. }
  1063. CGCUserSession *pSession = FindUserSession( steamID );
  1064. if( !pSession )
  1065. {
  1066. // Load their SO cache. Remember, we already have their steam ID locked.
  1067. VPROF_BUDGET( "CGCBase::YieldingStartPlaying - Load SOCache", VPROF_BUDGETGROUP_STEAM );
  1068. CGCSharedObjectCache *pSOCache = YieldingFindOrLoadSOCache( steamID );
  1069. if ( !pSOCache )
  1070. {
  1071. EmitError( SPEW_GC, "Failed to get cache for user %s\n", steamID.Render() );
  1072. return;
  1073. }
  1074. // Create session of app-specific type
  1075. VPROF_BUDGET( "CGCBase::YieldingStartPlaying - CreateUserSession", VPROF_BUDGETGROUP_STEAM );
  1076. pSession = CreateUserSession( steamID, pSOCache );
  1077. if ( !pSession )
  1078. {
  1079. EmitError( SPEW_GC, "Failed to create user session for %s\n", steamID.Render() );
  1080. return;
  1081. }
  1082. VPROF_BUDGET( "CGCBase::YieldingStartPlaying - LRU Update", VPROF_BUDGETGROUP_STEAM );
  1083. RemoveCacheFromLRU( pSOCache );
  1084. CGCUserSession **ppSession = m_hashUserSessions.PvRecordInsert( steamID.ConvertToUint64() );
  1085. *ppSession = pSession;
  1086. SetUserSessionDetails( pSession, pkvDetails );
  1087. // Do game-specific logic here. Note that we're still holding the game server
  1088. // lock...
  1089. VPROF_BUDGET( "CGCBase::YieldingStartPlaying - Game-specific start playing", VPROF_BUDGETGROUP_STEAM );
  1090. YieldingSessionStartPlaying( pSession );
  1091. }
  1092. else if ( pSession->BIsShuttingDown() )
  1093. {
  1094. pkvDetails->deleteThis();
  1095. pkvDetails = NULL;
  1096. return;
  1097. }
  1098. else
  1099. {
  1100. // Update secure flag, etc from KV details, if any
  1101. SetUserSessionDetails( pSession, pkvDetails );
  1102. }
  1103. if ( pkvDetails )
  1104. {
  1105. pkvDetails->deleteThis();
  1106. pkvDetails = NULL;
  1107. }
  1108. VPROF_BUDGET( "CGCBase::YieldingStartPlaying - Game Server binding", VPROF_BUDGETGROUP_STEAM );
  1109. // Make sure the server exists and then try to join it
  1110. if ( gsSteamID.IsValid() && gsSteamID.BGameServerAccount() && BYieldingLockSteamID( gsSteamID, __FILE__, __LINE__ ) )
  1111. {
  1112. // First, try to obtain a session through ordinary means, by validating
  1113. // the session
  1114. if ( YieldingGetLockedGSSession( gsSteamID, __FILE__, __LINE__ ) != NULL )
  1115. {
  1116. // Maintain lock balance
  1117. UnlockSteamID( gsSteamID );
  1118. }
  1119. else
  1120. {
  1121. // Failed to get a session --- probably an AM is down.
  1122. // This is hopefully relatively rare, as it's not ideal.
  1123. // log it
  1124. if ( enable_startplaying_gameserver_creation_spew.GetBool() )
  1125. {
  1126. netadr_t serverAdr( unServerAddr, usServerPort );
  1127. EmitInfo( SPEW_GC, 2, LOG_ALWAYS, "Creating gameserver session %s @ %s as a result of user %s StartPlaying.\n", gsSteamID.Render(), serverAdr.ToString(), steamID.Render() );
  1128. }
  1129. YieldingFindOrCreateGSSession( gsSteamID, unServerAddr, usServerPort, NULL, 0 );
  1130. }
  1131. // Mark that we are joined to this server
  1132. pSession->BSetServer( gsSteamID );
  1133. // Done, clean up lock
  1134. UnlockSteamID( gsSteamID );
  1135. }
  1136. else
  1137. {
  1138. // Steam was sometimes sending us messages with zero Steam ID, even when we're on a server.
  1139. if ( cv_debug_steam_startplaying.GetBool() )
  1140. EmitInfo( SPEW_GC, 1, 1, "YieldingStartPlaying ( user = %s ) with invalid GS steam ID %s, calling LeaveServer\n", steamID.Render(), gsSteamID.Render() );
  1141. pSession->BLeaveServer();
  1142. }
  1143. }
  1144. //-----------------------------------------------------------------------------
  1145. // Purpose: Called when a player stops playing our game
  1146. //-----------------------------------------------------------------------------
  1147. void CGCBase::YieldingStopPlaying( const CSteamID & steamID )
  1148. {
  1149. // Should be one-to-one correspondence in these data structures
  1150. Assert( (size_t)m_mapStartPlayingQueueIndexBySteamID.Count() == (size_t)m_llStartPlaying.Count() );
  1151. // Check if they have an entry in the startplaying queue, then get rid of it!
  1152. BRemoveStartPlayingQueueEntry( steamID );
  1153. if ( !BLockSteamIDImmediate( steamID ) )
  1154. {
  1155. CGCUserSession *pSession = FindUserSession( steamID );
  1156. if ( !pSession )
  1157. {
  1158. return;
  1159. }
  1160. pSession->SetIsShuttingDown( true );
  1161. if( !BYieldingLockSteamID( steamID, __FILE__, __LINE__ ) )
  1162. {
  1163. EmitError( SPEW_GC, "Unable to lock steamID %s in YieldingStopPlaying\n", steamID.Render() );
  1164. return;
  1165. }
  1166. }
  1167. CGCUserSession *pSession = FindUserSession( steamID );
  1168. if( pSession )
  1169. {
  1170. pSession->BLeaveServer();
  1171. YieldingSessionStopPlaying( pSession );
  1172. if( pSession->GetSOCache() )
  1173. {
  1174. AddCacheToLRU( pSession->GetSOCache() );
  1175. }
  1176. m_hashUserSessions.Remove( steamID.ConvertToUint64() );
  1177. delete pSession;
  1178. }
  1179. // Clean up lock. Even if the session is gone and there's nothing
  1180. // for the lock to protect, we need this to avoid spurious asserts that check
  1181. // lock imbalance
  1182. UnlockSteamID( steamID );
  1183. }
  1184. //-----------------------------------------------------------------------------
  1185. // Purpose: Called when a gameserver stops running for our game
  1186. //-----------------------------------------------------------------------------
  1187. void CGCBase::YieldingStartGameserver( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData )
  1188. {
  1189. VPROF_BUDGET( "CGCBase::YieldingStartGameserver", VPROF_BUDGETGROUP_STEAM );
  1190. if ( m_bIsShuttingDown )
  1191. return;
  1192. if( !BYieldingLockSteamID( steamID, __FILE__, __LINE__ ) )
  1193. {
  1194. EmitError( SPEW_GC, "Failed to lock steamID %s in YieldingStartGameserver\n", steamID.Render() );
  1195. return;
  1196. }
  1197. YieldingFindOrCreateGSSession( steamID, unServerAddr, usServerPort, pubVarData, cubVarData );
  1198. // Clean up
  1199. UnlockSteamID( steamID );
  1200. }
  1201. //-----------------------------------------------------------------------------
  1202. // Purpose: Called when a gameserver stops running for our game
  1203. //-----------------------------------------------------------------------------
  1204. void CGCBase::YieldingStopGameserver( const CSteamID & steamID )
  1205. {
  1206. // Should be one-to-one correspondence in these data structures
  1207. Assert( (size_t)m_mapStartPlayingQueueIndexBySteamID.Count() == (size_t)m_llStartPlaying.Count() );
  1208. // Check if they have an entry in the startplaying queue, then get rid of it!
  1209. BRemoveStartPlayingQueueEntry( steamID );
  1210. if ( !BLockSteamIDImmediate( steamID ) )
  1211. {
  1212. CGCGSSession *pSession = FindGSSession( steamID );
  1213. if ( !pSession )
  1214. {
  1215. return;
  1216. }
  1217. pSession->SetIsShuttingDown( true );
  1218. if( !BYieldingLockSteamID( steamID, __FILE__, __LINE__ ) )
  1219. {
  1220. EmitError( SPEW_GC, "Unable to lock steamID %s in YieldingStopGameserver\n", steamID.Render() );
  1221. return;
  1222. }
  1223. }
  1224. CGCGSSession *pSession = FindGSSession( steamID );
  1225. if( pSession )
  1226. {
  1227. pSession->RemoveAllUsers();
  1228. YieldingSessionStopServer( pSession );
  1229. if( pSession->GetSOCache() )
  1230. {
  1231. AddCacheToLRU( pSession->GetSOCache() );
  1232. }
  1233. m_hashGSSessions.Remove( steamID.ConvertToUint64() );
  1234. delete pSession;
  1235. }
  1236. // Clean up lock. Even if the session is gone and there's nothing
  1237. // for the lock to protect, we need this to avoid spurious asserts that check
  1238. // lock imbalance
  1239. UnlockSteamID( steamID );
  1240. }
  1241. IMsgNetPacket *CreateIMsgNetPacket( GCProtoBufMsgSrc eReplyType, const CSteamID senderID, uint32 nGCDirIndex, uint32 unMsgType, void *pubData, uint32 cubData )
  1242. {
  1243. VPROF_BUDGET( "CreateIMsgNetPacket", VPROF_BUDGETGROUP_STEAM );
  1244. if( 0 != ( unMsgType & k_EMsgProtoBufFlag ) )
  1245. {
  1246. if ( cubData < sizeof( ProtoBufMsgHeader_t ) )
  1247. {
  1248. uint32 unMsgTypeNoFlag = unMsgType & (~k_EMsgProtoBufFlag);
  1249. AssertMsg3( false, "Received packet %s(%u) from %s less than the minimum protobuf size", PchMsgNameFromEMsg( unMsgTypeNoFlag ), unMsgTypeNoFlag, senderID.Render() );
  1250. return NULL;
  1251. }
  1252. // make a new packet for the message so we can dispatch it
  1253. // The CNetPacket takes ownership of the buffer allocated above
  1254. CNetPacket *pGCPacket = CNetPacketPool::AllocNetPacket();
  1255. pGCPacket->Init( cubData );
  1256. // copy the bits for the message over to the full size buffer
  1257. Q_memcpy( pGCPacket->PubData(), pubData, cubData );
  1258. CProtoBufNetPacket *pMsgNetPacket = new CProtoBufNetPacket( pGCPacket, eReplyType, senderID, nGCDirIndex, unMsgType & ( ~k_EMsgProtoBufFlag ) );
  1259. // release the inner packet since the wrapper now has a ref to it
  1260. pGCPacket->Release();
  1261. if ( !pMsgNetPacket->IsValid() )
  1262. {
  1263. pMsgNetPacket->Release();
  1264. return NULL;
  1265. }
  1266. return pMsgNetPacket;
  1267. }
  1268. else
  1269. {
  1270. //note that we do not currently support reply to GC messages through this pipeline
  1271. AssertMsg( eReplyType != GCProtoBufMsgSrc_FromGC, "Warning: Encountered a message from GC to GC that was not of protobuff type, will be unable to reply to this message. Message type: %d", unMsgType );
  1272. if ( cubData < sizeof( GCMsgHdrEx_t ) - sizeof( GCMsgHdr_t ) )
  1273. {
  1274. AssertMsg( false, "Received packet %s(%u) from %s less than the minimum struct size", PchMsgNameFromEMsg( unMsgType ), unMsgType, senderID.Render() );
  1275. return NULL;
  1276. }
  1277. // Determine the size of the packet. sizeof(GCMsgHdr_t) was not sent as part of the data
  1278. uint32 unFullSize = cubData + sizeof( GCMsgHdr_t );
  1279. // make a new packet for the message so we can dispatch it
  1280. // The CNetPacket takes ownership of the buffer allocated above
  1281. CNetPacket *pGCPacket = CNetPacketPool::AllocNetPacket();
  1282. pGCPacket->Init( unFullSize );
  1283. //fill in our header and copy over the body
  1284. uint8 *pFullPacket = pGCPacket->PubData();
  1285. // get the header so we can fix it up
  1286. GCMsgHdrEx_t *pHdr = (GCMsgHdrEx_t *)pFullPacket;
  1287. //pHdr->m_nSrcGCDirIndex = nGCDirIndex;
  1288. pHdr->m_eMsg = unMsgType;
  1289. pHdr->m_ulSteamID = senderID.ConvertToUint64();
  1290. // copy the bits for the message over to the full size buffer
  1291. Q_memcpy( pFullPacket+sizeof(GCMsgHdr_t), pubData, cubData );
  1292. CStructNetPacket *pMsgNetPacket = new CStructNetPacket( pGCPacket );
  1293. // release the packet
  1294. pGCPacket->Release();
  1295. return pMsgNetPacket;
  1296. }
  1297. }
  1298. //-----------------------------------------------------------------------------
  1299. // Purpose: Processes an incoming message from the client by turning it into a
  1300. // CGCMsg and sending it on to a job.
  1301. //-----------------------------------------------------------------------------
  1302. void CGCBase::MessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData )
  1303. {
  1304. VPROF_BUDGET( "CGCBase::MessageFromClient", VPROF_BUDGETGROUP_STEAM );
  1305. // if we don't have a GCHost yet, we won't be able to do much with this message
  1306. if( !GGCHost() )
  1307. return;
  1308. if ( OnMessageFromClient( senderID, unMsgType, pubData, cubData ) )
  1309. return;
  1310. // Rate limit messages from ordinary clients
  1311. if ( senderID.IsValid() )
  1312. {
  1313. MsgType_t eMsg = unMsgType & ~k_EMsgProtoBufFlag;
  1314. if ( m_MsgRateLimit.BIsRateLimited( senderID, eMsg ) )
  1315. {
  1316. g_RateLimitTracker.TrackRateLimitedMsg( senderID, eMsg );
  1317. return;
  1318. }
  1319. }
  1320. // !FIXME! DOTAMERGE
  1321. uint32 nGCDirIndex = 0; // GetGCDirIndex()
  1322. IMsgNetPacket *pMsgNetPacket = CreateIMsgNetPacket( GCProtoBufMsgSrc_FromSteamID, senderID, nGCDirIndex, unMsgType, pubData, cubData );
  1323. if ( NULL == pMsgNetPacket )
  1324. return;
  1325. // dispatch the packet (some messages require special consideration)
  1326. switch( unMsgType )
  1327. {
  1328. case k_EGCMsgWGRequest:
  1329. m_wgJobMgr.BHandleMsg( pMsgNetPacket );
  1330. g_theMessageList.TallySendMessage( pMsgNetPacket->GetEMsg(), cubData );
  1331. break;
  1332. default:
  1333. GetJobMgr().BRouteMsgToJob( this, pMsgNetPacket, JobMsgInfo_t( pMsgNetPacket->GetEMsg(), pMsgNetPacket->GetSourceJobID(), pMsgNetPacket->GetTargetJobID(), k_EServerTypeGC ) );
  1334. g_theMessageList.TallySendMessage( pMsgNetPacket->GetEMsg(), cubData );
  1335. break;
  1336. }
  1337. // release the packet
  1338. pMsgNetPacket->Release();
  1339. }
  1340. //-----------------------------------------------------------------------------
  1341. // Purpose: Sends a message to the given SteamID
  1342. //-----------------------------------------------------------------------------
  1343. bool CGCBase::BSendGCMsgToClient( const CSteamID & steamIDTarget, const CGCMsgBase& msg )
  1344. {
  1345. g_theMessageList.TallySendMessage( msg.Hdr().m_eMsg, msg.CubPkt() - sizeof(GCMsgHdr_t) );
  1346. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  1347. {
  1348. VPROF_BUDGET( "GCHost - SendMessageToClient", VPROF_BUDGETGROUP_STEAM );
  1349. return m_pHost->BSendMessageToClient( m_unAppID, steamIDTarget, msg.Hdr().m_eMsg, msg.PubPkt() + sizeof(GCMsgHdr_t), msg.CubPkt() - sizeof(GCMsgHdr_t) );
  1350. }
  1351. }
  1352. //-----------------------------------------------------------------------------
  1353. // Purpose: Used to send protobuf system messages to a client
  1354. //-----------------------------------------------------------------------------
  1355. class CProtoBufClientSendHandler : public CProtoBufMsgBase::IProtoBufSendHandler
  1356. {
  1357. public:
  1358. CProtoBufClientSendHandler( const CSteamID & steamIDTarget )
  1359. : m_steamIDTarget( steamIDTarget ), m_cubSent( 0 ) {}
  1360. virtual bool BAsyncSend( MsgType_t eMsg, const uint8 *pubMsgBytes, uint32 cubSize ) OVERRIDE
  1361. {
  1362. m_cubSent = cubSize;
  1363. // !FIXME! DOTAMERGE
  1364. //return GGCInterface()->BProcessSystemMessage( eMsg | k_EMsgProtoBufFlag, pubMsgBytes, cubSize );
  1365. g_theMessageList.TallySendMessage( eMsg & ~k_EMsgProtoBufFlag, cubSize );
  1366. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  1367. {
  1368. VPROF_BUDGET( "GCHost - SendMessageToClient (ProtoBuf)", VPROF_BUDGETGROUP_STEAM );
  1369. return GGCHost()->BSendMessageToClient( GGCBase()->GetAppID(), m_steamIDTarget, eMsg | k_EMsgProtoBufFlag, pubMsgBytes, cubSize );
  1370. }
  1371. }
  1372. uint32 GetCubSent() const { return m_cubSent; }
  1373. private:
  1374. uint32 m_cubSent;
  1375. CSteamID m_steamIDTarget;
  1376. };
  1377. //-----------------------------------------------------------------------------
  1378. // Purpose: Used to send protobuf system messages into the GC
  1379. //-----------------------------------------------------------------------------
  1380. class CProtoBufSystemSendHandler : public CProtoBufMsgBase::IProtoBufSendHandler
  1381. {
  1382. public:
  1383. CProtoBufSystemSendHandler()
  1384. : m_cubSent( 0 ) {}
  1385. virtual bool BAsyncSend( MsgType_t eMsg, const uint8 *pubMsgBytes, uint32 cubSize ) OVERRIDE
  1386. {
  1387. m_cubSent = cubSize;
  1388. // !FIXME! DOTAMERGE
  1389. //return GGCInterface()->BProcessSystemMessage( eMsg | k_EMsgProtoBufFlag, pubMsgBytes, cubSize );
  1390. g_theMessageList.TallySendMessage( eMsg & ~k_EMsgProtoBufFlag, cubSize );
  1391. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  1392. {
  1393. VPROF_BUDGET( "GCHost - SendMessageToSystem (ProtoBuf)", VPROF_BUDGETGROUP_STEAM );
  1394. return GGCHost()->BSendMessageToClient( GGCBase()->GetAppID(), CSteamID(), eMsg | k_EMsgProtoBufFlag, pubMsgBytes, cubSize );
  1395. }
  1396. }
  1397. uint32 GetCubSent() const { return m_cubSent; }
  1398. private:
  1399. uint32 m_cubSent;
  1400. };
  1401. //-----------------------------------------------------------------------------
  1402. // Purpose: Sends a message to the given SteamID
  1403. //-----------------------------------------------------------------------------
  1404. bool CGCBase::BSendGCMsgToClient( const CSteamID & steamIDTarget, const CProtoBufMsgBase& msg )
  1405. {
  1406. CProtoBufClientSendHandler sender( steamIDTarget );
  1407. return msg.BAsyncSend( sender );
  1408. }
  1409. //-----------------------------------------------------------------------------
  1410. // Purpose: Sends a system message to the GC Host
  1411. //-----------------------------------------------------------------------------
  1412. bool CGCBase::BSendSystemMessage( const CGCMsgBase& msg, uint32 *pcubSent )
  1413. {
  1414. uint32 cubSent = msg.CubPkt() - sizeof(GCMsgHdr_t);
  1415. if ( NULL != pcubSent )
  1416. {
  1417. *pcubSent = cubSent;
  1418. }
  1419. // !FIXME! DOTAMERGE
  1420. //return GGCInterface()->BProcessSystemMessage( msg.Hdr().m_eMsg, msg.PubPkt() + sizeof(GCMsgHdr_t), cubSent );
  1421. return BSendGCMsgToClient( CSteamID(), msg );
  1422. }
  1423. //-----------------------------------------------------------------------------
  1424. // Purpose: Sends a system message to the GC Host
  1425. //-----------------------------------------------------------------------------
  1426. bool CGCBase::BSendSystemMessage( const CProtoBufMsgBase & msg, uint32 *pcubSent )
  1427. {
  1428. CProtoBufSystemSendHandler sender;
  1429. bool bRet = msg.BAsyncSend( sender );
  1430. if ( NULL != pcubSent )
  1431. {
  1432. *pcubSent = sender.GetCubSent();
  1433. }
  1434. return bRet;
  1435. }
  1436. bool CGCBase::BSendSystemMessage( const ::google::protobuf::Message &msgOut, MsgType_t eSendMsg )
  1437. {
  1438. CProtoBufSystemSendHandler sender;
  1439. CMsgProtoBufHeader hdr;
  1440. return CProtoBufMsgBase::BAsyncSendProto( sender, eSendMsg, hdr, msgOut );
  1441. }
  1442. //-----------------------------------------------------------------------------
  1443. // Purpose: send msgOut to the place that msgIn came from
  1444. //-----------------------------------------------------------------------------
  1445. bool CGCBase::BReplyToMessage( CGCMsgBase &msgOut, const CGCMsgBase &msgIn )
  1446. {
  1447. // Don't reply if the source is not expecting it
  1448. if ( !msgIn.BIsExpectingReply() )
  1449. return true;
  1450. msgOut.Hdr().m_JobIDTarget = msgIn.Hdr().m_JobIDSource;
  1451. return BSendGCMsgToClient( msgIn.Hdr().m_ulSteamID, msgOut );
  1452. }
  1453. //-----------------------------------------------------------------------------
  1454. // Purpose: send msgOut to the place that msgIn came from
  1455. //-----------------------------------------------------------------------------
  1456. bool CGCBase::BReplyToMessage( CProtoBufMsgBase &msgOut, const CProtoBufMsgBase &msgIn )
  1457. {
  1458. // Don't reply if the source is not expecting it
  1459. if ( !msgIn.GetJobIDSource() )
  1460. return true;
  1461. msgOut.SetJobIDTarget( msgIn.GetJobIDSource() );
  1462. return BSendGCMsgToClient( msgIn.GetClientSteamID(), msgOut );
  1463. }
  1464. //-----------------------------------------------------------------------------
  1465. // Purpose: Sends a message to the given SteamID
  1466. //-----------------------------------------------------------------------------
  1467. bool CGCBase::BSendGCMsgToClientWithPreSerializedBody( const CSteamID & steamIDTarget, MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const
  1468. {
  1469. CProtoBufClientSendHandler sender( steamIDTarget );
  1470. return CProtoBufMsgBase::BAsyncSendWithPreSerializedBody( sender, eMsgType, hdr, pubBody, cubBody );
  1471. }
  1472. //-----------------------------------------------------------------------------
  1473. // Purpose: Sends a message that has already been packed to the system handler
  1474. //-----------------------------------------------------------------------------
  1475. bool CGCBase::BSendGCMsgToSystemWithPreSerializedBody( MsgType_t eMsgType, const CMsgProtoBufHeader& hdr, const byte *pubBody, uint32 cubBody ) const
  1476. {
  1477. CProtoBufSystemSendHandler sender;
  1478. return CProtoBufMsgBase::BAsyncSendWithPreSerializedBody( sender, eMsgType, hdr, pubBody, cubBody );
  1479. }
  1480. //-----------------------------------------------------------------------------
  1481. // Purpose: send msgOut to the place that msgIn came from
  1482. //-----------------------------------------------------------------------------
  1483. bool CGCBase::BReplyToMessageWithPreSerializedBody( MsgType_t eMsgType, const CProtoBufMsgBase &msgIn, const byte *pubBody, uint32 cubBody ) const
  1484. {
  1485. // Don't reply if the source is not expecting it
  1486. if ( !msgIn.GetJobIDSource() )
  1487. return true;
  1488. if( temp_list_mismatched_replies.GetBool() && !msgIn.BIsExpectingReply() )
  1489. {
  1490. EG_MSG( g_EGMessages, "Message %s was sent to client %s which did not expect a reply\n", PchMsgNameFromEMsg( eMsgType ), msgIn.GetClientSteamID().Render() );
  1491. }
  1492. CMsgProtoBufHeader hdr;
  1493. hdr.set_job_id_target( msgIn.GetJobIDSource() );
  1494. //is this a system message or a client message we are responding to?
  1495. bool bSystemReply = ( msgIn.GetClientSteamID() == k_steamIDNil );
  1496. if( bSystemReply )
  1497. {
  1498. return BSendGCMsgToSystemWithPreSerializedBody( eMsgType, hdr, pubBody, cubBody );
  1499. }
  1500. else
  1501. {
  1502. return BSendGCMsgToClientWithPreSerializedBody( msgIn.GetClientSteamID(), eMsgType, hdr, pubBody, cubBody );
  1503. }
  1504. }
  1505. //-----------------------------------------------------------------------------
  1506. // Purpose: send msgOut to the place that msgIn came from
  1507. //-----------------------------------------------------------------------------
  1508. bool CGCBase::BYldSendMessageAndGetReply( const CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg )
  1509. {
  1510. CJob& curJob = GJobCur();
  1511. msgOut.ExpectingReply( curJob.GetJobID() );
  1512. if ( !BSendGCMsgToClient( steamIDTarget, msgOut ) )
  1513. return false;
  1514. if( !curJob.BYieldingWaitForMsg( pMsgIn, eMsg, steamIDTarget ) )
  1515. return false;
  1516. return true;
  1517. }
  1518. //bool CGCBase::BYldSendGCMessageAndGetReply( int32 nGCDirIndex, CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg )
  1519. //{
  1520. // CJob& curJob = GJobCur();
  1521. // msgOut.ExpectingReply( curJob.GetJobID() );
  1522. //
  1523. // if ( !BSendGCMessage( nGCDirIndex, msgOut ) )
  1524. // return false;
  1525. //
  1526. // if( !curJob.BYieldingWaitForMsg( pMsgIn, eMsg, CSteamID() ) )
  1527. // return false;
  1528. //
  1529. // return true;
  1530. //}
  1531. bool CGCBase::BYldSendSystemMessageAndGetReply( CGCMsgBase &msgOut, CGCMsgBase *pMsgIn, MsgType_t eMsg )
  1532. {
  1533. CJob& curJob = GJobCur();
  1534. msgOut.ExpectingReply( curJob.GetJobID() );
  1535. if ( !BSendSystemMessage( msgOut ) )
  1536. return false;
  1537. if( !curJob.BYieldingWaitForMsg( pMsgIn, eMsg, CSteamID() ) )
  1538. return false;
  1539. return true;
  1540. }
  1541. bool CGCBase::BYldSendSystemMessageAndGetReply( CProtoBufMsgBase &msgOut, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg )
  1542. {
  1543. CJob& curJob = GJobCur();
  1544. msgOut.ExpectingReply( curJob.GetJobID() );
  1545. if ( !BSendSystemMessage( msgOut ) )
  1546. return false;
  1547. if( !curJob.BYieldingWaitForMsg( pMsgIn, eMsg, CSteamID() ) )
  1548. return false;
  1549. return true;
  1550. }
  1551. bool CGCBase::BYldSendSystemMessageAndGetReply( const ::google::protobuf::Message &msgSend, MsgType_t eSendMsg, ::google::protobuf::Message *pMsgResponse, MsgType_t eRespondMsg )
  1552. {
  1553. CJob& curJob = GJobCur();
  1554. CMsgProtoBufHeader hdr;
  1555. hdr.set_job_id_source( curJob.GetJobID() );
  1556. CProtoBufSystemSendHandler sender;
  1557. CProtoBufMsgBase::BAsyncSendProto( sender, eSendMsg, hdr, msgSend );
  1558. CProtoBufPtrMsg protoMsg( pMsgResponse );
  1559. //return curJob.BYieldingWaitForMsg( &protoMsg, eRespondMsg, CSteamID() );
  1560. return curJob.BYieldingWaitForMsg( &protoMsg, eRespondMsg ); // !FIXME! For some reason system replies are coming back with a universe and instance set (but account ID zero).
  1561. }
  1562. //-----------------------------------------------------------------------------
  1563. // Purpose: Creates a new session for the steam ID
  1564. //-----------------------------------------------------------------------------
  1565. CGCUserSession *CGCBase::CreateUserSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache ) const
  1566. {
  1567. return new CGCUserSession( steamID, pSOCache );
  1568. }
  1569. //-----------------------------------------------------------------------------
  1570. // Purpose: Creates a new session for the steam ID
  1571. //-----------------------------------------------------------------------------
  1572. CGCGSSession *CGCBase::CreateGSSession( const CSteamID & steamID, CGCSharedObjectCache *pSOCache, uint32 unServerAddr, uint16 usServerPort ) const
  1573. {
  1574. return new CGCGSSession( steamID, pSOCache, unServerAddr, usServerPort );
  1575. }
  1576. //-----------------------------------------------------------------------------
  1577. // Purpose: Locks the session for this steam ID and returns it. Returns NULL
  1578. // if the lock could not be granted or if the session could not be
  1579. // found.
  1580. //-----------------------------------------------------------------------------
  1581. CGCUserSession *CGCBase::YieldingGetLockedUserSession( const CSteamID & steamID, const char *pszFilename, int nLineNum )
  1582. {
  1583. if( !steamID.BIndividualAccount() )
  1584. return NULL;
  1585. if( !BYieldingLockSteamID( steamID, pszFilename, nLineNum ) )
  1586. return NULL;
  1587. CGCUserSession *pSession = FindUserSession( steamID );
  1588. if( !pSession )
  1589. {
  1590. //EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Unable to find session %s to lock it. Attempting to fetch it from the AM\n", steamID.Render() );
  1591. pSession = (CGCUserSession *)YieldingRequestSession( steamID );
  1592. if( !pSession )
  1593. {
  1594. UnlockSteamID( steamID );
  1595. }
  1596. }
  1597. return pSession;
  1598. }
  1599. //-----------------------------------------------------------------------------
  1600. // Purpose: Checks if a user is in the start playing queue
  1601. //-----------------------------------------------------------------------------
  1602. bool CGCBase::BUserSessionPending( const CSteamID & steamID ) const
  1603. {
  1604. int nStartPlayingMapIndex = m_mapStartPlayingQueueIndexBySteamID.Find( steamID );
  1605. return ( nStartPlayingMapIndex != m_mapStartPlayingQueueIndexBySteamID.InvalidIndex() );
  1606. }
  1607. //-----------------------------------------------------------------------------
  1608. // Purpose: Returns the session for this steamID or NULL if that session could
  1609. // not be found.
  1610. //-----------------------------------------------------------------------------
  1611. CGCUserSession *CGCBase::FindUserSession( const CSteamID & steamID ) const
  1612. {
  1613. // we should only call this on individual ids
  1614. if ( !steamID.IsValid() )
  1615. {
  1616. AssertMsg1( steamID.IsValid(), "CGCBase::FindUserSession was passed invalid Steam ID %s", steamID.Render() );
  1617. return NULL;
  1618. }
  1619. if ( !steamID.BIndividualAccount() )
  1620. {
  1621. AssertMsg1( steamID.BIndividualAccount(), "CGCBase::FindUserSession was passed non-individual Steam ID %s", steamID.Render() );
  1622. return NULL;
  1623. }
  1624. CGCUserSession **ppSession = m_hashUserSessions.PvRecordFind( steamID.ConvertToUint64() );
  1625. if( ppSession )
  1626. {
  1627. (*ppSession)->MarkAccess();
  1628. return *ppSession;
  1629. }
  1630. else
  1631. {
  1632. return NULL;
  1633. }
  1634. }
  1635. //-----------------------------------------------------------------------------
  1636. // Purpose: Returns true if the session associated with the steam id is online, false otherwise
  1637. //-----------------------------------------------------------------------------
  1638. bool CGCBase::BYieldingIsOnline( const CSteamID & steamID )
  1639. {
  1640. CGCMsg< MsgGCValidateSession_t > msg( k_EGCMsgValidateSession );
  1641. msg.Body().m_ulSteamID = steamID.ConvertToUint64();
  1642. msg.ExpectingReply( GJobCur().GetJobID() );
  1643. if( !BSendSystemMessage( msg ) )
  1644. return false;
  1645. CGCMsg< MsgGCValidateSessionResponse_t > msgReply;
  1646. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgValidateSessionResponse ) )
  1647. {
  1648. EmitWarning( SPEW_GC, LOG_ALWAYS, "Didn't get reply from AM for %s in YieldingRequestSession\n", steamID.Render() );
  1649. return false;
  1650. }
  1651. return msgReply.Body().m_bOnline;
  1652. }
  1653. //-----------------------------------------------------------------------------
  1654. // Purpose: Looks up a session from the AM for the provided steam ID.
  1655. //-----------------------------------------------------------------------------
  1656. template <typename T >
  1657. class CScopedIncrement
  1658. {
  1659. public:
  1660. inline CScopedIncrement( T & counter) : m_counter(counter) { ++m_counter; }
  1661. inline ~CScopedIncrement() { --m_counter; }
  1662. private:
  1663. T &m_counter;
  1664. };
  1665. CGCSession *CGCBase::YieldingRequestSession( const CSteamID & steamID )
  1666. {
  1667. AssertRunningJob();
  1668. if( !steamID.BIndividualAccount() && !steamID.BGameServerAccount() )
  1669. return NULL;
  1670. Assert( IsSteamIDUnlockedOrLockedByCurJob( steamID ) );
  1671. // Check if we already have info in the logon queue for this SteamID
  1672. int nStartPlayingMapIndex = m_mapStartPlayingQueueIndexBySteamID.Find( steamID );
  1673. if ( nStartPlayingMapIndex != m_mapStartPlayingQueueIndexBySteamID.InvalidIndex() )
  1674. {
  1675. // Sanity
  1676. int idxStartPlayingQueue = m_mapStartPlayingQueueIndexBySteamID[ nStartPlayingMapIndex ];
  1677. Assert( m_llStartPlaying[ idxStartPlayingQueue ].m_steamID == steamID );
  1678. // Pull the logon out of the queue and execute it NOW
  1679. YieldingExecuteStartPlayingQueueEntryByIndex( idxStartPlayingQueue );
  1680. // Now return the session that was created, if any
  1681. return FindUserOrGSSession( steamID );
  1682. }
  1683. CGCMsg< MsgGCValidateSession_t > msg( k_EGCMsgValidateSession );
  1684. msg.Body().m_ulSteamID = steamID.ConvertToUint64();
  1685. msg.ExpectingReply( GJobCur().GetJobID() );
  1686. if( !BSendSystemMessage( msg ) )
  1687. return NULL;
  1688. CScopedIncrement<int> increment( m_nRequestSessionJobsActive );
  1689. CGCMsg< MsgGCValidateSessionResponse_t > msgReply;
  1690. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgValidateSessionResponse ) )
  1691. {
  1692. EmitWarning( SPEW_GC, LOG_ALWAYS, "Didn't get reply from AM for %s in YieldingRequestSession\n", steamID.Render() );
  1693. return NULL;
  1694. }
  1695. if( steamID.BIndividualAccount() )
  1696. {
  1697. if( msgReply.Body().m_bOnline )
  1698. {
  1699. CUtlBuffer bufVarData;
  1700. if( msgReply.CubVarData() )
  1701. {
  1702. bufVarData.Put( msgReply.PubVarData(), msgReply.CubVarData() );
  1703. }
  1704. // Check if they have an entry in the startplaying queue, then get rid of it!
  1705. // They data we just received is the most up-to-date we have. We should
  1706. // prefer this data over anything in the queue for sure.
  1707. BRemoveStartPlayingQueueEntry( steamID );
  1708. YieldingStartPlaying( steamID, msgReply.Body().m_ulSteamIDGS, msgReply.Body().m_unServerAddr, msgReply.Body().m_usServerPort, msgReply.CubVarData() ? &bufVarData : NULL );
  1709. return FindUserSession( steamID );
  1710. }
  1711. else
  1712. {
  1713. //EmitWarning( SPEW_GC, LOG_ALWAYS, "Reply from AM is logging off %s in YieldingRequestSession\n", steamID.Render() );
  1714. YieldingStopPlaying( steamID );
  1715. return NULL;
  1716. }
  1717. }
  1718. else
  1719. {
  1720. if( msgReply.Body().m_bOnline )
  1721. {
  1722. YieldingStartGameserver( steamID, msgReply.Body().m_unServerAddr, msgReply.Body().m_usServerPort, msgReply.PubVarData(), msgReply.CubVarData() );
  1723. return FindGSSession( steamID );
  1724. }
  1725. else
  1726. {
  1727. //EmitWarning( SPEW_GC, LOG_ALWAYS, "Reply from AM is stopping %s in YieldingRequestSession\n", steamID.Render() );
  1728. YieldingStopGameserver( steamID );
  1729. return NULL;
  1730. }
  1731. }
  1732. }
  1733. //-----------------------------------------------------------------------------
  1734. // Purpose: Send outgoing HTTP request to some other server. Probably a WebAPI
  1735. // request to steam itself, but it could be a request on a more
  1736. // remote server.
  1737. //-----------------------------------------------------------------------------
  1738. bool CGCBase::BYieldingSendHTTPRequest( const CHTTPRequest *pRequest, CHTTPResponse *pResponse )
  1739. {
  1740. if ( !pRequest || !pResponse )
  1741. {
  1742. AssertMsg( false, "Bad parameters for BYieldingSendHTTPRequest" );
  1743. return false;
  1744. }
  1745. CMsgHttpResponse msgResponse;
  1746. if( !BYldSendSystemMessageAndGetReply( pRequest->GetProtoObj(), k_EGCMsgSendHTTPRequest, &msgResponse, k_EGCMsgSendHTTPRequestResponse ) )
  1747. {
  1748. ReportHTTPError( CFmtStr( "No response to HTTP system message for %s", pRequest->GetURL() ), CGCEmitGroup::kMsg_Error );
  1749. return false;
  1750. }
  1751. if ( !msgResponse.has_status_code() )
  1752. {
  1753. ReportHTTPError( CFmtStr( "No status code on HTTP response for %s", pRequest->GetURL() ), CGCEmitGroup::kMsg_Error );
  1754. return false;
  1755. }
  1756. //log the result of this request
  1757. if( msgResponse.status_code() != k_EHTTPStatusCode200OK )
  1758. {
  1759. ReportHTTPError( CFmtStr( "Invalid status code %u for %s", msgResponse.status_code(), pRequest->GetURL() ), CGCEmitGroup::kMsg_Warning );
  1760. }
  1761. else
  1762. {
  1763. ReportHTTPError( CFmtStr( "Success status code for %s", pRequest->GetURL() ), CGCEmitGroup::kMsg_Verbose );
  1764. }
  1765. pResponse->DeserializeFromProtoBuf( msgResponse );
  1766. return true;
  1767. }
  1768. //-----------------------------------------------------------------------------
  1769. // Purpose: Send an outgoing HTTP request and parse the result into KeyValues.
  1770. //-----------------------------------------------------------------------------
  1771. EResult CGCBase::YieldingSendHTTPRequestKV( const CHTTPRequest *pRequest, KeyValues *pKVResponse )
  1772. {
  1773. CHTTPResponse apiResponse;
  1774. if ( !BYieldingSendHTTPRequest( pRequest, &apiResponse ) )
  1775. {
  1776. EmitError( SPEW_GC, __FUNCTION__ ": web call to %s timed out\n", pRequest->GetURL() );
  1777. return k_EResultTimeout;
  1778. }
  1779. if ( k_EHTTPStatusCode200OK != apiResponse.GetStatusCode() )
  1780. {
  1781. EmitError( SPEW_GC, __FUNCTION__ ": web call to %s got failure code %d\n", pRequest->GetURL(), apiResponse.GetStatusCode() );
  1782. return k_EResultRemoteCallFailed;
  1783. }
  1784. pKVResponse->UsesEscapeSequences( true );
  1785. if ( !pKVResponse->LoadFromBuffer( "webResponse", *apiResponse.GetBodyBuffer() ) )
  1786. {
  1787. EmitError( SPEW_GC, "Web call to %s could not parse response\n", pRequest->GetURL() );
  1788. return k_EResultRemoteCallFailed;
  1789. }
  1790. return k_EResultOK;
  1791. }
  1792. //-----------------------------------------------------------------------------
  1793. // Purpose: Locks the session for this steam ID and returns it. Returns NULL
  1794. // if the lock could not be granted or if the session could not be
  1795. // found.
  1796. //-----------------------------------------------------------------------------
  1797. CGCGSSession *CGCBase::YieldingGetLockedGSSession( const CSteamID & steamID, const char *pszFilename, int nLineNum )
  1798. {
  1799. if( !steamID.BGameServerAccount() )
  1800. return NULL;
  1801. if( !BYieldingLockSteamID( steamID, pszFilename, nLineNum ) )
  1802. return NULL;
  1803. CGCGSSession *pSession = FindGSSession( steamID );
  1804. if( !pSession )
  1805. {
  1806. pSession = (CGCGSSession *)YieldingRequestSession( steamID );
  1807. if( !pSession )
  1808. {
  1809. UnlockSteamID( steamID );
  1810. }
  1811. }
  1812. return pSession;
  1813. }
  1814. void CGCBase::ReportHTTPError( const char* pszError, CGCEmitGroup::EMsgLevel eLevel )
  1815. {
  1816. //see if we can find a match
  1817. int nIndex = m_HTTPErrors.Find( pszError );
  1818. if( nIndex != m_HTTPErrors.InvalidIndex() )
  1819. {
  1820. //just increment our count
  1821. m_HTTPErrors[ nIndex ]->m_nCount++;
  1822. m_HTTPErrors[ nIndex ]->m_eSeverity = MIN( eLevel, m_HTTPErrors[ nIndex ]->m_eSeverity );
  1823. }
  1824. else
  1825. {
  1826. //add one
  1827. SHTTPError* pError = new SHTTPError;
  1828. pError->m_sStr = pszError;
  1829. pError->m_nCount = 1;
  1830. pError->m_eSeverity = eLevel;
  1831. m_HTTPErrors.Insert( pError->m_sStr, pError );
  1832. }
  1833. if( !m_DumpHTTPErrorsSchedule.BIsScheduled() )
  1834. {
  1835. m_DumpHTTPErrorsSchedule.ScheduleMS( this, &CGCBase::DumpHTTPErrors, 1000 );
  1836. }
  1837. }
  1838. void CGCBase::DumpHTTPErrors()
  1839. {
  1840. FOR_EACH_MAP_FAST( m_HTTPErrors, nCurrError )
  1841. {
  1842. SHTTPError* pError = m_HTTPErrors[ nCurrError ];
  1843. EG_EMIT( g_EGHTTPRequest, m_HTTPErrors[ nCurrError ]->m_eSeverity, "%s - %d times\n", pError->m_sStr.String(), pError->m_nCount );
  1844. delete pError;
  1845. }
  1846. m_HTTPErrors.RemoveAll();
  1847. }
  1848. //-----------------------------------------------------------------------------
  1849. // Purpose: Returns the session for this steamID or NULL if that session could
  1850. // not be found.
  1851. //-----------------------------------------------------------------------------
  1852. CGCGSSession *CGCBase::YieldingFindOrCreateGSSession( const CSteamID & steamID, uint32 unServerAddr, uint16 usServerPort, const uint8 *pubVarData, uint32 cubVarData )
  1853. {
  1854. Assert( IsSteamIDLockedByJob( steamID, &GJobCur() ) );
  1855. // If it's not a game server ID, then we shouldn't make a session for it.
  1856. if( !steamID.BGameServerAccount() )
  1857. return NULL;
  1858. MEM_ALLOC_CREDIT_( "YieldingFindOrCreateGSSession" );
  1859. // if var data came with this StartPlaying message, parse it into a KV and stick it on the session
  1860. KeyValues *pkvDetails = NULL;
  1861. if( pubVarData && cubVarData )
  1862. {
  1863. CUtlBuffer bufDetails;
  1864. bufDetails.Put( pubVarData, cubVarData );
  1865. pkvDetails = new KeyValues( "SessionDetails" );
  1866. if( !pkvDetails->ReadAsBinary( bufDetails ) )
  1867. {
  1868. EmitError( SPEW_GC, "Unable to parse session details for %s\n", steamID.Render() );
  1869. pkvDetails->deleteThis();
  1870. pkvDetails = NULL;
  1871. }
  1872. }
  1873. // // Since we might have to lock the session in some cases, let's just always grab the lock here,
  1874. // // to keep things simpler.
  1875. // if ( !BYieldingLockSteamID( steamID, __FILE__, __LINE__ ) )
  1876. // return NULL;
  1877. CGCGSSession *pSession = FindGSSession( steamID );
  1878. CGCSharedObjectCache *pSOCache = NULL;
  1879. if( !pSession )
  1880. {
  1881. pSOCache = YieldingFindOrLoadSOCache( steamID );
  1882. // Did anybody create a session while we held the lock?
  1883. // We hold the lock, and you must hold the lock to create
  1884. // the session, so this race condition should be impossible
  1885. pSession = FindGSSession( steamID );
  1886. Assert( pSession == NULL );
  1887. }
  1888. if( !pSession )
  1889. {
  1890. // Create session of app-specific type
  1891. pSession = CreateGSSession( steamID, pSOCache, unServerAddr, usServerPort );
  1892. Assert( pSession );
  1893. if ( !pSession )
  1894. {
  1895. AssertMsg1( false, "Failed creating GC GS session for %llu", steamID.ConvertToUint64() );
  1896. if ( pkvDetails )
  1897. {
  1898. pkvDetails->deleteThis();
  1899. }
  1900. //UnlockSteamID( steamID ); // I like to clean up after myself
  1901. return NULL;
  1902. }
  1903. RemoveCacheFromLRU( pSOCache );
  1904. CGCGSSession **ppSession = m_hashGSSessions.PvRecordInsert( steamID.ConvertToUint64() );
  1905. *ppSession = pSession;
  1906. // Do game-specific work
  1907. YieldingSessionStartServer( pSession );
  1908. }
  1909. else
  1910. {
  1911. if ( unServerAddr != 0 && usServerPort != 0 && ( unServerAddr != pSession->GetAddr() || usServerPort != pSession->GetPort() ) )
  1912. {
  1913. UpdateGSSessionAddress( pSession, unServerAddr, usServerPort );
  1914. }
  1915. }
  1916. if( pkvDetails )
  1917. {
  1918. uint32 ip = pkvDetails->GetInt( "ip", 0 );
  1919. if ( ip != 0 )
  1920. pSession->m_unIPPublic = ip;
  1921. pSession->m_osType = static_cast<EOSType>( pkvDetails->GetInt( "osType", k_eOSUnknown ) );
  1922. pSession->m_bIsTestSession = pkvDetails->GetInt( "isTestSession", 0 ) != 0;
  1923. pkvDetails->deleteThis();
  1924. }
  1925. //UnlockSteamID( steamID ); // I like to clean up after myself
  1926. return pSession;
  1927. }
  1928. //-----------------------------------------------------------------------------
  1929. // Purpose: Called when a Session is moved to a different address.
  1930. //-----------------------------------------------------------------------------
  1931. void CGCBase::UpdateGSSessionAddress( CGCGSSession *pSession, uint32 unServerAddr, uint16 usServerPort )
  1932. {
  1933. pSession->SetIPAndPort( unServerAddr, usServerPort );
  1934. }
  1935. //-----------------------------------------------------------------------------
  1936. // Purpose: Returns the session for this steamID or NULL if that session could
  1937. // not be found.
  1938. //-----------------------------------------------------------------------------
  1939. CGCGSSession *CGCBase::FindGSSession( const CSteamID & steamID ) const
  1940. {
  1941. // we should only call this on server ids
  1942. if ( !steamID.IsValid() || steamID.GetAccountID() == 0 )
  1943. {
  1944. AssertMsg1( false, "CGCBase::FindGSSession was passed invalid Steam ID %s", steamID.Render() );
  1945. return NULL;
  1946. }
  1947. if ( !steamID.BGameServerAccount() )
  1948. {
  1949. AssertMsg1( steamID.BGameServerAccount(), "CGCBase::FindGSSession was passed non-gameserver Steam ID %s", steamID.Render() );
  1950. return NULL;
  1951. }
  1952. CGCGSSession **ppSession = m_hashGSSessions.PvRecordFind( steamID.ConvertToUint64() );
  1953. if( ppSession )
  1954. {
  1955. (*ppSession)->MarkAccess();
  1956. return *ppSession;
  1957. }
  1958. else
  1959. {
  1960. return NULL;
  1961. }
  1962. }
  1963. //-----------------------------------------------------------------------------
  1964. // Purpose: Locate session from appropriate table, depending on if it's
  1965. // an individual or gameserver ID
  1966. //-----------------------------------------------------------------------------
  1967. CGCSession *CGCBase::FindUserOrGSSession( const CSteamID & steamID ) const
  1968. {
  1969. if ( steamID.BIndividualAccount() )
  1970. return FindUserSession( steamID );
  1971. if ( steamID.BGameServerAccount() )
  1972. return FindGSSession( steamID );
  1973. AssertMsg1( false, "CGCBase::FindUserOrGSSession, steam ID %s isn't an individual or a gameserver ID", steamID.Render() );
  1974. return NULL;
  1975. }
  1976. //-----------------------------------------------------------------------------
  1977. // Purpose: Wakes up the job waiting for this SQL result
  1978. //-----------------------------------------------------------------------------
  1979. void CGCBase::SQLResults( GID_t gidContextID )
  1980. {
  1981. VPROF_BUDGET( "CGCBase::SQLResults", VPROF_BUDGETGROUP_STEAM );
  1982. m_JobMgr.BResumeSQLJob( gidContextID );
  1983. }
  1984. //-----------------------------------------------------------------------------
  1985. // Purpose: Finds the cache in the map for a new session
  1986. //-----------------------------------------------------------------------------
  1987. CGCSharedObjectCache *CGCBase::FindSOCache( const CSteamID & steamID )
  1988. {
  1989. CUtlMap< CSteamID, CGCSharedObjectCache *, int >::IndexType_t nCache = m_mapSOCache.Find( steamID );
  1990. if( m_mapSOCache.IsValidIndex( nCache ) )
  1991. return m_mapSOCache[nCache];
  1992. else
  1993. return NULL;
  1994. }
  1995. //-----------------------------------------------------------------------------
  1996. // Purpose:
  1997. //-----------------------------------------------------------------------------
  1998. bool CGCBase::BYieldingLoadSOCache( CGCSharedObjectCache *pSOCache )
  1999. {
  2000. return true;
  2001. }
  2002. //-----------------------------------------------------------------------------
  2003. // Purpose:
  2004. //-----------------------------------------------------------------------------
  2005. void CGCBase::YieldingSOCacheLoaded( CGCSharedObjectCache *pSOCache )
  2006. {
  2007. // remove it, so we don't stomp the copy in memcached
  2008. m_rbtreeSOCachesWithDirtyVersions.Remove( pSOCache->GetOwner() );
  2009. // stomp the version with the one we set in memcached previously if possible, otherwise, re-add it to the set
  2010. if ( !BYieldingRetrieveCacheVersion( pSOCache ) )
  2011. {
  2012. m_rbtreeSOCachesWithDirtyVersions.InsertIfNotFound( pSOCache->GetOwner() );
  2013. }
  2014. }
  2015. //-----------------------------------------------------------------------------
  2016. // Purpose: Removes the cache for this steamID
  2017. //-----------------------------------------------------------------------------
  2018. void CGCBase::RemoveSOCache( const CSteamID & steamID )
  2019. {
  2020. CUtlMap< CSteamID, CGCSharedObjectCache *, int >::IndexType_t nCache = m_mapSOCache.Find( steamID );
  2021. if( m_mapSOCache.IsValidIndex( nCache ) )
  2022. {
  2023. CGCSharedObjectCache *pSOCache = m_mapSOCache[nCache];
  2024. pSOCache->RemoveAllSubscribers();
  2025. if( pSOCache->BIsDatabaseDirty() )
  2026. {
  2027. EmitError( SPEW_GC, "Attempting to remove SO Cache %s while it was dirty. Adding to Writeback instead\n", steamID.Render() );
  2028. pSOCache->DumpDirtyObjects();
  2029. AddCacheToWritebackQueue( pSOCache );
  2030. // adding the cache to the LRU list too, just so it will go away once writeback does its thing
  2031. if( !m_listCachesToUnload.IsValidIndex( pSOCache->GetLRUHandle() ) )
  2032. {
  2033. AddCacheToLRU( pSOCache );
  2034. }
  2035. }
  2036. else
  2037. {
  2038. RemoveCacheFromLRU(pSOCache);
  2039. delete pSOCache;
  2040. m_mapSOCache.RemoveAt( nCache );
  2041. }
  2042. }
  2043. }
  2044. //-----------------------------------------------------------------------------
  2045. // Purpose: Enqueues a flush instruction to Econ service for Web Inventory to update
  2046. //-----------------------------------------------------------------------------
  2047. void CGCBase::FlushInventoryCache( AccountID_t unAccountID )
  2048. {
  2049. VPROF_BUDGET( "FlushInventoryCache - enqueue", VPROF_BUDGETGROUP_STEAM );
  2050. m_rbFlushInventoryCacheAccounts.InsertIfNotFound( unAccountID );
  2051. }
  2052. //-----------------------------------------------------------------------------
  2053. // Purpose: Finds the cache in the map for a new session and locks it
  2054. //-----------------------------------------------------------------------------
  2055. bool CGCBase::UnloadUnusedCaches( uint32 unMaxCacheCount, CLimitTimer *pLimitTimer )
  2056. {
  2057. VPROF_BUDGET( "UnloadUnusedCaches", VPROF_BUDGETGROUP_STEAM );
  2058. uint32 unCachesUnloaded = 0;
  2059. for( uint32 unCache = m_listCachesToUnload.Head(), unNextCache = m_listCachesToUnload.InvalidIndex(); unCache != m_listCachesToUnload.InvalidIndex(); unCache = unNextCache )
  2060. {
  2061. unNextCache = m_listCachesToUnload.Next( unCache );
  2062. // only remove caches until we are under our limit
  2063. if( (uint32)m_mapSOCache.Count() <= unMaxCacheCount )
  2064. return false;
  2065. // only loop until we need to stop consuming heartbeat time. We'll finish in later frames
  2066. if( pLimitTimer && pLimitTimer->BLimitReached() )
  2067. return true;
  2068. CSteamID ownerID = m_listCachesToUnload[ unCache ];
  2069. CGCSharedObjectCache *pSOCache = FindSOCache( ownerID );
  2070. Assert( pSOCache );
  2071. if( !pSOCache )
  2072. {
  2073. EmitError( SPEW_GC, "Cache for %s could not be found even though it is in the LRU list\n", ownerID.Render() );
  2074. m_listCachesToUnload.Remove( unCache );
  2075. continue;
  2076. }
  2077. // make sure there's no session using this cache
  2078. if( ( ownerID.BIndividualAccount() && FindUserSession( ownerID ) )
  2079. || ( ownerID.BGameServerAccount() && FindGSSession( ownerID ) ) )
  2080. {
  2081. EmitError( SPEW_GC, "Cache for %s has a session even though it is in the LRU list\n", ownerID.Render() );
  2082. Assert( pSOCache->GetLRUHandle() == unCache );
  2083. if ( pSOCache->GetLRUHandle() != unCache )
  2084. {
  2085. EmitError( SPEW_GC, "Cache for %s has a different LRU handle than the one retrieved from the iterator! 0x%08x vs 0x%08x\n", ownerID.Render(), pSOCache->GetLRUHandle(), unCache );
  2086. }
  2087. RemoveCacheFromLRU( pSOCache );
  2088. continue;
  2089. }
  2090. // Locked steam IDs mean someone is using the cache.
  2091. // Being in the writeback queue means that you haven't actually been unused for very long.
  2092. // Just move on to the next one in those cases.
  2093. if( IsSteamIDLocked( ownerID ) || pSOCache->GetInWriteback() )
  2094. continue;
  2095. // either count down by one or still in LRU?
  2096. int iPreRemoveCount = m_listCachesToUnload.Count();
  2097. // remove and delete the cache (which will remove it from the LRU list too.)
  2098. RemoveSOCache( ownerID );
  2099. unCachesUnloaded++;
  2100. if ( iPreRemoveCount != m_listCachesToUnload.Count() + 1 &&
  2101. iPreRemoveCount != m_listCachesToUnload.Count() )
  2102. {
  2103. EmitError( SPEW_GC, "CGCBase::UnloadUnusedCaches() sanity check failed! List size changed dramatically removing 0x%08x; delta %i\n", unCache, iPreRemoveCount - m_listCachesToUnload.Count() );
  2104. }
  2105. }
  2106. return false;
  2107. }
  2108. //-----------------------------------------------------------------------------
  2109. // Purpose: Does some sanity checks on the SO cache LRU
  2110. //-----------------------------------------------------------------------------
  2111. void CGCBase::VerifySOCacheLRU()
  2112. {
  2113. CUtlRBTree<CSteamID, int> rbTreeUsersEncountered( 0, m_listCachesToUnload.Count(), DefLessFunc( CSteamID ) );
  2114. for( uint32 unCache = m_listCachesToUnload.Head(), unNextCache = m_listCachesToUnload.InvalidIndex(); unCache != m_listCachesToUnload.InvalidIndex(); unCache = unNextCache )
  2115. {
  2116. unNextCache = m_listCachesToUnload.Next( unCache );
  2117. CSteamID ownerID = m_listCachesToUnload[ unCache ];
  2118. CGCSharedObjectCache *pSOCache = FindSOCache( ownerID );
  2119. if ( !pSOCache )
  2120. {
  2121. EmitError( SPEW_GC, "CGCBase::UnloadUnusedCaches() sanity[0] check failed! Empty cache in list in slot 0x%08x\n", unCache );
  2122. continue;
  2123. }
  2124. if ( pSOCache->GetLRUHandle() != unCache )
  2125. {
  2126. EmitError( SPEW_GC, "CGCBase::UnloadUnusedCaches() sanity[1] check failed! Cache entry mismatch [ 0x%08x vs 0x%08x ] (owner: %s)\n", pSOCache->GetLRUHandle(), unCache, pSOCache->GetOwner().Render() );
  2127. }
  2128. if ( !rbTreeUsersEncountered.IsValidIndex( rbTreeUsersEncountered.InsertIfNotFound( ownerID ) ) )
  2129. {
  2130. EmitError( SPEW_GC, "CGCBase::UnloadUnusedCaches() sanity[2] check failed! Duplicate entry in list for 0x%08x (owner: %s)\n", unCache, pSOCache->GetOwner().Render() );
  2131. }
  2132. }
  2133. }
  2134. //-----------------------------------------------------------------------------
  2135. // Purpose: Adds the cache to the LRU list
  2136. //-----------------------------------------------------------------------------
  2137. void CGCBase::AddCacheToLRU( CGCSharedObjectCache * pSOCache )
  2138. {
  2139. Assert( pSOCache->GetLRUHandle() == m_listCachesToUnload.InvalidIndex() );
  2140. #if WITH_SOCACHE_LRU_DEBUGGING
  2141. if ( pSOCache->GetLRUHandle() != m_listCachesToUnload.InvalidIndex() )
  2142. {
  2143. EmitError( SPEW_GC, "CGCBase::AddCacheToLRU() sanity[4] check failed! Adding SO Cache with existing LRU handle: 0x%08x\n", pSOCache->GetLRUHandle() );
  2144. }
  2145. #endif
  2146. // remove it just in case. Crashes are bad.
  2147. RemoveCacheFromLRU( pSOCache );
  2148. #if WITH_SOCACHE_LRU_DEBUGGING
  2149. Assert( pSOCache->GetLRUHandle() == m_listCachesToUnload.InvalidIndex() );
  2150. if ( pSOCache->GetLRUHandle() != m_listCachesToUnload.InvalidIndex() )
  2151. {
  2152. EmitError( SPEW_GC, "CGCBase::AddCacheToLRU() sanity[5] check failed! Adding SO Cache with existing LRU handle: 0x%08x\n", pSOCache->GetLRUHandle() );
  2153. }
  2154. #endif
  2155. pSOCache->SetLRUHandle( m_listCachesToUnload.AddToTail( pSOCache->GetOwner() ) );
  2156. }
  2157. //-----------------------------------------------------------------------------
  2158. // Purpose: Removes the cache from the LRU list
  2159. //-----------------------------------------------------------------------------
  2160. void CGCBase::RemoveCacheFromLRU( CGCSharedObjectCache * pSOCache )
  2161. {
  2162. #if WITH_SOCACHE_LRU_DEBUGGING
  2163. if ( m_listCachesToUnload.IsValidIndex( pSOCache->GetLRUHandle() ) == ( pSOCache->GetLRUHandle() == m_listCachesToUnload.InvalidIndex() ) )
  2164. {
  2165. EmitError( SPEW_GC, "CGCBase::RemoveCacheFromLRU() sanity[6] check failed! SO Cache has an invalid index, but IsValidIndex() is returning true: 0x%08x\n", pSOCache->GetLRUHandle() );
  2166. }
  2167. #endif
  2168. if( m_listCachesToUnload.IsValidIndex( pSOCache->GetLRUHandle() ) )
  2169. {
  2170. if( m_listCachesToUnload[ pSOCache->GetLRUHandle() ] != pSOCache->GetOwner() )
  2171. {
  2172. EmitError( SPEW_GC, "CGCBase::RemoveCacheFromLRU() Attempting to remove SOCache LRU index %d for %s, which really holds %s\n",
  2173. pSOCache->GetLRUHandle(), pSOCache->GetOwner().Render(), m_listCachesToUnload[ pSOCache->GetLRUHandle() ].Render() );
  2174. }
  2175. else
  2176. {
  2177. m_listCachesToUnload.Remove( pSOCache->GetLRUHandle() );
  2178. }
  2179. }
  2180. pSOCache->SetLRUHandle( m_listCachesToUnload.InvalidIndex() );
  2181. }
  2182. //-----------------------------------------------------------------------------
  2183. // Purpose: Finds the cache in the map for a new session and locks it
  2184. //-----------------------------------------------------------------------------
  2185. CGCSharedObjectCache *CGCBase::YieldingGetLockedSOCache( const CSteamID &steamID, const char *pszFilename, int nLineNum )
  2186. {
  2187. if( !BYieldingLockSteamID( steamID, pszFilename, nLineNum ) )
  2188. return NULL;
  2189. return YieldingFindOrLoadSOCache( steamID );
  2190. }
  2191. //-----------------------------------------------------------------------------
  2192. // Purpose: Finds the cache in the map for a new session
  2193. //-----------------------------------------------------------------------------
  2194. CGCSharedObjectCache *CGCBase::YieldingFindOrLoadSOCache( const CSteamID &steamID )
  2195. {
  2196. AssertRunningJob();
  2197. if( !steamID.IsValid() )
  2198. {
  2199. AssertMsg1( false, "Unable to load SO cache for invalid steam ID %s", steamID.Render() );
  2200. EmitError( SPEW_GC, "Unable to load SO cache for invalid steam ID %s (instance: %d)\n", steamID.Render(), steamID.GetUnAccountInstance() );
  2201. return NULL;
  2202. }
  2203. // check to see if the SO cache is being loaded--if so, then we yield until it is done
  2204. // the reason we are not just locking the steam id is because the current job may have
  2205. // a lock on something else, and jobs can only have one lock active at a time.
  2206. CJobTime timeStartedWaiting;
  2207. timeStartedWaiting.SetToJobTime();
  2208. while ( m_rbtreeSOCachesBeingLoaded.Find( steamID ) != m_rbtreeSOCachesBeingLoaded.InvalidIndex() )
  2209. {
  2210. // !TEST! Looks like we might have a bug where we're spinning here waiting forever.
  2211. // Add a timeout just in case.
  2212. if ( timeStartedWaiting.CServerMicroSecsPassed() > 180 * k_nMillion )
  2213. {
  2214. AssertMsg1( false, "Timed out waiting for SO cache %s to finish loading", steamID.Render() );
  2215. return false;
  2216. }
  2217. GJobCur().BYieldingWaitOneFrame();
  2218. }
  2219. CGCSharedObjectCache *pSOCache = FindSOCache( steamID );
  2220. if( !pSOCache )
  2221. {
  2222. m_rbtreeSOCachesBeingLoaded.Insert( steamID );
  2223. pSOCache = CreateSOCache( steamID );
  2224. CJobTime timeStartedLoading;
  2225. timeStartedLoading.SetToJobTime();
  2226. if( BYieldingLoadSOCache( pSOCache ) )
  2227. {
  2228. if ( FindSOCache( steamID ) != NULL )
  2229. {
  2230. EmitError( SPEW_GC, "HOLY FUCKING SHIT WE ARE DUPLICATING SO CACHES [%s]\n", steamID.Render() );
  2231. }
  2232. m_mapSOCache.Insert( steamID, pSOCache );
  2233. float flSecondsToLoad = (float)timeStartedLoading.CServerMicroSecsPassed() / (float)k_nMillion;
  2234. if ( flSecondsToLoad > 10.0f )
  2235. {
  2236. EmitInfo( SPEW_GC, 4, 1, "Loading of SO cache for %s took %.1fs\n", steamID.Render(), flSecondsToLoad );
  2237. }
  2238. //mark this cache as loaded so that it's version can change again
  2239. pSOCache->SetDetectVersionChanges( false );
  2240. CJobTime timeStartedNotify;
  2241. timeStartedNotify.SetToJobTime();
  2242. YieldingSOCacheLoaded( pSOCache );
  2243. float flSecondsToNotify = (float)timeStartedNotify.CServerMicroSecsPassed() / (float)k_nMillion;
  2244. if ( flSecondsToNotify > 10.0f )
  2245. {
  2246. EmitInfo( SPEW_GC, 1, 1, "YieldingSOCacheLoaded for %s took %.1fs\n", steamID.Render(), flSecondsToNotify );
  2247. }
  2248. AddCacheToLRU( pSOCache ); // in case the cache isn't about to be attached to a session
  2249. m_rbtreeSOCachesBeingLoaded.Remove( steamID );
  2250. }
  2251. else
  2252. {
  2253. AssertMsg1( false, "Unable to load SO cache for %llu", steamID.ConvertToUint64() );
  2254. EmitError( SPEW_GC, "Unable to load SO cache for %llu\n", steamID.ConvertToUint64() );
  2255. delete pSOCache;
  2256. m_rbtreeSOCachesBeingLoaded.Remove( steamID );
  2257. return NULL;
  2258. }
  2259. }
  2260. else
  2261. {
  2262. // if the cache is in the LRU, move it to the end of the list
  2263. if( m_listCachesToUnload.IsValidIndex( pSOCache->GetLRUHandle() ) )
  2264. {
  2265. RemoveCacheFromLRU( pSOCache );
  2266. AddCacheToLRU( pSOCache );
  2267. }
  2268. }
  2269. return pSOCache;
  2270. }
  2271. //-----------------------------------------------------------------------------
  2272. // Purpose: Reloads the SO cache
  2273. //-----------------------------------------------------------------------------
  2274. void CGCBase::YieldingReloadCache( CGCSharedObjectCache *pSOCache )
  2275. {
  2276. Assert( IsSteamIDLockedByJob( pSOCache->GetOwner(), &GJobCur() ) );
  2277. if( !IsSteamIDLockedByJob( pSOCache->GetOwner(), &GJobCur() ) )
  2278. return;
  2279. // Flush all pending writes
  2280. CSQLAccess sqlAccess;
  2281. sqlAccess.BBeginTransaction( "CGCBase::YieldingReloadCache - Flush writes" );
  2282. pSOCache->YieldingStageAllWrites( sqlAccess );
  2283. if ( !sqlAccess.BCommitTransaction( true ) )
  2284. {
  2285. EmitError( SPEW_SHAREDOBJ, "%s: Unable to flush pending writes for %s, reload failed",
  2286. __FUNCTION__, pSOCache->GetOwner().Render() );
  2287. return;
  2288. }
  2289. // load the data into a new cache
  2290. CGCSharedObjectCache *pNewCache = CreateSOCache( pSOCache->GetOwner() );
  2291. if( !BYieldingLoadSOCache( pNewCache ) )
  2292. {
  2293. EmitError( SPEW_SHAREDOBJ, "Unable to reload cache for %s because of a SQL error", pSOCache->GetOwner().Render() );
  2294. return;
  2295. }
  2296. // process every object in the new cache and move it to the old one if necessary
  2297. FOR_EACH_MAP_FAST( CSharedObject::GetFactories(), nType )
  2298. {
  2299. int nTypeID = CSharedObject::GetFactories().Key( nType );
  2300. // remove all the old items of this type
  2301. CSharedObjectTypeCache *pOldTypeCache = pSOCache->FindTypeCache( nTypeID );
  2302. if( pOldTypeCache )
  2303. {
  2304. for( uint32 nCurrObj = 0; nCurrObj < pOldTypeCache->GetCount(); )
  2305. {
  2306. //not all objects should be deleted (for example lobbies/parties), so for those objects
  2307. //don't delete and instead just skip over them
  2308. if( pOldTypeCache->GetObject( nCurrObj )->BShouldDeleteByCache() )
  2309. {
  2310. pSOCache->RemoveObject( *pOldTypeCache->GetObject( nCurrObj ) );
  2311. }
  2312. else
  2313. {
  2314. nCurrObj++;
  2315. }
  2316. }
  2317. }
  2318. // add all the new objects of this type
  2319. CSharedObjectTypeCache *pNewTypeCache = pNewCache->FindTypeCache( nTypeID );
  2320. if( pNewTypeCache )
  2321. {
  2322. for( uint unObject = 0; unObject < pNewTypeCache->GetCount(); unObject++ )
  2323. {
  2324. pSOCache->AddObject( pNewTypeCache->GetObject( unObject ) );
  2325. }
  2326. }
  2327. }
  2328. // remove all the objects in the new cache
  2329. pNewCache->RemoveAllObjectsWithoutDeleting();
  2330. delete pNewCache;
  2331. // if there's a session for this cache, tell it about the reload
  2332. if( pSOCache->GetOwner().BIndividualAccount() )
  2333. {
  2334. CGCUserSession *pUserSession = FindUserSession( pSOCache->GetOwner() );
  2335. if( pUserSession )
  2336. pUserSession->YieldingSOCacheReloaded();
  2337. }
  2338. else if( pSOCache->GetOwner().BGameServerAccount() )
  2339. {
  2340. CGCGSSession *pGSSession = FindGSSession( pSOCache->GetOwner() );
  2341. if( pGSSession )
  2342. pGSSession->YieldingSOCacheReloaded();
  2343. }
  2344. }
  2345. //-----------------------------------------------------------------------------
  2346. // Purpose: Factory method to create a CGCSharedObjectCache
  2347. // Input : &steamID - steamID that will own the CGCSharedObjectCache
  2348. // Output : Returns a new CGCSharedObjectCache
  2349. //-----------------------------------------------------------------------------
  2350. CGCSharedObjectCache *CGCBase::CreateSOCache( const CSteamID &steamID )
  2351. {
  2352. return new CGCSharedObjectCache( steamID );
  2353. }
  2354. //-----------------------------------------------------------------------------
  2355. // Purpose: yields until the lock on the specified steamID is taken
  2356. // Input : &steamID - steamID to lock
  2357. // Output : Returns true on success, false on failure.
  2358. //-----------------------------------------------------------------------------
  2359. bool CGCBase::BYieldingLockSteamID( const CSteamID &steamID, const char *pszFilename, int nLineNum )
  2360. {
  2361. AssertRunningJob();
  2362. Assert( steamID.GetEAccountType() != k_EAccountTypePending );
  2363. // lookup
  2364. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2365. if ( !pLock )
  2366. {
  2367. // no lock yet, insert one
  2368. pLock = m_hashSteamIDLocks.PvRecordInsert( steamID );
  2369. pLock->SetName( steamID );
  2370. pLock->SetLockSubType( steamID.GetAccountID() );
  2371. if ( steamID.BIndividualAccount() )
  2372. {
  2373. pLock->SetLockType( k_nLockTypeIndividual );
  2374. }
  2375. else if ( steamID.BGameServerAccount() )
  2376. {
  2377. pLock->SetLockType( k_nLockTypeGameServer );
  2378. }
  2379. else
  2380. {
  2381. AssertMsg1( false, "Lock taken for unexpected steamID: %s", steamID.Render() );
  2382. }
  2383. }
  2384. Assert( pLock );
  2385. if ( !pLock )
  2386. {
  2387. EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Unable to create lock for %s\n", steamID.Render() );
  2388. return false;
  2389. }
  2390. return GJobCur()._BYieldingAcquireLock( pLock, pszFilename, nLineNum );
  2391. }
  2392. //-----------------------------------------------------------------------------
  2393. // Purpose: locks a pair of steam IDs, grabbing the highest account ID first
  2394. // to satisfy the deadlock-avoidance code in the job system
  2395. //-----------------------------------------------------------------------------
  2396. bool CGCBase::BYieldingLockSteamIDPair( const CSteamID &steamIDA, const CSteamID &steamIDB, const char *pszFilename, int nLineNum )
  2397. {
  2398. if( steamIDA == steamIDB )
  2399. return BYieldingLockSteamID( steamIDA, pszFilename, nLineNum );
  2400. //
  2401. // !FIXME! This is really not the correct sort criteron to use. The correct
  2402. // criteria is to use the full lock priority. For example,
  2403. // what if we pass a gameserver ID and a user ID. The whole
  2404. // concept of locking two SteamID's is probably broken when we split up
  2405. // things on the GC, though, so this might not be worth fixing.
  2406. //
  2407. if( steamIDA.GetAccountID() < steamIDB.GetAccountID() )
  2408. {
  2409. if( !BYieldingLockSteamID( steamIDB, pszFilename, nLineNum ) )
  2410. return false;
  2411. if( !BYieldingLockSteamID( steamIDA, pszFilename, nLineNum ) )
  2412. {
  2413. UnlockSteamID( steamIDB );
  2414. return false;
  2415. }
  2416. }
  2417. else
  2418. {
  2419. if( !BYieldingLockSteamID( steamIDA, pszFilename, nLineNum ) )
  2420. return false;
  2421. if( !BYieldingLockSteamID( steamIDB, pszFilename, nLineNum ) )
  2422. {
  2423. UnlockSteamID( steamIDA );
  2424. return false;
  2425. }
  2426. }
  2427. return true;
  2428. }
  2429. //-----------------------------------------------------------------------------
  2430. // Purpose: locks the specified steamID
  2431. // Input : &steamID - steamID to unlock
  2432. //-----------------------------------------------------------------------------
  2433. bool CGCBase::BLockSteamIDImmediate( const CSteamID &steamID )
  2434. {
  2435. AssertRunningJob();
  2436. Assert( steamID.GetEAccountType() != k_EAccountTypePending );
  2437. // lookup
  2438. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2439. if ( pLock == NULL )
  2440. {
  2441. // no lock yet, insert one
  2442. pLock = m_hashSteamIDLocks.PvRecordInsert( steamID );
  2443. Assert( pLock != NULL );
  2444. if ( pLock == NULL )
  2445. {
  2446. return false;
  2447. }
  2448. if ( steamID.BIndividualAccount() )
  2449. {
  2450. pLock->SetLockType( k_nLockTypeIndividual );
  2451. }
  2452. else if ( steamID.BGameServerAccount() )
  2453. {
  2454. pLock->SetLockType( k_nLockTypeGameServer );
  2455. }
  2456. else
  2457. {
  2458. AssertMsg1( false, "Lock taken for unexpected steamID: %s", steamID.Render() );
  2459. }
  2460. pLock->SetName( steamID );
  2461. pLock->SetLockSubType( steamID.GetAccountID() );
  2462. }
  2463. return GJobCur().BAcquireLockImmediate( pLock );
  2464. }
  2465. //-----------------------------------------------------------------------------
  2466. // Purpose: unlocks the specified steamID
  2467. // Input : &steamID - steamID to unlock
  2468. //-----------------------------------------------------------------------------
  2469. void CGCBase::UnlockSteamID( const CSteamID &steamID )
  2470. {
  2471. AssertRunningJob();
  2472. Assert( steamID.GetEAccountType() != k_EAccountTypePending );
  2473. // lookup
  2474. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2475. Assert( pLock );
  2476. if ( !pLock )
  2477. {
  2478. AssertMsg2( false, "UnlockSteamID( '%s' ) called by %s but unable to find lock in map", steamID.Render(), GJobCur().GetName() );
  2479. return;
  2480. }
  2481. if ( pLock->GetJobLocking() != &GJobCur() )
  2482. {
  2483. AssertMsg2( false, "UnlockSteamID( '%s' ) called when job %s doesn't own the lock", steamID.Render(), GJobCur().GetName() );
  2484. return;
  2485. }
  2486. GJobCur().ReleaseLock( pLock );
  2487. }
  2488. //-----------------------------------------------------------------------------
  2489. // Purpose: returns true if the specified steamID is locked
  2490. //-----------------------------------------------------------------------------
  2491. bool CGCBase::IsSteamIDLocked( const CSteamID &steamID )
  2492. {
  2493. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2494. if ( pLock )
  2495. return pLock->BIsLocked();
  2496. return false;
  2497. }
  2498. //-----------------------------------------------------------------------------
  2499. // Purpose: returns true if the specified steamID is locked by the specified job
  2500. //-----------------------------------------------------------------------------
  2501. bool CGCBase::IsSteamIDLockedByJob( const CSteamID &steamID, const CJob *pJob ) const
  2502. {
  2503. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2504. if ( pLock )
  2505. return ( pLock->GetJobLocking() == pJob );
  2506. return false;
  2507. }
  2508. //-----------------------------------------------------------------------------
  2509. // Purpose: returns true if the specified steamID is locked by the current job
  2510. //-----------------------------------------------------------------------------
  2511. bool CGCBase::IsSteamIDLockedByCurJob( const CSteamID &steamID ) const
  2512. {
  2513. AssertRunningJob();
  2514. return IsSteamIDLockedByJob( steamID, &GJobCur() );
  2515. }
  2516. //-----------------------------------------------------------------------------
  2517. // Purpose: returns true if the specified steamID is unlocked, or locked by the current job
  2518. //-----------------------------------------------------------------------------
  2519. bool CGCBase::IsSteamIDUnlockedOrLockedByCurJob( const CSteamID &steamID )
  2520. {
  2521. AssertRunningJob();
  2522. // lookup
  2523. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2524. if ( !pLock )
  2525. {
  2526. // Unlocked
  2527. return true;
  2528. }
  2529. // It is in the hash of locks and is locked return true only if it is locked by the current job
  2530. if ( pLock->BIsLocked() )
  2531. {
  2532. return ( pLock->GetJobLocking() == &GJobCur() );
  2533. }
  2534. else
  2535. {
  2536. return true;
  2537. }
  2538. }
  2539. //-----------------------------------------------------------------------------
  2540. // Purpose: returns a pointer to the lock for the steamID, or NULL if none
  2541. //-----------------------------------------------------------------------------
  2542. const CLock *CGCBase::FindSteamIDLock( const CSteamID &steamID )
  2543. {
  2544. // lookup
  2545. return m_hashSteamIDLocks.PvRecordFind( steamID );
  2546. }
  2547. //-----------------------------------------------------------------------------
  2548. // Purpose: returns a pointer to the job holding the lock for this steamID, NULL if none
  2549. //-----------------------------------------------------------------------------
  2550. CJob *CGCBase::PJobHoldingLock( const CSteamID &steamID )
  2551. {
  2552. AssertRunningJob();
  2553. // lookup
  2554. CLock *pLock = m_hashSteamIDLocks.PvRecordFind( steamID );
  2555. if ( !pLock || !pLock->BIsLocked() )
  2556. {
  2557. // Unlocked
  2558. return NULL;
  2559. }
  2560. // Return the job holding the lock
  2561. return pLock->GetJobLocking();
  2562. }
  2563. //-----------------------------------------------------------------------------
  2564. // Purpose: returns a pointer to the job holding the lock for this steamID, NULL if none
  2565. //-----------------------------------------------------------------------------
  2566. bool CGCBase::YieldingWritebackDirtyCaches( uint32 unSecondToDelayWrite )
  2567. {
  2568. CSQLAccess sqlAccess;
  2569. CUtlVector< CGCSharedObjectCache * > vecCachesWritten;
  2570. uint32 unWrittenCount = 0;
  2571. sqlAccess.BBeginTransaction( "CGCBase::YieldingWritebackDirtyCaches()" );
  2572. RTime32 unFirstTimeToWrite = time( NULL ) - unSecondToDelayWrite;
  2573. FOR_EACH_VEC( m_vecCacheWritebacks, nCache )
  2574. {
  2575. CGCSharedObjectCache *pSOCache = m_vecCacheWritebacks[ nCache ];
  2576. // if this cache entered the writeback list too frequently, skip it for now
  2577. if( unSecondToDelayWrite > 0 && pSOCache->GetWritebackTime() > unFirstTimeToWrite )
  2578. {
  2579. continue;
  2580. }
  2581. // if we can't get the lock for ourselves, catch it on the next time around
  2582. if( !BLockSteamIDImmediate( pSOCache->GetOwner() ) )
  2583. {
  2584. continue;
  2585. }
  2586. unWrittenCount += pSOCache->YieldingStageAllWrites( sqlAccess );
  2587. vecCachesWritten.AddToTail( pSOCache );
  2588. m_vecCacheWritebacks.Remove( nCache );
  2589. nCache--;
  2590. // don't hog all the CPU. Yield and wait for the next frame if
  2591. // we've been running for too long. Go ahead and write these
  2592. // caches so we don't hold their locks forever though.
  2593. if( GJobCur().GetMicrosecondsRun() > (uint64)(writeback_queue_max_accumulate_time.GetInt() * k_nThousand) ||
  2594. ( writeback_queue_max_caches.GetInt() > 0 && vecCachesWritten.Count() > writeback_queue_max_caches.GetInt() ) )
  2595. {
  2596. // We've spent enough time accumulating work. Time to run some SQL
  2597. // queries.
  2598. break;
  2599. }
  2600. }
  2601. // Commit the transaction
  2602. if( !sqlAccess.BCommitTransaction( true ) )
  2603. {
  2604. // the transaction failed. Put those caches back on the TODO list
  2605. EmitError( SPEW_GC, "CGCBase::YieldingWritebackDirtyCaches() - Writeback failed\n" );
  2606. m_vecCacheWritebacks.AddMultipleToTail( vecCachesWritten.Count(), vecCachesWritten.Base() );
  2607. FOR_EACH_VEC( vecCachesWritten, nCache )
  2608. {
  2609. CGCSharedObjectCache *pSOCache = vecCachesWritten[nCache];
  2610. UnlockSteamID( pSOCache->GetOwner() );
  2611. }
  2612. return false;
  2613. }
  2614. else
  2615. {
  2616. // the transaction was successful. Tell those caches to forget their dirtiness
  2617. FOR_EACH_VEC( vecCachesWritten, nCache )
  2618. {
  2619. CGCSharedObjectCache *pSOCache = vecCachesWritten[nCache];
  2620. pSOCache->SetInWriteback( false );
  2621. UnlockSteamID( pSOCache->GetOwner() );
  2622. }
  2623. return true;
  2624. }
  2625. }
  2626. //-----------------------------------------------------------------------------
  2627. // Purpose:
  2628. //-----------------------------------------------------------------------------
  2629. void CGCBase::AddCacheToWritebackQueue( CGCSharedObjectCache *pSOCache )
  2630. {
  2631. Assert( pSOCache );
  2632. if ( ( g_pJobCur != NULL ) && PJobHoldingLock( pSOCache->GetOwner() ) != g_pJobCur && !GGCBase()->BIsSOCacheBeingLoaded( pSOCache->GetOwner() ) )
  2633. {
  2634. AssertMsg2( false, "CGCBase::AddCacheToWritebackQueue called by job %s for %s, but job does not own lock", g_pJobCur->GetName(), pSOCache->GetOwner().Render() );
  2635. }
  2636. if( !pSOCache->GetInWriteback() )
  2637. {
  2638. m_vecCacheWritebacks.AddToTail( pSOCache );
  2639. pSOCache->SetInWriteback( true );
  2640. }
  2641. }
  2642. //-----------------------------------------------------------------------------
  2643. // Purpose:
  2644. //-----------------------------------------------------------------------------
  2645. bool CGCBase::BYieldingRetrieveCacheVersion( CGCSharedObjectCache *pSOCache )
  2646. {
  2647. if ( !socache_persist_version_via_memcached.GetBool() )
  2648. {
  2649. // We'll keep doing the updates, but fail to restore it if not requested.
  2650. return false;
  2651. }
  2652. CFmtStr1024 key( "SOCacheVersionV2_%llu", pSOCache->GetOwner().ConvertToUint64() );
  2653. GCMemcachedGetResult_t data;
  2654. if ( !BYieldingMemcachedGet( key.Access(), data ) || !data.m_bKeyFound || sizeof( uint64 ) != data.m_bufValue.Count() )
  2655. {
  2656. #ifdef _DEBUG
  2657. EmitInfo( SPEW_CONSOLE, 3, 3, "SOCacheVersion - Failed to retrieve SO Cache version for: %s\n", pSOCache->GetOwner().Render() );
  2658. #endif
  2659. return false;
  2660. }
  2661. //we have a memcached version, so make sure that our version matches what was stored in memcache
  2662. uint64 unVersion = *( (uint64 *)data.m_bufValue.Base() );
  2663. pSOCache->SetVersion( unVersion );
  2664. #ifdef _DEBUG
  2665. EmitInfo( SPEW_CONSOLE, 3, 3, "SOCacheVersion::Load - Loaded version from memcached for %s (%llu)\n", pSOCache->GetOwner().Render(), pSOCache->GetVersion() );
  2666. #endif
  2667. return true;
  2668. }
  2669. //-----------------------------------------------------------------------------
  2670. // Purpose:
  2671. //-----------------------------------------------------------------------------
  2672. void CGCBase::AddCacheToVersionChangedList( CGCSharedObjectCache *pSOCache )
  2673. {
  2674. m_rbtreeSOCachesWithDirtyVersions.InsertIfNotFound( pSOCache->GetOwner() );
  2675. }
  2676. //-----------------------------------------------------------------------------
  2677. // Purpose:
  2678. //-----------------------------------------------------------------------------
  2679. void CGCBase::UpdateSOCacheVersions()
  2680. {
  2681. CUtlVector<CUtlString> vecSetKeys( 0, m_rbtreeSOCachesWithDirtyVersions.Count() );
  2682. CUtlVector<GCMemcachedBuffer_t> vecSetValues( 0, m_rbtreeSOCachesWithDirtyVersions.Count() );
  2683. CUtlBuffer bufData( 0, ( sizeof( uint64 ) * m_rbtreeSOCachesWithDirtyVersions.Count() ) + 1 );
  2684. CUtlVector<CUtlString> vecDeleteKeys( 0, m_rbtreeSOCachesWithDirtyVersions.Count() );
  2685. for ( int idx = 0; idx < m_rbtreeSOCachesWithDirtyVersions.MaxElement(); ++idx )
  2686. {
  2687. if ( !m_rbtreeSOCachesWithDirtyVersions.IsValidIndex( idx ) )
  2688. continue;
  2689. const CSteamID &steamID = m_rbtreeSOCachesWithDirtyVersions[idx];
  2690. // if the SO Cache is being loaded, ignore
  2691. if ( m_rbtreeSOCachesBeingLoaded.Find( steamID ) != m_rbtreeSOCachesBeingLoaded.InvalidIndex() )
  2692. continue;
  2693. CSharedObjectCache *pSOCache = FindSOCache( steamID );
  2694. if ( pSOCache )
  2695. {
  2696. CUtlString &strKey = vecSetKeys[ vecSetKeys.AddToTail() ];
  2697. strKey.Format( "SOCacheVersionV2_%llu", steamID.ConvertToUint64() );
  2698. GCMemcachedBuffer_t &bufVal = vecSetValues[ vecSetValues.AddToTail() ];
  2699. bufVal.m_pubData = (byte *)bufData.Base() + bufData.TellPut();
  2700. bufVal.m_cubData = sizeof( uint64 );
  2701. bufData.PutInt64( pSOCache->GetVersion() );
  2702. #ifdef _DEBUG
  2703. EmitInfo( SPEW_CONSOLE, 3, 3, "SOCacheVersion - storing version in memcached for %s (%llu).\n", steamID.Render(), pSOCache->GetVersion() );
  2704. #endif
  2705. }
  2706. else
  2707. {
  2708. // SO Cache is gone, so to be safe, remove the cached version number from memcached
  2709. CUtlString &strKey = vecDeleteKeys[ vecDeleteKeys.AddToTail() ];
  2710. strKey.Format( "SOCacheVersionV2_%llu", steamID.ConvertToUint64() );
  2711. #ifdef _DEBUG
  2712. EmitInfo( SPEW_CONSOLE, 3, 3, "SOCacheVersion - no SO Cache, removing version in memcached for %s.\n", steamID.Render() );
  2713. #endif
  2714. }
  2715. }
  2716. if ( vecSetKeys.Count() > 0 )
  2717. {
  2718. BMemcachedSet( vecSetKeys, vecSetValues );
  2719. }
  2720. if ( vecDeleteKeys.Count() > 0 )
  2721. {
  2722. BMemcachedDelete( vecDeleteKeys );
  2723. }
  2724. m_rbtreeSOCachesWithDirtyVersions.RemoveAll();
  2725. }
  2726. //-----------------------------------------------------------------------------
  2727. // Purpose: Returns the publisher access key for Steam Web APIs. This is just
  2728. // a stub and must be implimented by a child class if they want this
  2729. // funtionality.
  2730. //-----------------------------------------------------------------------------
  2731. const char *CGCBase::GetSteamAPIKey()
  2732. {
  2733. AssertMsg( false, "GetWebAPIKey(): Implement me!" );
  2734. EmitError( SPEW_CONSOLE, "GetWebAPIKey(): Implement me!\n" );
  2735. return "InvalidKey";
  2736. }
  2737. //-----------------------------------------------------------------------------
  2738. // Purpose: Returns true if the protobuf object was stored successfully, false otherwise
  2739. //-----------------------------------------------------------------------------
  2740. bool CGCBase::BMemcachedSet( const char *pKey, const ::google::protobuf::Message &protoBufObj )
  2741. {
  2742. // build key
  2743. CUtlVector< CUtlString > vecKeys;
  2744. int idx = vecKeys.AddToTail();
  2745. vecKeys[idx].Set( pKey );
  2746. // allocate buffer we will use to stuff into the memcached buffer
  2747. CUtlVector< CGCBase::GCMemcachedBuffer_t > vecValues;
  2748. uint32 unSize = protoBufObj.ByteSize();
  2749. void *pvBuf = stackalloc( unSize );
  2750. protoBufObj.SerializeWithCachedSizesToArray( (uint8*)pvBuf );
  2751. // stuff the data into the memcached buffer
  2752. CGCBase::GCMemcachedBuffer_t buffer;
  2753. buffer.m_pubData = pvBuf;
  2754. buffer.m_cubData = unSize;
  2755. vecValues.AddToTail( buffer );
  2756. return BMemcachedSet( vecKeys, vecValues );
  2757. }
  2758. //-----------------------------------------------------------------------------
  2759. // Purpose: Returns true if the memcached value stored via pKey was removed succesfully, false otherwise
  2760. //-----------------------------------------------------------------------------
  2761. bool CGCBase::BMemcachedDelete( const char *pKey )
  2762. {
  2763. CUtlVector< CUtlString > vecKeys;
  2764. int idx = vecKeys.AddToTail();
  2765. vecKeys[idx].Set( pKey );
  2766. return BMemcachedDelete( vecKeys );
  2767. }
  2768. //-----------------------------------------------------------------------------
  2769. // Purpose: Returns true if the protobuf object was retrieved from memcached successfully, false otherwise
  2770. //-----------------------------------------------------------------------------
  2771. bool CGCBase::BYieldingMemcachedGet( const char *pKey, ::google::protobuf::Message &protoBufMsg )
  2772. {
  2773. // build key
  2774. CUtlVector< CUtlString > vecKeys;
  2775. int idx = vecKeys.AddToTail();
  2776. vecKeys[idx].Set( pKey );
  2777. // get results
  2778. CUtlVector< CGCBase::GCMemcachedGetResult_t > vecResults;
  2779. if ( !BYieldingMemcachedGet( vecKeys, vecResults ) || vecResults.Count() != 1 || vecResults[0].m_bKeyFound == false )
  2780. {
  2781. return false;
  2782. }
  2783. if ( !protoBufMsg.ParseFromArray( vecResults[0].m_bufValue.Base(), vecResults[0].m_bufValue.Count() ) )
  2784. {
  2785. return false;
  2786. }
  2787. if ( !protoBufMsg.IsInitialized() )
  2788. {
  2789. return false;
  2790. }
  2791. return true;
  2792. }
  2793. //-----------------------------------------------------------------------------
  2794. // Purpose: Set the keys and values into memcached
  2795. //-----------------------------------------------------------------------------
  2796. bool CGCBase::BMemcachedSet( const CUtlVector<CUtlString> &vecKeys, const CUtlVector<GCMemcachedBuffer_t> &vecValues )
  2797. {
  2798. Assert( vecKeys.Count() == vecValues.Count() );
  2799. if ( vecKeys.Count() != vecValues.Count() )
  2800. return false;
  2801. CProtoBufMsg<CGCMsgMemCachedSet> msgRequest( k_EGCMsgMemCachedSet );
  2802. for ( int i = 0; i < vecKeys.Count(); ++i )
  2803. {
  2804. CGCMsgMemCachedSet_KeyPair *keypair = msgRequest.Body().add_keys();
  2805. keypair->set_name( vecKeys[i].String() );
  2806. keypair->set_value( vecValues[i].m_pubData, vecValues[i].m_cubData );
  2807. }
  2808. if( !BSendSystemMessage( msgRequest ) )
  2809. return false;
  2810. // There is no reply to setting in memcached
  2811. return true;
  2812. }
  2813. //-----------------------------------------------------------------------------
  2814. // Purpose: Overload for a single key/value
  2815. //-----------------------------------------------------------------------------
  2816. bool CGCBase::BMemcachedSet( const CUtlString &strKey, const CUtlBuffer &buf )
  2817. {
  2818. CUtlVector<CUtlString> memcachedMemberKeys( 0, 1 );
  2819. CUtlVector<CGCBase::GCMemcachedBuffer_t> memcachedMemberValues( 0, 1 );
  2820. memcachedMemberKeys.AddToTail( strKey );
  2821. CGCBase::GCMemcachedBuffer_t &memcachedBuffer = memcachedMemberValues[ memcachedMemberValues.AddToTail() ];
  2822. memcachedBuffer.m_pubData = buf.Base();
  2823. memcachedBuffer.m_cubData = buf.TellPut();
  2824. return BMemcachedSet( memcachedMemberKeys, memcachedMemberValues );
  2825. }
  2826. //-----------------------------------------------------------------------------
  2827. // Purpose: Delete the keys in memcached
  2828. //-----------------------------------------------------------------------------
  2829. bool CGCBase::BMemcachedDelete( const CUtlVector<CUtlString> &vecKeys )
  2830. {
  2831. CProtoBufMsg<CGCMsgMemCachedDelete> msgRequest( k_EGCMsgMemCachedDelete );
  2832. for ( int i = 0; i < vecKeys.Count(); ++i )
  2833. {
  2834. msgRequest.Body().add_keys( vecKeys[i].String() );
  2835. }
  2836. if( !BSendSystemMessage( msgRequest ) )
  2837. return false;
  2838. // There is no reply to deleting in memcached
  2839. return true;
  2840. }
  2841. //-----------------------------------------------------------------------------
  2842. // Purpose: Overload for a single key/value
  2843. //-----------------------------------------------------------------------------
  2844. bool CGCBase::BMemcachedDelete( const CUtlString &strKey )
  2845. {
  2846. CUtlVector<CUtlString> vecKeys( 0, 1 );
  2847. vecKeys.AddToTail( strKey );
  2848. return BMemcachedDelete( vecKeys );
  2849. }
  2850. //-----------------------------------------------------------------------------
  2851. // Purpose: Get the key's values from memcached
  2852. //-----------------------------------------------------------------------------
  2853. bool CGCBase::BYieldingMemcachedGet( const CUtlVector<CUtlString> &vecKeys, CUtlVector<GCMemcachedGetResult_t> &vecResults )
  2854. {
  2855. CProtoBufMsg<CGCMsgMemCachedGet> msgRequest( k_EGCMsgMemCachedGet );
  2856. for ( int i = 0; i < vecKeys.Count(); ++i )
  2857. {
  2858. msgRequest.Body().add_keys( vecKeys[i].String() );
  2859. }
  2860. msgRequest.ExpectingReply( GJobCur().GetJobID() );
  2861. if( !BSendSystemMessage( msgRequest ) )
  2862. return false;
  2863. CProtoBufMsg<CGCMsgMemCachedGetResponse> msgResponse;
  2864. if( !GJobCur().BYieldingWaitForMsg( &msgResponse, k_EGCMsgMemCachedGetResponse ) )
  2865. {
  2866. EmitWarning( SPEW_GC, LOG_ALWAYS, "Didn't get reply from IS for BYieldingMemcachedGet\n" );
  2867. return false;
  2868. }
  2869. Assert( msgRequest.Body().keys_size() == msgResponse.Body().values_size() );
  2870. if ( msgRequest.Body().keys_size() != msgResponse.Body().values_size() )
  2871. {
  2872. EmitWarning( SPEW_GC, LOG_ALWAYS, "Mismatched reply from IS for BYieldingMemcachedGet, asked for %d keys, got %d back\n", (int)msgRequest.Body().keys_size(), (int)msgResponse.Body().values_size() );
  2873. return false; // Doesn't match what we asked for!
  2874. }
  2875. vecResults.Purge();
  2876. vecResults.EnsureCapacity( msgResponse.Body().values_size() );
  2877. for ( int i = 0; i < msgResponse.Body().values_size(); ++i )
  2878. {
  2879. GCMemcachedGetResult_t &result = vecResults[ vecResults.AddToTail() ];
  2880. result.m_bKeyFound = msgResponse.Body().values(i).found();
  2881. if ( result.m_bKeyFound )
  2882. {
  2883. result.m_bufValue.Copy( &(*msgResponse.Body().values(i).value().begin()), msgResponse.Body().values(i).value().size() );
  2884. }
  2885. }
  2886. return true;
  2887. }
  2888. //-----------------------------------------------------------------------------
  2889. // Purpose: Overload for a single key/value
  2890. //-----------------------------------------------------------------------------
  2891. bool CGCBase::BYieldingMemcachedGet( const CUtlString &strKeys, GCMemcachedGetResult_t &result )
  2892. {
  2893. CUtlVector<CUtlString> memcachedMemberKeys( 0, 1 );
  2894. CUtlVector<GCMemcachedGetResult_t> memcachedResults;
  2895. memcachedMemberKeys.AddToTail( strKeys );
  2896. bool bRet = BYieldingMemcachedGet( memcachedMemberKeys, memcachedResults );
  2897. if ( !bRet )
  2898. return false;
  2899. Assert( 1 == memcachedResults.Count() );
  2900. if ( 1 != memcachedResults.Count() )
  2901. return false;
  2902. result.m_bKeyFound = memcachedResults[0].m_bKeyFound;
  2903. result.m_bufValue.Swap( memcachedResults[0].m_bufValue );
  2904. return true;
  2905. }
  2906. //-----------------------------------------------------------------------------
  2907. bool CGCBase::BYieldingGetIPLocations( CUtlVector<uint32> &vecIPs, CUtlVector<CIPLocationInfo> &infos )
  2908. {
  2909. CProtoBufMsg<CGCMsgGetIPLocation> msgRequest( k_EGCMsgGetIPLocation );
  2910. FOR_EACH_VEC( vecIPs, i )
  2911. {
  2912. msgRequest.Body().add_ips( vecIPs[i] );
  2913. }
  2914. msgRequest.ExpectingReply( GJobCur().GetJobID() );
  2915. if( !BSendSystemMessage( msgRequest ) )
  2916. return false;
  2917. // We don't need to worry about a reply mismatch in this case. The message
  2918. // has sufficient data so that we can match up the reply properly.
  2919. GJobCur().ClearFailedToReceivedMsgType( k_EGCMsgGetIPLocationResponse );
  2920. CProtoBufMsg<CGCMsgGetIPLocationResponse> msgResponse;
  2921. if( !GJobCur().BYieldingWaitForMsg( &msgResponse, k_EGCMsgGetIPLocationResponse ) )
  2922. {
  2923. EmitWarning( SPEW_GC, LOG_ALWAYS, "Didn't get reply from IS for BYieldingGetIPLocation\n" );
  2924. return false;
  2925. }
  2926. for ( int i = 0; i < msgResponse.Body().infos_size(); i++ )
  2927. {
  2928. infos.AddToTail( msgResponse.Body().infos( i ) );
  2929. }
  2930. return true;
  2931. }
  2932. //-----------------------------------------------------------------------------
  2933. bool CGCBase::BYieldingUpdateGeoLocation( CUtlVector<CSteamID> const &requestedVecSteamIds )
  2934. {
  2935. CUtlVector<uint32> vecIPs;
  2936. CUtlVector<CSteamID> vecSteamIds;
  2937. FOR_EACH_VEC( requestedVecSteamIds, i )
  2938. {
  2939. const CSteamID memberSteamID = requestedVecSteamIds[i];
  2940. CGCSession *pSession = FindUserOrGSSession( memberSteamID );
  2941. if( pSession )
  2942. {
  2943. if ( !pSession->GetIPPublic() )
  2944. {
  2945. EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYieldingUpdateGeoLocation Session %s IP == 0, unable to retrieve\n", memberSteamID.Render() ) ;
  2946. continue;
  2947. }
  2948. if ( !pSession->HasGeoLocation() )
  2949. {
  2950. vecIPs.AddToTail( pSession->GetIPPublic() );
  2951. vecSteamIds.AddToTail( memberSteamID );
  2952. }
  2953. }
  2954. }
  2955. if (!vecIPs.Count())
  2956. return true;
  2957. #define iptod(x) ((x)>>24&0xff), ((x)>>16&0xff), ((x)>>8&0xff), ((x)&0xff)
  2958. FOR_EACH_VEC( vecIPs, i )
  2959. {
  2960. EmitInfo( SPEW_GC, geolocation_spewlevel.GetInt(), geolocation_loglevel.GetInt(), "BYieldingUpdateGeoLocation GetIPLocation[%d] = (%s,%u.%u.%u.%u)\n", i, vecSteamIds[i].Render(), iptod( vecIPs[i] ) ) ;
  2961. }
  2962. CUtlVector<CIPLocationInfo> infos;
  2963. if ( BYieldingGetIPLocations( vecIPs, infos ) )
  2964. {
  2965. // The current IS has a bug where the IP will be blank/zero in the replies. If infos.Count() == vecIPs.Count() assume the order is correct
  2966. if ( vecSteamIds.Count() == vecIPs.Count() && vecIPs.Count() == infos.Count() )
  2967. {
  2968. FOR_EACH_VEC( vecSteamIds, i )
  2969. {
  2970. CGCSession *pSession = FindUserOrGSSession( vecSteamIds[i] );
  2971. if ( pSession )
  2972. {
  2973. EmitInfo( SPEW_GC, geolocation_spewlevel.GetInt(), geolocation_loglevel.GetInt(), "BYieldingUpdateGeoLocation[MATCHED] SetIPLocation[%s(%u.%u.%u.%u)] = (%6.3f,%6.3f)\n", pSession->GetSteamID().Render(), iptod( vecIPs[i] ), infos[i].latitude(), infos[i].longitude() );
  2974. pSession->SetGeoLocation( infos[i].latitude(), infos[i].longitude() );
  2975. }
  2976. }
  2977. }
  2978. else
  2979. {
  2980. FOR_EACH_VEC( vecSteamIds, i )
  2981. {
  2982. FOR_EACH_VEC( infos, j )
  2983. {
  2984. if ( infos[j].ip() == vecIPs[i] )
  2985. {
  2986. CGCSession *pSession = FindUserOrGSSession( vecSteamIds[i] );
  2987. if ( pSession )
  2988. {
  2989. EmitInfo( SPEW_GC, 4, LOG_ALWAYS, "BYieldingUpdateGeoLocation[SEARCHED] SetIPLocation[%s(%u.%u.%u.%u)] = (%6.3f,%6.3f)\n", pSession->GetSteamID().Render(), iptod( vecIPs[i] ), infos[j].latitude(), infos[j].longitude() );
  2990. pSession->SetGeoLocation( infos[j].latitude(), infos[j].longitude() );
  2991. }
  2992. }
  2993. }
  2994. }
  2995. }
  2996. return true;
  2997. }
  2998. return false;
  2999. }
  3000. //-----------------------------------------------------------------------------
  3001. // Purpose: Populate the KeyValues with the stats
  3002. //-----------------------------------------------------------------------------
  3003. void CGCBase::SystemStats_Update( CGCMsgGetSystemStatsResponse &msgStats )
  3004. {
  3005. msgStats.set_active_jobs( m_JobMgr.CountJobs() );
  3006. msgStats.set_yielding_jobs( m_JobMgr.CountYieldingJobs() );
  3007. msgStats.set_user_sessions( m_hashUserSessions.Count() );
  3008. msgStats.set_game_server_sessions( m_hashGSSessions.Count() );
  3009. msgStats.set_socaches( m_mapSOCache.Count() );
  3010. msgStats.set_socaches_to_unload( m_listCachesToUnload.Count() );
  3011. msgStats.set_socaches_loading( m_rbtreeSOCachesBeingLoaded.Count() );
  3012. msgStats.set_writeback_queue( m_vecCacheWritebacks.Count() );
  3013. msgStats.set_steamid_locks( m_hashSteamIDLocks.Count() );
  3014. msgStats.set_logon_queue( m_llStartPlaying.Count() );
  3015. msgStats.set_logon_jobs( m_nStartPlayingJobCount );
  3016. }
  3017. //-----------------------------------------------------------------------------
  3018. // Purpose: Returns the singleton GC object
  3019. //-----------------------------------------------------------------------------
  3020. CGCBase *GGCBase()
  3021. {
  3022. return g_pGCBase;
  3023. }
  3024. //-----------------------------------------------------------------------------
  3025. // Purpose: Spews information about the active locks on the GC
  3026. //-----------------------------------------------------------------------------
  3027. int LockSortFunc( CLock * const *lhs, CLock * const *rhs )
  3028. {
  3029. return (*rhs)->GetWaitingCount() - (*lhs)->GetWaitingCount();
  3030. }
  3031. void CGCBase::DumpSteamIDLocks( bool bFull, int nMax )
  3032. {
  3033. CUtlVector<CLock *> vecLocks;
  3034. for( CLock *pLock = m_hashSteamIDLocks.PvRecordFirst(); NULL != pLock; pLock = m_hashSteamIDLocks.PvRecordNext( pLock ) )
  3035. {
  3036. if( pLock->BIsLocked() )
  3037. {
  3038. vecLocks.AddToTail( pLock );
  3039. }
  3040. }
  3041. vecLocks.Sort( LockSortFunc );
  3042. if( nMax > vecLocks.Count() || bFull )
  3043. {
  3044. nMax = vecLocks.Count();
  3045. }
  3046. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%d locks total. %d locked, %d displayed\n", m_hashSteamIDLocks.Count(), vecLocks.Count(), nMax );
  3047. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Lock Holding Job First Waiting Job Wait Count Lock Time\n" );
  3048. for( int nLock = 0; nLock < nMax; nLock++ )
  3049. {
  3050. CLock *pLock = vecLocks[nLock];
  3051. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-24s %-22s %-22s %-11d %d\n",
  3052. pLock->GetName(),
  3053. pLock->GetJobLocking() ? pLock->GetJobLocking()->GetName() : "--",
  3054. pLock->GetJobWaitingQueueHead() ? pLock->GetJobWaitingQueueHead()->GetName() : "--",
  3055. pLock->GetWaitingCount(),
  3056. (int) ( pLock->GetMicroSecondsSinceLock() / k_nMillion ) );
  3057. }
  3058. }
  3059. //-----------------------------------------------------------------------------
  3060. // Purpose: Dumps informations about currently running jobs
  3061. //-----------------------------------------------------------------------------
  3062. void CGCBase::DumpJobs( const char *pszJobName, int nMax, int nPrintLocksMax ) const
  3063. {
  3064. m_JobMgr.DumpJobs( pszJobName, nMax, nPrintLocksMax );
  3065. }
  3066. //-----------------------------------------------------------------------------
  3067. // Purpose: Dumps information about a specific job
  3068. //-----------------------------------------------------------------------------
  3069. void CGCBase::DumpJob( JobID_t jobID, int nPrintLocksMax ) const
  3070. {
  3071. m_JobMgr.DumpJob( jobID, nPrintLocksMax );
  3072. }
  3073. //-----------------------------------------------------------------------------
  3074. // Purpose: Returns counts of core objects
  3075. //-----------------------------------------------------------------------------
  3076. int CGCBase::GetSOCacheCount() const
  3077. {
  3078. return m_mapSOCache.Count();
  3079. }
  3080. bool CGCBase::IsSOCached( const CSharedObject *pObj, uint32 nTypeID ) const
  3081. {
  3082. // OPT: If there are many caches, this is very slow - it would be faster have a ref count on the shared object to track this.
  3083. // However this is debug only code.
  3084. #if defined( DEBUG )
  3085. FOR_EACH_MAP_FAST( m_mapSOCache, i )
  3086. {
  3087. CGCSharedObjectCache *pCache = m_mapSOCache[ i ];
  3088. if ( pCache->IsObjectCached( pObj, nTypeID ) )
  3089. {
  3090. return true;
  3091. }
  3092. if ( pCache->IsObjectDirty( pObj ) )
  3093. {
  3094. Assert( false );
  3095. return true;
  3096. }
  3097. }
  3098. #else
  3099. AssertMsg( false, "Calling IsSOCached() in release mode. This is a debug only function" );
  3100. #endif
  3101. return false;
  3102. }
  3103. int CGCBase::GetUserSessionCount() const
  3104. {
  3105. return m_hashUserSessions.Count();
  3106. }
  3107. int CGCBase::GetGSSessionCount() const
  3108. {
  3109. return m_hashGSSessions.Count();
  3110. }
  3111. //-----------------------------------------------------------------------------
  3112. // Purpose: Mark that we are shutting down
  3113. //-----------------------------------------------------------------------------
  3114. void CGCBase::SetIsShuttingDown()
  3115. {
  3116. m_bIsShuttingDown = true;
  3117. GetJobMgr().SetIsShuttingDown();
  3118. }
  3119. //-----------------------------------------------------------------------------
  3120. // Purpose: Sets whether we are profiling or not
  3121. //-----------------------------------------------------------------------------
  3122. void CGCBase::SetProfilingEnabled( bool bEnabled )
  3123. {
  3124. if ( bEnabled )
  3125. {
  3126. m_bStartProfiling = true;
  3127. }
  3128. else
  3129. {
  3130. m_bStopProfiling = true;
  3131. }
  3132. }
  3133. //-----------------------------------------------------------------------------
  3134. // Purpose: Sets whether to spew about vprof imbalances
  3135. //-----------------------------------------------------------------------------
  3136. void CGCBase::SetDumpVprofImbalances( bool bEnabled )
  3137. {
  3138. m_bDumpVprofImbalances = bEnabled;
  3139. }
  3140. //-----------------------------------------------------------------------------
  3141. // Purpose: Returns whether we are spewing vprof imbalances
  3142. //-----------------------------------------------------------------------------
  3143. bool CGCBase::GetVprofImbalances()
  3144. {
  3145. return m_bDumpVprofImbalances;
  3146. }
  3147. //-----------------------------------------------------------------------------
  3148. // Purpose: Returns a steam ID for a user-provided input. Works with accountID,
  3149. // steam account name, or steam ID.
  3150. //-----------------------------------------------------------------------------
  3151. CSteamID CGCBase::YieldingGuessSteamIDFromInput( const char *pchInput )
  3152. {
  3153. AssertRunningJob();
  3154. if( !pchInput )
  3155. {
  3156. EmitError( SPEW_CONSOLE, "Invalid NULL string passed to YieldingGuessSteamIDFromInput\n" );
  3157. return CSteamID();
  3158. }
  3159. EUniverse localUniverse = m_pHost->GetUniverse();
  3160. // Is it a 64 bit Steam ID?
  3161. if ( pchInput[0] >= '0' && pchInput[0] <= '9' )
  3162. {
  3163. CSteamID steamID( V_atoui64( pchInput ) );
  3164. if ( steamID.IsValid() )
  3165. return steamID;
  3166. }
  3167. // quoted
  3168. // See if it's a profile link. If it is, clip the SteamID from it.
  3169. const char *pszProfilePrepend = "steamcommunity.com/profiles/";
  3170. int iInputLen = Q_strlen(pchInput);
  3171. int iProfilePrependLen = Q_strlen(pszProfilePrepend);
  3172. const char *pszFound = NULL;
  3173. if ( (pszFound = Q_stristr( pchInput, pszProfilePrepend )) != NULL )
  3174. {
  3175. if ( iInputLen > ((pszFound + iProfilePrependLen) - pchInput) )
  3176. {
  3177. CSteamID steamID;
  3178. steamID.SetFromString( (pszFound + iProfilePrependLen), localUniverse );
  3179. if ( steamID.IsValid() )
  3180. return steamID;
  3181. }
  3182. }
  3183. // See if it's an id link.
  3184. const char *pszIDPrepend = "steamcommunity.com/id/";
  3185. int iIDPrependLen = Q_strlen(pszIDPrepend);
  3186. if ( (pszFound = Q_stristr( pchInput, pszIDPrepend )) != NULL )
  3187. {
  3188. if ( iInputLen > ((pszFound + iIDPrependLen) - pchInput) )
  3189. {
  3190. char szMaxURL[512];
  3191. Q_strncpy( szMaxURL, (pszFound + iIDPrependLen), sizeof(szMaxURL) );
  3192. // Trim off a trailing slash
  3193. int iURLLen = Q_strlen(szMaxURL);
  3194. if ( szMaxURL[iURLLen-1] == '/' || pchInput[iURLLen-1] == '\\' )
  3195. {
  3196. szMaxURL[iURLLen-1] = '\0';
  3197. }
  3198. CUtlVector< CSteamID > vecIDs;
  3199. if ( BYieldingLookupAccount( k_EFindAccountTypeURL, szMaxURL, &vecIDs ) )
  3200. {
  3201. // Should only ever find a single account for a URL
  3202. if ( vecIDs.Count() == 1 )
  3203. return vecIDs[0];
  3204. }
  3205. }
  3206. }
  3207. CGCMsg< MsgGCEmpty_t > msg( k_EGCMsgLookupAccountFromInput );
  3208. msg.AddStrData( pchInput );
  3209. msg.ExpectingReply( GJobCur().GetJobID() );
  3210. if( !BSendSystemMessage( msg ) )
  3211. {
  3212. EmitError( SPEW_CONSOLE, "Unable to query GCHost in YieldingGuessSteamIDFromInput\n" );
  3213. return CSteamID();
  3214. }
  3215. CGCMsg< MsgGCLookupAccountResponse > msgReply;
  3216. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgGenericReply ) )
  3217. {
  3218. EmitError( SPEW_CONSOLE, "No response from GCHost in YieldingGuessSteamIDFromInput\n" );
  3219. return CSteamID();
  3220. }
  3221. return CSteamID( msgReply.Body().m_ulSteamID );
  3222. }
  3223. //-----------------------------------------------------------------------------
  3224. // Purpose: Returns all matching Steam IDs for the specified query.
  3225. // Returns: true if a response was received from Steam. The list may still be
  3226. // empty in that case.
  3227. //-----------------------------------------------------------------------------
  3228. bool CGCBase::BYieldingLookupAccount( EAccountFindType eFindType, const char *pchInput, CUtlVector< CSteamID > *prSteamIDs )
  3229. {
  3230. if ( eFindType == k_EFindAccountTypeURL )
  3231. {
  3232. CSteamAPIRequest apiRequest( k_EHTTPMethodGET, "ISteamUser", "ResolveVanityURL", 1 );
  3233. apiRequest.SetGETParamString( "vanityurl", pchInput );
  3234. KeyValuesAD kvAPIResponse( "response" );
  3235. CUtlString sWebApiErrMsg;
  3236. EResult eResult = YieldingSendWebAPIRequest( apiRequest, kvAPIResponse, sWebApiErrMsg, false );
  3237. if ( k_EResultOK != eResult )
  3238. {
  3239. // Emit an error on the less-common errors
  3240. if ( k_EResultNoMatch != eResult )
  3241. {
  3242. EmitError( SPEW_GC, "WebAPI error looking up vanity URL by GC. %s\n", sWebApiErrMsg.String() );
  3243. }
  3244. return false;
  3245. }
  3246. prSteamIDs->AddToTail( CSteamID( kvAPIResponse->GetUint64( "steamid" ) ) );
  3247. return true;
  3248. }
  3249. else
  3250. {
  3251. CProtoBufMsg< CMsgAMFindAccounts > msg( k_EGCMsgFindAccounts );
  3252. msg.Body().set_search_type( eFindType );
  3253. msg.Body().set_search_string( pchInput );
  3254. msg.ExpectingReply( GJobCur().GetJobID() );
  3255. if( !BSendSystemMessage( msg ) )
  3256. {
  3257. EmitError( SPEW_GC, "Unable to send GCMsgFindAccounts\n" );
  3258. return false;
  3259. }
  3260. CProtoBufMsg< CMsgAMFindAccountsResponse > response;
  3261. if( !GJobCur().BYieldingWaitForMsg( &response, k_EGCMsgGenericReply ) )
  3262. {
  3263. EmitError( SPEW_GC, "No response to GCMsgFindAccounts\n" );
  3264. return false;
  3265. }
  3266. for( int i=0; i<response.Body().steam_id_size(); i++ )
  3267. {
  3268. prSteamIDs->AddToTail( CSteamID( response.Body().steam_id( i ) ) );
  3269. }
  3270. return true;
  3271. }
  3272. }
  3273. GC_CON_COMMAND( gc_search_vanityurl, "Tests searching for an account by vanity URL" )
  3274. {
  3275. CUtlVector< CSteamID > vecIDs;
  3276. if ( GGCBase()->BYieldingLookupAccount( k_EFindAccountTypeURL, args[1], &vecIDs ) )
  3277. {
  3278. Msg( "Search success.\n" );
  3279. FOR_EACH_VEC( vecIDs, i )
  3280. {
  3281. CSteamID result = vecIDs[i];
  3282. Msg( "Result: %llu\n", result.ConvertToUint64() );
  3283. }
  3284. }
  3285. else
  3286. {
  3287. Msg( "Search failure.\n" );
  3288. }
  3289. }
  3290. //-----------------------------------------------------------------------------
  3291. // Purpose: Dumps a summary of the GC's status
  3292. //-----------------------------------------------------------------------------
  3293. bool CGCBase::BYieldingRecordSupportAction( const CSteamID & actorID, const CSteamID & targetID, const char *pchData, const char *pchNote )
  3294. {
  3295. CGCMsg< MsgGCRecordSupportAction_t > msgRecordSupportAction( k_EGCMsgRecordSupportAction );
  3296. msgRecordSupportAction.Body().m_unAccountID = targetID.GetAccountID();
  3297. msgRecordSupportAction.Body().m_unActorID = actorID.GetAccountID();
  3298. msgRecordSupportAction.AddStrData( pchData );
  3299. msgRecordSupportAction.AddStrData( pchNote );
  3300. msgRecordSupportAction.ExpectingReply( GJobCur().GetJobID() );
  3301. GGCBase()->BSendSystemMessage( msgRecordSupportAction );
  3302. CGCMsg< MsgGCEmpty_t > msgReply;
  3303. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgGenericReply ) )
  3304. {
  3305. EmitError( SPEW_GC, "No reply received to support action message\n" );
  3306. return false;
  3307. }
  3308. else
  3309. {
  3310. return true;
  3311. }
  3312. }
  3313. //-----------------------------------------------------------------------------
  3314. // Purpose: Posts a steam alert to the alert alias for this GC's app.
  3315. //-----------------------------------------------------------------------------
  3316. void CGCBase::PostAlert( EAlertType eAlertType, bool bIsCritical, const char *pchAlertText, const CUtlVector< CUtlString > *pvecExtendedInfo, bool bAlsoSpew )
  3317. {
  3318. CProtoBufMsg< CMsgNotifyWatchdog > msg( k_EGCMsgPostAlert );
  3319. msg.Body().set_alert_type( eAlertType );
  3320. msg.Body().set_critical( bIsCritical );
  3321. if( !pvecExtendedInfo )
  3322. {
  3323. msg.Body().set_text( pchAlertText );
  3324. }
  3325. else
  3326. {
  3327. // put all the messages in one giant string and set that as the text
  3328. // figure out how big "giant" is
  3329. uint32 unSize = Q_strlen( pchAlertText ) + 2; // header + \n + null
  3330. FOR_EACH_VEC( *pvecExtendedInfo, nLine )
  3331. {
  3332. unSize += pvecExtendedInfo->Element( nLine ).Length();
  3333. }
  3334. // walk the strings again to assemble the buffer
  3335. CUtlBuffer bufMessage( 0, unSize, CUtlBuffer::TEXT_BUFFER );
  3336. bufMessage.PutString( pchAlertText );
  3337. bufMessage.PutString( "\n" );
  3338. FOR_EACH_VEC( *pvecExtendedInfo, nLine )
  3339. {
  3340. bufMessage.PutString( pvecExtendedInfo->Element( nLine ).Get() );
  3341. }
  3342. msg.Body().set_text( (const char *)bufMessage.Base() );
  3343. }
  3344. if( bAlsoSpew )
  3345. {
  3346. EmitError( SPEW_GC, "%s", msg.Body().text().c_str() );
  3347. }
  3348. BSendSystemMessage( msg );
  3349. }
  3350. //-----------------------------------------------------------------------------
  3351. // Purpose: Fills the vector with all package IDs this account has a license to
  3352. //-----------------------------------------------------------------------------
  3353. bool CGCBase::BYieldingGetAccountLicenses( const CSteamID & steamID, CUtlVector< PackageLicense_t > & vecPackages )
  3354. {
  3355. CProtoBufMsg< CMsgAMGetLicenses > msg( k_EGCMsgGetLicenses );
  3356. msg.Body().set_steamid( steamID.ConvertToUint64() );
  3357. msg.ExpectingReply( GJobCur().GetJobID() );
  3358. if( !BSendSystemMessage( msg ) )
  3359. {
  3360. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Unable to send GetAccountLicenses system message\n" );
  3361. return false;
  3362. }
  3363. CProtoBufMsg< CMsgAMGetLicensesResponse > msgReply;
  3364. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgGenericReply ) )
  3365. {
  3366. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Timeout waiting for GetAccountLicenses reply\n" );
  3367. return false;
  3368. }
  3369. if( msgReply.Body().result() != k_EResultOK )
  3370. {
  3371. EmitWarning( SPEW_GC, SPEW_ALWAYS, "GetAccountLicenses for %s failed with %d\n", steamID.Render(), msgReply.Body().result() );
  3372. return false;
  3373. }
  3374. vecPackages.RemoveAll();
  3375. vecPackages.EnsureCapacity( msgReply.Body().license_size() );
  3376. for( int i=0; i < msgReply.Body().license_size(); i++ )
  3377. {
  3378. const CMsgPackageLicense &msgPackage = msgReply.Body().license( i );
  3379. //skip packages that they directly don't own (they may be lent to them via library sharing, and we don't want to grant based on that).
  3380. //we count account ID of zero as matching so we can deal with old Steam versions that didn't provide this field
  3381. if( ( msgPackage.owner_id() != steamID.GetAccountID() ) && ( msgPackage.owner_id() != 0 ) )
  3382. continue;
  3383. PackageLicense_t package;
  3384. package.m_unPackageID = msgPackage.package_id();
  3385. package.m_rtimeCreated = msgPackage.time_created();
  3386. vecPackages.AddToTail( package );
  3387. }
  3388. return true;
  3389. }
  3390. //-----------------------------------------------------------------------------
  3391. // Purpose: Fills the vector with all package IDs this account has a license to
  3392. //-----------------------------------------------------------------------------
  3393. bool CGCBase::BYieldingAddFreeLicense( const CSteamID & steamID, uint32 unPackageID, uint32 unIPPublic, const char *pchStoreCountryCode )
  3394. {
  3395. CProtoBufMsg< CMsgAMAddFreeLicense > msg( k_EGCMsgAddFreeLicense );
  3396. msg.Body().set_steamid( steamID.ConvertToUint64() );
  3397. msg.Body().set_packageid( unPackageID );
  3398. if( unIPPublic )
  3399. msg.Body().set_ip_public( unIPPublic );
  3400. if( pchStoreCountryCode )
  3401. msg.Body().set_store_country_code( pchStoreCountryCode );
  3402. msg.ExpectingReply( GJobCur().GetJobID() );
  3403. if( !BSendSystemMessage( msg ) )
  3404. {
  3405. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Unable to send GetAccountLicenses system message\n" );
  3406. return false;
  3407. }
  3408. CProtoBufMsg< CMsgAMAddFreeLicenseResponse > msgReply;
  3409. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgAddFreeLicenseResponse ) )
  3410. {
  3411. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Timeout waiting for GetAccountLicenses reply\n" );
  3412. return false;
  3413. }
  3414. if( msgReply.Body().eresult() != k_EResultOK )
  3415. {
  3416. EmitWarning( SPEW_GC, SPEW_ALWAYS, "BYieldingAddFreeLicense for %s failed with %d\n", steamID.Render(), msgReply.Body().eresult() );
  3417. return false;
  3418. }
  3419. return true;
  3420. }
  3421. //-----------------------------------------------------------------------------
  3422. // Purpose: Fills the vector with all package IDs this account has a license to
  3423. //-----------------------------------------------------------------------------
  3424. int CGCBase::YieldingGrantGuestPass( const CSteamID & steamID, uint32 unPackageID, uint32 unPassesToGrant, int32 nDaysToExpiration )
  3425. {
  3426. CProtoBufMsg<CMsgAMGrantGuestPasses2> msg( k_EGCMsgGrantGuestPass );
  3427. msg.Body().set_steam_id( steamID.ConvertToUint64() );
  3428. msg.Body().set_package_id( unPackageID );
  3429. msg.Body().set_passes_to_grant( unPassesToGrant );
  3430. msg.Body().set_days_to_expiration( nDaysToExpiration );
  3431. msg.ExpectingReply( GJobCur().GetJobID() );
  3432. if( !BSendSystemMessage( msg ) )
  3433. {
  3434. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Unable to send GrantGuestPass system message\n" );
  3435. return 0;
  3436. }
  3437. CProtoBufMsg<CMsgAMGrantGuestPasses2Response> msgReply;
  3438. if( !GJobCur().BYieldingWaitForMsg( &msgReply, k_EGCMsgGrantGuestPassResponse ) )
  3439. {
  3440. EmitWarning( SPEW_GC, SPEW_ALWAYS, "Timeout waiting for GrantGuestPass reply\n" );
  3441. return 0;
  3442. }
  3443. if( msgReply.Body().eresult() != k_EResultOK )
  3444. {
  3445. EmitWarning( SPEW_GC, SPEW_ALWAYS, "YieldingGrantGuestPass for %s failed with %d\n", steamID.Render(), msgReply.Body().eresult() );
  3446. return 0;
  3447. }
  3448. return msgReply.Body().passes_granted();
  3449. }
  3450. //-----------------------------------------------------------------------------
  3451. // Purpose: Gets data for an account
  3452. //-----------------------------------------------------------------------------
  3453. const CAccountDetails *CGCBase::YieldingGetAccountDetails( const CSteamID & steamID, bool bForceReload )
  3454. {
  3455. return m_AccountDetailsManager.YieldingGetAccountDetails( steamID, bForceReload );
  3456. }
  3457. //-----------------------------------------------------------------------------
  3458. // Purpose: Gets the current persona name for an account
  3459. //-----------------------------------------------------------------------------
  3460. const char *CGCBase::YieldingGetPersonaName( const CSteamID & steamID, const char *szUnknownName )
  3461. {
  3462. const char *szPersonaName = m_AccountDetailsManager.YieldingGetPersonaName( steamID );
  3463. return szPersonaName ? szPersonaName : szUnknownName;
  3464. }
  3465. //-----------------------------------------------------------------------------
  3466. // Purpose: Clears a persona name from the cache
  3467. //-----------------------------------------------------------------------------
  3468. void CGCBase::ClearCachedPersonaName( const CSteamID & steamID )
  3469. {
  3470. m_AccountDetailsManager.ClearCachedPersonaName( steamID );
  3471. }
  3472. //-----------------------------------------------------------------------------
  3473. // Purpose: Tells us to load the persona name for a user, but not wait on it
  3474. //-----------------------------------------------------------------------------
  3475. void CGCBase::PreloadPersonaName( const CSteamID & steamID )
  3476. {
  3477. m_AccountDetailsManager.PreloadPersonaName( steamID );
  3478. }
  3479. //-----------------------------------------------------------------------------
  3480. // Purpose: Sends a message to the web API servers letting them know what the
  3481. // methods and interfaces are for this GC.
  3482. //-----------------------------------------------------------------------------
  3483. bool CGCBase::BSendWebApiRegistration()
  3484. {
  3485. // if we aren't initialized enough to have a GCHost, just skip this
  3486. // registration request. We'll register later in our init process.
  3487. if( !m_pHost )
  3488. return false;
  3489. if( CGCWebAPIInterfaceMapRegistrar::VecInstance().Count() > 0 )
  3490. {
  3491. CGCMsg< MsgGCWebAPIRegisterInterfaces_t > msgWebRegistration( k_EGCMsgWebAPIRegisterInterfaces );
  3492. msgWebRegistration.Body().m_cInterfaces = CGCWebAPIInterfaceMapRegistrar::VecInstance().Count();
  3493. CUtlBuffer bufRegistrations;
  3494. FOR_EACH_VEC( CGCWebAPIInterfaceMapRegistrar::VecInstance(), nInterface )
  3495. {
  3496. KeyValues *pkvInterface = CGCWebAPIInterfaceMapRegistrar::VecInstance()[ nInterface ]();
  3497. Assert( pkvInterface );
  3498. if( !pkvInterface )
  3499. return false;
  3500. KVPacker packer;
  3501. packer.WriteAsBinary( pkvInterface, bufRegistrations );
  3502. pkvInterface->deleteThis();
  3503. }
  3504. msgWebRegistration.AddVariableLenData( bufRegistrations.Base(), bufRegistrations.TellPut() );
  3505. if( !BSendSystemMessage( msgWebRegistration ) )
  3506. return false;
  3507. }
  3508. return true;
  3509. }
  3510. //-----------------------------------------------------------------------------
  3511. // Purpose: Dumps a summary of the GC's status
  3512. //-----------------------------------------------------------------------------
  3513. void CGCBase::Dump() const
  3514. {
  3515. char rtimeBuf[k_RTimeRenderBufferSize];
  3516. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "GC Status for %d: path=%s\n", m_unAppID, m_sPath.Get() );
  3517. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tLogon Surge: %s\n", BIsInLogonSurge() ? "Yes" : "No" );
  3518. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tStartPlaying: waiting=%d, jobs running=%d of %d\n", m_llStartPlaying.Count(), m_nStartPlayingJobCount, cv_concurrent_start_playing_limit.GetInt() );
  3519. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tJobs: active=%d, yielding=%d\n", m_JobMgr.CountJobs(), m_JobMgr.CountYieldingJobs() );
  3520. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tSessions: user=%d, gameserver=%d\n", m_hashUserSessions.Count(), m_hashGSSessions.Count() );
  3521. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tCaches: %d (%d waiting to unload, %d currently loading, %s %d /+ %d)\n", m_mapSOCache.Count(), m_listCachesToUnload.Count(), m_rbtreeSOCachesBeingLoaded.Count(),
  3522. ( ( ( m_jobidFlushInventoryCacheAccounts == k_GIDNil ) || !m_JobMgr.BJobExists( m_jobidFlushInventoryCacheAccounts ) ) ? "last flushed" : "currently flushing" ),
  3523. m_numFlushInventoryCacheAccountsLastScheduled, m_rbFlushInventoryCacheAccounts.Count() );
  3524. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tWriteback Queue: %d (oldest: %s)\n", m_vecCacheWritebacks.Count(), m_vecCacheWritebacks.Count() > 0 ? CRTime::Render( m_vecCacheWritebacks[0]->GetWritebackTime(), rtimeBuf ) : "none" );
  3525. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\tYieldingRequestSession: %d active\n", m_nRequestSessionJobsActive );
  3526. m_AccountDetailsManager.Dump();
  3527. }
  3528. //-----------------------------------------------------------------------------
  3529. // Purpose: Dumps a summary of the GC's status
  3530. //-----------------------------------------------------------------------------
  3531. const char *CGCBase::GetCDNURL() const
  3532. {
  3533. if( m_sCDNURL.IsEmpty() )
  3534. {
  3535. switch( m_pHost->GetUniverse() )
  3536. {
  3537. case k_EUniverseDev:
  3538. case k_EUniverseBeta:
  3539. m_sCDNURL.Format( "http://cdn.beta.steampowered.com/apps/%d/", GetAppID() );
  3540. break;
  3541. case k_EUniversePublic:
  3542. default:
  3543. m_sCDNURL.Format( "http://media.steampowered.com/apps/%d/", GetAppID() );
  3544. break;
  3545. }
  3546. }
  3547. return m_sCDNURL.Get();
  3548. }
  3549. //-----------------------------------------------------------------------------
  3550. // Purpose: Prints an assert to the console
  3551. //-----------------------------------------------------------------------------
  3552. void CGCBase::AssertCallbackFunc( const char *pchFile, int nLine, const char *pchMessage )
  3553. {
  3554. if ( !ThreadInMainThread() ) // !KLUDGE!
  3555. {
  3556. EmitWarning( SPEW_GC, 4, "Thread assert %s(%d): %s\n", pchFile, nLine, pchMessage );
  3557. return;
  3558. }
  3559. // Our spew handler should have already spewed this once, no need to spew it again
  3560. //EmitError( SPEW_CONSOLE, "%s (%d): %s\n", V_GetFileName( pchFile ), nLine, pchMessage );
  3561. if ( !Plat_IsInDebugSession() )
  3562. {
  3563. char rchCleanedJobName[48] = "";
  3564. if ( ThreadInMainThread() && g_pJobCur != NULL )
  3565. {
  3566. const char *pszJobName = g_pJobCur->GetName();
  3567. int l = 0;
  3568. while ( l < sizeof(rchCleanedJobName)-1 )
  3569. {
  3570. char c = pszJobName[l];
  3571. if ( c == '\0' )
  3572. break;
  3573. if ( !V_isalnum( c ) )
  3574. {
  3575. c = '_';
  3576. }
  3577. rchCleanedJobName[l] = c;
  3578. ++l;
  3579. }
  3580. rchCleanedJobName[l] = 0;
  3581. }
  3582. // Throttle writing of minidumps on a file / line / job basis
  3583. CFmtStr sFileAndLine( "assert_%s(%d)%s%s",
  3584. V_GetFileName( pchFile ),
  3585. nLine,
  3586. rchCleanedJobName[0] ? "_" : "",
  3587. rchCleanedJobName
  3588. );
  3589. static CUtlDict< CCopyableUtlVector< RTime32 > > s_dictAsserts;
  3590. int iDict = s_dictAsserts.Find( sFileAndLine.Access() );
  3591. if ( !s_dictAsserts.IsValidIndex( iDict ) )
  3592. {
  3593. iDict = s_dictAsserts.Insert( sFileAndLine.Access() );
  3594. }
  3595. CCopyableUtlVector< RTime32 > &vecTimes = s_dictAsserts[iDict];
  3596. int nStale = 0;
  3597. while ( nStale < vecTimes.Count() && ( CRTime::RTime32TimeCur() - vecTimes[nStale] ) > (uint32)cv_assert_minidump_window.GetInt() )
  3598. {
  3599. nStale++;
  3600. }
  3601. vecTimes.RemoveMultipleFromHead( nStale );
  3602. bool bWriteDump = ( vecTimes.Count() < cv_assert_max_minidumps_in_window.GetInt() );
  3603. if ( bWriteDump )
  3604. {
  3605. vecTimes.AddToTail( CRTime::RTime32TimeCur() );
  3606. CUtlString sCurJob;
  3607. if ( ThreadInMainThread() && g_pJobCur != NULL )
  3608. {
  3609. sCurJob.Format( "[From job %s]\n", g_pJobCur->GetName() );
  3610. }
  3611. // Write the dump
  3612. CUtlString sDumpComment;
  3613. sDumpComment.Format( "%s%s%s(%d): %s",
  3614. GGCBase()->GetIsShuttingDown() ? "[During shutdown]\n" : "", // Asserts during shutdown are much more often spurious. Let's make it clear if a shutdown happens during shutdown
  3615. sCurJob.String(), // The name of the current job name is often an incredibly useful piece of info. If the dumps are not valid, this can narrow the search space immensely
  3616. pchFile,
  3617. nLine,
  3618. pchMessage
  3619. );
  3620. SetMinidumpComment( sDumpComment.String() );
  3621. WriteMiniDump( sFileAndLine.Access() );
  3622. SetMinidumpComment( "" ); // just for grins
  3623. }
  3624. }
  3625. }
  3626. //-----------------------------------------------------------------------------
  3627. // Purpose: Claims all the memory for the GC
  3628. //-----------------------------------------------------------------------------
  3629. void CGCBase::Validate( CValidator &validator, const char *pchName )
  3630. {
  3631. VPROF_BUDGET( "CGCBase::Validate", VPROF_BUDGETGROUP_STEAM );
  3632. // these are INSIDE the function instead of outside so the interface
  3633. // doesn't change
  3634. #ifdef DBGFLAG_VALIDATE
  3635. VALIDATE_SCOPE();
  3636. // Validate the global message list
  3637. g_theMessageList.Validate( validator, "g_theMessageList" );
  3638. // Validate the network global memory pool
  3639. g_MemPoolMsg.Validate( validator, "g_MemPoolMsg" );
  3640. CNetPacketPool::ValidateGlobals( validator );
  3641. CJobMgr::ValidateStatics( validator, "CJobMgr" );
  3642. CJob::ValidateStatics( validator, "CJob" );
  3643. ValidateTempTextBuffers( validator );
  3644. ValidateObj( m_JobMgr );
  3645. ValidateObj( m_sPath );
  3646. ValidateObj( m_hashUserSessions );
  3647. for( CGCUserSession **ppSession = m_hashUserSessions.PvRecordFirst(); ppSession != NULL; ppSession = m_hashUserSessions.PvRecordNext( ppSession ) )
  3648. {
  3649. ValidatePtr( *ppSession );
  3650. }
  3651. ValidateObj( m_hashGSSessions );
  3652. for( CGCGSSession **ppSession = m_hashGSSessions.PvRecordFirst(); ppSession != NULL; ppSession = m_hashGSSessions.PvRecordNext( ppSession ) )
  3653. {
  3654. ValidatePtr( *ppSession );
  3655. }
  3656. // validate the SQL access layer
  3657. CRecordBase::ValidateStatics( validator, "CRecordBase" );
  3658. GSchemaFull().Validate( validator, "GSchemaFull" );
  3659. CRecordInfo::ValidateStatics( validator, "CRecordInfo" );
  3660. CSharedObject::ValidateStatics( validator );
  3661. OnValidate( validator, pchName );
  3662. #endif // DBGFLAG_VALIDATE
  3663. }
  3664. EResult YieldingSendWebAPIRequest( CSteamAPIRequest &request, KeyValues *pKVResponse, CUtlString &errMsg, bool b200MeansSuccess )
  3665. {
  3666. CHTTPResponse apiResponse;
  3667. if ( !GGCBase()->BYieldingSendHTTPRequest( &request, &apiResponse ) )
  3668. {
  3669. errMsg.Format( "Did not get a response" );
  3670. return k_EResultTimeout;
  3671. }
  3672. if ( k_EHTTPStatusCode200OK != apiResponse.GetStatusCode() )
  3673. {
  3674. errMsg.Format( "HTTP status code %d", apiResponse.GetStatusCode() );
  3675. // if ( k_EResultOK != pKVResponse->GetInt( "result", k_EResultFail ) )
  3676. // {
  3677. // EmitError( SPEW_GC, "Web call to %s failed with error %d: %s\n",
  3678. // request.GetURL(),
  3679. // pKVResponse->GetInt( "error/errorcode", k_EResultFail ),
  3680. // pKVResponse->GetString( "error/errordesc" ) );
  3681. // return pKVResponse->GetInt( "error/errorcode", k_EResultFail );
  3682. // }
  3683. return k_EResultFail;
  3684. }
  3685. if ( apiResponse.GetBodyBuffer() )
  3686. {
  3687. pKVResponse->UsesEscapeSequences( true );
  3688. if ( !pKVResponse->LoadFromBuffer( "webResponse", *apiResponse.GetBodyBuffer() ) )
  3689. {
  3690. errMsg.Format( "Failed to parse keyvalues result" );
  3691. return k_EResultFail;
  3692. }
  3693. }
  3694. if ( b200MeansSuccess )
  3695. {
  3696. return k_EResultOK;
  3697. }
  3698. int result = pKVResponse->GetInt( "success", -1 );
  3699. if ( result < 0 )
  3700. {
  3701. errMsg = "Reply missing result code";
  3702. return k_EResultFail;
  3703. }
  3704. errMsg = pKVResponse->GetString( "message", "" );
  3705. if ( result != k_EResultOK && errMsg.IsEmpty() )
  3706. {
  3707. errMsg = "(Unknown error)";
  3708. }
  3709. return (EResult)result;
  3710. }
  3711. GC_CON_COMMAND( ip_geolocation, "<a.b.c.d> Perform geolocation lookup" )
  3712. {
  3713. if ( args.ArgC() < 2 )
  3714. {
  3715. EmitError( SPEW_GC, "Pass at least one IP to lookup\n" );
  3716. return;
  3717. }
  3718. // Get List of IP's to query
  3719. CUtlVector<uint32> vecIPs;
  3720. for ( int i = 1 ; i < args.ArgC() ; ++i )
  3721. {
  3722. netadr_t adr;
  3723. adr.SetFromString( args[i] );
  3724. if ( adr.GetIPHostByteOrder() == 0 )
  3725. {
  3726. EmitInfo( SPEW_GC, 1, 1, "%s is not a valid IP\n", args[i] );
  3727. }
  3728. else
  3729. {
  3730. vecIPs.AddToTail( adr.GetIPHostByteOrder() );
  3731. }
  3732. }
  3733. if ( vecIPs.Count() <= 0 )
  3734. return;
  3735. // Do the query
  3736. CUtlVector<CIPLocationInfo> vecInfos;
  3737. vecInfos.SetCount( vecIPs.Count() );
  3738. GGCBase()->BYieldingGetIPLocations( vecIPs, vecInfos );
  3739. for ( int i = 0 ; i < vecInfos.Count() ; ++i )
  3740. {
  3741. netadr_t adr( vecInfos[i].ip(), 0 );
  3742. EmitInfo( SPEW_GC, 1, 1, "%s: %.1f, %.1f\n", adr.ToString( true ), vecInfos[i].latitude(), vecInfos[i].longitude() );
  3743. }
  3744. }
  3745. } // namespace GCSDK