aspnetcore/src/AspNetCoreModuleV1/IISLib/treehash.h

851 lines
20 KiB
C++

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include <crtdbg.h>
#include "rwlock.h"
#include "prime.h"
template <class _Record>
class TREE_HASH_NODE
{
template <class _Record>
friend class TREE_HASH_TABLE;
private:
// Next node in the hash table look-aside
TREE_HASH_NODE<_Record> *_pNext;
// links in the tree structure
TREE_HASH_NODE * _pParentNode;
TREE_HASH_NODE * _pFirstChild;
TREE_HASH_NODE * _pNextSibling;
// actual record
_Record * _pRecord;
// hash value
PCWSTR _pszPath;
DWORD _dwHash;
};
template <class _Record>
class TREE_HASH_TABLE
{
protected:
typedef BOOL
(PFN_DELETE_IF)(
_Record * pRecord,
PVOID pvContext
);
typedef VOID
(PFN_APPLY)(
_Record * pRecord,
PVOID pvContext
);
public:
TREE_HASH_TABLE(
BOOL fCaseSensitive
) : _ppBuckets( NULL ),
_nBuckets( 0 ),
_nItems( 0 ),
_fCaseSensitive( fCaseSensitive )
{
}
virtual
~TREE_HASH_TABLE();
virtual
VOID
ReferenceRecord(
_Record * pRecord
) = 0;
virtual
VOID
DereferenceRecord(
_Record * pRecord
) = 0;
virtual
PCWSTR
GetKey(
_Record * pRecord
) = 0;
DWORD
Count()
{
return _nItems;
}
virtual
VOID
Clear();
HRESULT
Initialize(
DWORD nBucketSize
);
DWORD
CalcHash(
PCWSTR pszKey
)
{
return _fCaseSensitive ? HashString(pszKey) : HashStringNoCase(pszKey);
}
virtual
VOID
FindKey(
PCWSTR pszKey,
_Record ** ppRecord
);
virtual
HRESULT
InsertRecord(
_Record * pRecord
);
virtual
VOID
DeleteKey(
PCWSTR pszKey
);
virtual
VOID
DeleteIf(
PFN_DELETE_IF pfnDeleteIf,
PVOID pvContext
);
VOID
Apply(
PFN_APPLY pfnApply,
PVOID pvContext
);
private:
BOOL
FindNodeInternal(
PCWSTR pszKey,
DWORD dwHash,
TREE_HASH_NODE<_Record> ** ppNode,
TREE_HASH_NODE<_Record> *** pppPreviousNodeNextPointer = NULL
);
HRESULT
AddNodeInternal(
PCWSTR pszPath,
DWORD dwHash,
_Record * pRecord,
TREE_HASH_NODE<_Record> * pParentNode,
TREE_HASH_NODE<_Record> ** ppNewNode
);
HRESULT
AllocateNode(
PCWSTR pszPath,
DWORD dwHash,
_Record * pRecord,
TREE_HASH_NODE<_Record> * pParentNode,
TREE_HASH_NODE<_Record> ** ppNewNode
);
VOID
DeleteNode(
TREE_HASH_NODE<_Record> * pNode
)
{
if (pNode->_pRecord != NULL)
{
DereferenceRecord(pNode->_pRecord);
pNode->_pRecord = NULL;
}
HeapFree(GetProcessHeap(),
0,
pNode);
}
VOID
DeleteNodeInternal(
TREE_HASH_NODE<_Record> ** ppPreviousNodeNextPointer,
TREE_HASH_NODE<_Record> * pNode
);
VOID
RehashTableIfNeeded(
VOID
);
TREE_HASH_NODE<_Record> ** _ppBuckets;
DWORD _nBuckets;
DWORD _nItems;
BOOL _fCaseSensitive;
CWSDRWLock _tableLock;
};
template <class _Record>
HRESULT
TREE_HASH_TABLE<_Record>::AllocateNode(
PCWSTR pszPath,
DWORD dwHash,
_Record * pRecord,
TREE_HASH_NODE<_Record> * pParentNode,
TREE_HASH_NODE<_Record> ** ppNewNode
)
{
//
// Allocate enough extra space for pszPath
//
DWORD cchPath = (DWORD) wcslen(pszPath);
if (cchPath >= ((0xffffffff - sizeof(TREE_HASH_NODE<_Record>))/sizeof(WCHAR) - 1))
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
TREE_HASH_NODE<_Record> *pNode = (TREE_HASH_NODE<_Record> *)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(TREE_HASH_NODE<_Record>) + (cchPath+1)*sizeof(WCHAR));
if (pNode == NULL)
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
memcpy(pNode+1, pszPath, (cchPath+1)*sizeof(WCHAR));
pNode->_pszPath = (PCWSTR)(pNode+1);
pNode->_dwHash = dwHash;
pNode->_pNext = pNode->_pNextSibling = pNode->_pFirstChild = NULL;
pNode->_pParentNode = pParentNode;
pNode->_pRecord = pRecord;
*ppNewNode = pNode;
return S_OK;
}
template <class _Record>
HRESULT
TREE_HASH_TABLE<_Record>::Initialize(
DWORD nBuckets
)
{
HRESULT hr = S_OK;
if ( nBuckets == 0 )
{
hr = E_INVALIDARG;
goto Failed;
}
hr = _tableLock.Init();
if ( FAILED( hr ) )
{
goto Failed;
}
if (nBuckets >= 0xffffffff/sizeof(TREE_HASH_NODE<_Record> *))
{
hr = E_INVALIDARG;
goto Failed;
}
_ppBuckets = (TREE_HASH_NODE<_Record> **)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
nBuckets*sizeof(TREE_HASH_NODE<_Record> *));
if (_ppBuckets == NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto Failed;
}
_nBuckets = nBuckets;
return S_OK;
Failed:
if (_ppBuckets)
{
HeapFree(GetProcessHeap(),
0,
_ppBuckets);
_ppBuckets = NULL;
}
return hr;
}
template <class _Record>
TREE_HASH_TABLE<_Record>::~TREE_HASH_TABLE()
{
if (_ppBuckets == NULL)
{
return;
}
_ASSERTE(_nItems == 0);
HeapFree(GetProcessHeap(),
0,
_ppBuckets);
_ppBuckets = NULL;
_nBuckets = 0;
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::Clear()
{
TREE_HASH_NODE<_Record> *pCurrent;
TREE_HASH_NODE<_Record> *pNext;
_tableLock.ExclusiveAcquire();
for (DWORD i=0; i<_nBuckets; i++)
{
pCurrent = _ppBuckets[i];
_ppBuckets[i] = NULL;
while (pCurrent != NULL)
{
pNext = pCurrent->_pNext;
DeleteNode(pCurrent);
pCurrent = pNext;
}
}
_nItems = 0;
_tableLock.ExclusiveRelease();
}
template <class _Record>
BOOL
TREE_HASH_TABLE<_Record>::FindNodeInternal(
PCWSTR pszKey,
DWORD dwHash,
TREE_HASH_NODE<_Record> ** ppNode,
TREE_HASH_NODE<_Record> *** pppPreviousNodeNextPointer
)
/*++
Return value indicates whether the item is found
key, dwHash - key and hash for the node to find
ppNode - on successful return, the node found, on failed return, the first
node with hash value greater than the node to be found
pppPreviousNodeNextPointer - the pointer to previous node's _pNext
This routine may be called under either read or write lock
--*/
{
TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer;
TREE_HASH_NODE<_Record> *pNode;
BOOL fFound = FALSE;
ppPreviousNodeNextPointer = _ppBuckets + (dwHash % _nBuckets);
pNode = *ppPreviousNodeNextPointer;
while (pNode != NULL)
{
if (pNode->_dwHash == dwHash)
{
if (CompareStringOrdinal(pszKey,
-1,
pNode->_pszPath,
-1,
!_fCaseSensitive) == CSTR_EQUAL)
{
fFound = TRUE;
break;
}
}
else if (pNode->_dwHash > dwHash)
{
break;
}
ppPreviousNodeNextPointer = &(pNode->_pNext);
pNode = *ppPreviousNodeNextPointer;
}
*ppNode = pNode;
if (pppPreviousNodeNextPointer != NULL)
{
*pppPreviousNodeNextPointer = ppPreviousNodeNextPointer;
}
return fFound;
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::FindKey(
PCWSTR pszKey,
_Record ** ppRecord
)
{
TREE_HASH_NODE<_Record> *pNode;
*ppRecord = NULL;
DWORD dwHash = CalcHash(pszKey);
_tableLock.SharedAcquire();
if (FindNodeInternal(pszKey, dwHash, &pNode) &&
pNode->_pRecord != NULL)
{
ReferenceRecord(pNode->_pRecord);
*ppRecord = pNode->_pRecord;
}
_tableLock.SharedRelease();
}
template <class _Record>
HRESULT
TREE_HASH_TABLE<_Record>::AddNodeInternal(
PCWSTR pszPath,
DWORD dwHash,
_Record * pRecord,
TREE_HASH_NODE<_Record> * pParentNode,
TREE_HASH_NODE<_Record> ** ppNewNode
)
/*++
Return value is HRESULT indicating sucess or failure
pszPath, dwHash, pRecord - path, hash value and record to be inserted
pParentNode - this will be the parent of the node being inserted
ppNewNode - on successful return, the new node created and inserted
This function may be called under a read or write lock
--*/
{
TREE_HASH_NODE<_Record> *pNewNode;
TREE_HASH_NODE<_Record> *pNextNode;
TREE_HASH_NODE<_Record> **ppNextPointer;
HRESULT hr;
//
// Ownership of pRecord is not transferred to pNewNode yet, so remember
// to either set it to null before deleting pNewNode or add an extra
// reference later - this is to make sure we do not do an extra ref/deref
// which users may view as getting flushed out of the hash-table
//
hr = AllocateNode(pszPath,
dwHash,
pRecord,
pParentNode,
&pNewNode);
if (FAILED(hr))
{
return hr;
}
do
{
//
// Find the right place to add this node
//
if (FindNodeInternal(pszPath, dwHash, &pNextNode, &ppNextPointer))
{
//
// If node already there, record may still need updating
//
if (pRecord != NULL &&
InterlockedCompareExchangePointer((PVOID *)&pNextNode->_pRecord,
pRecord,
NULL) == NULL)
{
ReferenceRecord(pRecord);
hr = S_OK;
}
else
{
hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
}
// ownership of pRecord has either passed to existing record or
// not to anyone at all
pNewNode->_pRecord = NULL;
DeleteNode(pNewNode);
*ppNewNode = pNextNode;
return hr;
}
//
// If another node got inserted in betwen, we will have to retry
//
pNewNode->_pNext = pNextNode;
} while (InterlockedCompareExchangePointer((PVOID *)ppNextPointer,
pNewNode,
pNextNode) != pNextNode);
// pass ownership of pRecord now
if (pRecord != NULL)
{
ReferenceRecord(pRecord);
pRecord = NULL;
}
InterlockedIncrement((LONG *)&_nItems);
//
// update the parent
//
if (pParentNode != NULL)
{
ppNextPointer = &pParentNode->_pFirstChild;
do
{
pNextNode = *ppNextPointer;
pNewNode->_pNextSibling = pNextNode;
} while (InterlockedCompareExchangePointer((PVOID *)ppNextPointer,
pNewNode,
pNextNode) != pNextNode);
}
*ppNewNode = pNewNode;
return S_OK;
}
template <class _Record>
HRESULT
TREE_HASH_TABLE<_Record>::InsertRecord(
_Record * pRecord
)
/*++
This method inserts a node for this record and also empty nodes for paths
in the heirarchy leading upto this path
The insert is done under only a read-lock - this is possible by keeping
the hashes in a bucket in increasing order and using interlocked operations
to actually insert the item in the hash-bucket lookaside list and the parent
children list
Returns HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) if the record already exists.
Never leak this error to the end user because "*file* already exists" may be confusing.
--*/
{
PCWSTR pszKey = GetKey(pRecord);
STACK_STRU( strPartialPath, 256);
PWSTR pszPartialPath;
DWORD dwHash;
DWORD cchEnd;
HRESULT hr;
TREE_HASH_NODE<_Record> *pParentNode = NULL;
hr = strPartialPath.Copy(pszKey);
if (FAILED(hr))
{
goto Finished;
}
pszPartialPath = strPartialPath.QueryStr();
_tableLock.SharedAcquire();
//
// First find the lowest parent node present
//
for (cchEnd = strPartialPath.QueryCCH() - 1; cchEnd > 0; cchEnd--)
{
if (pszPartialPath[cchEnd] == L'/' || pszPartialPath[cchEnd] == L'\\')
{
pszPartialPath[cchEnd] = L'\0';
dwHash = CalcHash(pszPartialPath);
if (FindNodeInternal(pszPartialPath, dwHash, &pParentNode))
{
pszPartialPath[cchEnd] = pszKey[cchEnd];
break;
}
pParentNode = NULL;
}
}
//
// Now go ahead and add the rest of the tree (including our record)
//
for (; cchEnd <= strPartialPath.QueryCCH(); cchEnd++)
{
if (pszPartialPath[cchEnd] == L'\0')
{
dwHash = CalcHash(pszPartialPath);
hr = AddNodeInternal(
pszPartialPath,
dwHash,
(cchEnd == strPartialPath.QueryCCH()) ? pRecord : NULL,
pParentNode,
&pParentNode);
if (FAILED(hr) &&
hr != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
{
goto Finished;
}
pszPartialPath[cchEnd] = pszKey[cchEnd];
}
}
Finished:
_tableLock.SharedRelease();
if (SUCCEEDED(hr))
{
RehashTableIfNeeded();
}
return hr;
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::DeleteNodeInternal(
TREE_HASH_NODE<_Record> ** ppNextPointer,
TREE_HASH_NODE<_Record> * pNode
)
/*++
pNode is the node to be deleted
ppNextPointer is the pointer to the previous node's next pointer pointing
to this node
This function should be called under write-lock
--*/
{
//
// First remove this node from hash table
//
*ppNextPointer = pNode->_pNext;
//
// Now fixup parent
//
if (pNode->_pParentNode != NULL)
{
ppNextPointer = &pNode->_pParentNode->_pFirstChild;
while (*ppNextPointer != pNode)
{
ppNextPointer = &(*ppNextPointer)->_pNextSibling;
}
*ppNextPointer = pNode->_pNextSibling;
}
//
// Now remove all children recursively
//
TREE_HASH_NODE<_Record> *pChild = pNode->_pFirstChild;
TREE_HASH_NODE<_Record> *pNextChild;
while (pChild != NULL)
{
pNextChild = pChild->_pNextSibling;
ppNextPointer = _ppBuckets + (pChild->_dwHash % _nBuckets);
while (*ppNextPointer != pChild)
{
ppNextPointer = &(*ppNextPointer)->_pNext;
}
pChild->_pParentNode = NULL;
DeleteNodeInternal(ppNextPointer, pChild);
pChild = pNextChild;
}
DeleteNode(pNode);
_nItems--;
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::DeleteKey(
PCWSTR pszKey
)
{
TREE_HASH_NODE<_Record> *pNode;
TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer;
DWORD dwHash = CalcHash(pszKey);
_tableLock.ExclusiveAcquire();
if (FindNodeInternal(pszKey, dwHash, &pNode, &ppPreviousNodeNextPointer))
{
DeleteNodeInternal(ppPreviousNodeNextPointer, pNode);
}
_tableLock.ExclusiveRelease();
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::DeleteIf(
PFN_DELETE_IF pfnDeleteIf,
PVOID pvContext
)
{
TREE_HASH_NODE<_Record> *pNode;
TREE_HASH_NODE<_Record> **ppPreviousNodeNextPointer;
BOOL fDelete;
_tableLock.ExclusiveAcquire();
for (DWORD i=0; i<_nBuckets; i++)
{
ppPreviousNodeNextPointer = _ppBuckets + i;
pNode = *ppPreviousNodeNextPointer;
while (pNode != NULL)
{
//
// Non empty nodes deleted based on DeleteIf, empty nodes deleted
// if they have no children
//
fDelete = FALSE;
if (pNode->_pRecord != NULL)
{
if (pfnDeleteIf(pNode->_pRecord, pvContext))
{
fDelete = TRUE;
}
}
else if (pNode->_pFirstChild == NULL)
{
fDelete = TRUE;
}
if (fDelete)
{
if (pNode->_pFirstChild == NULL)
{
DeleteNodeInternal(ppPreviousNodeNextPointer, pNode);
}
else
{
DereferenceRecord(pNode->_pRecord);
pNode->_pRecord = NULL;
}
}
else
{
ppPreviousNodeNextPointer = &pNode->_pNext;
}
pNode = *ppPreviousNodeNextPointer;
}
}
_tableLock.ExclusiveRelease();
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::Apply(
PFN_APPLY pfnApply,
PVOID pvContext
)
{
TREE_HASH_NODE<_Record> *pNode;
_tableLock.SharedAcquire();
for (DWORD i=0; i<_nBuckets; i++)
{
pNode = _ppBuckets[i];
while (pNode != NULL)
{
if (pNode->_pRecord != NULL)
{
pfnApply(pNode->_pRecord, pvContext);
}
pNode = pNode->_pNext;
}
}
_tableLock.SharedRelease();
}
template <class _Record>
VOID
TREE_HASH_TABLE<_Record>::RehashTableIfNeeded(
VOID
)
{
TREE_HASH_NODE<_Record> **ppBuckets;
DWORD nBuckets;
TREE_HASH_NODE<_Record> *pNode;
TREE_HASH_NODE<_Record> *pNextNode;
TREE_HASH_NODE<_Record> **ppNextPointer;
TREE_HASH_NODE<_Record> *pNewNextNode;
DWORD nNewBuckets;
//
// If number of items has become too many, we will double the hash table
// size (we never reduce it however)
//
if (_nItems <= PRIME::GetPrime(2*_nBuckets))
{
return;
}
_tableLock.ExclusiveAcquire();
nNewBuckets = PRIME::GetPrime(2*_nBuckets);
if (_nItems <= nNewBuckets)
{
goto Finished;
}
nBuckets = nNewBuckets;
if (nBuckets >= 0xffffffff/sizeof(TREE_HASH_NODE<_Record> *))
{
goto Finished;
}
ppBuckets = (TREE_HASH_NODE<_Record> **)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
nBuckets*sizeof(TREE_HASH_NODE<_Record> *));
if (ppBuckets == NULL)
{
goto Finished;
}
//
// Take out nodes from the old hash table and insert in the new one, make
// sure to keep the hashes in increasing order
//
for (DWORD i=0; i<_nBuckets; i++)
{
pNode = _ppBuckets[i];
while (pNode != NULL)
{
pNextNode = pNode->_pNext;
ppNextPointer = ppBuckets + (pNode->_dwHash % nBuckets);
pNewNextNode = *ppNextPointer;
while (pNewNextNode != NULL &&
pNewNextNode->_dwHash <= pNode->_dwHash)
{
ppNextPointer = &pNewNextNode->_pNext;
pNewNextNode = pNewNextNode->_pNext;
}
pNode->_pNext = pNewNextNode;
*ppNextPointer = pNode;
pNode = pNextNode;
}
}
HeapFree(GetProcessHeap(), 0, _ppBuckets);
_ppBuckets = ppBuckets;
_nBuckets = nBuckets;
ppBuckets = NULL;
Finished:
_tableLock.ExclusiveRelease();
}