mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1286 lines
35 KiB
1286 lines
35 KiB
/*++
|
|
|
|
Copyright (c) 1998-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
hash.cxx
|
|
|
|
Abstract:
|
|
|
|
Contains the HTTP response cache hash table logic.
|
|
|
|
Author:
|
|
|
|
Alex Chen (alexch) 28-Mar-2001
|
|
|
|
Revision History:
|
|
|
|
George V. Reilly (GeorgeRe) 09-May-2001
|
|
Cleaned up and tuned up
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "hashp.h"
|
|
|
|
// Global Variables
|
|
|
|
ULONG g_UlHashTableBits;
|
|
ULONG g_UlHashTableSize;
|
|
ULONG g_UlHashTableMask;
|
|
ULONG g_UlHashIndexShift;
|
|
|
|
//
|
|
// Optimization: Use the space of (g_UlCacheLineSize - sizeof (HASHBUCKET))
|
|
// to store a few records (Hash, pUriCacheEntry) such that we can scan the
|
|
// records first before jumping to the single list for searching.
|
|
//
|
|
// g_UlNumOfHashUriKeys: The number of stored records in the space.
|
|
//
|
|
|
|
ULONG g_UlNumOfHashUriKeys;
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine determine the hash table size according to
|
|
(1) user-define value (reading from registry) or
|
|
(2) system memory size estimation, if (1) is not defined
|
|
|
|
Arguments:
|
|
|
|
HashTableBits - The number of buckets is (1 << HashTableBits)
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpGetHashTableSize(
|
|
IN LONG HashTableBits
|
|
)
|
|
{
|
|
SYSTEM_BASIC_INFORMATION sbi;
|
|
ULONG TotalPhysicalMemMB;
|
|
NTSTATUS Status;
|
|
|
|
Status = NtQuerySystemInformation(
|
|
SystemBasicInformation,
|
|
&sbi,
|
|
sizeof(sbi),
|
|
NULL);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
// Capture total physical memory, in terms of megabytes
|
|
TotalPhysicalMemMB = PAGES_TO_MEGABYTES(sbi.NumberOfPhysicalPages);
|
|
|
|
//
|
|
// HashTableBits is equal to DEFAULT_HASH_TABLE_BITS
|
|
// if it is not defined in the registry
|
|
//
|
|
|
|
if (HashTableBits != DEFAULT_HASH_TABLE_BITS)
|
|
{
|
|
// Use the registry value
|
|
// BUGBUG: We must check for reasonable values, so that a
|
|
// malicious or careless user doesn't cause us to eat up
|
|
// all of (Non)PagedPool.
|
|
|
|
g_UlHashTableBits = HashTableBits;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Registry value REGISTRY_HASH_TABLE_BITS is not defined,
|
|
// use system memory size estimation instead
|
|
//
|
|
|
|
MM_SYSTEMSIZE SystemSize = MmQuerySystemSize();
|
|
|
|
if (SystemSize == MmSmallSystem)
|
|
{
|
|
// Hash Table Size: 4K buckets
|
|
|
|
g_UlHashTableBits = 12;
|
|
}
|
|
else if (SystemSize == MmMediumSystem)
|
|
{
|
|
// Hash Table Size: 16K buckets
|
|
|
|
g_UlHashTableBits = 14;
|
|
}
|
|
else
|
|
{
|
|
// Hash Table Size: 64K buckets
|
|
|
|
// BUGBUG: A 64KB server is considered an MmLargeSystem.
|
|
// Tune g_UlHashTableBits according to physical memory
|
|
|
|
g_UlHashTableBits = 16;
|
|
}
|
|
}
|
|
|
|
g_UlHashIndexShift = g_UlCacheLineBits;
|
|
|
|
#ifdef HASH_TEST
|
|
g_UlHashTableBits = 3;
|
|
#endif
|
|
|
|
g_UlHashTableSize = (1 << g_UlHashTableBits);
|
|
g_UlHashTableMask = g_UlHashTableSize - 1;
|
|
} // UlpGetHashTableSize
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Validates that a locked HASHBUCKET is `compact'. If there are less than
|
|
g_UlNumOfHashUriKeys entries in a bucket, they are clumped together at
|
|
the beginning of the records array, and all the empty slots are at the
|
|
end. All empty slots must have Hash == HASH_INVALID_SIGNATURE and
|
|
pUriCacheEntry == NULL. Conversely, all the non-empty slots at the
|
|
beginning of the array must have point to valid UL_URI_CACHE_ENTRYs
|
|
and must have Hash == correct hash signature, which cannot be
|
|
HASH_INVALID_SIGNATURE. If the single list pointer is non-NULL, then
|
|
the records array must be full.
|
|
|
|
If the HASHBUCKET is compact, then we can abort a search for a key as
|
|
soon as we see HASH_INVALID_SIGNATURE. This invariant speeds up Find and
|
|
Insert at the cost of making Delete and Flush a little more
|
|
complex. Since we expect to do a lot more Finds than Deletes or Inserts,
|
|
this is an acceptable tradeoff.
|
|
|
|
Storing hash signatures means that we have a very fast test that
|
|
eliminates almost all false positives. We very seldom find two keys
|
|
that have matching hash signatures, but different strings.
|
|
|
|
Arguments:
|
|
|
|
pBucket - The hash bucket
|
|
|
|
--***************************************************************************/
|
|
BOOLEAN
|
|
UlpHashBucketIsCompact(
|
|
IN const PHASHBUCKET pBucket)
|
|
{
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
PUL_URI_CACHE_ENTRY pPrevUriCacheEntry = NULL;
|
|
PHASHURIKEY pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
ULONG i, j, Entries = 0;
|
|
|
|
// First, validate the records array
|
|
|
|
for (i = 0; i < g_UlNumOfHashUriKeys; i++)
|
|
{
|
|
ULONG Hash = pHashUriKey[i].Hash;
|
|
|
|
if (HASH_INVALID_SIGNATURE == Hash)
|
|
{
|
|
// There are no more valid entries in the records array
|
|
// and no singly linked list
|
|
ASSERT(NULL == pBucket->pUriCacheEntry);
|
|
pPrevUriCacheEntry = NULL;
|
|
|
|
for (j = i; j < g_UlNumOfHashUriKeys; j++)
|
|
{
|
|
ASSERT(NULL == pHashUriKey[j].pUriCacheEntry);
|
|
ASSERT(HASH_INVALID_SIGNATURE == pHashUriKey[j].Hash);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// non-empty slot
|
|
++Entries;
|
|
pUriCacheEntry = pHashUriKey[i].pUriCacheEntry;
|
|
|
|
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry));
|
|
ASSERT(pUriCacheEntry->Cached);
|
|
ASSERT(Hash == pUriCacheEntry->UriKey.Hash);
|
|
ASSERT(Hash == HashRandomizeBits(
|
|
HashStringNoCaseW(
|
|
pUriCacheEntry->UriKey.pUri,
|
|
0
|
|
)));
|
|
|
|
ASSERT(pPrevUriCacheEntry != pUriCacheEntry);
|
|
pPrevUriCacheEntry = pUriCacheEntry;
|
|
}
|
|
}
|
|
|
|
// Next, validate the singly linked list
|
|
|
|
for (pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
NULL != pUriCacheEntry;
|
|
pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pUriCacheEntry->BucketEntry.Next)
|
|
{
|
|
++Entries;
|
|
|
|
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry));
|
|
ASSERT(pUriCacheEntry->Cached);
|
|
|
|
ASSERT(pUriCacheEntry->UriKey.Hash
|
|
== HashRandomizeBits(
|
|
HashStringNoCaseW(
|
|
pUriCacheEntry->UriKey.pUri,
|
|
0
|
|
)));
|
|
|
|
ASSERT(pPrevUriCacheEntry != pUriCacheEntry);
|
|
pPrevUriCacheEntry = pUriCacheEntry;
|
|
}
|
|
|
|
ASSERT(Entries == pBucket->Entries);
|
|
|
|
return TRUE;
|
|
} // UlpHashBucketIsCompact
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine initialize the hash table
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
PoolType - Specifies the type of pool memory to allocate
|
|
HashTableBits - The number of buckets is (1 << HashTableBits)
|
|
|
|
Returns:
|
|
NTSTATUS - STATUS_SUCCESS or STATUS_NO_MEMORY
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlInitializeHashTable(
|
|
IN OUT PHASHTABLE pHashTable,
|
|
IN POOL_TYPE PoolType,
|
|
IN LONG HashTableBits
|
|
)
|
|
{
|
|
ULONG i;
|
|
ULONG_PTR CacheLineMask, CacheLineSize = g_UlCacheLineSize;
|
|
|
|
//
|
|
// First, get the hash table size from the registry.
|
|
// If not defined in the registry, determine the hash table
|
|
// size by the system memory size
|
|
//
|
|
|
|
UlpGetHashTableSize(HashTableBits);
|
|
|
|
#ifdef HASH_TEST
|
|
CacheLineSize = 64;
|
|
g_UlHashIndexShift = 6;
|
|
#endif
|
|
|
|
CacheLineMask = CacheLineSize - 1;
|
|
|
|
ASSERT((CacheLineSize & CacheLineMask) == 0); // power of 2
|
|
ASSERT(CacheLineSize == (1U << g_UlHashIndexShift));
|
|
|
|
pHashTable->Signature = UL_HASH_TABLE_POOL_TAG;
|
|
pHashTable->PoolType = PoolType;
|
|
|
|
pHashTable->NumberOfBytes = g_UlHashTableSize * CacheLineSize;
|
|
|
|
ASSERT(CacheLineSize > sizeof(HASHBUCKET));
|
|
|
|
// number of keys stored in the initial clump
|
|
g_UlNumOfHashUriKeys = (((ULONG) CacheLineSize - sizeof (HASHBUCKET))
|
|
/ sizeof(HASHURIKEY));
|
|
pHashTable->pBuckets = NULL;
|
|
|
|
#ifdef HASH_TEST
|
|
g_UlNumOfHashUriKeys = 3;
|
|
#endif
|
|
|
|
ASSERT((sizeof(HASHBUCKET) + g_UlNumOfHashUriKeys * sizeof(HASHURIKEY))
|
|
<= (1U << g_UlHashIndexShift));
|
|
|
|
// Allocate the memory
|
|
|
|
pHashTable->pAllocMem
|
|
= (PHASHBUCKET) UL_ALLOCATE_POOL(
|
|
PoolType,
|
|
pHashTable->NumberOfBytes + CacheLineMask,
|
|
UL_HASH_TABLE_POOL_TAG
|
|
);
|
|
|
|
if (NULL == pHashTable->pAllocMem)
|
|
{
|
|
pHashTable->Signature = MAKE_FREE_TAG(UL_HASH_TABLE_POOL_TAG);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
// Align the memory the cache line size boundary
|
|
|
|
pHashTable->pBuckets
|
|
= (PHASHBUCKET)((((ULONG_PTR)(pHashTable->pAllocMem)) + CacheLineMask)
|
|
& ~CacheLineMask);
|
|
|
|
// Initialize each bucket and padding array elements
|
|
|
|
for (i=0; i < g_UlHashTableSize; i++)
|
|
{
|
|
PHASHBUCKET pBucket = UlpHashTableIndexedBucket(pHashTable, i);
|
|
PHASHURIKEY pHashUriKey;
|
|
ULONG j;
|
|
|
|
UlInitializeRWSpinLock(&pBucket->RWSpinLock);
|
|
|
|
pBucket->pUriCacheEntry = NULL;
|
|
|
|
pBucket->Entries = 0;
|
|
|
|
pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
|
|
for (j = 0; j < g_UlNumOfHashUriKeys ;j++)
|
|
{
|
|
pHashUriKey[j].Hash = HASH_INVALID_SIGNATURE;
|
|
pHashUriKey[j].pUriCacheEntry = NULL;
|
|
}
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} // UlInitializeHashTable
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine terminates the hash table
|
|
(flush all entries and free the table).
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlTerminateHashTable(
|
|
IN PHASHTABLE pHashTable
|
|
)
|
|
{
|
|
if ( pHashTable->pAllocMem != NULL )
|
|
{
|
|
ASSERT(IS_VALID_HASHTABLE(pHashTable));
|
|
|
|
// Clear the hash table (delete all entries)
|
|
|
|
UlClearHashTable(pHashTable);
|
|
|
|
// Free the hash table buckets
|
|
|
|
UL_FREE_POOL(pHashTable->pAllocMem, UL_HASH_TABLE_POOL_TAG);
|
|
|
|
pHashTable->Signature = MAKE_FREE_TAG(UL_HASH_TABLE_POOL_TAG);
|
|
pHashTable->pAllocMem = pHashTable->pBuckets = NULL;
|
|
}
|
|
} // UlTerminateHashTable
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine does a cache lookup on a hash table
|
|
to see if there is a valid entry corresponding to the request key.
|
|
Increment the reference counter of the entry by 1 inside the lock
|
|
protection to ensure this entry will be still alive after returning
|
|
this entry back.
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
pUriKey - the search key
|
|
|
|
Returns:
|
|
|
|
PUL_URI_CACHE_ENTRY - pointer to entry or NULL
|
|
|
|
--***************************************************************************/
|
|
PUL_URI_CACHE_ENTRY
|
|
UlGetFromHashTable(
|
|
IN PHASHTABLE pHashTable,
|
|
IN PURI_KEY pUriKey
|
|
)
|
|
{
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
PHASHBUCKET pBucket;
|
|
PHASHURIKEY pHashUriKey;
|
|
ULONG i;
|
|
|
|
HASH_PAGED_CODE(pHashTable);
|
|
ASSERT(IS_VALID_HASHTABLE(pHashTable));
|
|
|
|
pBucket = UlpHashTableBucketFromUriKey(pHashTable, pUriKey);
|
|
|
|
UlAcquireRWSpinLockShared(&pBucket->RWSpinLock);
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
|
|
// Scan the records array first
|
|
|
|
for (i = 0; i < g_UlNumOfHashUriKeys; i++)
|
|
{
|
|
ULONG Hash = pHashUriKey[i].Hash;
|
|
|
|
if (HASH_INVALID_SIGNATURE == Hash)
|
|
{
|
|
// There are no more valid entries in the bucket
|
|
ASSERT(NULL == pBucket->pUriCacheEntry);
|
|
ASSERT(NULL == pHashUriKey[i].pUriCacheEntry);
|
|
|
|
pUriCacheEntry = NULL;
|
|
goto unlock;
|
|
}
|
|
|
|
if (Hash == pUriKey->Hash)
|
|
{
|
|
pUriCacheEntry = pHashUriKey[i].pUriCacheEntry;
|
|
|
|
ASSERT(NULL != pUriCacheEntry);
|
|
|
|
if (UlpEqualUriKeys(&pUriCacheEntry->UriKey, pUriKey))
|
|
{
|
|
goto addref;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(i == g_UlNumOfHashUriKeys);
|
|
|
|
// Jump to the single list for searching
|
|
|
|
for (pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
NULL != pUriCacheEntry;
|
|
pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pUriCacheEntry->BucketEntry.Next)
|
|
{
|
|
if (pUriCacheEntry->UriKey.Hash == pUriKey->Hash
|
|
&& UlpEqualUriKeys(&pUriCacheEntry->UriKey, pUriKey))
|
|
{
|
|
goto addref;
|
|
}
|
|
|
|
}
|
|
|
|
// Not found
|
|
|
|
ASSERT(NULL == pUriCacheEntry);
|
|
|
|
goto unlock;
|
|
|
|
addref:
|
|
ASSERT(NULL != pUriCacheEntry);
|
|
|
|
REFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, CHECKOUT);
|
|
|
|
unlock:
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
UlReleaseRWSpinLockShared(&pBucket->RWSpinLock);
|
|
|
|
return pUriCacheEntry;
|
|
|
|
} // UlGetFromHashTable
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine does a cache lookup on a hash table
|
|
to see if there is a valid entry corresponding to the request URI,
|
|
if found, delete this entry. However, increment the reference counter
|
|
of the entry by 1 insde the lock protection to ensure this entry will be
|
|
still alive after returning this entry back.
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
pUriKey - the search key
|
|
|
|
Returns:
|
|
|
|
PUL_URI_CACHE_ENTRY - pointer to entry removed from table or NULL
|
|
|
|
--***************************************************************************/
|
|
PUL_URI_CACHE_ENTRY
|
|
UlDeleteFromHashTable(
|
|
IN PHASHTABLE pHashTable,
|
|
IN PURI_KEY pUriKey
|
|
)
|
|
{
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
PUL_URI_CACHE_ENTRY PrevUriCacheEntry;
|
|
PHASHBUCKET pBucket;
|
|
PHASHURIKEY pHashUriKey;
|
|
ULONG i;
|
|
|
|
HASH_PAGED_CODE(pHashTable);
|
|
ASSERT(IS_VALID_HASHTABLE(pHashTable));
|
|
|
|
pBucket = UlpHashTableBucketFromUriKey(pHashTable, pUriKey);
|
|
|
|
UlAcquireRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
|
|
// Scan the records array first
|
|
|
|
for (i = 0; i < g_UlNumOfHashUriKeys; i++)
|
|
{
|
|
ULONG Hash = pHashUriKey[i].Hash;
|
|
|
|
if (HASH_INVALID_SIGNATURE == Hash)
|
|
{
|
|
ASSERT(NULL == pBucket->pUriCacheEntry);
|
|
ASSERT(NULL == pHashUriKey[i].pUriCacheEntry);
|
|
|
|
pUriCacheEntry = NULL;
|
|
goto unlock;
|
|
}
|
|
|
|
if (Hash == pUriKey->Hash)
|
|
{
|
|
pUriCacheEntry = pHashUriKey[i].pUriCacheEntry;
|
|
|
|
ASSERT(NULL != pUriCacheEntry);
|
|
|
|
if (UlpEqualUriKeys(&pUriCacheEntry->UriKey, pUriKey))
|
|
{
|
|
--pBucket->Entries;
|
|
|
|
if (pBucket->pUriCacheEntry)
|
|
{
|
|
// If there exists an entry in the single list,
|
|
// move it to the array
|
|
|
|
pHashUriKey[i].Hash
|
|
= pBucket->pUriCacheEntry->UriKey.Hash;
|
|
|
|
pHashUriKey[i].pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
|
|
pBucket->pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY)
|
|
pBucket->pUriCacheEntry->BucketEntry.Next;
|
|
}
|
|
else
|
|
{
|
|
// if this is not the last entry in the records array,
|
|
// move the last entry to this slot
|
|
ULONG j;
|
|
|
|
for (j = g_UlNumOfHashUriKeys; --j >= i; )
|
|
{
|
|
if (NULL != pHashUriKey[j].pUriCacheEntry)
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE
|
|
!= pHashUriKey[j].Hash);
|
|
|
|
ASSERT(j >= i);
|
|
|
|
pHashUriKey[i].Hash = pHashUriKey[j].Hash;
|
|
pHashUriKey[i].pUriCacheEntry
|
|
= pHashUriKey[j].pUriCacheEntry;
|
|
|
|
// Zap the last entry. Correct even if j == i
|
|
pHashUriKey[j].Hash = HASH_INVALID_SIGNATURE;
|
|
pHashUriKey[j].pUriCacheEntry = NULL;
|
|
|
|
goto unlock;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE
|
|
== pHashUriKey[j].Hash);
|
|
}
|
|
}
|
|
|
|
// We can't get here, since pHashUriKey[i] should
|
|
// have terminated the loop even if there wasn't
|
|
// any non-empty slot following it.
|
|
ASSERT(! "Overshot the deleted entry");
|
|
}
|
|
|
|
goto unlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(i == g_UlNumOfHashUriKeys);
|
|
|
|
// Jump to the single list for searching
|
|
|
|
pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
|
|
PrevUriCacheEntry = NULL;
|
|
|
|
while (NULL != pUriCacheEntry)
|
|
{
|
|
if (pUriCacheEntry->UriKey.Hash == pUriKey->Hash
|
|
&& UlpEqualUriKeys(&pUriCacheEntry->UriKey, pUriKey))
|
|
{
|
|
if (PrevUriCacheEntry == NULL)
|
|
{
|
|
// Delete First Entry
|
|
|
|
pBucket->pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pUriCacheEntry->BucketEntry.Next;
|
|
|
|
}
|
|
else
|
|
{
|
|
PrevUriCacheEntry->BucketEntry.Next
|
|
= pUriCacheEntry->BucketEntry.Next;
|
|
}
|
|
|
|
--pBucket->Entries;
|
|
|
|
goto unlock;
|
|
}
|
|
|
|
PrevUriCacheEntry = pUriCacheEntry;
|
|
pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pUriCacheEntry->BucketEntry.Next;
|
|
}
|
|
|
|
// Not found
|
|
|
|
ASSERT(NULL == pUriCacheEntry);
|
|
|
|
unlock:
|
|
ASSERT((LONG) pBucket->Entries >= 0);
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
UlReleaseRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
return pUriCacheEntry;
|
|
|
|
} // UlDeleteFromHashTable
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine does a cache lookup on a hash table
|
|
to see if a given entry exists, if not found, add this entry to the
|
|
hash table. Increment the reference counter of the entry by 1 insde the
|
|
lock protection.
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
pUriCacheEntry - the given entry
|
|
|
|
Returns
|
|
|
|
ULC_RETCODE - ULC_SUCCESS or ULC_KEY_EXISTS
|
|
|
|
--***************************************************************************/
|
|
ULC_RETCODE
|
|
UlAddToHashTable(
|
|
IN PHASHTABLE pHashTable,
|
|
IN PUL_URI_CACHE_ENTRY pUriCacheEntry
|
|
)
|
|
{
|
|
PUL_URI_CACHE_ENTRY pTmpUriCacheEntry;
|
|
PURI_KEY pUriKey = &(pUriCacheEntry->UriKey);
|
|
PHASHBUCKET pBucket;
|
|
PHASHURIKEY pHashUriKey;
|
|
LONG EmptySlot = INVALID_SLOT_INDEX;
|
|
ULONG i;
|
|
ULC_RETCODE rc;
|
|
|
|
HASH_PAGED_CODE(pHashTable);
|
|
ASSERT(IS_VALID_HASHTABLE(pHashTable));
|
|
|
|
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry));
|
|
ASSERT(pUriCacheEntry->Cached);
|
|
|
|
pBucket = UlpHashTableBucketFromUriKey(pHashTable, pUriKey);
|
|
|
|
UlAcquireRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
|
|
// Scan the records array first
|
|
|
|
for (i = 0; i < g_UlNumOfHashUriKeys; i++)
|
|
{
|
|
ULONG Hash = pHashUriKey[i].Hash;
|
|
|
|
if (HASH_INVALID_SIGNATURE == Hash)
|
|
{
|
|
ASSERT(NULL == pBucket->pUriCacheEntry);
|
|
ASSERT(NULL == pHashUriKey[i].pUriCacheEntry);
|
|
|
|
EmptySlot = (LONG) i;
|
|
goto insert;
|
|
}
|
|
|
|
if (Hash == pUriKey->Hash)
|
|
{
|
|
pTmpUriCacheEntry = pHashUriKey[i].pUriCacheEntry;
|
|
|
|
ASSERT(NULL != pTmpUriCacheEntry);
|
|
|
|
if (UlpEqualUriKeys(&pTmpUriCacheEntry->UriKey, pUriKey))
|
|
{
|
|
// duplicate key exists
|
|
pUriCacheEntry->Cached = FALSE;
|
|
rc = ULC_KEY_EXISTS;
|
|
|
|
goto unlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(i == g_UlNumOfHashUriKeys);
|
|
ASSERT(EmptySlot == INVALID_SLOT_INDEX);
|
|
|
|
// Jump to the single list for searching
|
|
|
|
for (pTmpUriCacheEntry = pBucket->pUriCacheEntry;
|
|
NULL != pTmpUriCacheEntry;
|
|
pTmpUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pTmpUriCacheEntry->BucketEntry.Next)
|
|
{
|
|
if (pTmpUriCacheEntry->UriKey.Hash == pUriKey->Hash
|
|
&& UlpEqualUriKeys(&pTmpUriCacheEntry->UriKey, pUriKey))
|
|
{
|
|
// duplicate key exists
|
|
pUriCacheEntry->Cached = FALSE;
|
|
rc = ULC_KEY_EXISTS;
|
|
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
insert:
|
|
//
|
|
// Not found, no duplicate key in hash table
|
|
//
|
|
|
|
if (EmptySlot != INVALID_SLOT_INDEX)
|
|
{
|
|
ASSERT(0 <= EmptySlot && EmptySlot < (LONG) g_UlNumOfHashUriKeys);
|
|
|
|
// First, try to add this entry to the array if there is an empty slot.
|
|
|
|
pHashUriKey[EmptySlot].Hash = pUriKey->Hash;
|
|
pHashUriKey[EmptySlot].pUriCacheEntry = pUriCacheEntry;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, add this entry to the head of the single list
|
|
|
|
pUriCacheEntry->BucketEntry.Next
|
|
= (PSINGLE_LIST_ENTRY) pBucket->pUriCacheEntry;
|
|
|
|
pBucket->pUriCacheEntry = pUriCacheEntry;
|
|
}
|
|
|
|
REFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, ADD);
|
|
|
|
ASSERT((LONG) pBucket->Entries >= 0);
|
|
|
|
++pBucket->Entries;
|
|
rc = ULC_SUCCESS;
|
|
|
|
unlock:
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
UlReleaseRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
return rc;
|
|
|
|
} // UlAddToHashTable
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes entries based on a caller-specified filter. The caller
|
|
provides a predicate function which takes a cache entry as a
|
|
parameter. The function will be called for each item in the cache.
|
|
If the function returns ULC_DELETE, the item will be removed.
|
|
Otherwise the item will remain in the cache.
|
|
|
|
All removals are done on a hash table bucket.
|
|
Walk through all the entries under this bucket.
|
|
|
|
Assume bucket exclusive lock is held.
|
|
|
|
Arguments:
|
|
|
|
pBucket - The hash table bucket
|
|
pFilterRoutine - A pointer to the filter function
|
|
pContext - A parameter to the filter function
|
|
pDeletedCount - A pointer to the number of deleted entries on this bucket
|
|
bStop - A pointer to a boolean variable returned to caller
|
|
(TRUE if the filter function asks for action stop)
|
|
--***************************************************************************/
|
|
BOOLEAN
|
|
UlpFilterFlushHashBucket(
|
|
IN PHASHBUCKET pBucket,
|
|
IN PUL_URI_FILTER pFilterRoutine,
|
|
IN PVOID pContext,
|
|
OUT PULONG pDeletedCount
|
|
)
|
|
{
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
PUL_URI_CACHE_ENTRY pPrevUriCacheEntry;
|
|
PUL_URI_CACHE_ENTRY pTmpUriCacheEntry;
|
|
UL_CACHE_PREDICATE result;
|
|
LONG ReferenceCount;
|
|
PHASHURIKEY pHashUriKey;
|
|
ULONG i;
|
|
LONG LastSlot;
|
|
BOOLEAN bStop = FALSE;
|
|
|
|
// Check if bucket exclusive lock is held
|
|
|
|
ASSERT( UlIsLockedExclusive(&pBucket->RWSpinLock) );
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
// Scan the single list first
|
|
|
|
pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
pPrevUriCacheEntry = NULL;
|
|
|
|
while (NULL != pUriCacheEntry)
|
|
{
|
|
BOOLEAN bDelete = FALSE;
|
|
|
|
result = (*pFilterRoutine)(pUriCacheEntry, pContext);
|
|
|
|
switch (result)
|
|
{
|
|
case ULC_ABORT:
|
|
bStop = TRUE;
|
|
goto end;
|
|
|
|
case ULC_NO_ACTION:
|
|
// nothing to do
|
|
break;
|
|
|
|
case ULC_PERFORM:
|
|
case ULC_PERFORM_STOP:
|
|
case ULC_DELETE:
|
|
case ULC_DELETE_STOP:
|
|
{
|
|
// Delete this entry
|
|
bDelete = TRUE;
|
|
|
|
ASSERT(pBucket->Entries > 0);
|
|
--pBucket->Entries;
|
|
|
|
pTmpUriCacheEntry = pUriCacheEntry;
|
|
|
|
if (NULL == pPrevUriCacheEntry)
|
|
{
|
|
// Delete First Entry
|
|
|
|
pBucket->pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY)
|
|
pUriCacheEntry->BucketEntry.Next;
|
|
|
|
pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
|
|
}
|
|
else
|
|
{
|
|
pPrevUriCacheEntry->BucketEntry.Next
|
|
= pUriCacheEntry->BucketEntry.Next;
|
|
|
|
pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY)
|
|
pPrevUriCacheEntry->BucketEntry.Next;
|
|
}
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
DEREFERENCE_URI_CACHE_ENTRY(pTmpUriCacheEntry, FILTER);
|
|
|
|
++(*pDeletedCount);
|
|
|
|
if (result == ULC_PERFORM_STOP || result == ULC_DELETE_STOP)
|
|
{
|
|
bStop = TRUE;
|
|
goto end;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!bDelete)
|
|
{
|
|
pPrevUriCacheEntry = pUriCacheEntry;
|
|
|
|
pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pUriCacheEntry->BucketEntry.Next;
|
|
}
|
|
}
|
|
|
|
pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
|
|
//
|
|
// Now, scan the records array.
|
|
//
|
|
// Because we keep the records array compact, we need to keep
|
|
// track of the last valid slot, so that we can move its contents
|
|
// to the slot that's being deleted.
|
|
//
|
|
|
|
LastSlot = INVALID_SLOT_INDEX;
|
|
|
|
if (NULL == pBucket->pUriCacheEntry)
|
|
{
|
|
for (i = g_UlNumOfHashUriKeys; i-- > 0; )
|
|
{
|
|
if (NULL != pHashUriKey[i].pUriCacheEntry)
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE != pHashUriKey[i].Hash);
|
|
LastSlot = (LONG) i;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE == pHashUriKey[i].Hash);
|
|
}
|
|
}
|
|
|
|
// Is records array completely empty?
|
|
if (LastSlot == INVALID_SLOT_INDEX)
|
|
goto end;
|
|
}
|
|
else
|
|
{
|
|
// final slot cannot be empty
|
|
ASSERT(HASH_INVALID_SIGNATURE
|
|
!= pHashUriKey[g_UlNumOfHashUriKeys-1].Hash);
|
|
}
|
|
|
|
// Walk through the records array
|
|
|
|
for (i = 0; i < g_UlNumOfHashUriKeys; i++)
|
|
{
|
|
pUriCacheEntry = pHashUriKey[i].pUriCacheEntry;
|
|
|
|
if (NULL == pUriCacheEntry)
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE == pHashUriKey[i].Hash);
|
|
goto end;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE != pHashUriKey[i].Hash);
|
|
}
|
|
|
|
result = (*pFilterRoutine)(pUriCacheEntry, pContext);
|
|
|
|
switch (result)
|
|
{
|
|
case ULC_ABORT:
|
|
bStop = TRUE;
|
|
goto end;
|
|
|
|
case ULC_NO_ACTION:
|
|
// nothing to do
|
|
break;
|
|
|
|
case ULC_PERFORM:
|
|
case ULC_PERFORM_STOP:
|
|
case ULC_DELETE:
|
|
case ULC_DELETE_STOP:
|
|
{
|
|
// Delete this entry
|
|
|
|
ASSERT(pBucket->Entries > 0);
|
|
--pBucket->Entries;
|
|
|
|
if (NULL != pBucket->pUriCacheEntry)
|
|
{
|
|
// If there exists an entry in the single list,
|
|
// move it to the array
|
|
|
|
ASSERT(LastSlot == INVALID_SLOT_INDEX);
|
|
|
|
pHashUriKey[i].Hash
|
|
= pBucket->pUriCacheEntry->UriKey.Hash;
|
|
|
|
pHashUriKey[i].pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
|
|
pBucket->pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY)
|
|
pBucket->pUriCacheEntry->BucketEntry.Next;
|
|
|
|
if (NULL == pBucket->pUriCacheEntry)
|
|
{
|
|
LastSlot = g_UlNumOfHashUriKeys - 1;
|
|
|
|
ASSERT(HASH_INVALID_SIGNATURE
|
|
!= pHashUriKey[LastSlot].Hash);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Move the entry in the last slot to this position,
|
|
// zap the last slot, and move LastSlot backwards
|
|
|
|
if (LastSlot != INVALID_SLOT_INDEX
|
|
&& (LONG) i < LastSlot)
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE
|
|
!= pHashUriKey[LastSlot].Hash);
|
|
|
|
pHashUriKey[i].Hash = pHashUriKey[LastSlot].Hash;
|
|
pHashUriKey[i].pUriCacheEntry
|
|
= pHashUriKey[LastSlot].pUriCacheEntry;
|
|
|
|
pHashUriKey[LastSlot].Hash = HASH_INVALID_SIGNATURE;
|
|
pHashUriKey[LastSlot].pUriCacheEntry = NULL;
|
|
|
|
if (--LastSlot == i)
|
|
LastSlot = INVALID_SLOT_INDEX;
|
|
else
|
|
ASSERT(HASH_INVALID_SIGNATURE
|
|
!= pHashUriKey[LastSlot].Hash);
|
|
}
|
|
else
|
|
{
|
|
// Just reset this array element
|
|
|
|
pHashUriKey[i].Hash = HASH_INVALID_SIGNATURE;
|
|
pHashUriKey[i].pUriCacheEntry = NULL;
|
|
LastSlot = INVALID_SLOT_INDEX;
|
|
}
|
|
}
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, FILTER);
|
|
|
|
++(*pDeletedCount);
|
|
|
|
if (result == ULC_PERFORM_STOP || result == ULC_DELETE_STOP)
|
|
{
|
|
bStop = TRUE;
|
|
goto end;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
end:
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
return bStop;
|
|
} // UlpFilterFlushHashBucket
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes entries based on a caller-specified filter. The caller
|
|
provides a predicate function which takes a cache entry as a
|
|
parameter. The function will be called with each item in the cache.
|
|
If the function returns ULC_DELETE, the item will be removed.
|
|
Otherwise the item will remain in the cache.
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
pFilterRoutine - A pointer to the filter function
|
|
pContext - A parameter to the filter function
|
|
pDeletedCount - A pointer to the number of deleted entries
|
|
|
|
Returns:
|
|
|
|
ULONG - Number of entries flushed from the table
|
|
|
|
--***************************************************************************/
|
|
ULONG
|
|
UlFilterFlushHashTable(
|
|
IN PHASHTABLE pHashTable,
|
|
IN PUL_URI_FILTER pFilterRoutine,
|
|
IN PVOID pContext
|
|
)
|
|
{
|
|
ULONG i;
|
|
BOOLEAN bStop = FALSE;
|
|
ULONG DeletedCount = 0;
|
|
|
|
HASH_PAGED_CODE(pHashTable);
|
|
ASSERT(IS_VALID_HASHTABLE(pHashTable));
|
|
|
|
//
|
|
// Scan and delete (if matching the filter) each bucket
|
|
// of the cache table.
|
|
//
|
|
|
|
for (i = 0; !bStop && i < g_UlHashTableSize; i++)
|
|
{
|
|
PHASHBUCKET pBucket = UlpHashTableIndexedBucket(pHashTable, i);
|
|
ULONG DeletedInBucket = 0;
|
|
|
|
UlAcquireRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
bStop = UlpFilterFlushHashBucket(
|
|
pBucket,
|
|
pFilterRoutine,
|
|
pContext,
|
|
&DeletedInBucket
|
|
);
|
|
|
|
UlReleaseRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
DeletedCount += DeletedInBucket;
|
|
}
|
|
|
|
return DeletedCount;
|
|
} // UlFilterFlushHashTable
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes all entries on a bucket.
|
|
|
|
Assume bucket exclusive lock is held.
|
|
|
|
Arguments:
|
|
|
|
pBucket - The hash table bucket
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpClearHashBucket(
|
|
IN PHASHBUCKET pBucket
|
|
)
|
|
{
|
|
PUL_URI_CACHE_ENTRY pUriCacheEntry;
|
|
PUL_URI_CACHE_ENTRY pTmpUriCacheEntry;
|
|
PHASHURIKEY pHashUriKey;
|
|
ULONG i;
|
|
LONG ReferenceCount;
|
|
|
|
// Check if bucket exclusive lock is held
|
|
|
|
ASSERT( UlIsLockedExclusive(&pBucket->RWSpinLock) );
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
// Scan the single list first
|
|
|
|
pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
|
|
while (NULL != pUriCacheEntry)
|
|
{
|
|
pTmpUriCacheEntry = pUriCacheEntry;
|
|
|
|
pBucket->pUriCacheEntry
|
|
= (PUL_URI_CACHE_ENTRY) pUriCacheEntry->BucketEntry.Next;
|
|
|
|
pUriCacheEntry = pBucket->pUriCacheEntry;
|
|
|
|
DEREFERENCE_URI_CACHE_ENTRY(pTmpUriCacheEntry, CLEAR);
|
|
}
|
|
|
|
ASSERT(NULL == pBucket->pUriCacheEntry);
|
|
|
|
pHashUriKey = UlpHashTableUriKeyFromBucket(pBucket);
|
|
|
|
// Scan the records array
|
|
|
|
for (i = 0; i < g_UlNumOfHashUriKeys; i++)
|
|
{
|
|
pUriCacheEntry = pHashUriKey[i].pUriCacheEntry;
|
|
|
|
if (NULL == pUriCacheEntry)
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE == pHashUriKey[i].Hash);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(HASH_INVALID_SIGNATURE != pHashUriKey[i].Hash);
|
|
}
|
|
|
|
DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, CLEAR);
|
|
}
|
|
|
|
pBucket->Entries = 0;
|
|
|
|
ASSERT(UlpHashBucketIsCompact(pBucket));
|
|
|
|
} // UlpClearHashBucket
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes all entries of the hash table.
|
|
|
|
Arguments:
|
|
|
|
pHashTable - The hash table
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlClearHashTable(
|
|
IN PHASHTABLE pHashTable
|
|
)
|
|
{
|
|
ULONG i;
|
|
PHASHBUCKET pBucket;
|
|
|
|
HASH_PAGED_CODE(pHashTable);
|
|
ASSERT(IS_VALID_HASHTABLE(pHashTable));
|
|
|
|
for (i = 0; i < g_UlHashTableSize ;i++)
|
|
{
|
|
pBucket = UlpHashTableIndexedBucket(pHashTable, i);
|
|
|
|
UlAcquireRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
|
|
UlpClearHashBucket(pBucket);
|
|
|
|
UlReleaseRWSpinLockExclusive(&pBucket->RWSpinLock);
|
|
}
|
|
} // UlClearHashTable
|
|
|