/*++ Copyright (c) 1999 Microsoft Corporation Module Name : usercache.cxx Abstract: Implements the common code shared by all the caches (file, meta, uri, token, ul-cached-response, etc) Author: Bilal Alam (balam) 11-Nov-2000 Environment: Win32 - User Mode Project: ULW3.DLL --*/ #include "precomp.hxx" #include "usercache.hxx" #include "cachehint.hxx" //static void CACHE_ENTRY_HASH::AddRefRecord( CACHE_ENTRY * pCacheEntry, int nIncr ) /*++ Routine Description: Dereference and possibly delete the cache entry. Note the destructor for the cache entry is private. Only this code should ever delete the cache entry Arguments: None Return Value: None --*/ { DBG_ASSERT( pCacheEntry->CheckSignature() ); if ( nIncr == +1 ) { pCacheEntry->ReferenceCacheEntry(); } else if ( nIncr == -1 ) { pCacheEntry->SetFlushed(); pCacheEntry->QueryCache()->IncFlushes(); pCacheEntry->QueryCache()->DecEntriesCached(); pCacheEntry->DereferenceCacheEntry(); } else { DBG_ASSERT( FALSE ); } } // // CACHE_ENTRY definitions // CACHE_ENTRY::CACHE_ENTRY( OBJECT_CACHE * pObjectCache, DWORD cTTLOverride ) { _cRefs = 1; _fFlushed = FALSE; _fCached = FALSE; if ( cTTLOverride != 0 ) { _cConfiguredTTL = cTTLOverride; } else { _cConfiguredTTL = pObjectCache->QueryConfiguredTTL(); } _cTTL = _cConfiguredTTL; _pObjectCache = pObjectCache; _pDirmonInvalidator = NULL; _dwSignature = CACHE_ENTRY_SIGNATURE; } CACHE_ENTRY::~CACHE_ENTRY( VOID ) { DBG_ASSERT( _cRefs == 0 ); if ( _pDirmonInvalidator != NULL ) { _pDirmonInvalidator->Release(); _pDirmonInvalidator = NULL; } if ( _fFlushed ) { DBG_ASSERT( QueryCache() != NULL ); QueryCache()->DecActiveFlushedEntries(); } _dwSignature = CACHE_ENTRY_SIGNATURE_FREE; } VOID CACHE_ENTRY::ReferenceCacheEntry( VOID ) /*++ Routine Description: Reference the given entry (duh) Arguments: None Return Value: None --*/ { LONG cRefs; cRefs = InterlockedIncrement( &_cRefs ); DBG_ASSERT( QueryCache() != NULL ); QueryCache()->DoReferenceTrace( this, cRefs ); } VOID CACHE_ENTRY::DereferenceCacheEntry( VOID ) /*++ Routine Description: Dereference and possibly delete the cache entry. Note the destructor for the cache entry is private. Only this code should ever delete the cache entry Arguments: None Return Value: None --*/ { LONG cRefs; OBJECT_CACHE * pObjectCache = QueryCache(); DBG_ASSERT( pObjectCache != NULL ); cRefs = InterlockedDecrement( &_cRefs ); pObjectCache->DoReferenceTrace( this, cRefs ); if ( cRefs == 0 ) { delete this; } } HRESULT CACHE_ENTRY::AddDirmonInvalidator( DIRMON_CONFIG * pDirmonConfig ) /*++ Routine Description: Setup dirmon invalidation for this cache entry Arguments: pDirmonConfig - path/token for use in monitoring directory Return Value: HRESULT --*/ { CDirMonitorEntry * pDME = NULL; HRESULT hr; hr = QueryCache()->AddDirmonInvalidator( pDirmonConfig, &pDME ); if ( FAILED( hr ) ) { return hr; } DBG_ASSERT( pDME != NULL ); // // Cleanup any old dir monitor entry // if ( _pDirmonInvalidator != NULL ) { _pDirmonInvalidator->Release(); } _pDirmonInvalidator = pDME; return NO_ERROR; } BOOL CACHE_ENTRY::Checkout( CACHE_USER * ) /*++ Routine Description: Checkout a cache entry Arguments: None Return Value: TRUE if the checkout was successful, else FALSE --*/ { ReferenceCacheEntry(); if ( QueryIsFlushed() ) { DereferenceCacheEntry(); QueryCache()->IncMisses(); return FALSE; } else { InterlockedExchange( &_cTTL, _cConfiguredTTL ); QueryCache()->IncHits(); return TRUE; } } BOOL CACHE_ENTRY::QueryIsOkToFlushTTL( VOID ) /*++ Routine Description: Called when the cache scavenger is invoked. This routine returns whether it is OK to flush this entry due to TTL Arguments: None Return Value: TRUE if it is OK to flush by TTL, else FALSE --*/ { // // Only do the TTL thing if the hash table holds the only reference to // the cache entry. We can be loose with this check as this is just an // optimization to prevent overzealous flushing // if ( _cRefs > 1 ) { return FALSE; } if ( InterlockedDecrement( &_cTTL ) == 0 ) { // // TTL has expired. However, we let the cache entry override this // expiry it wants to. Check that now // // // Entry be gone! // return TRUE; } else { return FALSE; } } BOOL CACHE_ENTRY::QueryIsOkToFlushMetadata( WCHAR * pszMetaPath, DWORD cchMetaPath ) /*++ Routine Description: Called whem metadata has changed. This routine returns whether the current cache entry should be flushed Arguments: pszMetaPath - Metabase path which changed cchMetaPath - Size of metabase path Return Value: TRUE if we should flush --*/ { BOOL fRet; DBG_ASSERT( pszMetaPath != NULL ); DBG_ASSERT( cchMetaPath != 0 ); if ( pszMetaPath[ cchMetaPath - 1 ] == L'/' ) { cchMetaPath--; } DBG_ASSERT( QueryCache()->QuerySupportsMetadataFlush() ); if ( QueryMetadataPath() == NULL ) { fRet = TRUE; } else if ( QueryMetadataPath()->QueryCCH() < cchMetaPath ) { fRet = FALSE; } else if ( _wcsnicmp( QueryMetadataPath()->QueryStr(), pszMetaPath, cchMetaPath ) == 0 ) { fRet = TRUE; } else { fRet = FALSE; } return fRet; } // // OBJECT_CACHE definitions // OBJECT_CACHE::OBJECT_CACHE( VOID ) /*++ Routine Description: Create an object cache. Obviously all the app-specific goo will be initialized in the derived class Arguments: None Return Value: None --*/ { _hTimer = NULL; _pHintManager = NULL; _cmsecScavengeTime = 0; _cmsecTTL = 0; _dwSupportedInvalidation = 0; _cCacheHits = 0; _cCacheMisses = 0; _cCacheFlushes = 0; _cActiveFlushedEntries = 0; _cFlushCalls = 0; _cEntriesCached = 0; _cTotalEntriesCached = 0; _cPerfCacheHits = 0; _cPerfCacheMisses = 0; _cPerfCacheFlushes = 0; _cPerfFlushCalls = 0; _cPerfTotalEntriesCached = 0; #if DBG _pTraceLog = CreateRefTraceLog( 2000, 0 ); #else _pTraceLog = NULL; #endif InitializeListHead( &_listEntry ); _dwSignature = OBJECT_CACHE_SIGNATURE; } VOID OBJECT_CACHE::UnregisterScavenger( VOID ) { if ( _hTimer != NULL ) { DeleteTimerQueueTimer( NULL, _hTimer, INVALID_HANDLE_VALUE ); _hTimer = NULL; } } OBJECT_CACHE::~OBJECT_CACHE( VOID ) { _dwSignature = OBJECT_CACHE_SIGNATURE_FREE; UnregisterScavenger(); if ( _pHintManager != NULL ) { delete _pHintManager; _pHintManager = NULL; } if ( _pTraceLog != NULL ) { DestroyRefTraceLog( _pTraceLog ); _pTraceLog = NULL; } } VOID OBJECT_CACHE::CleanupCacheEntryListItems( LIST_ENTRY * pListHead ) { CACHE_ENTRY * pCacheEntry; // // Free cache entry list items // while ( !IsListEmpty( pListHead ) ) { pCacheEntry = CONTAINING_RECORD( pListHead->Flink, CACHE_ENTRY, _listEntry ); RemoveEntryList( &pCacheEntry->_listEntry ); pCacheEntry->_listEntry.Flink = NULL; pCacheEntry->DereferenceCacheEntry(); } } //static VOID WINAPI OBJECT_CACHE::ScavengerCallback( PVOID pParam, BOOLEAN ) { OBJECT_CACHE * pObjectCache; LIST_ENTRY listHead; pObjectCache = (OBJECT_CACHE*) pParam; DBG_ASSERT( pObjectCache != NULL ); DBG_ASSERT( pObjectCache->CheckSignature() ); InitializeListHead( &listHead); pObjectCache->FlushByTTL( &listHead ); // // Remove all cache entries in the cache entry list // pObjectCache->CleanupCacheEntryListItems( &listHead ); } //static LK_PREDICATE OBJECT_CACHE::CacheFlushByRegChange( CACHE_ENTRY * pCacheEntry, VOID * pvState ) /*++ Routine Description: Determine whether given entry should be deleted due to TTL change. Arguments: pCacheEntry - Cache entry to check pvState - Pointer to cache Return Value: LKP_PERFORM - do the delete, LKP_NO_ACTION - do nothing --*/ { LIST_ENTRY * pListHead = static_cast( pvState ); DBG_ASSERT( pCacheEntry != NULL ); DBG_ASSERT( pCacheEntry->CheckSignature() ); pCacheEntry->ReferenceCacheEntry(); InsertHeadList( pListHead, &pCacheEntry->_listEntry ); return LKP_PERFORM; } //static LK_PREDICATE OBJECT_CACHE::CacheFlushByTTL( CACHE_ENTRY * pCacheEntry, VOID * pvState ) /*++ Routine Description: Determine whether given entry should be deleted due to TTL Arguments: pCacheEntry - Cache entry to check pvState - Pointer to cache Return Value: LKP_PERFORM - do the delete, LKP_NO_ACTION - do nothing --*/ { LIST_ENTRY * pListHead = static_cast( pvState ); DBG_ASSERT( pCacheEntry != NULL ); DBG_ASSERT( pCacheEntry->CheckSignature() ); if ( pCacheEntry->QueryIsOkToFlushTTL() ) { pCacheEntry->ReferenceCacheEntry(); InsertHeadList( pListHead, &pCacheEntry->_listEntry ); return LKP_PERFORM; } else { return LKP_NO_ACTION; } } //static LK_PREDICATE OBJECT_CACHE::CacheFlushByDirmon( CACHE_ENTRY * pCacheEntry, VOID * pvState ) /*++ Routine Description: Determine whether given entry should be deleted due to dir mon Arguments: pCacheEntry - Cache entry to check pvState - STRU of path which changed Return Value: LKP_PERFORM - do the delete, LKP_NO_ACTION - do nothing --*/ { STRU * pstrPath = (STRU*) pvState; OBJECT_CACHE * pCache; DBG_ASSERT( pCacheEntry != NULL ); DBG_ASSERT( pCacheEntry->CheckSignature() ); pCache = pCacheEntry->QueryCache(); DBG_ASSERT( pCache->CheckSignature() ); if ( pCacheEntry->QueryIsOkToFlushDirmon( pstrPath->QueryStr(), pstrPath->QueryCCH() ) ) { return LKP_PERFORM; } else { return LKP_NO_ACTION; } } //static LK_PREDICATE OBJECT_CACHE::CacheFlushByMetadata( CACHE_ENTRY * pCacheEntry, VOID * pvState ) /*++ Routine Description: Determine whether given entry should be deleted due to metadata change Arguments: pCacheEntry - Cache entry to check pvState - STRU with metapath which changed Return Value: LKP_PERFORM - do the delete, LKP_NO_ACTION - do nothing --*/ { STRU * pstrPath = (STRU*) pvState; OBJECT_CACHE * pCache; DBG_ASSERT( pCacheEntry != NULL ); DBG_ASSERT( pCacheEntry->CheckSignature() ); pCache = pCacheEntry->QueryCache(); DBG_ASSERT( pCache->CheckSignature() ); if ( pCacheEntry->QueryIsOkToFlushMetadata( pstrPath->QueryStr(), pstrPath->QueryCCH() ) ) { return LKP_PERFORM; } else { return LKP_NO_ACTION; } } VOID OBJECT_CACHE::FlushByRegChange( LIST_ENTRY * pListHead ) /*++ Routine Description: Flush any inactive cache entries who have outlived their TTL Arguments: pListHead - Head of the cache entry list to be deleted Return Value: None --*/ { IncFlushCalls(); // // Iterate the hash table, deleting expired itmes // _hashTable.DeleteIf( CacheFlushByRegChange, pListHead ); } VOID OBJECT_CACHE::FlushByTTL( LIST_ENTRY * pListHead ) /*++ Routine Description: Flush any inactive cache entries who have outlived their TTL Arguments: pListHead - Head of the cache entry list to be deleted Return Value: None --*/ { IncFlushCalls(); // // Iterate the hash table, deleting expired itmes // _hashTable.DeleteIf( CacheFlushByTTL, pListHead ); } VOID OBJECT_CACHE::DoDirmonInvalidationFlush( WCHAR * pszPath ) /*++ Routine Description: Flush all appropriate entries due to dirmon change notification Arguments: pszPath - Path that changed Return Value: None --*/ { STACK_STRU( strPath, 256 ); IncFlushCalls(); if ( SUCCEEDED( strPath.Copy( pszPath ) ) ) { _hashTable.DeleteIf( CacheFlushByDirmon, (VOID*) &strPath ); } } VOID OBJECT_CACHE::DoMetadataInvalidationFlush( WCHAR * pszMetaPath ) /*++ Routine Description: Flush all appropriate entries due to metadata change notification Arguments: pszMetaPath - Metabase path which changed Return Value: None --*/ { STACK_STRU( strPath, 256 ); IncFlushCalls(); if ( SUCCEEDED( strPath.Copy( pszMetaPath ) ) ) { _hashTable.DeleteIf( CacheFlushByMetadata, (VOID*) &strPath ); } } HRESULT OBJECT_CACHE::SetCacheConfiguration( DWORD cmsecScavengeTime, DWORD cmsecTTL, DWORD dwSupportedInvalidation, CACHE_HINT_CONFIG * pCacheHintConfig ) /*++ Routine Description: Do the general cache initialization here Arguments: cmsecScavengeTime - How often should a scavenger be run for this cache (should be no larger than the TTL expected for entries in this cache) cmsecTTL - TTL for entries in this cache dwSupportedInvalidation - How can the cache be invalidated? pCacheHintConfig - Cache hint configuration (NULL for no cache hints) Return Value: HRESULT --*/ { BOOL fRet; HRESULT hr; DBG_ASSERT( cmsecTTL >= cmsecScavengeTime ); // // Create a timer which fires every cmsecScavengeTime // _cmsecScavengeTime = cmsecScavengeTime; fRet = CreateTimerQueueTimer( &_hTimer, NULL, OBJECT_CACHE::ScavengerCallback, this, _cmsecScavengeTime, _cmsecScavengeTime, WT_EXECUTELONGFUNCTION ); if ( !fRet ) { return HRESULT_FROM_WIN32( GetLastError() ); } _cmsecTTL = cmsecTTL; _dwSupportedInvalidation = dwSupportedInvalidation; // // Should we setup a cache hint table // if ( pCacheHintConfig != NULL ) { _pHintManager = new CACHE_HINT_MANAGER; if ( _pHintManager == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DeleteTimerQueueTimer( NULL, _hTimer, INVALID_HANDLE_VALUE ); _hTimer = NULL; return hr; } hr = _pHintManager->Initialize( pCacheHintConfig ); if ( FAILED( hr ) ) { delete _pHintManager; _pHintManager = NULL; DeleteTimerQueueTimer( NULL, _hTimer, INVALID_HANDLE_VALUE ); _hTimer = NULL; return hr; } } return NO_ERROR; } HRESULT OBJECT_CACHE::FindCacheEntry( CACHE_KEY * pCacheKey, CACHE_ENTRY ** ppCacheEntry, BOOL * pfShouldCache ) /*++ Routine Description: Lookup key in cache Arguments: pCacheKey - Cache key to lookup ppCacheEntry - Points to cache entry on success pfShouldCache - Provides a hint if possible on whether we should cache (can be NULL indicating no hint is needed) Return Value: HRESULT --*/ { LK_RETCODE lkrc; HRESULT hr; CACHE_ENTRY * pCacheEntry = NULL; if ( ppCacheEntry == NULL || pCacheKey == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppCacheEntry = NULL; // // First do a lookup // lkrc = _hashTable.FindKey( pCacheKey, &pCacheEntry ); if ( lkrc == LK_SUCCESS ) { // // If this entry has been flushed, then it really isn't a hit // if ( pCacheEntry->QueryIsFlushed() ) { pCacheEntry->DereferenceCacheEntry(); } else { IncHits(); DBG_ASSERT( pCacheEntry != NULL ); *ppCacheEntry = pCacheEntry; return NO_ERROR; } } IncMisses(); // // Is a hint requested? // if ( pfShouldCache != NULL ) { *pfShouldCache = TRUE; if ( _pHintManager != NULL ) { hr = _pHintManager->ShouldCacheEntry( pCacheKey, pfShouldCache ); // // Ignore error // } } return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } HRESULT OBJECT_CACHE::FlushCacheEntry( CACHE_KEY * pCacheKey ) /*++ Routine Description: Flush given cache key Arguments: pCacheKey - Key to flush Return Value: HRESULT --*/ { LK_RETCODE lkrc; CACHE_ENTRY * pCacheEntry; if ( pCacheKey == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } // // First do a lookup // lkrc = _hashTable.FindKey( pCacheKey, &pCacheEntry ); if ( lkrc == LK_SUCCESS ) { DBG_ASSERT( pCacheEntry != NULL ); if ( !pCacheEntry->QueryIsFlushed() ) { _hashTable.DeleteRecord( pCacheEntry ); } pCacheEntry->DereferenceCacheEntry(); } return NO_ERROR; } HRESULT OBJECT_CACHE::AddCacheEntry( CACHE_ENTRY * pCacheEntry ) /*++ Routine Description: Lookup key in cache Arguments: pCacheEntry - Points to cache entry on success Return Value: HRESULT --*/ { LK_RETCODE lkrc; if ( pCacheEntry == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } DBG_ASSERT( pCacheEntry->QueryCached() == FALSE ); // // Now try to insert into hash table // pCacheEntry->SetCached( TRUE ); // // Replace a stale entry, if present // lkrc = _hashTable.InsertRecord( pCacheEntry, TRUE ); if ( lkrc == LK_SUCCESS ) { IncEntriesCached(); } else { pCacheEntry->SetCached( FALSE ); } return NO_ERROR; } HRESULT OBJECT_CACHE::AddDirmonInvalidator( DIRMON_CONFIG * pDirmonConfig, CDirMonitorEntry ** ppDME ) /*++ Routine Description: Add dirmon invalidator for this cache Arguments: pDirmonConfig - Configuration of dir monitor ppDME - filled with dir monitor entry to be attached to cache entry Return Value: HRESULT --*/ { HRESULT hr = NO_ERROR; if ( !QuerySupportsDirmonSpecific() && !QuerySupportsDirmonFlush() ) { return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } // // Start monitoring // DBG_ASSERT( g_pCacheManager != NULL ); hr = g_pCacheManager->MonitorDirectory( pDirmonConfig, ppDME ); if ( FAILED( hr ) ) { return hr; } DBG_ASSERT( *ppDME != NULL ); return NO_ERROR; }