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

870 lines
23 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1995-1996 Microsoft Corporation
Module Name :
acache.cxx
Abstract:
This module implements the Allocation cache handler and associated
objects.
Author:
Murali R. Krishnan ( MuraliK ) 12-Sept-1996
Environment:
Win32 - User Mode
Project:
Internet Server DLL
Functions Exported:
Revision History:
--*/
// TODO:
// * (Debug only) Add guard blocks before and after each allocation to detect
// under- and overruns.
// * (Debug only) Change the order of the freelist to FIFO (instead of
// LIFO) to help catch cases of a block being free'd while something
// else still points to it and then getting reused.
/************************************************************
* Include Headers
************************************************************/
#include "precomp.hxx"
#include <acache.hxx>
#include <irtlmisc.h>
#define PRIVATE_HEAP
//
// # of CPUs in machine (for allocation threshold scaling)
//
DWORD g_cCPU = 1;
//
// specifies the registry location to use for getting the ATQ Configuration
// (Global overrides)
//
CHAR g_PSZ_ACACHE_CONFIG_PARAMS_REG_KEY[] = ACACHE_REG_PARAMS_REG_KEY;
/************************************************************
* Inlined Documentation on Alloc-Cache
*
* Allocation Cache:
* This module is to cache the commonly allocated objects
* to serve following goals
* 1) we can have maximum reuse of blocks
* 2) avoid traffic to the process heap manager
* 3) gather statistics for understanding of usage
*
* Details on Allocation Cache:
* There is one ALLOC_CACHE_HANDLER (shortly ACH) object per
* object that we decide to cache. The ACH is initialized by
* the configuration supplied during its construction. ACH serves
* as the main object for allocation/free of the objects it is created
* to cache. ACH gathers statistics of various operations and provides
* hooks to export the gathered statistics. There is a periodic cleanup
* scavenger that frees up long unused blocks thus reducing the working
* set of the system.
*
* All ACH objects created are chained and maintained in the global
* list of allocation cache handler objects. This global list is used
* for enumeration, debugging, and statistics dumps
*
* Allocation cache Configuration:
*
* Each ACH object is created with the ALLOC_CACHE_CONFIGURATION that
* specifies the (concurrency factor, threshold, size) desired.
* The concurrency factor ensures that we support the specified level
* of concurrency in allocations. The threshold specifies the number
* of objects that we will maintain (max) in the free-list. When the
* threshold is exceeded the freed objects are pushed to the process
* pool until the currently active objects fall below the threshold.
* In addition, each ACH object also retains a read-only name for the
* object allocated - for friendly tracking purposes.
*
* There is also a global configuration parameter that specifies the
* Lookaside cleanup interval.
*
* Allocation and Free:
* Allocation allocates one free object from the free-list if any exist.
* Otherwise the allocation will result in fetching a new object from
* the process heap manager.
* A free adds the freed object to the free-list if the # free objects
* is less than the threshold specified. Otherwise the object is freed
* to the process heap manager.
* Statistics are gathered during both allocation and free operations.
*
* Statistics:
* Statistics are gathered during the alloc/free operations throughout
* the life-time of the ACH. These statistics are reported via the
* DumpStatsToHtml() exported function. The statistics can also be
* gathered by the NTSD helper function.
*
* Scheduled List cleanup:
* There is a scheduled work item for the lookaside cleanup interval.
* The callback function walks through the list of ACH items on global
* list and takes snapshot of the # allocation calls. On a subsequent
* walk-through, if the # allocation calls remains the same (which will
* be the case if there is no allocation activity), then, the entire
* list of alloced objects is pruned. This pruning reduces the working
* set of the process.
************************************************************/
/************************************************************
* Static Functions of ALLOC_CACHE_HANDLER
************************************************************/
CRITICAL_SECTION ALLOC_CACHE_HANDLER::sm_csItems;
LIST_ENTRY ALLOC_CACHE_HANDLER::sm_lItemsHead;
DWORD ALLOC_CACHE_HANDLER::sm_dwScheduleCookie = 0;
LONG ALLOC_CACHE_HANDLER::sm_nFillPattern = 0xACA50000 ;
// This class is used to implement the free list. We cast the free'd
// memory block to a CFreeList*. The signature is used to guard against
// double deletion. We also fill memory with a pattern.
class CFreeList
{
public:
SINGLE_LIST_ENTRY Next;
DWORD dwSig;
enum {
FREESIG = (('A') | ('C' << 8) | ('a' << 16) | (('$' << 24) | 0x80)),
};
};
/* class static */
BOOL
ALLOC_CACHE_HANDLER::Initialize(VOID)
{
// get the number of processors for this machine
// do it only for NT Server only (don't scale workstation)
if ( TsIsNtServer() ) {
SYSTEM_INFO si;
GetSystemInfo( &si );
g_cCPU = si.dwNumberOfProcessors;
} else {
g_cCPU = 1;
}
// initialize the class statics
InitializeListHead( &sm_lItemsHead);
INITIALIZE_CRITICAL_SECTION( &sm_csItems);
return ( TRUE);
} // ALLOC_CACHE_HANDLER::Initialize()
/* class static */
BOOL
ALLOC_CACHE_HANDLER::Cleanup(VOID)
{
DBG_ASSERT( sm_dwScheduleCookie == 0);
DBG_ASSERT( IsListEmpty(&sm_lItemsHead));
DeleteCriticalSection( &sm_csItems);
return ( TRUE);
} // ALLOC_CACHE_HANDLER::Cleanup()
/* class static */
VOID
ALLOC_CACHE_HANDLER::InsertNewItem( IN ALLOC_CACHE_HANDLER * pach)
{
EnterCriticalSection( &sm_csItems);
InsertTailList( &sm_lItemsHead, &pach->m_lItemsEntry);
LeaveCriticalSection( &sm_csItems);
return;
} // ALLOC_CACHE_HANDLER::InsertNewItem()
/* class static */
VOID
ALLOC_CACHE_HANDLER::RemoveItem( IN ALLOC_CACHE_HANDLER * pach)
{
EnterCriticalSection( &sm_csItems);
RemoveEntryList( &pach->m_lItemsEntry);
LeaveCriticalSection( &sm_csItems);
return;
} // ALLOC_CACHE_HANDLER::RemoveItem()
/* class static */
BOOL
ALLOC_CACHE_HANDLER::DumpStatsToHtml(
OUT CHAR * pchBuffer,
IN OUT LPDWORD lpcchBuffer )
/*++
Description:
This function dumps the stats on all allocation cached objects
to HTML format for diagnostics
Arguments:
pchBuffer - pointer to buffer that will contain the html results
lpcchBuffer - pointer to DWORD containing the size of buffer on entry
On return this contains the # of bytes written out to buffer
Return:
TRUE for success and FALSE for failure
Look at GetLastError() for the error code.
--*/
{
LIST_ENTRY * pEntry;
DWORD iCount, cch;
DWORD cbTotalMem = 0;
BOOL fRet = TRUE;
if ( (lpcchBuffer == NULL) ) {
SetLastError( ERROR_INVALID_PARAMETER);
return ( FALSE);
}
EnterCriticalSection( &sm_csItems);
if ( 300 < *lpcchBuffer ) {
// Print the header blob
cch = wsprintf( pchBuffer,
"\r\nAllocCacheTable Data <br>\r\n"
"<TABLE BORDER> <TR> "
"<TH> Item Name </TH> "
"<TH> Config(concurr, threshold, size) </TH> "
"<TH> # Total Items </TH> "
"<TH> # Alloc Calls </TH> "
"<TH> # Free Calls </TH> "
"<TH> # Free Entries </TH> "
"<TH> # Total Size (bytes) </TH> "
"<TH> Fill Pattern </TH> "
"<TH> Heap </TH> "
" </TR>\r\n"
);
} else {
cch = 300;
}
for ( pEntry = sm_lItemsHead.Flink, iCount = 0;
pEntry != &sm_lItemsHead;
pEntry = pEntry->Flink, iCount++
) {
ALLOC_CACHE_HANDLER * pach =
CONTAINING_RECORD( pEntry,
ALLOC_CACHE_HANDLER,
m_lItemsEntry
);
cbTotalMem += pach->m_acConfig.cbSize * pach->m_nTotal;
if ( (cch + 160 + strlen( pach->m_pszName)) < *lpcchBuffer) {
cch += wsprintf( pchBuffer + cch,
" <TR> <TD> [%d] %s </TD>"
" <TD> (%d, %d, %d) </TD>"
" <TD> %4d </TD>"
" <TD> %4d </TD>"
" <TD> %4d </TD>"
" <TD> %4d </TD>"
" <TD> %4d </TD>"
" <TD> 0x%08lX </TD>"
" <TD> %p </TD>"
" </TR>\r\n"
,
iCount, pach->m_pszName,
pach->m_acConfig.nConcurrency,
pach->m_acConfig.nThreshold,
pach->m_acConfig.cbSize,
pach->m_nTotal,
pach->m_nAllocCalls,
pach->m_nFreeCalls,
pach->m_nFreeEntries,
pach->m_acConfig.cbSize * pach->m_nTotal,
pach->m_nFillPattern,
pach->m_hHeap
);
} else {
cch += 160 + strlen( pach->m_pszName);
}
} // for
LeaveCriticalSection( &sm_csItems);
//
// dump the final summary
//
if ( (cch + 100 ) < *lpcchBuffer) {
cch += wsprintf( pchBuffer + cch,
" <b>"
" <TR> </TR>"
" <TR> <TD> Total </TD> <TD> </TD>"
" <TD> </TD>"
" <TD> </TD>"
" <TD> </TD>"
" <TD> </TD>"
" <TD> %4d </TD>"
" </TR>"
"</b>\r\n"
" </TABLE>\r\n\r\n"
,
cbTotalMem
);
} else {
cch += 100;
}
if ( *lpcchBuffer < cch ) {
SetLastError( ERROR_INSUFFICIENT_BUFFER);
fRet = FALSE;
}
*lpcchBuffer = cch;
return (fRet);
} // ALLOC_CACHE_HANDLER::DumpStatsToHtml()
extern "C"
BOOL AllocCacheDumpStatsToHtml( OUT CHAR * pch,
IN OUT LPDWORD lpcchBuff)
{
return ( ALLOC_CACHE_HANDLER::DumpStatsToHtml( pch, lpcchBuff));
}
/* class static */
BOOL
ALLOC_CACHE_HANDLER::SetLookasideCleanupInterval( VOID )
{
DWORD dwError;
DWORD dwVal = 0;
HKEY hkey;
dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
g_PSZ_ACACHE_CONFIG_PARAMS_REG_KEY,
0,
KEY_READ,
&hkey);
if ( dwError == NO_ERROR ) {
//
// get the lookaside list cleanup period
//
dwVal = I_AtqReadRegDword( hkey,
ACACHE_REG_LOOKASIDE_CLEANUP_INTERVAL,
ACACHE_REG_DEFAULT_CLEANUP_INTERVAL );
DBG_REQUIRE( !RegCloseKey( hkey ) );
}
if ( dwVal != 0 )
{
sm_dwScheduleCookie =
ScheduleWorkItem( ALLOC_CACHE_HANDLER::CleanupAllLookasides,
NULL,
dwVal * 1000,
TRUE );
if ( sm_dwScheduleCookie == 0 )
{
return FALSE;
}
}
return TRUE;
} // ALLOC_CACHE_HANDLER::SetLookasideCleanupInterval()
/* class static */
BOOL
ALLOC_CACHE_HANDLER::ResetLookasideCleanupInterval( VOID )
{
BOOL fReturn = TRUE;
if ( sm_dwScheduleCookie )
{
fReturn = RemoveWorkItem( sm_dwScheduleCookie );
if (fReturn) {
sm_dwScheduleCookie = 0;
}
}
return ( fReturn);
} // ALLOC_CACHE_HANDLER::ResetLookasideCleanupInterval()
/* class static */
VOID
WINAPI
ALLOC_CACHE_HANDLER::CleanupAllLookasides(
IN PVOID /* pvContext */
)
{
LIST_ENTRY * pEntry;
EnterCriticalSection( &sm_csItems);
for ( pEntry = sm_lItemsHead.Flink;
pEntry != &sm_lItemsHead;
pEntry = pEntry->Flink )
{
ALLOC_CACHE_HANDLER * pach =
CONTAINING_RECORD( pEntry,
ALLOC_CACHE_HANDLER,
m_lItemsEntry
);
IF_DEBUG( ALLOC_CACHE) {
DBGPRINTF(( DBG_CONTEXT,
"Cleaning lookaside list for '%s' handler\n",
pach->m_pszName ));
}
pach->CleanupLookaside( FALSE );
}
LeaveCriticalSection( &sm_csItems);
} // ALLOC_CACHE_HANDLER::CleanupAllLookasides()
/************************************************************
* Member Functions of ALLOC_CACHE_HANDLER
************************************************************/
ALLOC_CACHE_HANDLER::ALLOC_CACHE_HANDLER(
IN LPCSTR pszName,
IN const ALLOC_CACHE_CONFIGURATION * pacConfig,
IN BOOL fEnableCleanupAsserts /* = TRUE */
)
: m_fValid ( FALSE),
m_nTotal (0),
m_nAllocCalls (0),
m_nFreeCalls (0),
m_nFreeEntries (0),
m_pszName (pszName),
m_nLastAllocCount(0),
m_hHeap (NULL),
m_fCleanupAssertsEnabled(fEnableCleanupAsserts)
{
DBG_ASSERT( NULL != pacConfig );
m_acConfig = *pacConfig;
if ( pacConfig->nThreshold == INFINITE) {
// this will be compared against a signed value. So be careful.
m_acConfig.nThreshold = 0x7FFFFFFF;
} else {
// scale by the number of processors on MP machines
m_acConfig.nThreshold *= g_cCPU;
}
// make sure the block is big enough to hold a CFreeList
m_acConfig.cbSize = max(m_acConfig.cbSize, sizeof(CFreeList));
// round up the block size to a multiple of the size of a LONG (for
// the fill pattern in Free()).
m_acConfig.cbSize =
(m_acConfig.cbSize + sizeof(LONG) - 1) & ~(sizeof(LONG) - 1);
INITIALIZE_CRITICAL_SECTION( & m_csLock);
m_lHead.Next = NULL;
m_nFillPattern = InterlockedIncrement(&sm_nFillPattern);
//
// Create private heap
//
#ifdef PRIVATE_HEAP
if (TsIsNtServer())
m_hHeap = HeapCreate( 0, 0, 0 );
else
m_hHeap = IisHeap();
if( m_hHeap == NULL )
{
return;
}
#endif
ALLOC_CACHE_HANDLER::InsertNewItem( this);
m_fValid = TRUE;
return;
} // ALLOC_CACHE_HANDLER::ALLOC_CACHE_HANDLER()
ALLOC_CACHE_HANDLER::~ALLOC_CACHE_HANDLER(VOID)
{
if ( m_fValid) {
CleanupLookaside( TRUE );
DeleteCriticalSection( & m_csLock);
ALLOC_CACHE_HANDLER::RemoveItem( this);
#ifdef PRIVATE_HEAP
if ( m_hHeap )
{
if (TsIsNtServer())
DBG_REQUIRE( HeapDestroy( m_hHeap ) );
m_hHeap = NULL;
}
#endif
}
if (m_fCleanupAssertsEnabled) {
DBG_ASSERT( 0 == m_nTotal );
DBG_ASSERT( m_lHead.Next == NULL);
}
return;
} // ALLOC_CACHE_HANDLER::~ALLOC_CACHE_HANDLER()
VOID
ALLOC_CACHE_HANDLER::CleanupLookaside(
IN BOOL fForceCleanup
)
/*++
Description:
This function cleans up the lookaside list by removing excess storage space
used by the objects allocated by this instance. This function is
used by the periodic scavenging operation as well as for final cleanup.
Arguments:
fForceCleanup - forces a cleanup operation always.
Returns:
None
--*/
{
if ( !fForceCleanup )
{
//
// We are called for the regular scavenging operation
// Take a snapshot of the # allocation calls so that
// we may cleanup space when services are idle.
//
IF_DEBUG( ALLOC_CACHE) {
DBGPRINTF(( DBG_CONTEXT,
"AllocCalls = %ld, LastAllocCount = %ld\n",
m_nAllocCalls,
m_nLastAllocCount ));
}
if ( m_nAllocCalls != m_nLastAllocCount )
{
InterlockedExchange( &m_nLastAllocCount,
m_nAllocCalls );
return;
}
}
SINGLE_LIST_ENTRY listHeadCopy;
//
// make a copy of the first element in the list inside the lock
// Free the entire chain outside the locked section.
// Otherwise on a busy system the threads will be waiting for
// this thread to complete
//
Lock();
listHeadCopy.Next = m_lHead.Next;
//
// we are about to cleanup all entries -> so set state back properly.
//
m_lHead.Next = NULL;
m_nFreeEntries = 0; // no more free-entries available
Unlock();
//
// free up all the entries in the list
//
PSINGLE_LIST_ENTRY pl;
pl = PopEntryList( &listHeadCopy);
while ( pl != NULL) {
InterlockedDecrement( &m_nTotal);
#ifdef PRIVATE_HEAP
HeapFree( m_hHeap, 0, pl );
#else
::LocalFree(pl);
#endif
pl = PopEntryList( &listHeadCopy);
} // for
DBG_ASSERT( listHeadCopy.Next == NULL);
return;
} // ALLOC_CACHE_HANDLER::CleanupLookaside()
LPVOID
ALLOC_CACHE_HANDLER::Alloc( VOID )
{
LPVOID pv = NULL;
if ( m_nFreeEntries > 0) {
//
// There are free entries available - allocate from the free pool
//
// Only acquire the lock if there's potentially something to grab
Lock();
// Check again if the free entry is available.
if ( m_nFreeEntries > 0) {
pv = (LPVOID) PopEntryList( & m_lHead); // get the real object
m_nFreeEntries--;
}
Unlock();
if ( NULL != pv ) {
CFreeList* pfl = (CFreeList*) pv;
// If the signature is wrong then somebody's been scribbling
// on memory that they've freed
DBG_ASSERT(pfl->dwSig == CFreeList::FREESIG);
pfl->dwSig = 0; // clear; just in case caller never overwrites
}
}
if ( NULL == pv) {
//
// No free entry. Need to alloc a new object.
//
#ifdef PRIVATE_HEAP
DBG_ASSERT( m_hHeap != NULL );
pv = (LPVOID) HeapAlloc( m_hHeap,
HEAP_ZERO_MEMORY,
m_acConfig.cbSize );
#else
pv = (LPVOID) LocalAlloc( LPTR, m_acConfig.cbSize );
#endif
if ( NULL != pv) {
// update counters
InterlockedIncrement( &m_nTotal);
}
}
if ( NULL != pv ) {
InterlockedIncrement( &m_nAllocCalls);
}
return ( pv);
} // ALLOC_CACHE_HANDLER::Alloc()
BOOL
ALLOC_CACHE_HANDLER::Free( LPVOID pv)
{
// Assume that this is allocated using the Alloc() function
DBG_ASSERT( NULL != pv);
// use a signature to check against double deletions
CFreeList* pfl = (CFreeList*) pv;
DBG_ASSERT(pfl->dwSig != CFreeList::FREESIG);
#ifdef _DEBUG
// Fill the memory with an improbable pattern that is unique
// to this allocator (for identification in the debugger)
RtlFillMemoryUlong(pv, m_acConfig.cbSize, m_nFillPattern);
#else // !_DEBUG
// Start filling the space beyond the portion overlaid by the initial
// CFreeList. Fill at most 6 DWORDS.
LONG* pl = (LONG*) (pfl+1);
for (LONG cb = (LONG)min(6 * sizeof(LONG),m_acConfig.cbSize) - sizeof(CFreeList);
cb > 0;
cb -= sizeof(LONG))
{
*pl++ = m_nFillPattern;
}
#endif // !_DEBUG
// Now, set the signature
pfl->dwSig = CFreeList::FREESIG;
// store the items in the alloc cache.
if ( m_nFreeEntries >= m_acConfig.nThreshold) {
//
// threshold for free entries is exceeded. free the object to
// process pool
//
#ifdef PRIVATE_HEAP
HeapFree( m_hHeap, 0, pv );
#else
::LocalFree(pv);
#endif
InterlockedDecrement( &m_nTotal);
} else {
//
// Store the given pointer in the single linear list
//
Lock();
PushEntryList( &m_lHead, &pfl->Next);
m_nFreeEntries++;
Unlock();
}
InterlockedIncrement( &m_nFreeCalls);
return ( TRUE);
} // ALLOC_CACHE_HANDLER::Free()
VOID
ALLOC_CACHE_HANDLER::Print( VOID)
{
CHAR rgchBuffer[8192];
DWORD cchBuffer = sizeof(rgchBuffer);
DBG_REQUIRE( IpPrint( rgchBuffer, &cchBuffer));
DBGDUMP(( DBG_CONTEXT, rgchBuffer));
return;
} // ALLOC_CACHE_HANDLER::Print()
BOOL
ALLOC_CACHE_HANDLER::IpPrint( OUT CHAR * pchBuffer, IN OUT LPDWORD pcchSize)
{
DWORD cchUsed;
cchUsed = wsprintfA( pchBuffer,
"[%d]ALLOC_CACHE_HANDLER[%08p]. Config: "
" ObjSize = %d. Concurrency=%d. Thres=%d.\n"
" TotalObjs = %d. Calls: Alloc(%d), Free(%d)."
" FreeEntries = %d. FillPattern = 0x%08lX.\n"
,
GetCurrentThreadId(),
this,
m_acConfig.cbSize,
m_acConfig.nConcurrency,
m_acConfig.nThreshold,
m_nTotal, m_nAllocCalls, m_nFreeCalls,
m_nFreeEntries, m_nFillPattern
);
Lock();
// NYI: Print the list of individual pointers
Unlock();
DBG_ASSERT( *pcchSize > cchUsed);
*pcchSize = cchUsed;
return (TRUE);
} // ALLOC_CACHE_HANDLER::IpPrint()
VOID
ALLOC_CACHE_HANDLER::QueryStats( IN ALLOC_CACHE_STATISTICS * pacStats )
{
DBG_ASSERT( pacStats != NULL );
pacStats->acConfig = m_acConfig;
pacStats->nTotal = m_nTotal;
pacStats->nAllocCalls = m_nAllocCalls;
pacStats->nFreeCalls = m_nFreeCalls;
pacStats->nFreeEntries = m_nFreeEntries;
return;
} // ALLOC_CACHE_HANDLER::QueryStats()
//
// Global functions
//
DWORD
I_AtqReadRegDword(
IN HKEY hkey,
IN LPCSTR pszValueName,
IN DWORD dwDefaultValue )
/*++
NAME: I_AtqReadRegDword
SYNOPSIS: Reads a DWORD value from the registry.
ENTRY: hkey - Openned registry key to read
pszValueName - The name of the value.
dwDefaultValue - The default value to use if the
value cannot be read.
RETURNS DWORD - The value from the registry, or dwDefaultValue.
--*/
{
DWORD err;
DWORD dwBuffer;
DWORD cbBuffer = sizeof(dwBuffer);
DWORD dwType;
if( hkey != NULL ) {
err = RegQueryValueExA( hkey,
pszValueName,
NULL,
&dwType,
(LPBYTE)&dwBuffer,
&cbBuffer );
if( ( err == NO_ERROR ) && ( dwType == REG_DWORD ) ) {
dwDefaultValue = dwBuffer;
}
}
return dwDefaultValue;
} // I_AtqReadRegDword()
/************************ End of File ***********************/