summaryrefslogtreecommitdiff
path: root/gcsdk/sdocache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gcsdk/sdocache.cpp')
-rw-r--r--gcsdk/sdocache.cpp999
1 files changed, 999 insertions, 0 deletions
diff --git a/gcsdk/sdocache.cpp b/gcsdk/sdocache.cpp
new file mode 100644
index 0000000..bc6235f
--- /dev/null
+++ b/gcsdk/sdocache.cpp
@@ -0,0 +1,999 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Database Backed Object caching and manipulation
+//
+//=============================================================================
+#include "stdafx.h"
+#include "sdocache.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+GCConVar s_ConVarSDOCacheLRULimitMB( "@SDOCacheLRULimitMB", "400", FCVAR_REPLICATED );
+GCConVar s_ConVarSDOCacheMaxMemcachedReadJobs( "@SDOCacheMaxMemcachedReadJobs", "4", FCVAR_REPLICATED );
+GCConVar s_ConVarSDOCacheMaxMemcachedReadBatchSize( "@SDOCacheMaxMemcachedReadBatchSize", "100", FCVAR_REPLICATED );
+GCConVar s_ConVarSDOCacheMaxSQLReadJobsPerSDOType( "@SDOCacheMaxSQLReadJobsPerSDOType", "4", FCVAR_REPLICATED );
+GCConVar s_ConVarSDOCacheMaxSQLReadBatchSize( "@SDOCacheMaxSQLReadBatchSize", "100", FCVAR_REPLICATED );
+GCConVar s_ConVarSDOCacheMaxPendingSQLReads( "@SDOCacheMaxPendingSQLReads", "2000", FCVAR_REPLICATED );
+GCConVar s_ConVarSDOCacheMaxPendingMemcachedReads( "@SDOCacheMaxPendingMemcachedReads", "25000", FCVAR_REPLICATED );
+
+namespace GCSDK
+{
+// A string used to tell the difference between nil objects and actual objects in memcached
+const char k_rgchNilObjSerializedValue[] = "nilobj";
+
+
+// Global instance
+CSDOCache &GSDOCache()
+{
+ static CSDOCache s_SDOCache;
+ return s_SDOCache;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a key name that looks like "Prefix_%u" but faster
+//-----------------------------------------------------------------------------
+void CSDOCache::CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix )
+{
+ Assert( FMTSTR_STD_LEN - unPrefixLen > 10 + 1 );
+ V_memcpy( strDest.Access(), pchPrefix, unPrefixLen );
+ _i64toa( unSuffix, strDest.Access() + unPrefixLen, 10 );
+ strDest.Access()[ FMTSTR_STD_LEN - 1 ] = NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: constructor
+//-----------------------------------------------------------------------------
+CSDOCache::CSDOCache() :
+ m_cubLRUItems( 0 ),
+ m_mapTypeStats( DefLessFunc( int ) )
+{
+ memset( &m_StatsSDOCache, 0, sizeof( m_StatsSDOCache ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: destructor
+//-----------------------------------------------------------------------------
+CSDOCache::~CSDOCache()
+{
+ // delete all the entries from our cache
+ FOR_EACH_MAP_FAST( m_mapISDOLoaded, iMap )
+ {
+ delete m_mapISDOLoaded.Key( iMap );
+ }
+
+ FOR_EACH_MAP_FAST( m_mapQueuedRequests, iMap )
+ {
+ delete m_mapQueuedRequests.Key( iMap );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: registers an SDO type
+//-----------------------------------------------------------------------------
+void CSDOCache::RegisterSDO( int nType, const char *pchName )
+{
+ if ( !m_mapQueueSQLRequests.HasElement( nType ) )
+ {
+ m_mapQueueSQLRequests.Insert( nType, new SQLRequestManager_t );
+ int iMap = m_mapTypeStats.Insert( nType );
+ m_mapTypeStats[iMap].m_strName = pchName;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: a SDO object has been referenced
+//-----------------------------------------------------------------------------
+void CSDOCache::OnSDOReferenced( ISDO *pSDO )
+{
+ // lookup where the SDO is, it has where we are in the LRU
+ int iMap = m_mapISDOLoaded.Find( pSDO );
+ Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
+ if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
+ return;
+
+ // move us out of the LRU
+ if ( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) )
+ {
+ RemoveSDOFromLRU( iMap );
+ }
+ else
+ {
+ // we may not have been in the LRU if it's a custom insert
+ }
+
+ // Update stats
+ int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ m_mapTypeStats[iMapStats].m_nRefed++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: a SDO object has gone released
+//-----------------------------------------------------------------------------
+void CSDOCache::OnSDOReleased( ISDO *pSDO )
+{
+ // Update stats
+ int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ m_mapTypeStats[iMapStats].m_nRefed--;
+ }
+
+ // lookup where the SDO is, it has where we are in the LRU
+ int iMap = m_mapISDOLoaded.Find( pSDO );
+ if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
+ {
+ // we shouldn't be in the LRU
+ bool bInLRU = m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] );
+ Assert( !bInLRU );
+ if ( bInLRU )
+ return;
+
+ // count the bytes and move us to the head of the LRU
+ ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
+ LRUItem_t item = { pSDO, pSDO->CubBytesUsed() };
+ m_mapISDOLoaded[iMap] = m_listLRU.AddToTail( item );
+ m_cubLRUItems += item.m_cub;
+
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ m_mapTypeStats[iMapStats].m_cubUnrefed += item.m_cub;
+ }
+ }
+ else
+ {
+ // it's actually valid it's not in the SDO cache anymore - could be an orphaned pointer
+ if ( pSDO->GetRefCount() == 0 )
+ {
+ delete pSDO;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: pulls a cached SDO from the right maps
+//-----------------------------------------------------------------------------
+void CSDOCache::RemoveSDOFromLRU( int iMap )
+{
+ int iLRU = m_mapISDOLoaded[iMap];
+
+ Assert( m_listLRU.IsValidIndex( iLRU ) );
+ if ( !m_listLRU.IsValidIndex( iLRU ) )
+ return;
+
+ int iMapStats = m_mapTypeStats.Find( m_listLRU[iLRU].m_pSDO->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ m_mapTypeStats[iMapStats].m_cubUnrefed -= m_listLRU[iLRU].m_cub;
+ }
+
+ m_cubLRUItems -= m_listLRU[iLRU].m_cub;
+ m_listLRU.Remove( iLRU );
+ m_mapISDOLoaded[iMap] = m_listLRU.InvalidIndex();
+
+ // Update stats
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: writes a SDO to memcached
+//-----------------------------------------------------------------------------
+bool CSDOCache::WriteSDOToMemcached( ISDO *pSDO )
+{
+ CFmtStr strKey;
+ CUtlBuffer buf;
+
+ pSDO->GetMemcachedKeyName( strKey );
+ pSDO->WriteToBuffer( buf );
+ return GGCBase()->BMemcachedSet( strKey.Access(), buf );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: writes a SDO to memcached
+//-----------------------------------------------------------------------------
+bool CSDOCache::DeleteSDOFromMemcached( ISDO *pSDO )
+{
+ CFmtStr strKey;
+ pSDO->GetMemcachedKeyName( strKey );
+ return GGCBase()->BMemcachedDelete( strKey.Access() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: runs the queued batch loader
+//-----------------------------------------------------------------------------
+class CGCJobLoadSDOSetFromMemcached : public CGCJob
+{
+public:
+ CGCJobLoadSDOSetFromMemcached( CGCBase *pGC )
+ : CGCJob( pGC )
+ {
+ }
+
+ void AddSDOToLoad( ISDO *pSDO, int iRequestID )
+ {
+ int i = m_vecRequests.AddToTail();
+ m_vecRequests[i].m_pSDO = pSDO;
+ m_vecRequests[i].m_iRequestID = iRequestID;
+ }
+
+ bool BYieldingRunJob( void * )
+ {
+ Assert( m_vecRequests.Count() > 0 );
+ if ( 0 == m_vecRequests.Count() )
+ {
+ GSDOCache().OnMemcachedLoadJobComplete( GetJobID() );
+ return false;
+ }
+
+ // get the names of all the items
+ CUtlVector<CUtlString> vecKeys;
+ vecKeys.AddMultipleToTail( m_vecRequests.Count() );
+ FOR_EACH_VEC( m_vecRequests, i )
+ {
+ CFmtStr strKey;
+ m_vecRequests[i].m_pSDO->GetMemcachedKeyName( strKey );
+ vecKeys[i] = strKey;
+ }
+
+ // ask in a batch from memcached
+ CUtlVector<CGCBase::GCMemcachedGetResult_t> vecGetResults;
+ bool bGetSuccess = m_pGC->BYieldingMemcachedGet( vecKeys, vecGetResults );
+
+ // go through each request looking up the results
+ FOR_EACH_VEC( m_vecRequests, i )
+ {
+ if ( bGetSuccess && vecGetResults.IsValidIndex( i ) && vecGetResults[i].m_bKeyFound )
+ {
+ const CGCBase::GCMemcachedGetResult_t &result = vecGetResults[i];
+ bool bNilObj = ( result.m_bufValue.Count() == sizeof( k_rgchNilObjSerializedValue )
+ && 0 == V_memcmp( result.m_bufValue.Base(), k_rgchNilObjSerializedValue, sizeof( k_rgchNilObjSerializedValue ) ) );
+
+ // we've loaded OK
+ if ( bNilObj || ( result.m_bufValue.Count() && m_vecRequests[i].m_pSDO->BReadFromBuffer( result.m_bufValue.Base(), result.m_bufValue.Count() ) ) )
+ {
+ GSDOCache().OnSDOLoadSuccess( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID, bNilObj, &m_vecRequests[i].m_pSDO );
+ GSDOCache().GetStats().m_cItemsLoadedFromMemcached += 1;
+ GSDOCache().GetStats().m_cNilItemsLoadedFromMemcached += bNilObj ? 1 : 0;
+ }
+ else
+ {
+ // couldn't load; delete the entry
+ m_pGC->BMemcachedDelete( vecKeys[i] );
+ GSDOCache().OnMemcachedSDOLoadFailure( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID );
+ }
+ }
+ else
+ {
+ // post back failure
+ GSDOCache().OnMemcachedSDOLoadFailure( m_vecRequests[i].m_pSDO->GetType(), m_vecRequests[i].m_iRequestID );
+ }
+ }
+
+ GSDOCache().OnMemcachedLoadJobComplete( GetJobID() );
+ return true;
+ }
+
+private:
+ struct Request_t
+ {
+ int m_iRequestID;
+ ISDO *m_pSDO;
+ };
+
+ CUtlVector<Request_t> m_vecRequests;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: runs the queued batch loader
+//-----------------------------------------------------------------------------
+class CGCJobLoadSDOSetFromSQL : public CGCJob
+{
+public:
+ CGCJobLoadSDOSetFromSQL( CGCBase *pGC, int eSDOType )
+ : CGCJob( pGC ), m_eSDOType( eSDOType )
+ {
+ }
+
+ void AddSDOToLoad( ISDO *pSDO, int iRequestID )
+ {
+ m_vecPSDO.AddToTail( pSDO );
+ m_vecRequestID.AddToTail( iRequestID );
+ m_vecResults.AddToTail( false );
+ }
+
+ bool BYieldingRunJob( void * )
+ {
+ Assert( m_vecPSDO.Count() == m_vecRequestID.Count() && m_vecPSDO.Count() == m_vecResults.Count() );
+ Assert( m_vecPSDO.Count() > 0 );
+ if ( 0 == m_vecPSDO.Count()
+ || m_vecPSDO.Count() != m_vecRequestID.Count()
+ || m_vecPSDO.Count() != m_vecResults.Count() )
+ {
+ GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
+ return false;
+ }
+
+ // use the first item to load the rest
+ bool bSQLLayerSucceeded = m_vecPSDO[0]->BYldLoadFromSQL( m_vecPSDO, m_vecResults );
+
+ Assert( m_vecPSDO.Count() == m_vecRequestID.Count() && m_vecPSDO.Count() == m_vecResults.Count() );
+ Assert( m_vecPSDO.Count() > 0 );
+ if ( 0 == m_vecPSDO.Count()
+ || m_vecPSDO.Count() != m_vecRequestID.Count()
+ || m_vecPSDO.Count() != m_vecResults.Count() )
+ {
+ GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
+ return false;
+ }
+
+ // walk each result
+ FOR_EACH_VEC( m_vecRequestID, i )
+ {
+ if ( bSQLLayerSucceeded )
+ {
+ // loaded, great
+ GSDOCache().OnSDOLoadSuccess( m_eSDOType, m_vecRequestID[i], !m_vecResults[i], &m_vecPSDO[i] );
+ GSDOCache().WriteSDOToMemcached( m_vecPSDO[i] );
+
+ GSDOCache().GetStats().m_cItemsLoadedFromSQL += 1;
+ GSDOCache().GetStats().m_cNilItemsLoadedFromSQL += m_vecResults[i] ? 0 : 1;
+ }
+ else
+ {
+ // no good, item couldn't load
+ GSDOCache().OnSQLSDOLoadFailure( m_eSDOType, m_vecRequestID[i], bSQLLayerSucceeded );
+ GSDOCache().GetStats().m_cItemsFailedLoadFromSQL += 1;
+ }
+ }
+
+ GSDOCache().OnSQLLoadJobComplete( m_eSDOType, GetJobID() );
+ return true;
+ }
+
+private:
+ int m_eSDOType;
+
+ // these objects all stay in sync
+ // they need to be separate vectors so that they can be easily passed into other API's
+ CUtlVector<ISDO *> m_vecPSDO;
+ CUtlVector<int> m_vecRequestID;
+ CUtlVector<bool> m_vecResults;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: continues any jobs that were waiting on items
+//-----------------------------------------------------------------------------
+bool CSDOCache::BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer )
+{
+ bool bDoneWork = false;
+
+ // continue any jobs
+ while ( m_queueJobsToContinue.Count() && !limitTimer.BLimitReached() )
+ {
+ // pop the item off the head
+ JobToWake_t jobToWake = m_queueJobsToContinue[ m_queueJobsToContinue.Head() ];
+ m_queueJobsToContinue.Remove( m_queueJobsToContinue.Head() );
+ GGCBase()->GetJobMgr().BRouteWorkItemCompletedIfExists( jobToWake.m_jobID, !jobToWake.m_bLoadLayerSuccess );
+ }
+
+ // if we're over the limit, LRU an item
+ while ( !limitTimer.BLimitReached() && m_cubLRUItems > (uint32)( s_ConVarSDOCacheLRULimitMB.GetInt() * k_nMegabyte ) && m_listLRU.Count() )
+ {
+ // pull off the last item in the LRU
+ LRUItem_t item = m_listLRU[ m_listLRU.Head() ];
+
+ // kill the item
+ int iMap = m_mapISDOLoaded.Find( item.m_pSDO );
+ Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
+ if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
+ {
+ Assert( 0 == item.m_pSDO->GetRefCount() );
+ if ( 0 == item.m_pSDO->GetRefCount() )
+ {
+ RemoveSDOFromLRU( iMap );
+
+ int iMapStats = m_mapTypeStats.Find( item.m_pSDO->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ if ( item.m_pSDO->BNilObject() )
+ {
+ m_mapTypeStats[iMapStats].m_nNilObjects--;
+ }
+ else
+ {
+ m_mapTypeStats[iMapStats].m_nLoaded--;
+ }
+ }
+
+ m_mapISDOLoaded.RemoveAt( iMap );
+ delete item.m_pSDO;
+ }
+
+ m_StatsSDOCache.m_cItemsLRUd += 1;
+ m_StatsSDOCache.m_cBytesLRUd += item.m_cub;
+ }
+
+ bDoneWork = true;
+ }
+
+ // return true if there is still work remaining
+ return bDoneWork || m_queueJobsToContinue.Count() > 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: runs the queued batch loader
+//-----------------------------------------------------------------------------
+bool CSDOCache::BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer )
+{
+ bool bDoneWork = false;
+
+ // batch load the set
+ if ( m_queueMemcachedRequests.Count() )
+ {
+ // pass these off to a job
+ if ( m_vecMemcachedJobs.Count() < s_ConVarSDOCacheMaxMemcachedReadJobs.GetInt() )
+ {
+ CGCJobLoadSDOSetFromMemcached *pJob = new CGCJobLoadSDOSetFromMemcached( GGCBase() );
+
+ // add a full batch to the job
+ int cItemsInBatch = 0;
+ while ( m_queueMemcachedRequests.Count() && cItemsInBatch < s_ConVarSDOCacheMaxMemcachedReadBatchSize.GetInt() )
+ {
+ m_StatsSDOCache.m_cQueuedMemcachedRequests--;
+ int iMapQueuedRequest = m_queueMemcachedRequests[ m_queueMemcachedRequests.Head() ];
+ m_queueMemcachedRequests.Remove( m_queueMemcachedRequests.Head() );
+ Assert( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) );
+ if ( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) )
+ {
+ cItemsInBatch++;
+ pJob->AddSDOToLoad( m_mapQueuedRequests.Key( iMapQueuedRequest ), iMapQueuedRequest );
+ }
+ }
+
+ if ( cItemsInBatch > 0 )
+ {
+ m_StatMemcachedBatchSize.AddSample( cItemsInBatch * 100 );
+ m_StatsSDOCache.m_nMemcachedBatchSizeAvgx100 = (int)m_StatMemcachedBatchSize.GetAveragedSample();
+
+ // add to the list
+ m_vecMemcachedJobs.AddToTail( pJob->GetJobID() );
+
+ // start the job
+ pJob->StartJob( NULL );
+
+ // mark that we should be ran again
+ bDoneWork = true;
+ }
+ else
+ {
+ delete pJob;
+ }
+ }
+ }
+
+ // return if we still have work to do
+ return bDoneWork;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: runs the queued batch loader
+//-----------------------------------------------------------------------------
+bool CSDOCache::BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer )
+{
+ bool bDoneWork = false;
+
+ // loop through all items looking for batches to load
+ FOR_EACH_MAP_FAST( m_mapQueueSQLRequests, iMapType )
+ {
+ // batch load the set
+ SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iMapType];
+ if ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() )
+ {
+ // pass these off to a job
+ if ( sqlRequestManager->m_vecSQLJobs.Count() < s_ConVarSDOCacheMaxSQLReadJobsPerSDOType.GetInt() )
+ {
+ int nSDOType = m_mapQueueSQLRequests.Key( iMapType );
+ CGCJobLoadSDOSetFromSQL *pJob = new CGCJobLoadSDOSetFromSQL( GGCBase(), nSDOType );
+
+ // add a full batch to the job
+ int cItemsInBatch = 0;
+ while ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() && cItemsInBatch < s_ConVarSDOCacheMaxSQLReadBatchSize.GetInt() )
+ {
+ m_StatsSDOCache.m_cQueuedSQLRequests--;
+ int iMapQueuedRequest = sqlRequestManager->m_queueRequestIDsToLoadFromSQL[ sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Head() ];
+ sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Remove( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Head() );
+ Assert( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) );
+ if ( m_mapQueuedRequests.IsValidIndex( iMapQueuedRequest ) )
+ {
+ pJob->AddSDOToLoad( m_mapQueuedRequests.Key( iMapQueuedRequest ), iMapQueuedRequest );
+ cItemsInBatch++;
+ }
+ }
+
+ if ( cItemsInBatch > 0 )
+ {
+ m_StatSQLBatchSize.AddSample( cItemsInBatch * 100 );
+ m_StatsSDOCache.m_nSQLBatchSizeAvgx100 = (int)m_StatSQLBatchSize.GetAveragedSample();
+
+ // add to the list
+ sqlRequestManager->m_vecSQLJobs.AddToTail( pJob->GetJobID() );
+
+ // start the job
+ pJob->StartJob( NULL );
+ bDoneWork = true;
+ }
+ else
+ {
+ delete pJob;
+ }
+ }
+ }
+ }
+
+ // update stats
+ m_StatsSDOCache.m_cItemsInCache = m_mapISDOLoaded.Count();
+ m_StatsSDOCache.m_cItemsQueuedToLoad = m_mapQueuedRequests.Count();
+ m_StatsSDOCache.m_cBytesUnreferenced = m_cubLRUItems;
+ m_StatsSDOCache.m_cItemsUnreferenced = m_listLRU.Count();
+
+ if ( m_StatsSDOCache.m_cItemsUnreferenced )
+ {
+ // estimate the total bytes from the size of the average size of an unreferenced item
+ m_StatsSDOCache.m_cBytesInCacheEst = (m_StatsSDOCache.m_cItemsInCache * m_StatsSDOCache.m_cBytesUnreferenced) / m_StatsSDOCache.m_cItemsUnreferenced;
+ }
+
+ // return true if we still have work to do
+ return bDoneWork;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: queues a new item to try load from memcached
+//-----------------------------------------------------------------------------
+int CSDOCache::QueueMemcachedLoad( ISDO *pSDO )
+{
+ int iMap = -1;
+ if ( m_queueMemcachedRequests.Count() < s_ConVarSDOCacheMaxPendingMemcachedReads.GetInt() )
+ {
+ // insert a fresh item into the list
+ iMap = m_mapQueuedRequests.Insert( pSDO );
+
+ // add the key to the queue
+ m_queueMemcachedRequests.AddToTail( iMap );
+ m_StatsSDOCache.m_cQueuedMemcachedRequests++;
+ }
+ else
+ {
+ m_StatsSDOCache.m_cMemcachedRequestsRejectedTooBusy++;
+ delete pSDO;
+ }
+
+ return iMap;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: job results
+//-----------------------------------------------------------------------------
+void CSDOCache::OnSDOLoadSuccess( int eSDOType, int iRequestID, bool bNilObj, ISDO **ppSDO )
+{
+ Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
+ if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
+ return;
+
+ // set jobs waiting for the data to wake up
+ CCopyableUtlVector<JobID_t> &vecJobs = m_mapQueuedRequests[iRequestID];
+ FOR_EACH_VEC( vecJobs, i )
+ {
+ JobToWake_t jobToWake = { vecJobs[i], true /* success */ };
+ m_queueJobsToContinue.AddToTail( jobToWake );
+ }
+
+ // move from requested to loaded
+ ISDO *pSDO = m_mapQueuedRequests.Key( iRequestID );
+ m_mapQueuedRequests.RemoveAt( iRequestID );
+
+ // If the query succeeded but the object doesn't exist then we need to cache that
+ if ( bNilObj )
+ {
+ ISDO *pInvalidSDO = pSDO->AllocNilObject();
+ delete pSDO;
+ pSDO = pInvalidSDO;
+ *ppSDO = pSDO;
+ }
+ else
+ {
+ // do any extra initialization on the SDO
+ pSDO->PostLoadInit();
+ }
+
+ Assert( !m_mapISDOLoaded.HasElement( pSDO ) );
+ int iMap = m_mapISDOLoaded.Insert( pSDO, m_listLRU.InvalidIndex() );
+
+ // update stats
+ int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ if ( pSDO->BNilObject() )
+ {
+ m_mapTypeStats[iMapStats].m_nNilObjects++;
+ }
+ else
+ {
+ m_mapTypeStats[iMapStats].m_nLoaded++;
+ }
+ }
+
+ // put us in the LRU - if it's just a hint, we may not be referenced immediately
+ Assert( m_mapISDOLoaded.IsValidIndex( iMap ) );
+ if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
+ {
+ LRUItem_t item = { pSDO, pSDO->CubBytesUsed() };
+ m_mapISDOLoaded[iMap] = m_listLRU.AddToTail( item );
+ m_cubLRUItems += item.m_cub;
+
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ m_mapTypeStats[iMapStats].m_cubUnrefed += item.m_cub;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: job results
+//-----------------------------------------------------------------------------
+void CSDOCache::OnMemcachedSDOLoadFailure( int eSDOType, int iRequestID )
+{
+ // we've failed to load an item from memcached - mark as needing load from SQL
+ Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
+ if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
+ return;
+
+ Assert( eSDOType == m_mapQueuedRequests.Key( iRequestID )->GetType() );
+ if ( eSDOType != m_mapQueuedRequests.Key( iRequestID )->GetType() )
+ return;
+
+ int iSQLIndex = m_mapQueueSQLRequests.Find( eSDOType );
+ AssertMsg1( m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ), "Error, could not load SDO from SQL for unregistered type %d", eSDOType );
+ if ( !m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ) )
+ return;
+
+ SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iSQLIndex];
+
+ if ( sqlRequestManager->m_queueRequestIDsToLoadFromSQL.Count() < s_ConVarSDOCacheMaxPendingSQLReads.GetInt() )
+ {
+ sqlRequestManager->m_queueRequestIDsToLoadFromSQL.AddToTail( iRequestID );
+ m_StatsSDOCache.m_cQueuedSQLRequests++;
+ }
+ else
+ {
+ // too many outstanding items, reject and fail immediately
+ m_StatsSDOCache.m_cSQLRequestsRejectedTooBusy++;
+ OnSQLSDOLoadFailure( eSDOType, iRequestID, false /* loader failure */ );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: job results
+//-----------------------------------------------------------------------------
+void CSDOCache::OnSQLSDOLoadFailure( int eSDOType, int iRequestID, bool bSQLLayerSucceeded )
+{
+ Assert( m_mapQueuedRequests.IsValidIndex( iRequestID ) );
+ if ( !m_mapQueuedRequests.IsValidIndex( iRequestID ) )
+ return;
+
+ // failed to load from SQL
+ // set jobs waiting for the data to wake up
+ CCopyableUtlVector<JobID_t> &vecJobs = m_mapQueuedRequests[iRequestID];
+ FOR_EACH_VEC( vecJobs, i )
+ {
+ JobToWake_t jobToWake = { vecJobs[i], bSQLLayerSucceeded };
+ m_queueJobsToContinue.AddToTail( jobToWake );
+ }
+
+ // kill the object - no one should have references to it, since it's only in the request list
+ ISDO *pSDO = m_mapQueuedRequests.Key( iRequestID );
+ m_mapQueuedRequests.RemoveAt( iRequestID );
+
+ Assert( 0 == pSDO->GetRefCount() );
+ if ( 0 == pSDO->GetRefCount() )
+ {
+ delete pSDO;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: job results
+//-----------------------------------------------------------------------------
+void CSDOCache::OnMemcachedLoadJobComplete( JobID_t jobID )
+{
+ m_vecMemcachedJobs.FindAndRemove( jobID );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: job results
+//-----------------------------------------------------------------------------
+void CSDOCache::OnSQLLoadJobComplete( int eSDOType, JobID_t jobID )
+{
+ int iSQLIndex = m_mapQueueSQLRequests.Find( eSDOType );
+ AssertMsg1( m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ), "Error, could not load SDO from SQL for unregistered type %d", eSDOType );
+ if ( !m_mapQueueSQLRequests.IsValidIndex( iSQLIndex ) )
+ return;
+
+ SQLRequestManager_t *sqlRequestManager = m_mapQueueSQLRequests[iSQLIndex];
+ sqlRequestManager->m_vecSQLJobs.FindAndRemove( jobID );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: deletes all unreferenced objects
+//-----------------------------------------------------------------------------
+void CSDOCache::Flush()
+{
+ int cReferencedObjects = 0;
+ FOR_EACH_MAP_FAST( m_mapISDOLoaded, iMap )
+ {
+ if ( m_mapISDOLoaded.Key( iMap )->GetRefCount() == 0 )
+ {
+ int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() );
+ if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
+ {
+ if ( m_mapISDOLoaded.Key( iMap )->BNilObject() )
+ {
+ m_mapTypeStats[iMapStats].m_nNilObjects--;
+ }
+ else
+ {
+ m_mapTypeStats[iMapStats].m_nLoaded--;
+ }
+ }
+
+ RemoveSDOFromLRU( iMap );
+ DeleteSDOFromMemcached( m_mapISDOLoaded.Key( iMap ) );
+ delete m_mapISDOLoaded.Key( iMap );
+ m_mapISDOLoaded.RemoveAt( iMap );
+ }
+ else
+ {
+ cReferencedObjects++;
+ }
+ }
+
+ Assert( cReferencedObjects == 0 );
+ Assert( m_cubLRUItems == 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the number of bytes we estimate we have referenced
+//-----------------------------------------------------------------------------
+int CSDOCache::CubReferencedEst()
+{
+ return MAX( 0, (int)m_StatsSDOCache.m_cBytesInCacheEst - (int)m_StatsSDOCache.m_cBytesUnreferenced );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Prints information about the cache
+//-----------------------------------------------------------------------------
+void CSDOCache::Dump()
+{
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "SDO cache:\n" );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Cached: %llu\n", m_StatsSDOCache.m_cItemsInCache );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Estimated Bytes Cached: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesInCacheEst, 2, true ) );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Loads Queued: %llu\n", m_StatsSDOCache.m_cItemsQueuedToLoad );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Memcached Loads Queued: %llu\n", m_StatsSDOCache.m_cQueuedMemcachedRequests );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " SQL Loads Queued: %llu\n", m_StatsSDOCache.m_cQueuedSQLRequests );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Unreferenced: %llu\n", m_StatsSDOCache.m_cItemsUnreferenced );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Bytes Unreferenced: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesUnreferenced, 2, true ) );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items LRU'd: %llu\n", m_StatsSDOCache.m_cItemsLRUd );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Bytes LRU'd: %s\n", V_pretifymem( m_StatsSDOCache.m_cBytesLRUd, 2, true ) );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Loaded From Memcached: %llu\n", m_StatsSDOCache.m_cItemsLoadedFromMemcached );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Items Loaded From SQL: %llu\n", m_StatsSDOCache.m_cItemsLoadedFromSQL );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Nils Loaded From Memcached: %llu\n", m_StatsSDOCache.m_cNilItemsLoadedFromMemcached );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Nils Loaded From SQL: %llu\n", m_StatsSDOCache.m_cNilItemsLoadedFromSQL );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Average Memcached Batch: %f\n", (float)m_StatsSDOCache.m_nMemcachedBatchSizeAvgx100 / 100.0f );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Average SQL Batch: %f\n", (float)m_StatsSDOCache.m_nSQLBatchSizeAvgx100 / 100.0f );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Memcached Requests Rejected: %llu\n", m_StatsSDOCache.m_cMemcachedRequestsRejectedTooBusy );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " SQL Requests Rejected: %llu\n", m_StatsSDOCache.m_cSQLRequestsRejectedTooBusy );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, " Failed SQL Loads: %llu\n", m_StatsSDOCache.m_cItemsFailedLoadFromSQL );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" );
+
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Per-type stats\n" );
+ EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS,
+ "%-35s --- loaded --- referenced --- nil objects --- size (est)\n", "name" );
+
+ FOR_EACH_MAP_FAST( m_mapTypeStats, i )
+ {
+ int nLoaded = m_mapTypeStats[i].m_nLoaded + m_mapTypeStats[i].m_nNilObjects;
+ char *pchRefed = "unknown";
+ if ( m_mapTypeStats[i].m_nRefed < nLoaded && m_mapTypeStats[i].m_cubUnrefed > 0 )
+ {
+ pchRefed = V_pretifymem( ( ((int64)nLoaded * (int64)m_mapTypeStats[i].m_cubUnrefed) / ( nLoaded - m_mapTypeStats[i].m_nRefed ) ), 2, true );
+ }
+
+ 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 );
+ }
+}
+
+
+//**tempcomment** - This is good code. We'll hook it back up later
+//
+//static ConVar s_ConVarVerifyMemcacheDeletesBadEntries( "@VerifyMemcacheDeletesBadEntries", "1", FCVAR_REPLICATED );
+//
+////-----------------------------------------------------------------------------
+//// Purpose: verifies a set of memcached data against what's in SQL
+////-----------------------------------------------------------------------------
+//void CSDOCache::YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches )
+//{
+// // create the objects
+// CUtlVector<ISDO *> vecPSDOSQL;
+// CUtlVector<bool> vecSQLResults;
+// FOR_EACH_VEC( vecIDs, i )
+// {
+// vecPSDOSQL.AddToTail( pCreateSDOFunc( *this, vecIDs[i] ) );
+// vecSQLResults.AddToTail( false );
+// }
+//
+// CUtlVector<CUtlString> vecSKeysToDelete;
+//
+// // load them in a batch
+// bool bSQLLayerSucceeded = vecPSDOSQL[0]->BYldLoadFromSQL( vecIDs, vecPSDOSQL, vecSQLResults );
+// if ( bSQLLayerSucceeded )
+// {
+// // retrieve the memcache data
+// CUtlVector<CUtlString> vecSKeys;
+// FOR_EACH_VEC( vecPSDOSQL, i )
+// {
+// int iKey = vecSKeys.AddToTail();
+// vecPSDOSQL[i]->GetMemcachedKeyName( vecSKeys[iKey] );
+// }
+// CMemcachedAccess memcachedAccess( m_pServer->GetPMemcachedMgr() );
+// CUtlVector<MemcachedGetResult_t> vecMemcacheResults;
+// if ( memcachedAccess.BYldGetMulti( vecSKeys, vecMemcacheResults ) )
+// {
+// Assert( vecMemcacheResults.Count() == vecSQLResults.Count() );
+//
+// FOR_EACH_VEC( vecSQLResults, i )
+// {
+// bool bClearKey = false;
+// if ( vecSQLResults[i] && vecMemcacheResults[i].m_bKeyFound )
+// {
+// // compare the results
+// ISDO *pSDOCached = pCreateSDOFunc( *this, vecIDs[i] );
+// const CUtlAllocation &allocMemcache = vecMemcacheResults[i].m_bufValue;
+// if ( pSDOCached->BReadFromBuffer( allocMemcache.Base(), allocMemcache.Count() ) )
+// {
+// if ( pSDOCached->IsEqual( vecPSDOSQL[i] ) )
+// {
+// // cool
+// *pcMatches += 1;
+// }
+// else
+// {
+// // boo
+// *pcMismatches += 1;
+//
+// // print the differing bytes
+// /*
+// Msg( "key %s differs:\n", vecSKeys[i] );
+// for ( int n = 0; n < allocMemcache.Count(); n++ )
+// {
+// const byte *p1 = (byte*)bufSQL.Base() + n;
+// const byte *p2 = (byte*)bufCached.Base() + n;
+// if ( *p1 != *p2 )
+// {
+// Msg( " %3d: %2x %2x\n", n, *p1, *p2 );
+// }
+// }
+// */
+//
+// if ( s_ConVarVerifyMemcacheDeletesBadEntries.GetBool() )
+// bClearKey = true;
+// }
+// }
+// else
+// {
+// // data failed to parse, clear
+// bClearKey = true;
+// }
+// delete pSDOCached;
+//
+// if ( bClearKey )
+// vecSKeysToDelete.AddToTail( vecSKeys[i] );
+// }
+// }
+// }
+// }
+// else
+// {
+// // SQL failure, ignore
+// }
+//
+// // clear any suspect keys
+// if ( vecSKeysToDelete.Count() )
+// {
+// CMemcachedAccess memcachedAccess( m_pServer->GetPMemcachedMgr() );
+// memcachedAccess.BAsyncDeleteMulti( vecSKeysToDelete );
+// }
+//
+// // delete all the SDO objects
+// FOR_EACH_VEC( vecPSDOSQL, i )
+// {
+// delete vecPSDOSQL[i];
+// }
+//}
+//
+//</**tempcomment**> - This is good code. We'll hook it back up later
+
+
+//-----------------------------------------------------------------------------
+// Purpose: default comparison function - compares serialized versions
+//-----------------------------------------------------------------------------
+bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 )
+{
+ CUtlBuffer b1, b2;
+ pSDO1->WriteToBuffer( b1 );
+ pSDO2->WriteToBuffer( b2 );
+
+ return ( b1.TellPut() == b2.TellPut() && 0 == Q_memcmp( b1.Base(), b2.Base(), b1.TellPut() ) );
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+//-----------------------------------------------------------------------------
+// Purpose: validates memory
+//-----------------------------------------------------------------------------
+void CSDOCache::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+
+ for ( int i = k_ESDOTypeInvalid+1; i < k_ESDOTypeMax; i++ )
+ {
+ SDOSet_t &SDOSet = m_rgSDOSet[i];
+ ValidateObj( SDOSet.m_mapISDOLoaded );
+ ValidateObj( SDOSet.m_mapQueuedRequests );
+ ValidateObj( SDOSet.m_queueRequestIDsToLoadFromSQL );
+ ValidateObj( SDOSet.m_vecSQLJobs );
+
+ FOR_EACH_MAP_FAST( SDOSet.m_mapISDOLoaded, iMap )
+ {
+ ValidatePtr( SDOSet.m_mapISDOLoaded[iMap].m_pSDO );
+ }
+
+ FOR_EACH_MAP_FAST( SDOSet.m_mapQueuedRequests, iMap )
+ {
+ ValidatePtr( SDOSet.m_mapQueuedRequests[iMap].m_pSDO );
+ ValidateObj( SDOSet.m_mapQueuedRequests[iMap].m_vecJobsWaiting );
+ }
+ }
+
+ ValidateObj( m_queueMemcachedRequests );
+ ValidateObj( m_vecMemcachedJobs );
+ ValidateObj( m_queueJobsToContinue );
+ ValidateObj( m_listLRU );
+ ValidateObj( m_StatMemcachedBatchSize );
+ ValidateObj( m_StatSQLBatchSize );
+}
+#endif // DBGFLAG_VALIDATE
+
+} // namespace GCSDK