windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/infocomm/cache2/filecach.cxx
2020-09-26 16:20:57 +08:00

906 lines
18 KiB
C++

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1998 **/
/**********************************************************************/
/*
filecach.cxx
This module implements the private interface to the file cache
FILE HISTORY:
MCourage 09-Dec-1997 Created
*/
#include "tsunamip.hxx"
#include <tsunami.hxx>
#include "filecach.hxx"
#include "filehash.hxx"
#include "tlcach.h"
#include <pudebug.h>
/*
* Globals
*/
CFileHashTable * g_pFileInfoTable;
CFileCacheStats * g_pFileCacheStats;
HANDLE g_hFileCacheShutdownEvent;
BOOL g_fFileCacheShutdown;
CRITICAL_SECTION g_csUriInfo;
#if TSUNAMI_REF_DEBUG
PTRACE_LOG g_pFileRefTraceLog;
#endif
/*
* Private helper function declarations
*/
inline VOID I_DerefFileInfo(TS_OPEN_FILE_INFO *pOpenFile);
VOID I_AddRefIO(TS_OPEN_FILE_INFO *pOpenFile);
VOID I_DerefIO(TS_OPEN_FILE_INFO *pOpenFile);
BOOL FileFlushFilterAll(TS_OPEN_FILE_INFO *pOpenFile, PVOID pv);
/*
* function definitions
*/
BOOL
FileCache_Initialize(
IN DWORD dwMaxFiles
)
{
BOOL fReturn;
#if TSUNAMI_REF_DEBUG
g_pFileRefTraceLog = CreateRefTraceLog(
256, // LogSize
0 // ExtraBytesInHeader
);
#endif // TSUNAMI_REF_DEBUG
g_pFileInfoTable = new CFileHashTable("FCinfo");
g_pFileCacheStats = new CFileCacheStats;
g_fFileCacheShutdown = FALSE;
g_hFileCacheShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
INITIALIZE_CRITICAL_SECTION( &g_csUriInfo );
if (g_bEnableSequentialRead) {
TsCreateFileFlags |= FILE_FLAG_SEQUENTIAL_SCAN;
}
fReturn = (g_pFileInfoTable
&& g_pFileCacheStats
&& TS_OPEN_FILE_INFO::Initialize(dwMaxFiles)
&& (InitializeTwoLevelCache(g_dwMemCacheSize) == ERROR_SUCCESS)
&& g_hFileCacheShutdownEvent);
if (!fReturn) {
FileCache_Terminate();
}
return fReturn;
}
VOID
FileCache_Terminate(
VOID
)
{
g_fFileCacheShutdown = TRUE;
FlushFileCache();
//
// At this point the hash table is empty, but there will still
// be some oplocked files hanging around. We have to wait for
// all the oplock completions before moving on.
//
if (g_pFileCacheStats->GetFlushedEntries()) {
WaitForSingleObject(g_hFileCacheShutdownEvent, TS_FILE_CACHE_SHUTDOWN_TIMEOUT);
DBG_ASSERT( g_pFileCacheStats->GetFlushedEntries() == 0 );
}
DeleteCriticalSection( &g_csUriInfo );
CloseHandle(g_hFileCacheShutdownEvent);
g_hFileCacheShutdownEvent=NULL;
DBG_REQUIRE(TerminateTwoLevelCache() == ERROR_SUCCESS);
TS_OPEN_FILE_INFO::Cleanup();
delete g_pFileInfoTable;
DBGPRINTF(( DBG_CONTEXT,
"FileCache_Terminate: deleted g_pFileInfoTable.\n" ));
delete g_pFileCacheStats;
g_pFileInfoTable = NULL;
g_pFileCacheStats = NULL;
#if TSUNAMI_REF_DEBUG
if( g_pFileRefTraceLog != NULL ) {
DestroyRefTraceLog( g_pFileRefTraceLog );
g_pFileRefTraceLog = NULL;
}
#endif // TSUNAMI_REF_DEBUG
}
DWORD
CacheFile(
IN TS_OPEN_FILE_INFO * pOpenFile,
IN DWORD dwFlags
)
/*++
Routine Description:
Add a file info structure to the cache.
If the FCF_UNINITIALIZED flag is set, the file will be added to
the cache, but calls to CheckoutFile will be blocked until the
file is marked initialized with NotifyFileInitialized.
CheckoutFileEntry will not block. I expect that this flag will
always be set.
The FCF_FOR_IO flag indicates that the caller will be performing
I/O operations with the cached file handle. This flag will be
clear in most cases except in TsCreateFile.
Arguments:
pOpenFile - The structure to be cached.
pstrPath - The pathname that will be used to look up the cache entry.
dwFlags - Valid flags are FCF_UNINITIALIZED and FCF_FOR_IO.
Return Value:
TS_ERROR_SUCCESS
TS_ERROR_OUT_OF_MEMORY
TS_ERROR_ALREADY_CACHED
--*/
{
enum LK_RETCODE lkRetval;
DWORD dwRetval;
DBG_ASSERT( pOpenFile != NULL );
//
// The caller gets a reference to this file info object
//
pOpenFile->AddRef();
//
// Don't need to acquire the lock since the object is not yet
// in the cache.
//
pOpenFile->SetCached();
if (dwFlags & FCF_FOR_IO) {
pOpenFile->AddRefIO();
}
if (! (dwFlags & FCF_UNINITIALIZED)) {
pOpenFile->SetInitialized();
}
//
// Put it in the hash table
//
lkRetval = g_pFileInfoTable->InsertRecord(pOpenFile, false);
if (LK_SUCCESS == lkRetval) {
dwRetval = TS_ERROR_SUCCESS;
g_pFileCacheStats->IncFilesCached();
} else {
if (LK_ALLOC_FAIL == lkRetval) {
dwRetval = TS_ERROR_OUT_OF_MEMORY;
} else if (LK_KEY_EXISTS == lkRetval) {
dwRetval = TS_ERROR_ALREADY_CACHED;
} else {
//
// No other error should come to pass
//
dwRetval = TS_ERROR_OUT_OF_MEMORY;
DBG_ASSERT(FALSE);
}
pOpenFile->ClearCached();
if (dwFlags & FCF_FOR_IO) {
pOpenFile->DerefIO();
}
//
// Remove the reference we added.
// Don't call I_DerefFileInfo, because this
// object never made it to the cache.
// The caller will free the memory.
//
pOpenFile->Deref();
}
return dwRetval;
}
VOID
NotifyInitializedFile(
IN TS_OPEN_FILE_INFO * pOpenFile
)
/*++
Routine Description:
This function tells that cache that a file previously cached with
CacheUninitializedFile, is now ready for use.
Arguments:
pOpenFile - The file which is now initialized.
Return Value:
None.
--*/
{
BOOL bShouldClose;
CHECK_FILE_STATE( pOpenFile );
//
// Mark the file as initialized
//
pOpenFile->Lock();
pOpenFile->SetInitialized();
bShouldClose = pOpenFile->IsCloseable();
pOpenFile->Unlock();
//
// Clean up as neccessary
// Note that I don't have to worry about the IO refcount
// going back up because the file is marked as flushed.
//
if (bShouldClose) {
pOpenFile->CloseHandle();
}
//
// TODO: need to execute any notification code?
//
}
VOID
DecacheFile(
IN TS_OPEN_FILE_INFO * pOpenFile,
IN DWORD dwFlags
)
/*++
Routine Description:
Remove a file info entry from the cache. After a call to
DecacheFile the entry will not be returned by CheckoutFile. The
entry itself is cleaned up when the last CheckinFile occurs.
Calling DecacheFile checks the entry in.
The FCF_FOR_IO flag indicates that the caller will be performing
I/O operations with the cached file handle. I expect that this flag
will always be clear.
Arguments:
pOpenFile - The file info structure to be decached
dwFlags - Valid flags are FCF_FOR_IO, FCF_NO_DEREF.
Return Value:
None.
--*/
{
BOOL bShouldClose;
TS_OPEN_FILE_INFO * pHashFile;
LK_RETCODE lkrc;
CHECK_FILE_STATE( pOpenFile );
//
// remove the file from the hashtable
//
lkrc = g_pFileInfoTable->DeleteRecord(pOpenFile);
DBG_ASSERT( LK_SUCCESS == lkrc || LK_NO_SUCH_KEY == lkrc );
#if TSUNAMI_REF_DEBUG
if (LK_SUCCESS == lkrc) {
pOpenFile->TraceCheckpoint();
}
#endif
//
// update state
//
pOpenFile->Lock();
if (! pOpenFile->IsFlushed() ) {
pOpenFile->SetFlushed();
g_pFileCacheStats->IncFlushedEntries();
g_pFileCacheStats->DecFilesCached();
}
if (dwFlags & FCF_FOR_IO) {
I_DerefIO(pOpenFile);
}
bShouldClose = pOpenFile->IsCloseable();
pOpenFile->Unlock();
//
// Clean up as neccessary
// Note that I don't have to worry about the IO refcount
// going back up because the file is marked as flushed.
//
if (bShouldClose) {
pOpenFile->CloseHandle();
}
if (!(dwFlags & FCF_NO_DEREF)) {
I_DerefFileInfo(pOpenFile);
}
}
VOID
FlushFileCache(
VOID
)
/*++
Routine Description:
Removes all entries from the cache. Unlike DecacheFile, this
function does not check any entries in.
Arguments:
None
Return Value:
None
--*/
{
FilteredFlushFileCache(FileFlushFilterAll, NULL);
}
LK_PREDICATE
FileFlushCachePredicate(
TS_OPEN_FILE_INFO *pOpenFile,
void* pvState
)
{
TS_FILE_FLUSH_STATE * pFlushState = static_cast<TS_FILE_FLUSH_STATE*>(pvState);
LK_PREDICATE lkpAction;
if (pFlushState->pfnFilter(pOpenFile, pFlushState->pvParm)) {
//
// put it on the list
//
pOpenFile->AddRef(); // for the list
InsertHeadList(&pFlushState->ListHead, &pOpenFile->FlushList);
lkpAction = LKP_PERFORM;
} else {
lkpAction = LKP_NO_ACTION;
}
return lkpAction;
}
VOID
FilteredFlushFileCache(
IN PFCFILTERRTN pFilterRoutine,
IN PVOID pv
)
/*++
Routine Description:
Removes entries based on a caller specified filter. The caller
provides a boolean function which takes a cache entry as a
parameter. The function will be called with each item in the cache.
If the function returns TRUE, the item will be decached (but not
checked in). Otherwise the item will remain in the cache.
Arguments:
pFilterRoutine - A pointer to the filter function
pv - a parameter to the filter function
Return Value:
None
--*/
{
TS_FILE_FLUSH_STATE FlushState;
g_pFileCacheStats->IncFlushes();
//
// Initialize the flush state
//
FlushState.pfnFilter = pFilterRoutine;
InitializeListHead(&FlushState.ListHead);
FlushState.pvParm = pv;
//
// Delete elements from table and construct list
//
g_pFileInfoTable->DeleteIf(FileFlushCachePredicate, &FlushState);
//
// Update element state and close file handles
//
PLIST_ENTRY pEntry;
PLIST_ENTRY pNext;
TS_OPEN_FILE_INFO * pOpenFile;
BOOL bShouldClose;
for (pEntry = FlushState.ListHead.Flink;
pEntry != &FlushState.ListHead;
pEntry = pNext ) {
pNext = pEntry->Flink;
pOpenFile = CONTAINING_RECORD( pEntry, TS_OPEN_FILE_INFO, FlushList );
DBG_ASSERT( pOpenFile->CheckSignature() );
pOpenFile->Lock();
if (! pOpenFile->IsFlushed() ) {
pOpenFile->SetFlushed();
g_pFileCacheStats->IncFlushedEntries();
g_pFileCacheStats->DecFilesCached();
}
bShouldClose = pOpenFile->IsCloseable();
pOpenFile->Unlock();
if (bShouldClose) {
pOpenFile->CloseHandle();
}
I_DerefFileInfo(pOpenFile); // remove our list's reference
}
}
BOOL
CheckoutFile(
IN LPCSTR pstrPath,
IN DWORD dwFlags,
OUT TS_OPEN_FILE_INFO ** ppOpenFile
)
/*++
Routine Description:
Look up an entry in the cache and return it.
The FCF_FOR_IO flag indicates that the caller will be performing
I/O operations with the cached file handle. This flag will be clear
in most cases except in TsCreateFile.
Arguments:
pstrPath - The pathname of the desired file info in UPPERCASE!!
dwFlags - The only valid flag is FCF_FOR_IO.
ppOpenFile - On success this output points to the cached entry.
Otherwise it is not set.
Return Value:
TRUE if the item was found, FALSE otherwise.
Additional error information can be obtained from GetLastError()
--*/
{
CFileKey fileKey;
TS_OPEN_FILE_INFO * pOpenFile;
BOOL bRetVal = FALSE;
DWORD dwError;
DBG_ASSERT( pstrPath != NULL );
//
// Look in the hash table
//
fileKey.m_pszFileName = const_cast<char *>(pstrPath);
fileKey.m_cbFileName = strlen(pstrPath);
g_pFileInfoTable->FindKey(&fileKey, &pOpenFile);
if (NULL == pOpenFile) {
dwError = ERROR_FILE_NOT_FOUND;
goto exit;
}
//
// Make sure it's valid and update state
//
CHECK_FILE_STATE( pOpenFile );
pOpenFile->Lock();
DBG_ASSERT( pOpenFile->IsCached() );
if (dwFlags & FCF_FOR_IO) {
I_AddRefIO(pOpenFile);
}
pOpenFile->Unlock();
//
// Make sure it's initialized
//
if (!pOpenFile->IsInitialized()) {
int t = 1; // time to sleep
int i = 0; // number of times we've gone to sleep
while (!pOpenFile->IsInitialized() && i < c_SleepTimeout) {
Sleep(t);
if (t < c_dwSleepmax) {
t <<= 1;
}
i++;
}
}
if (!pOpenFile->IsInitialized()) {
//
// OK we've waited long enough. Just return failure.
//
dwError = ERROR_BUSY;
goto err;
}
if (pOpenFile->IsFlushed()) {
dwError = ERROR_FILE_NOT_FOUND;
goto err;
}
//
// At last, sweet success!
//
bRetVal = TRUE;
*ppOpenFile = pOpenFile;
exit:
if (!bRetVal) {
SetLastError(dwError);
}
if (dwFlags & FCF_FOR_IO) {
if (bRetVal) {
g_pFileCacheStats->IncHits();
} else {
g_pFileCacheStats->IncMisses();
}
}
return bRetVal;
err:
//
// if we added to the IO refcount we have to decrement
// that now.
//
if (dwFlags & FCF_FOR_IO) {
pOpenFile->Lock();
I_DerefIO(pOpenFile);
pOpenFile->Unlock();
}
//
// FindKey automatically increments the refcount so
// we must deref here.
//
I_DerefFileInfo(pOpenFile);
if (dwFlags & FCF_FOR_IO) {
g_pFileCacheStats->IncMisses();
}
SetLastError(dwError);
return FALSE;
}
BOOL
CheckoutFileEntry(
IN TS_OPEN_FILE_INFO * pOpenFile,
IN DWORD dwFlags
)
/*++
Routine Description:
This function checks out an entry to which the caller already has
a reference.
The FCF_FOR_IO flag indicates that the caller will be performing
I/O operations with the cached file handle.
Arguments:
pOpenFile - The file info structure to be checked out.
dwFlags - The only valid flag is FCF_FOR_IO.
Return Value:
TRUE - File was successfully checked out
FALSE - File was checked out, but should not be used by the
caller. (it's been flushed)
--*/
{
BOOL bSuccess;
CHECK_FILE_STATE( pOpenFile );
pOpenFile->AddRef();
pOpenFile->Lock();
if (dwFlags & FCF_FOR_IO) {
I_AddRefIO(pOpenFile);
}
if (pOpenFile->IsFlushed() == FALSE) {
bSuccess = TRUE;
} else {
bSuccess = FALSE;
}
if (dwFlags & FCF_FOR_IO) {
if (bSuccess) {
g_pFileCacheStats->IncHits();
} else {
g_pFileCacheStats->IncMisses();
}
}
pOpenFile->Unlock();
return bSuccess;
}
VOID
CheckinFile(
IN TS_OPEN_FILE_INFO * pOpenFile,
IN DWORD dwFlags
)
/*++
Routine Description:
Indicate that a previously checked out file info is no longer in use.
The FCF_FOR_IO flag indicates that the caller was be performing I/O
operations with the cached file handle. This flag will be clear in
most cases except in TsCloseHandle.
Arguments:
pOpenFile - The file info structure to be checked in.
dwFlags - The only valid flag is FCF_FOR_IO.
Return Value:
None.
--*/
{
BOOL bShouldClose;
CHECK_FILE_STATE( pOpenFile );
//
// update state
//
pOpenFile->Lock();
if (dwFlags & FCF_FOR_IO) {
I_DerefIO(pOpenFile);
}
bShouldClose = pOpenFile->IsCloseable();
pOpenFile->Unlock();
//
// Clean up as necessary
//
if (bShouldClose) {
pOpenFile->CloseHandle();
}
I_DerefFileInfo(pOpenFile);
}
inline VOID
I_DerefFileInfo(
TS_OPEN_FILE_INFO *pOpenFile
)
/*++
--*/
{
LONG lRefCount;
DBG_ASSERT( pOpenFile != NULL );
DBG_ASSERT( pOpenFile->CheckSignature() );
lRefCount = pOpenFile->Deref();
DBG_ASSERT( lRefCount >= 0 );
if (lRefCount == 0) {
DBG_ASSERT(pOpenFile->IsFlushed());
delete pOpenFile;
g_pFileCacheStats->DecFlushedEntries();
}
}
VOID
I_AddRefIO(
TS_OPEN_FILE_INFO * pOpenFile
)
/*++
Routine Description:
Calls pOpenFile->AddRefIO.
This should be called with the fileinfo lock held.
Arguments:
pOpenFile - the file to addref
Return Value:
None.
--*/
{
pOpenFile->AddRefIO();
}
VOID
I_DerefIO(
TS_OPEN_FILE_INFO * pOpenFile
)
/*++
Routine Description:
Calls pOpenFile->DerefIO.
This should be called with the fileinfo lock held.
Arguments:
pOpenFile - the file to deref
Return Value:
None.
--*/
{
pOpenFile->DerefIO();
}
VOID
TS_OPEN_FILE_INFO::Print(
VOID
) const
{
//
// Doesn't do anything. Ha!
//
}
BOOL
CFileCacheStats::DumpToHtml(
CHAR * pchBuffer,
LPDWORD lpcbBuffer) const
{
*lpcbBuffer = wsprintf(pchBuffer,
"<table>"
"<tr><td>Currently Cached Files</td><td align=right>%d</td></tr>"
"<tr><td>Total # of files added</td><td align=right>%d</td></tr>"
"<tr><td>Cache Hits</td><td align=right>%d</td></tr>"
"<tr><td>Cache Misses</td><td align=right>%d</td></tr>"
"<tr><td>Cache Flushes</td><td align=right>%d</td></tr>"
"<tr><td>Oplock Breaks</td><td align=right>%d</td></tr>"
"<tr><td>Oplock Breaks To None</td><td align=right>%d</td></tr>"
"<tr><td>Flushed entries in cache</td><td align=right>%d</td></tr>"
"<tr><td>Total # files flushed</td><td align=right>%d</td></tr>"
"</table>",
FilesCached,
TotalFilesCached,
Hits,
Misses,
Flushes,
OplockBreaks,
OplockBreaksToNone,
FlushedEntries,
TotalFlushed );
return TRUE;
}
BOOL
CFileCacheStats::QueryStats(
INETA_CACHE_STATISTICS * pCacheCtrs
) const
{
return FALSE;
}
BOOL
FileFlushFilterAll(
TS_OPEN_FILE_INFO *pOpenFile,
PVOID pv
)
{
return TRUE;
}
#if DBG
BOOL
CheckFileState(
TS_OPEN_FILE_INFO *pOpenFile
)
{
DBG_ASSERT( pOpenFile );
DBG_ASSERT( pOpenFile->CheckSignature() );
DBG_ASSERT( pOpenFile->IsCached() );
DBG_ASSERT( g_pFileInfoTable );
TS_OPEN_FILE_INFO * pHashFile;
BOOL bOK;
//
// I was going to do some interesting checks here
// but the synchronization is just too hard
//
bOK = TRUE;
return bOK;
}
#endif // DBG
//
// filecach.cxx
//