windows-nt/Source/XPSP1/NT/net/ias/protocol/proxy/iascache.cpp
2020-09-26 16:20:57 +08:00

449 lines
8.5 KiB
C++

///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, Microsoft Corp. All rights reserved.
//
// FILE
//
// iascache.cpp
//
// SYNOPSIS
//
// Defines classes for creating hash tables and caches.
//
// MODIFICATION HISTORY
//
// 02/07/2000 Original version.
// 04/25/2000 Decrement entries when item is removed.
//
///////////////////////////////////////////////////////////////////////////////
#include <proxypch.h>
#include <iascache.h>
///////////////////////////////////////////////////////////////////////////////
//
// HashTableEntry
//
///////////////////////////////////////////////////////////////////////////////
HashTableEntry::~HashTableEntry() throw ()
{ }
///////////////////////////////////////////////////////////////////////////////
//
// HashTableBase
//
///////////////////////////////////////////////////////////////////////////////
HashTableBase::HashTableBase(
HashKey hashFunction,
ULONG initialSize
) throw ()
: hash(hashFunction),
table(NULL),
buckets(initialSize ? initialSize : 1),
entries(0)
{
table = new Bucket[buckets];
memset(table, 0, buckets * sizeof(Bucket));
}
HashTableBase::~HashTableBase() throw ()
{
clear();
delete[] table;
}
void HashTableBase::clear() throw ()
{
lock();
Bucket* end = table + buckets;
// Iterate through the buckets.
for (Bucket* b = table; b != end; ++b)
{
// Iterate through the entries in the bucket.
for (HashTableEntry* entry = *b; entry; )
{
HashTableEntry* next = entry->next;
entry->Release();
entry = next;
}
}
// Zero out the table.
memset(table, 0, buckets * sizeof(Bucket));
entries = 0;
unlock();
}
bool HashTableBase::erase(const void* key) throw ()
{
HashTableEntry* entry = remove(key);
return entry ? entry->Release(), true : false;
}
HashTableEntry* HashTableBase::find(const void* key) throw ()
{
HashTableEntry* match = NULL;
ULONG hashval = hash(key);
lock();
for (match = table[hashval % buckets]; match; match = match->next)
{
if (match->matches(key))
{
match->AddRef();
break;
}
}
unlock();
return match;
}
bool HashTableBase::insert(
HashTableEntry& entry,
bool checkForDuplicates
) throw ()
{
HashTableEntry* match = NULL;
// Do what we can before acquiring the lock.
const void* key = entry.getKey();
ULONG hashval = hash(key);
lock();
// Resize the table to make room.
if (entries > buckets) { resize(buckets * 2); }
// Find the bucket.
Bucket* bucket = table + (hashval % buckets);
// Do we already have an entry with this key?
if (checkForDuplicates)
{
for (match = *bucket; match; match = match->next)
{
if (match->matches(key))
{
break;
}
}
}
if (!match)
{
// No, so stick it in the bucket.
entry.next = *bucket;
*bucket = &entry;
entry.AddRef();
++entries;
}
unlock();
return match ? false : true;
}
HashTableEntry* HashTableBase::remove(const void* key) throw ()
{
HashTableEntry* match = NULL;
ULONG hashval = hash(key);
lock();
for (HashTableEntry** entry = table + (hashval % buckets);
*entry != 0;
entry = &((*entry)->next))
{
if ((*entry)->matches(key))
{
match = *entry;
*entry = match->next;
--entries;
break;
}
}
unlock();
return match;
}
bool HashTableBase::resize(ULONG newSize) throw ()
{
if (!newSize) { newSize = 1; }
// Allocate memory for the new table.
Bucket* newTable = new (std::nothrow) Bucket[newSize];
// If the allocation failed, there's nothing else we can do.
if (!newTable) { return false; }
// Null out the buckets.
memset(newTable, 0, newSize * sizeof(Bucket));
lock();
// Save the old table.
Bucket* begin = table;
Bucket* end = table + buckets;
// Swap in the new table.
table = newTable;
buckets = newSize;
// Iterate through the old buckets.
for (Bucket* oldBucket = begin; oldBucket != end; ++oldBucket)
{
// Iterate through the entries in the bucket.
for (HashTableEntry* entry = *oldBucket; entry; )
{
// Save the next entry.
HashTableEntry* next = entry->next;
// Get the appropriate bucket.
Bucket* newBucket = table + (hash(entry->getKey()) % buckets);
// Add the node to the head of the new bucket.
entry->next = *newBucket;
*newBucket = entry;
// Move on.
entry = next;
}
}
unlock();
// Delete the old table.
delete[] begin;
return true;
}
///////////////////////////////////////////////////////////////////////////////
//
// CacheEntry
//
///////////////////////////////////////////////////////////////////////////////
inline CacheEntry* CacheEntry::prevInList() const throw ()
{
return blink;
}
inline CacheEntry* CacheEntry::nextInList() const throw ()
{
return flink;
}
inline void CacheEntry::removeFromList() throw ()
{
CacheEntry* oldFlink = flink;
CacheEntry* oldBlink = blink;
oldBlink->flink = oldFlink;
oldFlink->blink = oldBlink;
}
inline bool CacheEntry::isExpired(const ULONG64& now) const throw ()
{
return now > expiry;
}
inline bool CacheEntry::isExpired() const throw ()
{
return isExpired(GetSystemTime64());
}
inline void CacheEntry::setExpiry(ULONG64 ttl) throw ()
{
expiry = GetSystemTime64() + ttl;
}
///////////////////////////////////////////////////////////////////////////////
//
// HashTableBase
//
///////////////////////////////////////////////////////////////////////////////
CacheBase::CacheBase(
HashKey hashFunction,
ULONG initialSize,
ULONG maxCapacity,
ULONG timeToLive
) throw ()
: HashTableBase(hashFunction, initialSize),
flink(listAsEntry()), blink(listAsEntry()),
ttl(timeToLive * 10000i64),
maxEntries(maxCapacity)
{
}
CacheBase::~CacheBase() throw ()
{
clear();
buckets = 0;
}
void CacheBase::clear() throw ()
{
lock();
// Release all the entries.
for (iterator i = begin(); i != end(); (i++)->Release()) { }
// Reset the LRU list.
flink = blink = listAsEntry();
// Reset the hash table.
memset(table, 0, sizeof(Bucket) * buckets);
// There's nothing left.
entries = 0;
unlock();
}
ULONG CacheBase::evict() throw ()
{
lock();
ULONG retval = entries;
unsafe_evict();
retval = entries - retval;
unlock();
return retval;
}
bool CacheBase::erase(const void* key) throw ()
{
CacheEntry* entry = remove(key);
return entry ? entry->Release(), true : false;
}
CacheEntry* CacheBase::find(const void* key) throw ()
{
lock();
unsafe_evict();
// Look it up in the hash table.
HashTableEntry* he = HashTableBase::find(key);
CacheEntry* entry;
if (he)
{
// Whenever someone reads an entry, we reset the TTL.
entry = static_cast<CacheEntry*>(he);
entry->setExpiry(ttl);
entry->removeFromList();
push_front(entry);
}
else
{
entry = NULL;
}
unlock();
return entry;
}
bool CacheBase::insert(
CacheEntry& entry,
bool checkForDuplicates
) throw ()
{
lock();
unsafe_evict();
bool added = HashTableBase::insert(entry, checkForDuplicates);
if (added)
{
entry.setExpiry(ttl);
push_front(&entry);
}
unlock();
return added;
}
CacheEntry* CacheBase::remove(const void* key) throw ()
{
lock();
CacheEntry* entry = unsafe_remove(key);
unlock();
return entry;
}
void CacheBase::unsafe_evict() throw ()
{
while (entries > maxEntries )
{
unsafe_remove(blink->getKey())->Release();
}
while (entries && blink->isExpired())
{
unsafe_remove(blink->getKey())->Release();
}
}
CacheEntry* CacheBase::unsafe_remove(const void* key) throw ()
{
CacheEntry* entry = static_cast<CacheEntry*>(HashTableBase::remove(key));
if (entry)
{
entry->removeFromList();
}
return entry;
}
void CacheBase::push_front(CacheEntry* entry) throw ()
{
CacheEntry* listHead = listAsEntry();
CacheEntry* oldFlink = listHead->flink;
entry->flink = oldFlink;
entry->blink = listHead;
oldFlink->blink = entry;
listHead->flink = entry;
}