1366 lines
28 KiB
C
1366 lines
28 KiB
C
|
/*++
|
||
|
|
||
|
cintrnl.h
|
||
|
|
||
|
This file contains internal definitions for the cache library -
|
||
|
this stuff should not be of interest for most users.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#ifndef _CINTRNL_H_
|
||
|
#define _CINTRNL_H_
|
||
|
|
||
|
class CScheduleThread {
|
||
|
/*++
|
||
|
|
||
|
Class Description :
|
||
|
|
||
|
This is a base class for those objects which wish to be
|
||
|
called on a regularily scheduled basis.
|
||
|
|
||
|
The constructors for this class will automatically
|
||
|
put the object in a doubly linked list walked by a
|
||
|
background thread which periodically executes a virtual function.
|
||
|
|
||
|
|
||
|
--*/
|
||
|
private :
|
||
|
|
||
|
//
|
||
|
// Special constructor for the 'Head' element of the
|
||
|
// doubly linked list this class maintains.
|
||
|
//
|
||
|
CScheduleThread( BOOL fSpecial ) ;
|
||
|
|
||
|
protected :
|
||
|
|
||
|
//
|
||
|
// Has the scheduler been initialized ?
|
||
|
//
|
||
|
static BOOL s_fInitialized ;
|
||
|
|
||
|
//
|
||
|
// Crit sect protecting doubly linked list.
|
||
|
//
|
||
|
static CRITICAL_SECTION s_critScheduleList ;
|
||
|
|
||
|
//
|
||
|
// Handle to event used to terminate background thread.
|
||
|
//
|
||
|
static HANDLE s_hShutdown ;
|
||
|
|
||
|
//
|
||
|
// Handle to background thread.
|
||
|
//
|
||
|
static HANDLE s_hThread ;
|
||
|
|
||
|
//
|
||
|
// The head element of the doubly linked list.
|
||
|
//
|
||
|
static CScheduleThread s_Head ;
|
||
|
|
||
|
//
|
||
|
// The thread which calls our virtual functions
|
||
|
//
|
||
|
static DWORD WINAPI ScheduleThread( LPVOID lpv ) ;
|
||
|
|
||
|
//
|
||
|
// Previous and Next pointers which maintain doubly linked
|
||
|
// list of scheduled itesm.
|
||
|
//
|
||
|
class CScheduleThread* m_pPrev ;
|
||
|
class CScheduleThread* m_pNext ;
|
||
|
|
||
|
protected :
|
||
|
|
||
|
//
|
||
|
// Derived classes should override this function -
|
||
|
// it will be called on a regular basis by the scheduler thread.
|
||
|
//
|
||
|
virtual void Schedule( void ) {}
|
||
|
|
||
|
//
|
||
|
// Constructor and Destructor automagically manage
|
||
|
// insertion into doubly linked list of other scheduled items.
|
||
|
// These are protected as we want people to buid only
|
||
|
// derived objects which use this.
|
||
|
//
|
||
|
CScheduleThread() ;
|
||
|
|
||
|
//
|
||
|
// Member functions which put us into the regular schedule !
|
||
|
//
|
||
|
void AddToSchedule() ;
|
||
|
void RemoveFromSchedule() ;
|
||
|
|
||
|
public :
|
||
|
|
||
|
//
|
||
|
// Initialize the class - don't construct
|
||
|
// any derived objects before this is called.
|
||
|
//
|
||
|
static BOOL Init() ;
|
||
|
|
||
|
//
|
||
|
// Terminate class and background thread !
|
||
|
//
|
||
|
static void Term() ;
|
||
|
|
||
|
//
|
||
|
// Global which tells clients how frequently they
|
||
|
// will be called !
|
||
|
//
|
||
|
static DWORD dwNotificationSeconds ;
|
||
|
|
||
|
//
|
||
|
// Destructor is protected - we should only be invoked
|
||
|
// by derived class constructors
|
||
|
//
|
||
|
virtual ~CScheduleThread() ;
|
||
|
|
||
|
} ;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
class CacheState : public ICacheRefInterface {
|
||
|
/*++
|
||
|
|
||
|
Class Description :
|
||
|
|
||
|
This class will provide the base support for LRU
|
||
|
removal of items in the cache !
|
||
|
|
||
|
Base Class :
|
||
|
|
||
|
CQElement - we put these objects into
|
||
|
a TLockQueue to amortize LRU operations !
|
||
|
|
||
|
--*/
|
||
|
private :
|
||
|
|
||
|
//
|
||
|
// The following operations are not allowed !
|
||
|
//
|
||
|
CacheState() ;
|
||
|
CacheState( CacheState& ) ;
|
||
|
CacheState& operator=( CacheState& ) ;
|
||
|
|
||
|
//
|
||
|
// Tell us if this entry is older than the specified time !
|
||
|
//
|
||
|
BOOL
|
||
|
OlderThan( FILETIME& filetime ) {
|
||
|
return CompareFileTime( &m_LastAccess, &filetime ) <= 0 ;
|
||
|
}
|
||
|
|
||
|
protected :
|
||
|
|
||
|
//
|
||
|
// The LRU List which holds all of these items gets to
|
||
|
// know are innards !
|
||
|
//
|
||
|
friend class CLRUList ;
|
||
|
|
||
|
//
|
||
|
// For debug purposes - make the memory recognizable !
|
||
|
//
|
||
|
DWORD m_dwSignature ;
|
||
|
|
||
|
//
|
||
|
// The lock used to protect operations within the derived
|
||
|
// classes of this object - most operations within this class
|
||
|
// don't require locking, however all derived classes need
|
||
|
// various locking services, so we provide one lock for all users !
|
||
|
//
|
||
|
CACHELOCK m_lock ;
|
||
|
|
||
|
//
|
||
|
// Our signaure, and constants for supporting us implementing two
|
||
|
// kinds of reference counts !
|
||
|
//
|
||
|
enum CACHESTATE_CONSTANTS {
|
||
|
CACHESTATE_SIGNATURE = 'hcaC',
|
||
|
CLIENT_REF = 0x10000,
|
||
|
CLIENT_BITS = 0xFFFF0000
|
||
|
} ;
|
||
|
|
||
|
//
|
||
|
// Keep a reference count - number of references holding
|
||
|
// this in memory !
|
||
|
// NOTE : we hold two types of references in here -
|
||
|
// regular references added by AddRef() and Release()
|
||
|
// which are references from other caches
|
||
|
// As well as client references. To Add a client reference
|
||
|
// add CLIENT_REF atomically to this !
|
||
|
//
|
||
|
volatile long m_cRefs ;
|
||
|
|
||
|
//
|
||
|
// a long that we use as a lock for calls to LRUReference
|
||
|
//
|
||
|
volatile long m_lLRULock ;
|
||
|
|
||
|
//
|
||
|
// The last time this was touched - helps LRU algorithm !
|
||
|
//
|
||
|
FILETIME m_LastAccess ;
|
||
|
|
||
|
//
|
||
|
// The LRU List that owns this object !
|
||
|
//
|
||
|
public: class CLRUList* m_pOwner ;
|
||
|
|
||
|
//
|
||
|
// Pointers to maintain doubly linked list of
|
||
|
// items in the LRU chain !
|
||
|
//
|
||
|
protected: DLIST_ENTRY m_LRUList ;
|
||
|
|
||
|
//
|
||
|
// structure to keep track of all the References to a
|
||
|
// particular item ! - note m_lock is used to protect
|
||
|
// access to this list
|
||
|
//
|
||
|
DLIST_ENTRY m_ReferencesList ;
|
||
|
|
||
|
//
|
||
|
// The field used to chain in Hash Table buckets
|
||
|
//
|
||
|
DLIST_ENTRY m_HashList ;
|
||
|
|
||
|
|
||
|
//
|
||
|
// This Constructor is protected as we only ever want to see
|
||
|
// classes derived from this thing created !
|
||
|
//
|
||
|
CacheState( class CLRUList*,
|
||
|
long cClientRefs = 1
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// Destructor must be virtual - lots of derived classes !
|
||
|
//
|
||
|
virtual ~CacheState() ;
|
||
|
|
||
|
//
|
||
|
// Touch the structure in a fashion so that the LRU state
|
||
|
// is updated !
|
||
|
//
|
||
|
void
|
||
|
LRUReference( class CLRUList* pLRU ) ;
|
||
|
|
||
|
//
|
||
|
// calling this function insures that the correct Destructor is called !
|
||
|
//
|
||
|
void virtual
|
||
|
Destroy( void* pv ) = 0 ;
|
||
|
|
||
|
//
|
||
|
// Reference counting support - Add a reference
|
||
|
//
|
||
|
long
|
||
|
AddRef() ;
|
||
|
|
||
|
//
|
||
|
// Keeping track of clients - remove a client ref !
|
||
|
//
|
||
|
long
|
||
|
CheckIn( class CAllocatorCache* pAlloc = 0
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// Keeping track of clients - remove a client ref !
|
||
|
//
|
||
|
long
|
||
|
CheckInNoLocks( class CAllocatorCache* pAlloc = 0
|
||
|
) ;
|
||
|
|
||
|
virtual
|
||
|
BOOL
|
||
|
IsMasterReference() {
|
||
|
return TRUE ;
|
||
|
}
|
||
|
|
||
|
virtual
|
||
|
CacheState*
|
||
|
GetMasterReference() {
|
||
|
return this ;
|
||
|
}
|
||
|
|
||
|
CacheState*
|
||
|
AddRefMaster( ) {
|
||
|
TraceFunctEnter( "CacheState::AddRefMaster()" ) ;
|
||
|
m_lock.ShareLock() ;
|
||
|
CacheState* pReturn = GetMasterReference() ;
|
||
|
if( pReturn ) {
|
||
|
long l = pReturn->AddRef() ;
|
||
|
DebugTrace( DWORD_PTR(pReturn), "Added a reference to %x this %x", pReturn, this ) ;
|
||
|
}
|
||
|
m_lock.ShareUnlock() ;
|
||
|
return pReturn ;
|
||
|
}
|
||
|
|
||
|
|
||
|
public :
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
//
|
||
|
// The number of these things that have been allocated !
|
||
|
//
|
||
|
static long g_cCacheState ;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
//
|
||
|
// Remove a reference - when we return 0 we're destroyed !
|
||
|
//
|
||
|
long
|
||
|
Release( class CAllocatorCache *pAlloc,
|
||
|
void* pv
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// Provided to deal with failures during initialization of items
|
||
|
// being insert into the cache - this function ensures that the
|
||
|
// cache item ends up on the list for destruction !
|
||
|
//
|
||
|
void
|
||
|
FailedCheckOut( class CLRUList* p,
|
||
|
long cClientRefs,
|
||
|
CAllocatorCache* pAllocator,
|
||
|
void* pv
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// For allocating these we use this special operator new
|
||
|
// which goes through our allocation cache !
|
||
|
//
|
||
|
// NOTE : We take a reference because the cache MUST be provided !
|
||
|
//
|
||
|
void*
|
||
|
operator new( size_t size,
|
||
|
class CAllocatorCache& cache
|
||
|
) {
|
||
|
return cache.Allocate( size ) ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Exclusive Lock ourselves !
|
||
|
//
|
||
|
void
|
||
|
ExclusiveLock() {
|
||
|
m_lock.ExclusiveLock() ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Unlock ourselves
|
||
|
//
|
||
|
void
|
||
|
ExclusiveUnlock() {
|
||
|
m_lock.ExclusiveUnlock() ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Keeping track of clients - Add a client ref !
|
||
|
//
|
||
|
long
|
||
|
CheckOut( class CLRUList* p,
|
||
|
long cClientRefs = 1
|
||
|
) ;
|
||
|
|
||
|
long
|
||
|
ExternalCheckIn( ) ;
|
||
|
|
||
|
long
|
||
|
ExternalCheckInNoLocks( ) ;
|
||
|
|
||
|
//
|
||
|
// Check to see whether this element is still referenced
|
||
|
// by the hash table containing the cache !
|
||
|
//
|
||
|
BOOL
|
||
|
InCache() {
|
||
|
return !m_HashList.IsEmpty() ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see whether any Cache clients have a
|
||
|
// reference to this item !
|
||
|
// Return TRUE if any clients have added a reference !
|
||
|
//
|
||
|
BOOL
|
||
|
IsCheckedOut() {
|
||
|
_ASSERT( m_dwSignature == CACHESTATE_SIGNATURE ) ;
|
||
|
return (m_cRefs & CLIENT_BITS) != 0 ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Return TRUE if this item is in our LRU list !
|
||
|
//
|
||
|
BOOL
|
||
|
IsInLRUList() {
|
||
|
_ASSERT( m_dwSignature == CACHESTATE_SIGNATURE ) ;
|
||
|
// return whether we are in the LRU List !
|
||
|
return !m_LRUList.IsEmpty() ;
|
||
|
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
//
|
||
|
// This is used in _ASSERT's and stuff to check that the cache is correctly ordered
|
||
|
// by time !
|
||
|
//
|
||
|
BOOL
|
||
|
IsOlder( FILETIME filetimeIn,
|
||
|
FILETIME& filetimeOut
|
||
|
) ;
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// The following support functions are used to support manipulating
|
||
|
// these objects in the various kinds of doubly linked lists we may reside in
|
||
|
//
|
||
|
//
|
||
|
BOOL
|
||
|
FLockCandidate( BOOL fExpireChecks,
|
||
|
FILETIME& filetime,
|
||
|
BOOL& fToYoung
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// The following function locks an element of the cache.
|
||
|
// If this is the master cache entry, we will also look all associated items and return TRUE.
|
||
|
// If this is not the master, we lock only the one entry !
|
||
|
//
|
||
|
BOOL
|
||
|
FLockExpungeCandidate( CacheState*& pMaster ) ;
|
||
|
|
||
|
//
|
||
|
// This function pairs with FLockExpungeCandidate()
|
||
|
//
|
||
|
void
|
||
|
ReleaseLocks( CacheState* pMaster ) ;
|
||
|
|
||
|
//
|
||
|
// This is the other half of FLockExpungeCandidate -
|
||
|
// we ensure the destruction of the selected item !
|
||
|
//
|
||
|
void
|
||
|
FinishCandidate( CacheState* pMaster ) ;
|
||
|
|
||
|
//
|
||
|
// The following is not thread safe and is only for use during
|
||
|
// shutdown, where there should be no thread issues !
|
||
|
//
|
||
|
void
|
||
|
IsolateCandidate() ;
|
||
|
|
||
|
//
|
||
|
// This removes the item from the LRU List - Cache lock must be held exclusive or partially !
|
||
|
//
|
||
|
void
|
||
|
RemoveFromLRU() ;
|
||
|
|
||
|
//
|
||
|
// Define the Pointer to Function type that is so usefull
|
||
|
// for using the DLIST templates !
|
||
|
//
|
||
|
typedef DLIST_ENTRY* (*PFNDLIST)( class CacheState* pState ) ;
|
||
|
|
||
|
//
|
||
|
// Helper function for Doubly linked lists of these items in the
|
||
|
// Cache's hash tables !
|
||
|
//
|
||
|
inline static
|
||
|
DLIST_ENTRY*
|
||
|
HashDLIST( CacheState* p ) {
|
||
|
return &p->m_HashList ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Helper function for Doubly linked lists of these items in the
|
||
|
// LRU lists
|
||
|
//
|
||
|
inline static
|
||
|
DLIST_ENTRY*
|
||
|
LRUDLIST( CacheState* p ) {
|
||
|
return &p->m_LRUList ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Helper function for Doubly linked lists of these items in
|
||
|
// the reference lists - the list of items referencing the same
|
||
|
// cache item !
|
||
|
//
|
||
|
inline static
|
||
|
DLIST_ENTRY*
|
||
|
REFSDLIST( CacheState* p ) {
|
||
|
return &p->m_ReferencesList ;
|
||
|
}
|
||
|
} ;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Now Define some Doubly Linked Lists that we can use to manipulate the
|
||
|
// items in the cache in various ways !
|
||
|
//
|
||
|
typedef TDListHead< CacheState, &CacheState::LRUDLIST > LRULIST ;
|
||
|
typedef TDListHead< CacheState, &CacheState::REFSDLIST > REFSLIST ;
|
||
|
typedef TDListIterator< REFSLIST > REFSITER ;
|
||
|
|
||
|
|
||
|
class CacheTable : public CScheduleThread {
|
||
|
/*++
|
||
|
|
||
|
Class Description :
|
||
|
|
||
|
This class defines a call back interface which we hand to
|
||
|
the LRU List to manipulate the items in the cache !
|
||
|
|
||
|
--*/
|
||
|
public :
|
||
|
//
|
||
|
// Get the lock we use for manipulating the lock state of the table !
|
||
|
//
|
||
|
virtual CACHELOCK&
|
||
|
GetLock() = 0 ;
|
||
|
|
||
|
//
|
||
|
// Remove an item from the Cache's hash table !
|
||
|
//
|
||
|
virtual BOOL
|
||
|
RemoveEntry(
|
||
|
CacheState* pEntry
|
||
|
) = 0 ;
|
||
|
|
||
|
//
|
||
|
// Ask if we want to remove this particular item !
|
||
|
//
|
||
|
virtual BOOL
|
||
|
QueryRemoveEntry(
|
||
|
CacheState* pEntry
|
||
|
) = 0 ;
|
||
|
|
||
|
} ;
|
||
|
|
||
|
|
||
|
class CLRUList {
|
||
|
/*++
|
||
|
|
||
|
Class Description :
|
||
|
|
||
|
This class implements our LRU algorithms and selects
|
||
|
the elements that are to be deleted from the cache !
|
||
|
|
||
|
--*/
|
||
|
private :
|
||
|
|
||
|
//
|
||
|
// The head of the LRU List !
|
||
|
//
|
||
|
LRULIST m_LRUList ;
|
||
|
|
||
|
//
|
||
|
// A list of items that have been touched in the LRU List !
|
||
|
//
|
||
|
TLockQueue< CacheState > m_lqModify ;
|
||
|
|
||
|
//
|
||
|
// Maximum number of elements we should hold in this cache !
|
||
|
//
|
||
|
DWORD m_cMaxElements ;
|
||
|
|
||
|
//
|
||
|
// Number of items we inserted !
|
||
|
//
|
||
|
DWORD m_dwAverageInserts ;
|
||
|
|
||
|
//
|
||
|
// Number of items NOT in the LRU List !
|
||
|
//
|
||
|
DWORD m_cCheckedOut ;
|
||
|
|
||
|
//
|
||
|
// The number of subtract from the current time to
|
||
|
// determine our expire date !
|
||
|
//
|
||
|
ULARGE_INTEGER m_qwExpire ;
|
||
|
|
||
|
//
|
||
|
// This function actually selects the items that will be expired
|
||
|
//
|
||
|
void
|
||
|
SelectExpirations( DLIST_ENTRY& expireList ) ;
|
||
|
|
||
|
//
|
||
|
// Do the expires !
|
||
|
//
|
||
|
DWORD
|
||
|
DoExpirations( DLIST_ENTRY& expireList ) ;
|
||
|
|
||
|
//
|
||
|
// Don't allow copies!
|
||
|
//
|
||
|
CLRUList( CLRUList& ) ;
|
||
|
CLRUList& operator=( CLRUList& ) ;
|
||
|
|
||
|
|
||
|
public :
|
||
|
|
||
|
//
|
||
|
// Number of items in the cache !
|
||
|
//
|
||
|
long m_cItems ;
|
||
|
|
||
|
//CLRUList( ULARGE_INTEGER qwExpire ) ;
|
||
|
CLRUList() ;
|
||
|
|
||
|
//
|
||
|
// Initialize the LRU List with the parameters
|
||
|
// controlling the maximum number of elements and the time to live !
|
||
|
//
|
||
|
void
|
||
|
Init( DWORD cMaxInstances,
|
||
|
DWORD cLifeTimeSeconds
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// Something has changed - put it in the list of items needing
|
||
|
// to be examined !
|
||
|
//
|
||
|
// This function should only be called when the containing cache has
|
||
|
// a lock on the cache !
|
||
|
//
|
||
|
void
|
||
|
AddWorkQueue( CacheState* pbase ) ;
|
||
|
|
||
|
//
|
||
|
// This function examines each item in the Work Queue and does
|
||
|
// appropriate processing !
|
||
|
//
|
||
|
void
|
||
|
ProcessWorkQueue( CAllocatorCache* pAllocCache,
|
||
|
LPVOID lpv
|
||
|
) ;
|
||
|
|
||
|
//
|
||
|
// This function drains each item out of the Work Queue and releases
|
||
|
// them - called during shutdown/destruction of a cache !
|
||
|
//
|
||
|
void
|
||
|
DrainWorkQueue() ;
|
||
|
|
||
|
//
|
||
|
// Bump the number of items in the cache !
|
||
|
//
|
||
|
long
|
||
|
IncrementItems() {
|
||
|
return InterlockedIncrement( &m_cItems ) ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Decrease the number of items in the cache !
|
||
|
//
|
||
|
long
|
||
|
DecrementItems() {
|
||
|
return InterlockedDecrement( &m_cItems ) ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Do the work required to expire an item !
|
||
|
//
|
||
|
void
|
||
|
Expire( CacheTable* pTable,
|
||
|
CAllocatorCache* pCache,
|
||
|
DWORD& countExpired,
|
||
|
void* pv //per cache data !
|
||
|
) ;
|
||
|
|
||
|
BOOL
|
||
|
Empty( CacheTable* pTable,
|
||
|
CAllocatorCache* pCache,
|
||
|
void* pv
|
||
|
) ;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Remove a random set of elements in the table !
|
||
|
//
|
||
|
void
|
||
|
ExpungeItems(
|
||
|
CacheTable* pTable,
|
||
|
DWORD& countExpunged
|
||
|
) ;
|
||
|
|
||
|
} ;
|
||
|
|
||
|
|
||
|
|
||
|
template< class Data
|
||
|
>
|
||
|
class CCacheItemBase : public CacheState {
|
||
|
/*++
|
||
|
|
||
|
Class Desceription :
|
||
|
|
||
|
This class provides the interface defination for
|
||
|
items in the cache which may or may not hold the
|
||
|
key as well within the cache !
|
||
|
|
||
|
Arguments :
|
||
|
|
||
|
Data - the item we will hold within the Cache
|
||
|
|
||
|
--*/
|
||
|
protected:
|
||
|
|
||
|
//
|
||
|
// Constructor - initialize stuff to zero !
|
||
|
//
|
||
|
CCacheItemBase( class CLRUList* p,
|
||
|
Data* pData,
|
||
|
BOOL fClientRef
|
||
|
) :
|
||
|
CacheState( p, fClientRef ),
|
||
|
m_pData( pData ) {
|
||
|
}
|
||
|
|
||
|
~CCacheItemBase() {
|
||
|
//
|
||
|
// Somebody else must free our data elements !
|
||
|
//
|
||
|
_ASSERT(m_pData == 0 ) ;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
IsMasterReference() {
|
||
|
|
||
|
return
|
||
|
m_pData == 0 ||
|
||
|
m_pData->m_pCacheRefInterface == this ;
|
||
|
}
|
||
|
|
||
|
CacheState*
|
||
|
GetMasterReference( ) {
|
||
|
if( m_pData == 0 ) {
|
||
|
return this ;
|
||
|
}
|
||
|
return (CacheState*)m_pData->m_pCacheRefInterface ;
|
||
|
}
|
||
|
|
||
|
public :
|
||
|
|
||
|
//
|
||
|
// The actual item we are holding within the Cache !
|
||
|
//
|
||
|
Data* m_pData ;
|
||
|
|
||
|
//
|
||
|
// Check that our data items match !
|
||
|
// used for _ASSERT's
|
||
|
//
|
||
|
BOOL
|
||
|
IsMatch( Data* p ) {
|
||
|
return p == m_pData ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Must be able extract the Data Item !
|
||
|
//
|
||
|
Data*
|
||
|
PublicData( class CLRUList* p ) {
|
||
|
/*++
|
||
|
|
||
|
Routine Description :
|
||
|
|
||
|
This function adds a client reference to an item in the cache,
|
||
|
and sets up the necessary LRU Manipulations!
|
||
|
We also return the data pointer to the client, and add a reference
|
||
|
to ourselves to keep track of !
|
||
|
|
||
|
Arguments :
|
||
|
|
||
|
p - the LRUList that owns us !
|
||
|
|
||
|
Return Value :
|
||
|
|
||
|
Pointer to the Data item we contain - can be NULL !
|
||
|
|
||
|
--*/
|
||
|
TraceFunctEnter( "CCacheItemBase::PublicData" ) ;
|
||
|
|
||
|
_ASSERT( p != 0 ) ;
|
||
|
_ASSERT( p == m_pOwner ) ;
|
||
|
|
||
|
Data* pReturn = 0 ;
|
||
|
m_lock.ShareLock() ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "m_pData %x m_pCacheRef %x",
|
||
|
m_pData, m_pData ? m_pData->m_pCacheRefInterface : 0 ) ;
|
||
|
|
||
|
if( m_pData ) {
|
||
|
CacheState* pState = (CacheState*)m_pData->m_pCacheRefInterface ;
|
||
|
pState->CheckOut( p ) ;
|
||
|
pReturn = m_pData ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If there's no data in this item - it must not be checked out !
|
||
|
//
|
||
|
_ASSERT( pReturn || !IsCheckedOut() ) ;
|
||
|
|
||
|
m_lock.ShareUnlock() ;
|
||
|
return pReturn ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// This is called when the locks are NOT held -
|
||
|
// do all the correct logic for setting this item up !
|
||
|
//
|
||
|
BOOL
|
||
|
SetData( Data* pData,
|
||
|
CLRUList* pList,
|
||
|
long cClientRefs
|
||
|
) {
|
||
|
/*++
|
||
|
|
||
|
Routine Description :
|
||
|
|
||
|
Make this Cache Element point to some piece of data that was provided
|
||
|
by the end user.
|
||
|
|
||
|
NOTE :
|
||
|
The piece of data may be referenced by another cache - in which case
|
||
|
we must work well with it !!!!
|
||
|
|
||
|
Arguments :
|
||
|
|
||
|
pData - the Item the client wants us to refer to with our key
|
||
|
pList - the LRU List containing us !
|
||
|
|
||
|
fReference - Is the client putting the item in the cache and not keeping
|
||
|
his reference, this is TRUE if
|
||
|
|
||
|
|
||
|
--*/
|
||
|
|
||
|
TraceFunctEnter( "CCacheItemBase::SetData" ) ;
|
||
|
|
||
|
BOOL fReturn = FALSE ;
|
||
|
|
||
|
_ASSERT( pData != 0 ) ;
|
||
|
_ASSERT( pList != 0 ) ;
|
||
|
_ASSERT( pList == m_pOwner ) ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "pData %x pList %x cClientRefs %x", pData, pList, cClientRefs ) ;
|
||
|
|
||
|
//
|
||
|
// Is this an item referenced by another cache -
|
||
|
// NOTE : IF it is an item from another Cache it MUST be checked out with a
|
||
|
// client reference ! The m_pCacheRefInterface of an item MUST NEVER CHANGED
|
||
|
// as long as an item is checked out by clients, so the following dereference
|
||
|
// is safe !
|
||
|
//
|
||
|
CCacheItemBase* p = (CCacheItemBase*)pData->m_pCacheRefInterface ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "Examined Item - m_pCacheRefInterface %x", p ) ;
|
||
|
|
||
|
if( p != 0 ) {
|
||
|
//
|
||
|
// In this scenario it is meaningless to access for a client reference -
|
||
|
// the client MUST have gotten one from the other cache !
|
||
|
//
|
||
|
_ASSERT( cClientRefs == 0 ) ;
|
||
|
_ASSERT( p->IsCheckedOut() ) ;
|
||
|
|
||
|
//
|
||
|
// Ok make our cache reference the same item !
|
||
|
//
|
||
|
fReturn = p->AddCacheReference( this, pData ) ;
|
||
|
//
|
||
|
// This can fail if the user tries to insert the same name twice !
|
||
|
//
|
||
|
//_ASSERT( fReturn ) ;
|
||
|
} else {
|
||
|
m_lock.ExclusiveLock() ;
|
||
|
if( !m_pData ) {
|
||
|
//
|
||
|
// The Cache should never have refences outstanding with
|
||
|
// the data pointer equal to NULL - so _ASSERT that we're
|
||
|
// not checked out by a client !
|
||
|
//
|
||
|
_ASSERT( !IsCheckedOut() ) ;
|
||
|
//
|
||
|
// Now point to the data the client wants us to point at !
|
||
|
//
|
||
|
m_pData = pData ;
|
||
|
pData->m_pCacheRefInterface = this ;
|
||
|
//
|
||
|
// It worked - return TRUE !
|
||
|
//
|
||
|
fReturn = TRUE ;
|
||
|
//
|
||
|
// Now we are checked out by a client !
|
||
|
//
|
||
|
if( cClientRefs )
|
||
|
CheckOut( pList, cClientRefs ) ;
|
||
|
}
|
||
|
m_lock.ExclusiveUnlock() ;
|
||
|
}
|
||
|
return fReturn ;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Add an item to the list of caches referencing
|
||
|
// this cache item !
|
||
|
//
|
||
|
BOOL
|
||
|
InsertRef( CCacheItemBase<Data>* p,
|
||
|
Data* pData,
|
||
|
long cClientRefs = 0
|
||
|
) {
|
||
|
/*++
|
||
|
|
||
|
Routine Description :
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
--*/
|
||
|
|
||
|
TraceFunctEnter( "CCacheItemBase::InsertRef" ) ;
|
||
|
|
||
|
_ASSERT( p != 0 ) ;
|
||
|
_ASSERT( pData != 0 ) ;
|
||
|
_ASSERT( IsCheckedOut() ) ;
|
||
|
_ASSERT( pData->m_pCacheRefInterface == this ) ;
|
||
|
|
||
|
|
||
|
BOOL fReturn = FALSE ;
|
||
|
|
||
|
REFSITER refsiter( &m_ReferencesList ) ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "m_pData %x p %x p->m_pData %x cClientRefs %x",
|
||
|
m_pData, p, p->m_pData, cClientRefs ) ;
|
||
|
|
||
|
if( m_pData != 0 ) {
|
||
|
//
|
||
|
// Now grab the second lock !
|
||
|
//
|
||
|
// This _ASSERT isn't valid, the user can insert names that are already in use !
|
||
|
//
|
||
|
//_ASSERT( p->m_pData == 0 || p->m_pData == m_pData ) ;
|
||
|
if( p->m_pData == 0 ) {
|
||
|
//
|
||
|
// Insert p into the list of item in the list !
|
||
|
//
|
||
|
refsiter.InsertBefore( p ) ;
|
||
|
|
||
|
//
|
||
|
// We've added another reference to ourself !
|
||
|
// This comes from another Cache item, so count it specially !
|
||
|
//
|
||
|
long l = AddRef() ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "AddRef result %x this %x p %x", l, this, p ) ;
|
||
|
|
||
|
//
|
||
|
// Give a reference to the data we are holding !
|
||
|
//
|
||
|
p->m_pData = m_pData ;
|
||
|
fReturn = TRUE ;
|
||
|
} else if( p->m_pData == m_pData ) {
|
||
|
fReturn = TRUE ;
|
||
|
}
|
||
|
// This _ASSERT isn't valid, the user can insert names that are already in use !
|
||
|
//
|
||
|
//_ASSERT( p->m_pData == m_pData ) ;
|
||
|
}
|
||
|
|
||
|
if( fReturn && cClientRefs ) {
|
||
|
CheckOut( m_pOwner, cClientRefs ) ;
|
||
|
}
|
||
|
return fReturn ;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Add an item to the list of caches referencing
|
||
|
// this cache item !
|
||
|
//
|
||
|
virtual BOOL
|
||
|
AddCacheReference( class ICacheRefInterface* pInterface,
|
||
|
void* pv,
|
||
|
BOOL fReference = FALSE
|
||
|
) {
|
||
|
/*++
|
||
|
|
||
|
Routine Description :
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
--*/
|
||
|
|
||
|
TraceFunctEnter( "CCacheItemBase::AddCacheReference" ) ;
|
||
|
|
||
|
Data* pData = (Data*)pv ;
|
||
|
CCacheItemBase<Data>* p = (CCacheItemBase<Data>*)pInterface ;
|
||
|
BOOL fReturn = TRUE ;
|
||
|
|
||
|
_ASSERT( pData != 0 ) ;
|
||
|
_ASSERT( p != 0 ) ;
|
||
|
|
||
|
REFSITER refsiter( &m_ReferencesList ) ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "pInterface %x pv %x fReference %x", pInterface, pv, fReference ) ;
|
||
|
|
||
|
m_lock.ExclusiveLock() ;
|
||
|
p->m_lock.ExclusiveLock() ;
|
||
|
|
||
|
fReturn = InsertRef( p,
|
||
|
pData,
|
||
|
fReference
|
||
|
) ;
|
||
|
|
||
|
p->m_lock.ExclusiveUnlock() ;
|
||
|
_ASSERT( m_pData == pData ) ;
|
||
|
m_lock.ExclusiveUnlock() ;
|
||
|
|
||
|
return fReturn ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remove an item from the list of caches referencing
|
||
|
// this cache item !
|
||
|
//
|
||
|
virtual BOOL
|
||
|
RemoveCacheReference( BOOL fQueue ) {
|
||
|
|
||
|
TraceFunctEnter( "CCacheItemBase::RemoveCacheReference" ) ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "m_pData %x m_pData->m_pCacheRefInterface %x",
|
||
|
m_pData, m_pData ? m_pData->m_pCacheRefInterface: 0 ) ;
|
||
|
|
||
|
m_pData = 0 ;
|
||
|
|
||
|
//
|
||
|
// Well now - we're on our own - we should be expired !
|
||
|
//
|
||
|
if( fQueue )
|
||
|
LRUReference( m_pOwner ) ;
|
||
|
|
||
|
//
|
||
|
// p no longer has a reference - you can remove it !
|
||
|
//
|
||
|
return FALSE ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remove all references to the cache item !
|
||
|
//
|
||
|
virtual BOOL
|
||
|
RemoveAllReferences( ) {
|
||
|
|
||
|
return FALSE ;
|
||
|
}
|
||
|
|
||
|
} ;
|
||
|
|
||
|
|
||
|
template< class Data,
|
||
|
class Key,
|
||
|
class Constructor,
|
||
|
class PerCacheData
|
||
|
>
|
||
|
class CCacheItemKey : public CCacheItemBase< Data > {
|
||
|
/*++
|
||
|
|
||
|
Class Description :
|
||
|
|
||
|
This class item holds the key of the item we are
|
||
|
referencing within the cache !
|
||
|
We don't assume that the data objects hold the
|
||
|
keys for us - we do this ourselves.
|
||
|
|
||
|
--*/
|
||
|
private :
|
||
|
|
||
|
//
|
||
|
// This is the type of the key we are going to be holding !
|
||
|
//
|
||
|
Key m_key ;
|
||
|
|
||
|
//
|
||
|
// Make all these constructors and copiers private !
|
||
|
//
|
||
|
CCacheItemKey() ;
|
||
|
CCacheItemKey& operator=( CCacheItemKey& ) ;
|
||
|
|
||
|
protected :
|
||
|
//
|
||
|
// Destroy ourselves
|
||
|
//
|
||
|
void
|
||
|
Destroy( void* pv ) {
|
||
|
TraceFunctEnter( "CCacheItemKey::Destroy" ) ;
|
||
|
DebugTrace( (DWORD_PTR)this, "m_pData %x pv %x", m_pData, pv ) ;
|
||
|
PerCacheData* p = (PerCacheData*)pv ;
|
||
|
if( m_pData )
|
||
|
Constructor::StaticRelease( m_pData, pv ) ;
|
||
|
m_pData = 0 ;
|
||
|
CCacheItemKey::~CCacheItemKey() ;
|
||
|
}
|
||
|
|
||
|
public :
|
||
|
|
||
|
~CCacheItemKey() {
|
||
|
if( m_pData ) {
|
||
|
Constructor::StaticRelease( m_pData, 0 ) ;
|
||
|
m_pData = 0 ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Can only create by initializing the key !
|
||
|
//
|
||
|
CCacheItemKey( class CLRUList* p,
|
||
|
Key& k,
|
||
|
Data* pData,
|
||
|
long cClientRefs
|
||
|
) :
|
||
|
CCacheItemBase<Data>( p, pData, cClientRefs ),
|
||
|
m_key( k ) {
|
||
|
}
|
||
|
|
||
|
Key* GetKey() {
|
||
|
return &m_key ;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// This is called when no locks are held - assumes
|
||
|
// that we may find that data is in the item, or that
|
||
|
// we need to build it !
|
||
|
//
|
||
|
Data*
|
||
|
FindOrCreate(
|
||
|
CACHELOCK& cachelock,
|
||
|
Constructor& constructor,
|
||
|
PerCacheData& cachedata,
|
||
|
CLRUList* plrulist,
|
||
|
class CacheStats* pStats
|
||
|
) {
|
||
|
/*++
|
||
|
|
||
|
Routine Description :
|
||
|
|
||
|
This function executes our creation protocol with the client.
|
||
|
|
||
|
The constructor.Create() function is called to create a partially
|
||
|
constructed Item for the cache. We will check if the returned item
|
||
|
is referenced in another cache - if it is we will build the list
|
||
|
of CacheState objects referencing the same cache item.
|
||
|
|
||
|
The caller assumes that cachelock is released by the time we return !
|
||
|
|
||
|
Arguments :
|
||
|
|
||
|
cachelock - The lock for the containing cache, we get this so that
|
||
|
we can minimize the time it is held !
|
||
|
constructor -
|
||
|
The object that can build an item for the cache !
|
||
|
cachedata -
|
||
|
Some client data that they get for free !
|
||
|
plrulist -
|
||
|
The LRU list that we should use !
|
||
|
|
||
|
Return Value :
|
||
|
|
||
|
Pointer to a Data item if successfull !
|
||
|
|
||
|
--*/
|
||
|
|
||
|
TraceFunctEnter( "CCacheItemKey::FindOrCreate" ) ;
|
||
|
|
||
|
_ASSERT( plrulist == m_pOwner || plrulist == 0 ) ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "plrulist %x", plrulist ) ;
|
||
|
|
||
|
Data* pReturn = 0 ;
|
||
|
//
|
||
|
// First look to see if there happens to be something here already !
|
||
|
//
|
||
|
m_lock.ShareLock() ;
|
||
|
if( m_pData ) {
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "m_pData %x m_pData->m_pCacheRefInterface %x",
|
||
|
m_pData, m_pData->m_pCacheRefInterface ) ;
|
||
|
//
|
||
|
// We've found this item constructed in the cache -
|
||
|
// so check it out, AND put on the LRU work list !
|
||
|
//
|
||
|
CacheState* pState = (CacheState*)m_pData->m_pCacheRefInterface ;
|
||
|
pState->CheckOut( plrulist ) ;
|
||
|
pReturn = m_pData ;
|
||
|
}
|
||
|
m_lock.ShareUnlock() ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "pReturn %x", pReturn ) ;
|
||
|
|
||
|
//
|
||
|
// Did we find something !
|
||
|
//
|
||
|
if( pReturn ) {
|
||
|
//
|
||
|
// Because caller assumes this is unlocked - do so now !
|
||
|
//
|
||
|
cachelock.PartialUnlock() ;
|
||
|
} else {
|
||
|
//
|
||
|
// An item with no data - Must not be checked out !
|
||
|
//
|
||
|
_ASSERT( !IsCheckedOut() ) ;
|
||
|
_ASSERT( m_pData == 0 ) ;
|
||
|
|
||
|
//
|
||
|
// Partially build the data object we want to hold !
|
||
|
//
|
||
|
Data* pData = constructor.Create( m_key, cachedata ) ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "Created pData %x", pData ) ;
|
||
|
|
||
|
if( !pData ) {
|
||
|
//
|
||
|
// We failed - release our locks and go away !
|
||
|
//
|
||
|
cachelock.PartialUnlock() ;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// Figure out if we got a reference to an item already in the cache !
|
||
|
//
|
||
|
CCacheItemBase<Data>* p = (CCacheItemBase<Data>*)pData->m_pCacheRefInterface ;
|
||
|
|
||
|
//
|
||
|
// Grab the locks in the correct order for what we might need !
|
||
|
//
|
||
|
if( p ) {
|
||
|
p->ExclusiveLock() ;
|
||
|
//
|
||
|
// If he gave us an item, it must be checked out of whatever cache
|
||
|
// it came from - this ensures that it will not be destroyed while we
|
||
|
// access it, because we are not at the point of adding our own reference
|
||
|
// yet !
|
||
|
//
|
||
|
_ASSERT( p->IsCheckedOut() ) ;
|
||
|
_ASSERT( p->IsMatch( pData ) ) ;
|
||
|
}
|
||
|
m_lock.ExclusiveLock() ;
|
||
|
|
||
|
//
|
||
|
// Don't need to hold onto the cache anymore !
|
||
|
//
|
||
|
cachelock.PartialUnlock() ;
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "Create path - pData %x p %x m_pData %x", pData, p, m_pData ) ;
|
||
|
|
||
|
//
|
||
|
// Now do whatever is necessary to finish initialization ! -
|
||
|
// must always call Init() unless we're going to give up on this thing !
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// We should not change state as long as we've been holding either
|
||
|
// the cachelock or our item lock up until this point - which is the case !
|
||
|
//
|
||
|
_ASSERT( m_pData == 0 ) ;
|
||
|
|
||
|
|
||
|
if( pData->Init( m_key,
|
||
|
constructor,
|
||
|
cachedata ) ) {
|
||
|
if( !p ) {
|
||
|
m_pData = pData ;
|
||
|
pData->m_pCacheRefInterface = this ;
|
||
|
pReturn = m_pData ;
|
||
|
CheckOut( m_pOwner ) ;
|
||
|
} else {
|
||
|
//
|
||
|
// NOTE : If the client's constructor gave us an object from another
|
||
|
// cache they MUST add the client's reference for us - so we pass 0 to
|
||
|
// InsertRef(), so that we don't add yet another reference.
|
||
|
//
|
||
|
if( p->InsertRef( this, pData, 0 ) ) {
|
||
|
//
|
||
|
// Insert Ref should setup our m_pData pointer !
|
||
|
//
|
||
|
_ASSERT( m_pData == pData ) ;
|
||
|
pReturn = m_pData ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DebugTrace( (DWORD_PTR)this, "Create path - pReturn %x", pReturn ) ;
|
||
|
|
||
|
//
|
||
|
// NOTE :
|
||
|
// If pReturn==0 indicating some kind of error, than there should
|
||
|
// have been no client refs added at this point !
|
||
|
//
|
||
|
_ASSERT( pReturn || !IsCheckedOut() ) ;
|
||
|
_ASSERT( pReturn == m_pData ) ;
|
||
|
|
||
|
if( p ) {
|
||
|
//
|
||
|
// If he gave us an item, it must be checked out of whatever cache
|
||
|
// it came from - this ensures that it will not be destroyed while we
|
||
|
// access it, because we are not at the point of adding our own reference
|
||
|
// yet !
|
||
|
//
|
||
|
_ASSERT( p->IsCheckedOut() ) ;
|
||
|
p->ExclusiveUnlock() ;
|
||
|
}
|
||
|
|
||
|
if( pReturn ) {
|
||
|
IncrementStat( pStats, CACHESTATS::ITEMS ) ;
|
||
|
} else {
|
||
|
//
|
||
|
// Release the data item back to the user
|
||
|
// NOTE - Don't have the cachelock so can't give them
|
||
|
// the cachedata at this point !
|
||
|
//
|
||
|
constructor.Release( pData, 0 ) ;
|
||
|
|
||
|
//
|
||
|
// Insure that we get onto the expiration list - this CACHEENTRY
|
||
|
// should be removed at some point !
|
||
|
//
|
||
|
FailedCheckOut( plrulist, FALSE, 0, 0 ) ;
|
||
|
}
|
||
|
//
|
||
|
// Release the locks - this can be done before we go down the
|
||
|
// error path because we know that we won't get destroyed !
|
||
|
//
|
||
|
m_lock.ExclusiveUnlock() ;
|
||
|
}
|
||
|
}
|
||
|
return pReturn ;
|
||
|
}
|
||
|
} ;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#endif // _CINTRNL_H_
|