diff options
Diffstat (limited to 'gcsdk/sdocache.cpp')
| -rw-r--r-- | gcsdk/sdocache.cpp | 999 |
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 |