/*++ Copyright (c) 1997-1999 Microsoft Corporation Module Name: genhash.h Abstract: ******************************************************************************* ******************************************************************************* ** ** ** ** ** G E N E R I C H A S H T A B L E ** ** ** ** ** ******************************************************************************* ******************************************************************************* A generic hash table is an array of GENERIC_HASH_ROW_ENTRY structs. Each row entry contains an FRS_LIST struct that has a critical section, a list head and a count. Each entry in the table has a GENERIC_HASH_ENTRY_HEADER at the front of it with a list entry, a ULONG hash value and a reference count. Access to a row of the hash table is controlled by the critical section in the FRS_LIST struct. Parameters for a generic hash table: Number of rows in the hash table. Table name for error messages. A compare function for insert (collisions) and for lookups, A key offset into the node entry to the start of the key data for the compare function. A key length for the compare. A memory free function to use if the ref count goes to zero. A hash function on the key data, A display node routine that takes the address of an entry. For error msgs and dump table. RowLockEnabled, TRUE means row locking is enabled (i.e. multithread usage of table). (Always enabled. FALSE is TBI) RefCountEnabled, TRUE if ref counting on data entries is enabled. (Always enabled. FALSE is TBI) A LockTimeout value in milliseconds. (TBI) AN optional heap handle to pass to the memory free function usefull if all the table entries are coming out of a special heap. (TBI) OffsetsEnabled - If TRUE then all the pointers in the table are calculated as offsets relative to the the OffsetBase. This is useful if you want to save the table contents to disk and you have a designated chunk of memory that the table elements are allocated out of (including the table structs). (TBI) OffsetBase - see above. (TBI) Author: David Orbits [davidor] 22-Apr-1997 Environment: User Mode Service Revision History: --*/ #ifndef _GENHASH_DEFINED_ #define _GENHASH_DEFINED_ typedef struct _GENERIC_HASH_TABLE_ *PGENERIC_HASH_TABLE; // // The free routine is called by the generic table package whenever // it needs to deallocate memory from the table that was allocated by calling // the user supplied allocation function. // typedef VOID (NTAPI *PGENERIC_HASH_FREE_ROUTINE) ( PGENERIC_HASH_TABLE Table, PVOID Buffer ); // // The compare routine is called on lookups to find an entry and on inserts // to check for duplicates. // typedef BOOL (NTAPI *PGENERIC_HASH_COMPARE_ROUTINE) ( PVOID Buf1, PVOID Buf2, ULONG Length ); // // The hash calc routine is called to generate the hash value of the key data // on lookups and inserts. // typedef ULONG (NTAPI *PGENERIC_HASH_CALC_ROUTINE) ( PVOID Buf, ULONG Length ); // // The filter function for use by GhtCleanTableByFilter. // typedef BOOL (NTAPI *PGENERIC_HASH_FILTER_ROUTINE) ( PGENERIC_HASH_TABLE Table, PVOID Buf, PVOID Context ); // // The print routine is called to dump an element. // typedef VOID (NTAPI *PGENERIC_HASH_PRINT_ROUTINE) ( PGENERIC_HASH_TABLE Table, PVOID Buffer ); // // The argument function passed to GhtEnumerateTable(). // typedef ULONG_PTR (NTAPI *PGENERIC_HASH_ENUM_ROUTINE) ( PGENERIC_HASH_TABLE Table, PVOID Buffer, PVOID Context ); #define GHT_ACTION_NOOP 0 #define GHT_ACTION_REMOVE 1 #define GHT_ACTION_DELETE 2 // // Status code defs. DON'T REORDER. ADD TO END ONLY. // typedef enum _GHT_STATUS { GHT_STATUS_SUCCESS = 0, GHT_STATUS_REMOVED, GHT_STATUS_LOCKCONFLICT, GHT_STATUS_LOCKTIMEOUT, GHT_STATUS_NOT_FOUND, GHT_STATUS_FAILURE } GHT_STATUS; // // Each entry that is placed in a hash table must start with a // GENERIC_HASH_ENTRY_HEADER. It is used to link the entries in a hash row, // holds the ULONG hash value for quick lookups and holds the reference // count on the entry. // typedef struct _GENERIC_HASH_ENTRY_HEADER { USHORT Type; // Type and size must match def in USHORT Size; // FRS_NODE_HEADER to use FrsAllocType(). LIST_ENTRY ListEntry; ULONG HashValue; LONG ReferenceCount; } GENERIC_HASH_ENTRY_HEADER, *PGENERIC_HASH_ENTRY_HEADER; // // The GENERIC_HASH_ROW_ENTRY is the list head for each row in the table. // It has the lock for the row, a row count and some row access stats. // typedef struct _GENERIC_HASH_ROW_ENTRY { FRS_LIST HashRow; ULONG RowInserts; ULONG RowRemoves; ULONG RowCompares; ULONG RowLookups; ULONG RowLookupFails; } GENERIC_HASH_ROW_ENTRY, *PGENERIC_HASH_ROW_ENTRY; VOID GhtDestroyTable( PGENERIC_HASH_TABLE HashTable ); ULONG GhtCleanTableByFilter( PGENERIC_HASH_TABLE HashTable, IN PGENERIC_HASH_ENUM_ROUTINE Function, PVOID Context ); #if DBG #define GHT_DUMP_TABLE(_Sev_, _HashTable_) GhtDumpTable(_Sev_, _HashTable_) VOID GhtDumpTable( ULONG Severity, PGENERIC_HASH_TABLE HashTable ); #else DBG #define GHT_DUMP_TABLE(_Sev_, _HashTable_) #endif DBG ULONG_PTR GhtEnumerateTable( IN PGENERIC_HASH_TABLE HashTable, IN PGENERIC_HASH_ENUM_ROUTINE Function, IN PVOID Context ); ULONG_PTR GhtEnumerateTableNoRef( IN PGENERIC_HASH_TABLE HashTable, IN PGENERIC_HASH_ENUM_ROUTINE Function, IN PVOID Context ); PGENERIC_HASH_ENTRY_HEADER GhtGetNextEntry( IN PGENERIC_HASH_TABLE HashTable, PGENERIC_HASH_ENTRY_HEADER HashEntry ); ULONG GhtCountEntries( IN PGENERIC_HASH_TABLE HashTable ); PGENERIC_HASH_ENTRY_HEADER GhtGetEntryNumber( IN PGENERIC_HASH_TABLE HashTable, IN LONG EntryNumber ); PGENERIC_HASH_TABLE GhtCreateTable( PCHAR ArgName, ULONG NumberRows, ULONG KeyOffset, ULONG KeyLength, PGENERIC_HASH_FREE_ROUTINE GhtFree, PGENERIC_HASH_COMPARE_ROUTINE GhtCompare, PGENERIC_HASH_CALC_ROUTINE GhtHashCalc, PGENERIC_HASH_PRINT_ROUTINE GhtPrint ); GHT_STATUS GhtLookup2( PGENERIC_HASH_TABLE HashTable, PVOID pKeyValue, BOOL WaitIfLocked, PVOID *RetHashEntry, ULONG DupIndex ); // // If duplicates are present then Return the first one in the list. // This is the oldest duplicate based on insertion order. New Inserts always // go to the end of the duplicate list. // #define GhtLookup(_Table, _key, _wait, _retval) \ GhtLookup2(_Table, _key, _wait, _retval, 1) // // If duplicates are present then return the last one in the list. // This is the most recent duplicate inserted. // #define GhtLookupNewest(_Table, _key, _wait, _retval) \ GhtLookup2(_Table, _key, _wait, _retval, 0) GHT_STATUS GhtInsert( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked, BOOL DuplicatesOk ); GHT_STATUS GhtDeleteEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ); GHT_STATUS GhtRemoveEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ); GHT_STATUS GhtReferenceEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ); GHT_STATUS GhtDereferenceEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ); GHT_STATUS GhtAdjustRefCountByKey( PGENERIC_HASH_TABLE HashTable, PVOID pKeyValue, LONG Delta, ULONG ActionIfZero, BOOL WaitIfLocked, PVOID *RetHashEntry ); GHT_STATUS GhtSwapEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID OldHashEntryArg, PVOID NewHashEntryArg, BOOL WaitIfLocked ); /* The hash function returns a 32 bit ULONG used to index the table. kernrate It keeps stats on # acitve entries, ... Each hash row header element has an FRS_LIST, a count of lookups, deletes, collisions, ... Each hash entry (allocated by the caller for inserts) has a standard header. The GENERIC_HASH_ENTRY_HEADER has list_entry, ULONG HashValue, Ref Count. This is followed by user node specific data. PGENERIC_HASH_TABLE GhtCreateTable( PCHAR ArgName, ULONG NumberRows, ULONG KeyOffset, ULONG KeyLength, PGENERIC_HASH_FREE_ROUTINE GhtFree, PGENERIC_HASH_COMPARE_ROUTINE GhtCompare, PGENERIC_HASH_CALC_ROUTINE GhtHashCalc, PGENERIC_HASH_PRINT_ROUTINE GhtPrint ) Create a hash table. VOID GhtDestroyTable(PGEN_HASH_TABLE) Free all the elements in the table and free the hash table structures. GHT_STATUS GhtInsert(PGEN_HASH_HEADER, WaitIfLocked) takes the tablectx, and a PGEN_HASH_HEADER. It calls the hash function with a ptr to the key data (entry+key offset) which returns a ULONG that is stored in GEN_HASH_HEADER.HashValue. Insert then calculates the index of HashValue Mod TableLenth. With the index it finds the hash row header and acquires the row lock (optional). It then walks the list looking for a hash value match. The entires are kept in ascending order so the lookup stops as soon as new entry value is < the list entry value. It then inserts the entry in the table, updates the counts in the row header and releases the lock and returns. If it finds a match it calls the user compare function with NewEntry+offset and ListEntry+offset to validate the match. The validate returns true if it matches and false if it fails (i.e. continue walking the list). Handling Duplicates ???? Insert returns GHT_STATUS_SUCCESS if the entry was inserted and GHT_STATUS_FAILURE if this is a duplicate node (the compare function returned TRUE). The refcount is incremented if the node was inserted and RefCounting is enabled. If the row is locked and WaitIfLocked is FALSE then return status GHT_STATUS_LOCKCONFLICT else wait on the row. GHT_STATUS GhtInsertAndLockRow(PGEN_HASH_HEADER, WaitIfLocked, &PGEN_HASH_ROW_HANDLE) Same as GhtInsert but leave the row locked if insert was successful. It returns a Row handle for unlock. GHT_STATUS GhtLookup(pKeyValue, WaitIfLocked, &PGEN_HASH_HEADER) like an insert but it takes a pointer to a key value as an argument along with the table ctx. The row index is computed, the row is optionally locked and the list is searched for the entry. In this case the validation routine is called with a ptr to the arg key value and the ListEntry+NodeKeyOffset. If the entry is found then a ptr to the entry is returned with an optional reference count incremented and status GHT_STATUS_SUCCESS. If the entry is not found return status GHT_STATUS_NOT_FOUND. If the row is locked and WaitIfLocked is TRUE then we wait on the row event. If the row is locked and WaitIfLocked is FALSE then return status GHT_STATUS_LOCKCONFLICT. In this case you can't tell if the entry is in the table. GHT_STATUS GHTLookupAndLockRow(pKeyValue, WaitIfLocked, &PGEN_HASH_HEADER, &PGEN_HASH_ROW_HANDLE) Do a lookup and leave row locked if entry found. Returns entry address if found or NULL if not found or row was locked and WaitIfLocked is FALSE. Return the RowHandle if the entry was found or NULL if the row was locked and WaitIfLocked is FALSE. Status returns: GHT_STATUS_SUCCESS found entry and row is locked. GHT_STATUS_LOCKCONFLICT row is locked, don't know status of entry GHT_STATUS_NOT_FOUND Entry is not in table. GHT_STATUS GhtDeleteEntryByKey(pKeyValue, WaitIfLocked, &PGEN_HASH_HEADER) Does a lookup and a delete entry. Locks the row after the lookup and unlocks the row after the delete. Returns a pointer to the entry or if a free memory routine is provided, it frees the entry. Return GHT_STATUS_NOT_FOUND if the entry is not in the table. Return GHT_STATUS_SUCCESS if the entry was deleted. Return GHT_STATUS_FAILURE if the ref count was not 1. The entry was not deleted. Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. GHT_STATUS GhtDeleteEntryByAddress(PGEN_HASH_HEADER, WaitIfLocked) takes an entry address and fetches the hash value to acquire the row lock. Remove the entry from the row and call the memory free function to release the entries memory. Drop the row lock. Return GHT_STATUS_SUCCESS if we deleted the entry and the ref count was 1. Return GHT_STATUS_FAILURE if the ref count was not 1. The entry was not deleted. Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry. GHT_STATUS GhtRemoveEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ) Takes HashEntry address and fetches the hash value to acquire the row lock. Remove the entry from the table. The reference count is decremented. If the ref count is > 2 when this call is made (1 for the caller and 1 for being in the table) then another thread may have a ref to the entry. If you move the entry to another hash table the caller should be sure that other threads with references can deal with the table change. GHT_STATUS GhtLockRowByKey(pKeyValue, WaitIfLocked, &PGEN_HASH_ROW_HANDLE) takes a key value and locks the row and returns the row handle for use by unlock. This routine does not do a lookup so it doesn't matter if the entry specified by the key is in the table or not. Return GHT_STATUS_SUCCESS if we got the lock. Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. GHT_STATUS GhtLockRowByAddress(PGEN_HASH_HEADER, WaitIfLocked, &PGEN_HASH_ROW_HANDLE) takes an entry address and fetches the hash value to acquire the row lock. It returns the row handle for unlock. Return GHT_STATUS_SUCCESS if we got the lock. Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry. GHT_STATUS GhtLockTable(WaitTime) Locks all the rows in the table and returns with GHT_STATUS_SUCCESS. Fails with GHT_STATUS_LOCKTIMEOUT if it takes > WaitTime millisec. This is useful if you need to snapshot the table. GHT_STATUS UnlockTable() Unlock all the rows in the table. Only do this if you locked 'em all. GHT_STATUS GhtUnLockRowByKey(pKeyValue) takes a key value, generates the row handle and unlocks the row. This routine does not do a lookup so it doesn't matter if the entry specified by the key is in the table or not. Return GHT_STATUS_SUCCESS if we released the lock. Raises an exception if the ROW lock was not held by this thread. GHT_STATUS GHTUnlockRowByAddress(PGEN_HASH_HEADER) takes an entry address and fetches the hash value to release the row lock and signal the event. Raises an exception if the ROW lock was not held by this thread. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry making the saved hash value invalid. GHT_STATUS GHTUnlockRowByHandle(PGEN_HASH_ROW_HANDLE) takes a hashvalue as a row handle to release the lock (and signal the event). Raises an exception if the ROW lock was not held by this thread. GHT_STATUS GHTDecrementRefCount(PGEN_HASH_HEADER, FreeIfZero) take the entry address and use the hash value to get the row lock and decrement the ref count. If the refcount goes to zero remove the entry from the table. If FreeIfZero is TRUE then delete the entry. Return GHT_STATUS_REMOVED if the entry is removed from the table. Otherwise return GHT_STATUS_SUCCESS. Since the caller already has a ref count there is no possiblity of the entry having been deleted. GHT_STATUS GhtIncrementRefCount(PGEN_HASH_HEADER) take the entry address and use the hash value to get the row lock and increment the ref count. Return GHT_STATUS_SUCCESS. Since the caller already has a ref count (since they have an address) there is no possiblity of the entry having been deleted. GHT_STATUS GhtAdjustRefCountByKey( PGENERIC_HASH_TABLE HashTable, PVOID pKeyValue, LONG Delta, ULONG ActionIfZero, BOOL WaitIfLocked, PVOID *RetHashEntry ) #define GHT_ACTION_NOOP 0 #define GHT_ACTION_REMOVE 1 #define GHT_ACTION_DELETE 2 Use the keyValue to get the row lock and find the entry. Add delta to the ref count. If ref cnt is zero The value of ActionIfZero determines one of noop, remove entry, remove and delete entry. Return GHT_STATUS_NOT_FOUND if the entry is not found in the table. Otherwise return GHT_STATUS_SUCCESS. If the element is removed from the table the address is returned in RethashEntry. GHT_STATUS GhtSwapEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID OldHashEntryArg, PVOID NewHashEntryArg, BOOL WaitIfLocked ) This routine replaces an existing old hash entry with a new entry. It verifies tha the old hash entry is still in the table. It assumes that the key value of the new entry is the same as the old entry. NO CHECK IS MADE. The expected use is when the caller needs to reallocate an entry with more storage. NOTE ALSO: The reference count is copied from the old entry to the new one. Using this routine means that the caller is using GhtDecrementRefCountByKey() and GhtIncrementRefCountByKey() to access the ref counts on any element in the table since the entry could get swapped making the pointer invalid. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry and your entry address is pointing at freed memory. A Lookup which gave you the address bumps the reference count. An insert in which you kept the address does NOT bump the reference count. GHT_STATUS GHTResizeTable() Acquire all the row locks, build a new row header array. walk thru the old table removing the elements and using the saved hash value to insert them into the new row header array. Update the ptr to the base of the new row header array and Free the old row header array. If the wait to acquire all the row locks is excessive (> 3 sec) then return GHT_STATUS_LOCKTIMEOUT to avoid deadlock. GHT_STATUS GhtEnumerateTable GhtDumpTable GhtEnumTableWithFunction walk the table and call the specified function for each entry. GhtGetTableStats Return status codes. GHT_STATUS_SUCCESS GHT_STATUS_REMOVED GHT_STATUS_LOCKCONFLICT GHT_STATUS_LOCKTIMEOUT GHT_STATUS_NOT_FOUND GHT_STATUS_FAILURE */ #endif // _GENHASH_DEFINED_