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.

999 lines
33 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Database Backed Object caching and manipulation
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include "sdocache.h"
  8. // memdbgon must be the last include file in a .cpp file!!!
  9. #include "tier0/memdbgon.h"
  10. GCConVar s_ConVarSDOCacheLRULimitMB( "@SDOCacheLRULimitMB", "400", FCVAR_REPLICATED );
  11. GCConVar s_ConVarSDOCacheMaxMemcachedReadJobs( "@SDOCacheMaxMemcachedReadJobs", "4", FCVAR_REPLICATED );
  12. GCConVar s_ConVarSDOCacheMaxMemcachedReadBatchSize( "@SDOCacheMaxMemcachedReadBatchSize", "100", FCVAR_REPLICATED );
  13. GCConVar s_ConVarSDOCacheMaxSQLReadJobsPerSDOType( "@SDOCacheMaxSQLReadJobsPerSDOType", "4", FCVAR_REPLICATED );
  14. GCConVar s_ConVarSDOCacheMaxSQLReadBatchSize( "@SDOCacheMaxSQLReadBatchSize", "100", FCVAR_REPLICATED );
  15. GCConVar s_ConVarSDOCacheMaxPendingSQLReads( "@SDOCacheMaxPendingSQLReads", "2000", FCVAR_REPLICATED );
  16. GCConVar s_ConVarSDOCacheMaxPendingMemcachedReads( "@SDOCacheMaxPendingMemcachedReads", "25000", FCVAR_REPLICATED );
  17. namespace GCSDK
  18. {
  19. // A string used to tell the difference between nil objects and actual objects in memcached
  20. const char k_rgchNilObjSerializedValue[] = "nilobj";
  21. // Global instance
  22. CSDOCache &GSDOCache()
  23. {
  24. static CSDOCache s_SDOCache;
  25. return s_SDOCache;
  26. }
  27. //-----------------------------------------------------------------------------
  28. // Purpose: Creates a key name that looks like "Prefix_%u" but faster
  29. //-----------------------------------------------------------------------------
  30. void CSDOCache::CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix )
  31. {
  32. Assert( FMTSTR_STD_LEN - unPrefixLen > 10 + 1 );
  33. V_memcpy( strDest.Access(), pchPrefix, unPrefixLen );
  34. _i64toa( unSuffix, strDest.Access() + unPrefixLen, 10 );
  35. strDest.Access()[ FMTSTR_STD_LEN - 1 ] = NULL;
  36. }
  37. //-----------------------------------------------------------------------------
  38. // Purpose: constructor
  39. //-----------------------------------------------------------------------------
  40. CSDOCache::CSDOCache() :
  41. m_cubLRUItems( 0 ),
  42. m_mapTypeStats( DefLessFunc( int ) )
  43. {
  44. memset( &m_StatsSDOCache, 0, sizeof( m_StatsSDOCache ) );
  45. }
  46. //-----------------------------------------------------------------------------
  47. // Purpose: destructor
  48. //-----------------------------------------------------------------------------
  49. CSDOCache::~CSDOCache()
  50. {
  51. // delete all the entries from our cache
  52. FOR_EACH_MAP_FAST( m_mapISDOLoaded, iMap )
  53. {
  54. delete m_mapISDOLoaded.Key( iMap );
  55. }
  56. FOR_EACH_MAP_FAST( m_mapQueuedRequests, iMap )
  57. {
  58. delete m_mapQueuedRequests.Key( iMap );
  59. }
  60. }
  61. //-----------------------------------------------------------------------------
  62. // Purpose: registers an SDO type
  63. //-----------------------------------------------------------------------------
  64. void CSDOCache::RegisterSDO( int nType, const char *pchName )
  65. {
  66. if ( !m_mapQueueSQLRequests.HasElement( nType ) )
  67. {
  68. m_mapQueueSQLRequests.Insert( nType, new SQLRequestManager_t );
  69. int iMap = m_mapTypeStats.Insert( nType );
  70. m_mapTypeStats[iMap].m_strName = pchName;
  71. }
  72. }
  73. //-----------------------------------------------------------------------------
  74. // Purpose: a SDO object has been referenced
  75. //-----------------------------------------------------------------------------
  76. void CSDOCache::OnSDOReferenced( ISDO *pSDO )
  77. {
  78. // lookup where the SDO is, it has where we are in the LRU
  79. int iMap = m_mapISDOLoaded.Find( pSDO );
  80. Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
  81. if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
  82. return;
  83. // move us out of the LRU
  84. if ( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) )
  85. {
  86. RemoveSDOFromLRU( iMap );
  87. }
  88. else
  89. {
  90. // we may not have been in the LRU if it's a custom insert
  91. }
  92. // Update stats
  93. int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
  94. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  95. {
  96. m_mapTypeStats[iMapStats].m_nRefed++;
  97. }
  98. }
  99. //-----------------------------------------------------------------------------
  100. // Purpose: a SDO object has gone released
  101. //-----------------------------------------------------------------------------
  102. void CSDOCache::OnSDOReleased( ISDO *pSDO )
  103. {
  104. // Update stats
  105. int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
  106. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  107. {
  108. m_mapTypeStats[iMapStats].m_nRefed--;
  109. }
  110. // lookup where the SDO is, it has where we are in the LRU
  111. int iMap = m_mapISDOLoaded.Find( pSDO );
  112. if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
  113. {
  114. // we shouldn't be in the LRU
  115. bool bInLRU = m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] );
  116. Assert( !bInLRU );
  117. if ( bInLRU )
  118. return;
  119. // count the bytes and move us to the head of the LRU
  120. ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
  121. LRUItem_t item = { pSDO, pSDO->CubBytesUsed() };
  122. m_mapISDOLoaded[iMap] = m_listLRU.AddToTail( item );
  123. m_cubLRUItems += item.m_cub;
  124. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  125. {
  126. m_mapTypeStats[iMapStats].m_cubUnrefed += item.m_cub;
  127. }
  128. }
  129. else
  130. {
  131. // it's actually valid it's not in the SDO cache anymore - could be an orphaned pointer
  132. if ( pSDO->GetRefCount() == 0 )
  133. {
  134. delete pSDO;
  135. }
  136. }
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose: pulls a cached SDO from the right maps
  140. //-----------------------------------------------------------------------------
  141. void CSDOCache::RemoveSDOFromLRU( int iMap )
  142. {
  143. int iLRU = m_mapISDOLoaded[iMap];
  144. Assert( m_listLRU.IsValidIndex( iLRU ) );
  145. if ( !m_listLRU.IsValidIndex( iLRU ) )
  146. return;
  147. int iMapStats = m_mapTypeStats.Find( m_listLRU[iLRU].m_pSDO->GetType() );
  148. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  149. {
  150. m_mapTypeStats[iMapStats].m_cubUnrefed -= m_listLRU[iLRU].m_cub;
  151. }
  152. m_cubLRUItems -= m_listLRU[iLRU].m_cub;
  153. m_listLRU.Remove( iLRU );
  154. m_mapISDOLoaded[iMap] = m_listLRU.InvalidIndex();
  155. // Update stats
  156. }
  157. //-----------------------------------------------------------------------------
  158. // Purpose: writes a SDO to memcached
  159. //-----------------------------------------------------------------------------
  160. bool CSDOCache::WriteSDOToMemcached( ISDO *pSDO )
  161. {
  162. CFmtStr strKey;
  163. CUtlBuffer buf;
  164. pSDO->GetMemcachedKeyName( strKey );
  165. pSDO->WriteToBuffer( buf );
  166. return GGCBase()->BMemcachedSet( strKey.Access(), buf );
  167. }
  168. //-----------------------------------------------------------------------------
  169. // Purpose: writes a SDO to memcached
  170. //-----------------------------------------------------------------------------
  171. bool CSDOCache::DeleteSDOFromMemcached( ISDO *pSDO )
  172. {
  173. CFmtStr strKey;
  174. pSDO->GetMemcachedKeyName( strKey );
  175. return GGCBase()->BMemcachedDelete( strKey.Access() );
  176. }
  177. //-----------------------------------------------------------------------------
  178. // Purpose: runs the queued batch loader
  179. //-----------------------------------------------------------------------------
  180. class CGCJobLoadSDOSetFromMemcached : public CGCJob
  181. {
  182. public:
  183. CGCJobLoadSDOSetFromMemcached( CGCBase *pGC )
  184. : CGCJob( pGC )
  185. {
  186. }
  187. void AddSDOToLoad( ISDO *pSDO, int iRequestID )
  188. {
  189. int i = m_vecRequests.AddToTail();
  190. m_vecRequests[i].m_pSDO = pSDO;
  191. m_vecRequests[i].m_iRequestID = iRequestID;
  192. }
  193. bool BYieldingRunJob( void * )
  194. {
  195. Assert( m_vecRequests.Count() > 0 );
  196. if ( 0 == m_vecRequests.Count() )
  197. {
  198. GSDOCache().OnMemcachedLoadJobComplete( GetJobID() );
  199. return false;
  200. }
  201. // get the names of all the items
  202. CUtlVector<CUtlString> vecKeys;
  203. vecKeys.AddMultipleToTail( m_vecRequests.Count() );
  204. FOR_EACH_VEC( m_vecRequests, i )
  205. {
  206. CFmtStr strKey;
  207. m_vecRequests[i].m_pSDO->GetMemcachedKeyName( strKey );
  208. vecKeys[i] = strKey;
  209. }
  210. // ask in a batch from memcached
  211. CUtlVector<CGCBase::GCMemcachedGetResult_t> vecGetResults;
  212. bool bGetSuccess = m_pGC->BYieldingMemcachedGet( vecKeys, vecGetResults );
  213. // go through each request looking up the results
  214. FOR_EACH_VEC( m_vecRequests, i )
  215. {
  216. if ( bGetSuccess && vecGetResults.IsValidIndex( i ) && vecGetResults[i].m_bKeyFound )
  217. {
  218. const CGCBase::GCMemcachedGetResult_t &result = vecGetResults[i];
  219. bool bNilObj = ( result.m_bufValue.Count() == sizeof( k_rgchNilObjSerializedValue )
  220. && 0 == V_memcmp( result.m_bufValue.Base(), k_rgchNilObjSerializedValue, sizeof( k_rgchNilObjSerializedValue ) ) );
  221. // we've loaded OK
  222. if ( bNilObj || ( result.m_bufValue.Count() && m_vecRequests[i].m_pSDO->BReadFromBuffer( result.m_bufValue.Base(), result.m_bufValue.Count() ) ) )
  223. {
  224. GSDOCache().OnSDOLoadSuccess( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID, bNilObj, &m_vecRequests[i].m_pSDO );
  225. GSDOCache().GetStats().m_cItemsLoadedFromMemcached += 1;
  226. GSDOCache().GetStats().m_cNilItemsLoadedFromMemcached += bNilObj ? 1 : 0;
  227. }
  228. else
  229. {
  230. // couldn't load; delete the entry
  231. m_pGC->BMemcachedDelete( vecKeys[i] );
  232. GSDOCache().OnMemcachedSDOLoadFailure( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID );
  233. }
  234. }
  235. else
  236. {
  237. // post back failure
  238. GSDOCache().OnMemcachedSDOLoadFailure( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID );
  239. }
  240. }
  241. GSDOCache().OnMemcachedLoadJobComplete( GetJobID() );
  242. return true;
  243. }
  244. private:
  245. struct Request_t
  246. {
  247. int m_iRequestID;
  248. ISDO *m_pSDO;
  249. };
  250. CUtlVector<Request_t> m_vecRequests;
  251. };
  252. //-----------------------------------------------------------------------------
  253. // Purpose: runs the queued batch loader
  254. //-----------------------------------------------------------------------------
  255. class CGCJobLoadSDOSetFromSQL : public CGCJob
  256. {
  257. public:
  258. CGCJobLoadSDOSetFromSQL( CGCBase *pGC, int eSDOType )
  259. : CGCJob( pGC ), m_eSDOType( eSDOType )
  260. {
  261. }
  262. void AddSDOToLoad( ISDO *pSDO, int iRequestID )
  263. {
  264. m_vecPSDO.AddToTail( pSDO );
  265. m_vecRequestID.AddToTail( iRequestID );
  266. m_vecResults.AddToTail( false );
  267. }
  268. bool BYieldingRunJob( void * )
  269. {
  270. Assert( m_vecPSDO.Count() == m_vecRequestID.Count() && m_vecPSDO.Count() == m_vecResults.Count() );
  271. Assert( m_vecPSDO.Count() > 0 );
  272. if ( 0 == m_vecPSDO.Count()
  273. || m_vecPSDO.Count() != m_vecRequestID.Count()
  274. || m_vecPSDO.Count() != m_vecResults.Count() )
  275. {
  276. GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
  277. return false;
  278. }
  279. // use the first item to load the rest
  280. bool bSQLLayerSucceeded = m_vecPSDO[0]->BYldLoadFromSQL( m_vecPSDO, m_vecResults );
  281. Assert( m_vecPSDO.Count() == m_vecRequestID.Count() && m_vecPSDO.Count() == m_vecResults.Count() );
  282. Assert( m_vecPSDO.Count() > 0 );
  283. if ( 0 == m_vecPSDO.Count()
  284. || m_vecPSDO.Count() != m_vecRequestID.Count()
  285. || m_vecPSDO.Count() != m_vecResults.Count() )
  286. {
  287. GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
  288. return false;
  289. }
  290. // walk each result
  291. FOR_EACH_VEC( m_vecRequestID, i )
  292. {
  293. if ( bSQLLayerSucceeded )
  294. {
  295. // loaded, great
  296. GSDOCache().OnSDOLoadSuccess( m_eSDOType, m_vecRequestID[i], !m_vecResults[i], &m_vecPSDO[i] );
  297. GSDOCache().WriteSDOToMemcached( m_vecPSDO[i] );
  298. GSDOCache().GetStats().m_cItemsLoadedFromSQL += 1;
  299. GSDOCache().GetStats().m_cNilItemsLoadedFromSQL += m_vecResults[i] ? 0 : 1;
  300. }
  301. else
  302. {
  303. // no good, item couldn't load
  304. GSDOCache().OnSQLSDOLoadFailure( m_eSDOType, m_vecRequestID[i], bSQLLayerSucceeded );
  305. GSDOCache().GetStats().m_cItemsFailedLoadFromSQL += 1;
  306. }
  307. }
  308. GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
  309. return true;
  310. }
  311. private:
  312. int m_eSDOType;
  313. // these objects all stay in sync
  314. // they need to be separate vectors so that they can be easily passed into other API's
  315. CUtlVector<ISDO *> m_vecPSDO;
  316. CUtlVector<int> m_vecRequestID;
  317. CUtlVector<bool> m_vecResults;
  318. };
  319. //-----------------------------------------------------------------------------
  320. // Purpose: continues any jobs that were waiting on items
  321. //-----------------------------------------------------------------------------
  322. bool CSDOCache::BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer )
  323. {
  324. bool bDoneWork = false;
  325. // continue any jobs
  326. while ( m_queueJobsToContinue.Count() && !limitTimer.BLimitReached() )
  327. {
  328. // pop the item off the head
  329. JobToWake_t jobToWake = m_queueJobsToContinue[ m_queueJobsToContinue.Head() ];
  330. m_queueJobsToContinue.Remove( m_queueJobsToContinue.Head() );
  331. GGCBase()->GetJobMgr().BRouteWorkItemCompletedIfExists( jobToWake.m_jobID, !jobToWake.m_bLoadLayerSuccess );
  332. }
  333. // if we're over the limit, LRU an item
  334. while ( !limitTimer.BLimitReached() && m_cubLRUItems > (uint32)( s_ConVarSDOCacheLRULimitMB.GetInt() * k_nMegabyte ) && m_listLRU.Count() )
  335. {
  336. // pull off the last item in the LRU
  337. LRUItem_t item = m_listLRU[ m_listLRU.Head() ];
  338. // kill the item
  339. int iMap = m_mapISDOLoaded.Find( item.m_pSDO );
  340. Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
  341. if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
  342. {
  343. Assert( 0 == item.m_pSDO->GetRefCount() );
  344. if ( 0 == item.m_pSDO->GetRefCount() )
  345. {
  346. RemoveSDOFromLRU( iMap );
  347. int iMapStats = m_mapTypeStats.Find( item.m_pSDO->GetType() );
  348. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  349. {
  350. if ( item.m_pSDO->BNilObject() )
  351. {
  352. m_mapTypeStats[iMapStats].m_nNilObjects--;
  353. }
  354. else
  355. {
  356. m_mapTypeStats[iMapStats].m_nLoaded--;
  357. }
  358. }
  359. m_mapISDOLoaded.RemoveAt( iMap );
  360. delete item.m_pSDO;
  361. }
  362. m_StatsSDOCache.m_cItemsLRUd += 1;
  363. m_StatsSDOCache.m_cBytesLRUd += item.m_cub;
  364. }
  365. bDoneWork = true;
  366. }
  367. // return true if there is still work remaining
  368. return bDoneWork || m_queueJobsToContinue.Count() > 0;
  369. }
  370. //-----------------------------------------------------------------------------
  371. // Purpose: runs the queued batch loader
  372. //-----------------------------------------------------------------------------
  373. bool CSDOCache::BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer )
  374. {
  375. bool bDoneWork = false;
  376. // batch load the set
  377. if ( m_queueMemcachedRequests.Count() )
  378. {
  379. // pass these off to a job
  380. if ( m_vecMemcachedJobs.Count() < s_ConVarSDOCacheMaxMemcachedReadJobs.GetInt() )
  381. {
  382. CGCJobLoadSDOSetFromMemcached *pJob = new CGCJobLoadSDOSetFromMemcached( GGCBase() );
  383. // add a full batch to the job
  384. int cItemsInBatch = 0;
  385. while ( m_queueMemcachedRequests.Count() && cItemsInBatch < s_ConVarSDOCacheMaxMemcachedReadBatchSize.GetInt() )
  386. {
  387. m_StatsSDOCache.m_cQueuedMemcachedRequests--;
  388. int iMapQueuedRequest = m_queueMemcachedRequests[ m_queueMemcachedRequests.Head() ];
  389. m_queueMemcachedRequests.Remove( m_queueMemcachedRequests.Head() );
  390. Assert( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) );
  391. if ( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) )
  392. {
  393. cItemsInBatch++;
  394. pJob->AddSDOToLoad( m_mapQueuedRequests.Key( iMapQueuedRequest ), iMapQueuedRequest );
  395. }
  396. }
  397. if ( cItemsInBatch > 0 )
  398. {
  399. m_StatMemcachedBatchSize.AddSample( cItemsInBatch * 100 );
  400. m_StatsSDOCache.m_nMemcachedBatchSizeAvgx100 = (int)m_StatMemcachedBatchSize.GetAveragedSample();
  401. // add to the list
  402. m_vecMemcachedJobs.AddToTail( pJob->GetJobID() );
  403. // start the job
  404. pJob->StartJob( NULL );
  405. // mark that we should be ran again
  406. bDoneWork = true;
  407. }
  408. else
  409. {
  410. delete pJob;
  411. }
  412. }
  413. }
  414. // return if we still have work to do
  415. return bDoneWork;
  416. }
  417. //-----------------------------------------------------------------------------
  418. // Purpose: runs the queued batch loader
  419. //-----------------------------------------------------------------------------
  420. bool CSDOCache::BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer )
  421. {
  422. bool bDoneWork = false;
  423. // loop through all items looking for batches to load
  424. FOR_EACH_MAP_FAST( m_mapQueueSQLRequests, iMapType )
  425. {
  426. // batch load the set
  427. SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iMapType];
  428. if ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() )
  429. {
  430. // pass these off to a job
  431. if ( sqlRequestManager->m_vecSQLJobs.Count() < s_ConVarSDOCacheMaxSQLReadJobsPerSDOType.GetInt() )
  432. {
  433. int nSDOType = m_mapQueueSQLRequests.Key( iMapType );
  434. CGCJobLoadSDOSetFromSQL *pJob = new CGCJobLoadSDOSetFromSQL( GGCBase(), nSDOType );
  435. // add a full batch to the job
  436. int cItemsInBatch = 0;
  437. while ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() && cItemsInBatch < s_ConVarSDOCacheMaxSQLReadBatchSize.GetInt() )
  438. {
  439. m_StatsSDOCache.m_cQueuedSQLRequests--;
  440. int iMapQueuedRequest = sqlRequestManager->m_queueRequestIDsToLoadFromSQL[ sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Head() ];
  441. sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Remove( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Head() );
  442. Assert( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) );
  443. if ( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) )
  444. {
  445. pJob->AddSDOToLoad( m_mapQueuedRequests.Key( iMapQueuedRequest ), iMapQueuedRequest );
  446. cItemsInBatch++;
  447. }
  448. }
  449. if ( cItemsInBatch > 0 )
  450. {
  451. m_StatSQLBatchSize.AddSample( cItemsInBatch * 100 );
  452. m_StatsSDOCache.m_nSQLBatchSizeAvgx100 = (int)m_StatSQLBatchSize.GetAveragedSample();
  453. // add to the list
  454. sqlRequestManager->m_vecSQLJobs.AddToTail( pJob->GetJobID() );
  455. // start the job
  456. pJob->StartJob( NULL );
  457. bDoneWork = true;
  458. }
  459. else
  460. {
  461. delete pJob;
  462. }
  463. }
  464. }
  465. }
  466. // update stats
  467. m_StatsSDOCache.m_cItemsInCache = m_mapISDOLoaded.Count();
  468. m_StatsSDOCache.m_cItemsQueuedToLoad = m_mapQueuedRequests.Count();
  469. m_StatsSDOCache.m_cBytesUnreferenced = m_cubLRUItems;
  470. m_StatsSDOCache.m_cItemsUnreferenced = m_listLRU.Count();
  471. if ( m_StatsSDOCache.m_cItemsUnreferenced )
  472. {
  473. // estimate the total bytes from the size of the average size of an unreferenced item
  474. m_StatsSDOCache.m_cBytesInCacheEst = (m_StatsSDOCache.m_cItemsInCache * m_StatsSDOCache.m_cBytesUnreferenced) / m_StatsSDOCache.m_cItemsUnreferenced;
  475. }
  476. // return true if we still have work to do
  477. return bDoneWork;
  478. }
  479. //-----------------------------------------------------------------------------
  480. // Purpose: queues a new item to try load from memcached
  481. //-----------------------------------------------------------------------------
  482. int CSDOCache::QueueMemcachedLoad( ISDO *pSDO )
  483. {
  484. int iMap = -1;
  485. if ( m_queueMemcachedRequests.Count() < s_ConVarSDOCacheMaxPendingMemcachedReads.GetInt() )
  486. {
  487. // insert a fresh item into the list
  488. iMap = m_mapQueuedRequests.Insert( pSDO );
  489. // add the key to the queue
  490. m_queueMemcachedRequests.AddToTail( iMap );
  491. m_StatsSDOCache.m_cQueuedMemcachedRequests++;
  492. }
  493. else
  494. {
  495. m_StatsSDOCache.m_cMemcachedRequestsRejectedTooBusy++;
  496. delete pSDO;
  497. }
  498. return iMap;
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Purpose: job results
  502. //-----------------------------------------------------------------------------
  503. void CSDOCache::OnSDOLoadSuccess( int eSDOType, int iRequestID, bool bNilObj, ISDO **ppSDO )
  504. {
  505. Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
  506. if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
  507. return;
  508. // set jobs waiting for the data to wake up
  509. CCopyableUtlVector<JobID_t> &vecJobs = m_mapQueuedRequests[iRequestID];
  510. FOR_EACH_VEC( vecJobs, i )
  511. {
  512. JobToWake_t jobToWake = { vecJobs[i], true /* success */ };
  513. m_queueJobsToContinue.AddToTail( jobToWake );
  514. }
  515. // move from requested to loaded
  516. ISDO *pSDO = m_mapQueuedRequests.Key( iRequestID );
  517. m_mapQueuedRequests.RemoveAt( iRequestID );
  518. // If the query succeeded but the object doesn't exist then we need to cache that
  519. if ( bNilObj )
  520. {
  521. ISDO *pInvalidSDO = pSDO->AllocNilObject();
  522. delete pSDO;
  523. pSDO = pInvalidSDO;
  524. *ppSDO = pSDO;
  525. }
  526. else
  527. {
  528. // do any extra initialization on the SDO
  529. pSDO->PostLoadInit();
  530. }
  531. Assert( !m_mapISDOLoaded.HasElement( pSDO ) );
  532. int iMap = m_mapISDOLoaded.Insert( pSDO, m_listLRU.InvalidIndex() );
  533. // update stats
  534. int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
  535. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  536. {
  537. if ( pSDO->BNilObject() )
  538. {
  539. m_mapTypeStats[iMapStats].m_nNilObjects++;
  540. }
  541. else
  542. {
  543. m_mapTypeStats[iMapStats].m_nLoaded++;
  544. }
  545. }
  546. // put us in the LRU - if it's just a hint, we may not be referenced immediately
  547. Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
  548. if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
  549. {
  550. LRUItem_t item = { pSDO, pSDO->CubBytesUsed() };
  551. m_mapISDOLoaded[iMap] = m_listLRU.AddToTail( item );
  552. m_cubLRUItems += item.m_cub;
  553. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  554. {
  555. m_mapTypeStats[iMapStats].m_cubUnrefed += item.m_cub;
  556. }
  557. }
  558. }
  559. //-----------------------------------------------------------------------------
  560. // Purpose: job results
  561. //-----------------------------------------------------------------------------
  562. void CSDOCache::OnMemcachedSDOLoadFailure( int eSDOType, int iRequestID )
  563. {
  564. // we've failed to load an item from memcached - mark as needing load from SQL
  565. Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
  566. if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
  567. return;
  568. Assert( eSDOType == m_mapQueuedRequests.Key( iRequestID )->GetType() );
  569. if ( eSDOType != m_mapQueuedRequests.Key( iRequestID )->GetType() )
  570. return;
  571. int iSQLIndex = m_mapQueueSQLRequests.Find( eSDOType );
  572. AssertMsg1( m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ), "Error, could not load SDO from SQL for unregistered type %d", eSDOType );
  573. if ( !m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ) )
  574. return;
  575. SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iSQLIndex];
  576. if ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() < s_ConVarSDOCacheMaxPendingSQLReads.GetInt() )
  577. {
  578. sqlRequestManager->m_queueRequestIDsToLoadFromSQL.AddToTail( iRequestID );
  579. m_StatsSDOCache.m_cQueuedSQLRequests++;
  580. }
  581. else
  582. {
  583. // too many outstanding items, reject and fail immediately
  584. m_StatsSDOCache.m_cSQLRequestsRejectedTooBusy++;
  585. OnSQLSDOLoadFailure( eSDOType, iRequestID, false /* loader failure */ );
  586. }
  587. }
  588. //-----------------------------------------------------------------------------
  589. // Purpose: job results
  590. //-----------------------------------------------------------------------------
  591. void CSDOCache::OnSQLSDOLoadFailure( int eSDOType, int iRequestID, bool bSQLLayerSucceeded )
  592. {
  593. Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
  594. if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
  595. return;
  596. // failed to load from SQL
  597. // set jobs waiting for the data to wake up
  598. CCopyableUtlVector<JobID_t> &vecJobs = m_mapQueuedRequests[iRequestID];
  599. FOR_EACH_VEC( vecJobs, i )
  600. {
  601. JobToWake_t jobToWake = { vecJobs[i], bSQLLayerSucceeded };
  602. m_queueJobsToContinue.AddToTail( jobToWake );
  603. }
  604. // kill the object - no one should have references to it, since it's only in the request list
  605. ISDO *pSDO = m_mapQueuedRequests.Key( iRequestID );
  606. m_mapQueuedRequests.RemoveAt( iRequestID );
  607. Assert( 0 == pSDO->GetRefCount() );
  608. if ( 0 == pSDO->GetRefCount() )
  609. {
  610. delete pSDO;
  611. }
  612. }
  613. //-----------------------------------------------------------------------------
  614. // Purpose: job results
  615. //-----------------------------------------------------------------------------
  616. void CSDOCache::OnMemcachedLoadJobComplete( JobID_t jobID )
  617. {
  618. m_vecMemcachedJobs.FindAndRemove( jobID );
  619. }
  620. //-----------------------------------------------------------------------------
  621. // Purpose: job results
  622. //-----------------------------------------------------------------------------
  623. void CSDOCache::OnSQLLoadJobComplete( int eSDOType, JobID_t jobID )
  624. {
  625. int iSQLIndex = m_mapQueueSQLRequests.Find( eSDOType );
  626. AssertMsg1( m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ), "Error, could not load SDO from SQL for unregistered type %d", eSDOType );
  627. if ( !m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ) )
  628. return;
  629. SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iSQLIndex];
  630. sqlRequestManager->m_vecSQLJobs.FindAndRemove( jobID );
  631. }
  632. //-----------------------------------------------------------------------------
  633. // Purpose: deletes all unreferenced objects
  634. //-----------------------------------------------------------------------------
  635. void CSDOCache::Flush()
  636. {
  637. int cReferencedObjects = 0;
  638. FOR_EACH_MAP_FAST( m_mapISDOLoaded, iMap )
  639. {
  640. if ( m_mapISDOLoaded.Key( iMap )->GetRefCount() == 0 )
  641. {
  642. int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() );
  643. if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
  644. {
  645. if ( m_mapISDOLoaded.Key( iMap )->BNilObject() )
  646. {
  647. m_mapTypeStats[iMapStats].m_nNilObjects--;
  648. }
  649. else
  650. {
  651. m_mapTypeStats[iMapStats].m_nLoaded--;
  652. }
  653. }
  654. RemoveSDOFromLRU( iMap );
  655. DeleteSDOFromMemcached( m_mapISDOLoaded.Key( iMap ) );
  656. delete m_mapISDOLoaded.Key( iMap );
  657. m_mapISDOLoaded.RemoveAt( iMap );
  658. }
  659. else
  660. {
  661. cReferencedObjects++;
  662. }
  663. }
  664. Assert( cReferencedObjects == 0 );
  665. Assert( m_cubLRUItems == 0 );
  666. }
  667. //-----------------------------------------------------------------------------
  668. // Purpose: returns the number of bytes we estimate we have referenced
  669. //-----------------------------------------------------------------------------
  670. int CSDOCache::CubReferencedEst()
  671. {
  672. return MAX( 0, (int)m_StatsSDOCache.m_cBytesInCacheEst - (int)m_StatsSDOCache.m_cBytesUnreferenced );
  673. }
  674. //-----------------------------------------------------------------------------
  675. // Purpose: Prints information about the cache
  676. //-----------------------------------------------------------------------------
  677. void CSDOCache::Dump()
  678. {
  679. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "SDO cache:\n" );
  680. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Cached: %llu\n", m_StatsSDOCache.m_cItemsInCache );
  681. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Estimated Bytes Cached: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesInCacheEst, 2, true ) );
  682. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Loads Queued: %llu\n", m_StatsSDOCache.m_cItemsQueuedToLoad );
  683. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Memcached Loads Queued: %llu\n", m_StatsSDOCache.m_cQueuedMemcachedRequests );
  684. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " SQL Loads Queued: %llu\n", m_StatsSDOCache.m_cQueuedSQLRequests );
  685. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Unreferenced: %llu\n", m_StatsSDOCache.m_cItemsUnreferenced );
  686. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Bytes Unreferenced: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesUnreferenced, 2, true ) );
  687. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items LRU'd: %llu\n", m_StatsSDOCache.m_cItemsLRUd );
  688. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Bytes LRU'd: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesLRUd, 2, true ) );
  689. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Loaded From Memcached: %llu\n", m_StatsSDOCache.m_cItemsLoadedFromMemcached );
  690. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Loaded From SQL: %llu\n", m_StatsSDOCache.m_cItemsLoadedFromSQL );
  691. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Nils Loaded From Memcached: %llu\n", m_StatsSDOCache.m_cNilItemsLoadedFromMemcached );
  692. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Nils Loaded From SQL: %llu\n", m_StatsSDOCache.m_cNilItemsLoadedFromSQL );
  693. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Average Memcached Batch: %f\n", (float)m_StatsSDOCache.m_nMemcachedBatchSizeAvgx100 / 100.0f );
  694. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Average SQL Batch: %f\n", (float)m_StatsSDOCache.m_nSQLBatchSizeAvgx100 / 100.0f );
  695. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Memcached Requests Rejected: %llu\n", m_StatsSDOCache.m_cMemcachedRequestsRejectedTooBusy );
  696. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " SQL Requests Rejected: %llu\n", m_StatsSDOCache.m_cSQLRequestsRejectedTooBusy );
  697. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Failed SQL Loads: %llu\n", m_StatsSDOCache.m_cItemsFailedLoadFromSQL );
  698. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
  699. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Per-type stats\n" );
  700. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS,
  701. "%-35s --- loaded --- referenced --- nil objects --- size (est)\n", "name" );
  702. FOR_EACH_MAP_FAST( m_mapTypeStats, i )
  703. {
  704. int nLoaded = m_mapTypeStats[i].m_nLoaded + m_mapTypeStats[i].m_nNilObjects;
  705. char *pchRefed = "unknown";
  706. if ( m_mapTypeStats[i].m_nRefed < nLoaded && m_mapTypeStats[i].m_cubUnrefed > 0 )
  707. {
  708. pchRefed = V_pretifymem( ( ((int64)nLoaded * (int64)m_mapTypeStats[i].m_cubUnrefed) / ( nLoaded - m_mapTypeStats[i].m_nRefed ) ), 2, true );
  709. }
  710. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-35s %11d %14d %15d %14s\n", m_mapTypeStats[i].m_strName.String(), m_mapTypeStats[i].m_nLoaded, m_mapTypeStats[i].m_nRefed, m_mapTypeStats[i].m_nNilObjects, pchRefed );
  711. }
  712. }
  713. //**tempcomment** - This is good code. We'll hook it back up later
  714. //
  715. //static ConVar s_ConVarVerifyMemcacheDeletesBadEntries( "@VerifyMemcacheDeletesBadEntries", "1", FCVAR_REPLICATED );
  716. //
  717. ////-----------------------------------------------------------------------------
  718. //// Purpose: verifies a set of memcached data against what's in SQL
  719. ////-----------------------------------------------------------------------------
  720. //void CSDOCache::YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches )
  721. //{
  722. // // create the objects
  723. // CUtlVector<ISDO *> vecPSDOSQL;
  724. // CUtlVector<bool> vecSQLResults;
  725. // FOR_EACH_VEC( vecIDs, i )
  726. // {
  727. // vecPSDOSQL.AddToTail( pCreateSDOFunc( *this, vecIDs[i] ) );
  728. // vecSQLResults.AddToTail( false );
  729. // }
  730. //
  731. // CUtlVector<CUtlString> vecSKeysToDelete;
  732. //
  733. // // load them in a batch
  734. // bool bSQLLayerSucceeded = vecPSDOSQL[0]->BYldLoadFromSQL( vecIDs, vecPSDOSQL, vecSQLResults );
  735. // if ( bSQLLayerSucceeded )
  736. // {
  737. // // retrieve the memcache data
  738. // CUtlVector<CUtlString> vecSKeys;
  739. // FOR_EACH_VEC( vecPSDOSQL, i )
  740. // {
  741. // int iKey = vecSKeys.AddToTail();
  742. // vecPSDOSQL[i]->GetMemcachedKeyName( vecSKeys[iKey] );
  743. // }
  744. // CMemcachedAccess memcachedAccess( m_pServer->GetPMemcachedMgr() );
  745. // CUtlVector<MemcachedGetResult_t> vecMemcacheResults;
  746. // if ( memcachedAccess.BYldGetMulti( vecSKeys, vecMemcacheResults ) )
  747. // {
  748. // Assert( vecMemcacheResults.Count() == vecSQLResults.Count() );
  749. //
  750. // FOR_EACH_VEC( vecSQLResults, i )
  751. // {
  752. // bool bClearKey = false;
  753. // if ( vecSQLResults[i] && vecMemcacheResults[i].m_bKeyFound )
  754. // {
  755. // // compare the results
  756. // ISDO *pSDOCached = pCreateSDOFunc( *this, vecIDs[i] );
  757. // const CUtlAllocation &allocMemcache = vecMemcacheResults[i].m_bufValue;
  758. // if ( pSDOCached->BReadFromBuffer( allocMemcache.Base(), allocMemcache.Count() ) )
  759. // {
  760. // if ( pSDOCached->IsEqual( vecPSDOSQL[i] ) )
  761. // {
  762. // // cool
  763. // *pcMatches += 1;
  764. // }
  765. // else
  766. // {
  767. // // boo
  768. // *pcMismatches += 1;
  769. //
  770. // // print the differing bytes
  771. // /*
  772. // Msg( "key %s differs:\n", vecSKeys[i] );
  773. // for ( int n = 0; n < allocMemcache.Count(); n++ )
  774. // {
  775. // const byte *p1 = (byte*)bufSQL.Base() + n;
  776. // const byte *p2 = (byte*)bufCached.Base() + n;
  777. // if ( *p1 != *p2 )
  778. // {
  779. // Msg( " %3d: %2x %2x\n", n, *p1, *p2 );
  780. // }
  781. // }
  782. // */
  783. //
  784. // if ( s_ConVarVerifyMemcacheDeletesBadEntries.GetBool() )
  785. // bClearKey = true;
  786. // }
  787. // }
  788. // else
  789. // {
  790. // // data failed to parse, clear
  791. // bClearKey = true;
  792. // }
  793. // delete pSDOCached;
  794. //
  795. // if ( bClearKey )
  796. // vecSKeysToDelete.AddToTail( vecSKeys[i] );
  797. // }
  798. // }
  799. // }
  800. // }
  801. // else
  802. // {
  803. // // SQL failure, ignore
  804. // }
  805. //
  806. // // clear any suspect keys
  807. // if ( vecSKeysToDelete.Count() )
  808. // {
  809. // CMemcachedAccess memcachedAccess( m_pServer->GetPMemcachedMgr() );
  810. // memcachedAccess.BAsyncDeleteMulti( vecSKeysToDelete );
  811. // }
  812. //
  813. // // delete all the SDO objects
  814. // FOR_EACH_VEC( vecPSDOSQL, i )
  815. // {
  816. // delete vecPSDOSQL[i];
  817. // }
  818. //}
  819. //
  820. //</**tempcomment**> - This is good code. We'll hook it back up later
  821. //-----------------------------------------------------------------------------
  822. // Purpose: default comparison function - compares serialized versions
  823. //-----------------------------------------------------------------------------
  824. bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 )
  825. {
  826. CUtlBuffer b1, b2;
  827. pSDO1->WriteToBuffer( b1 );
  828. pSDO2->WriteToBuffer( b2 );
  829. return ( b1.TellPut() == b2.TellPut() && 0 == Q_memcmp( b1.Base(), b2.Base(), b1.TellPut() ) );
  830. }
  831. #ifdef DBGFLAG_VALIDATE
  832. //-----------------------------------------------------------------------------
  833. // Purpose: validates memory
  834. //-----------------------------------------------------------------------------
  835. void CSDOCache::Validate( CValidator &validator, const char *pchName )
  836. {
  837. VALIDATE_SCOPE();
  838. for ( int i = k_ESDOTypeInvalid+1; i < k_ESDOTypeMax; i++ )
  839. {
  840. SDOSet_t &SDOSet = m_rgSDOSet[i];
  841. ValidateObj( SDOSet.m_mapISDOLoaded );
  842. ValidateObj( SDOSet.m_mapQueuedRequests );
  843. ValidateObj( SDOSet.m_queueRequestIDsToLoadFromSQL );
  844. ValidateObj( SDOSet.m_vecSQLJobs );
  845. FOR_EACH_MAP_FAST( SDOSet.m_mapISDOLoaded, iMap )
  846. {
  847. ValidatePtr( SDOSet.m_mapISDOLoaded[iMap].m_pSDO );
  848. }
  849. FOR_EACH_MAP_FAST( SDOSet.m_mapQueuedRequests, iMap )
  850. {
  851. ValidatePtr( SDOSet.m_mapQueuedRequests[iMap].m_pSDO );
  852. ValidateObj( SDOSet.m_mapQueuedRequests[iMap].m_vecJobsWaiting );
  853. }
  854. }
  855. ValidateObj( m_queueMemcachedRequests );
  856. ValidateObj( m_vecMemcachedJobs );
  857. ValidateObj( m_queueJobsToContinue );
  858. ValidateObj( m_listLRU );
  859. ValidateObj( m_StatMemcachedBatchSize );
  860. ValidateObj( m_StatSQLBatchSize );
  861. }
  862. #endif // DBGFLAG_VALIDATE
  863. } // namespace GCSDK