/*++ Copyright (c) 1998-2002 Microsoft Corporation Module Name : LKRhash.cpp Abstract: Implements LKRhash: a fast, scalable, cache- and MP-friendly hash table Author: Paul (Per-Ake) Larson, palarson@microsoft.com, July 1997 Murali R. Krishnan (MuraliK) George V. Reilly (GeorgeRe) 06-Jan-1998 Environment: Win32 - User Mode Project: Internet Information Server RunTime Library Revision History: Jan 1998 - Massive cleanup and rewrite. Templatized. 10/01/1998 - Change name from LKhash to LKRhash --*/ #include "precomp.hxx" #define DLL_IMPLEMENTATION #define IMPLEMENTATION_EXPORT #include #ifndef __LKRHASH_NO_NAMESPACE__ #define LKRHASH_NS LKRhash #else // __LKRHASH_NO_NAMESPACE__ #define LKRHASH_NS #endif // __LKRHASH_NO_NAMESPACE__ #include "_locks.h" #ifdef LKRHASH_ALLOCATOR_NEW # define DECLARE_ALLOCATOR(CLASS) \ CLKRhashAllocator* LKRHASH_NS::CLASS::sm_palloc = NULL # define DECLARE_ALLOCATOR_LHTSUBCLASS(CLASS) \ CLKRhashAllocator* LKRHASH_NS::CLKRLinearHashTable::CLASS::sm_palloc = NULL // DECLARE_ALLOCATOR(CLKRLinearHashTable); // DECLARE_ALLOCATOR(CLKRHashTable); DECLARE_ALLOCATOR(CNodeClump); DECLARE_ALLOCATOR(CSmallSegment); DECLARE_ALLOCATOR(CMediumSegment); DECLARE_ALLOCATOR(CLargeSegment); #endif // LKRHASH_ALLOCATOR_NEW static bool s_fInitialized = false; static LONG g_nLkrInitCount = 0; CSimpleLock g_lckLkrInit; // ------------------------------------------------------------------------- // Initialize per-class allocators // ------------------------------------------------------------------------- bool LKRHashTableInit() { bool f = true; #define INIT_ALLOCATOR(CLASS, N) \ LKRHASH_ALLOCATOR_INIT(LKRHASH_NS::CLASS, N, f) #define INIT_ALLOCATOR_LHTSUBCLASS(CLASS, N) \ LKRHASH_ALLOCATOR_INIT(LKRHASH_NS::CLKRLinearHashTable::CLASS, N, f) g_lckLkrInit.Enter(); LONG nCount = g_nLkrInitCount; UNREFERENCED_PARAMETER(nCount); IRTLTRACE1("LKRHashTableInit, %ld\n", nCount); if (++g_nLkrInitCount == 1) { // INIT_ALLOCATOR(CLKRLinearHashTable, 20); // INIT_ALLOCATOR(CLKRHashTable, 4); INIT_ALLOCATOR(CNodeClump, 200); INIT_ALLOCATOR(CSmallSegment, 5); INIT_ALLOCATOR(CMediumSegment, 5); INIT_ALLOCATOR(CLargeSegment, 5); s_fInitialized = f; } g_lckLkrInit.Leave(); return f; } // LKRHashTableInit // ------------------------------------------------------------------------- // Destroy per-class allocators // ------------------------------------------------------------------------- void LKRHashTableUninit() { #define UNINIT_ALLOCATOR(CLASS) \ LKRHASH_ALLOCATOR_UNINIT(LKRHASH_NS::CLASS) #define UNINIT_ALLOCATOR_LHTSUBCLASS(CLASS) \ LKRHASH_ALLOCATOR_UNINIT(LKRHASH_NS::CLKRLinearHashTable::CLASS) g_lckLkrInit.Enter(); LONG nCount = g_nLkrInitCount; UNREFERENCED_PARAMETER(nCount); if (--g_nLkrInitCount == 0) { // UNINIT_ALLOCATOR(CLKRLinearHashTable); // UNINIT_ALLOCATOR(CLKRHashTable); UNINIT_ALLOCATOR(CNodeClump); UNINIT_ALLOCATOR(CSmallSegment); UNINIT_ALLOCATOR(CMediumSegment); UNINIT_ALLOCATOR(CLargeSegment); s_fInitialized = false; } g_lckLkrInit.Leave(); IRTLTRACE1("LKRHashTableUninit done, %ld\n", nCount); } // LKRHashTableUninit #ifndef __LKRHASH_NO_NAMESPACE__ namespace LKRhash { #endif // !__LKRHASH_NO_NAMESPACE__ // See if countdown loops are faster than countup loops for traversing // a CNodeClump #ifdef LKR_COUNTDOWN #define FOR_EACH_NODE(x) for (x = NODES_PER_CLUMP; --x >= 0; ) #else // !LKR_COUNTDOWN #define FOR_EACH_NODE(x) for (x = 0; x < NODES_PER_CLUMP; ++x) #endif // !LKR_COUNTDOWN // ------------------------------------------------------------------------- // class static member variables // ------------------------------------------------------------------------- #ifdef LOCK_INSTRUMENTATION LONG CBucket::sm_cBuckets = 0; LONG CLKRLinearHashTable::sm_cTables = 0; #endif // LOCK_INSTRUMENTATION #ifndef LKR_NO_GLOBAL_LIST CLockedDoubleList CLKRLinearHashTable::sm_llGlobalList; CLockedDoubleList CLKRHashTable::sm_llGlobalList; #endif // LKR_NO_GLOBAL_LIST // CLKRLinearHashTable -------------------------------------------------------- // Public Constructor for class CLKRLinearHashTable. // ------------------------------------------------------------------------- CLKRLinearHashTable::CLKRLinearHashTable( LPCSTR pszName, // An identifier for debugging PFnExtractKey pfnExtractKey, // Extract key from record PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key PFnEqualKeys pfnEqualKeys, // Compare two keys PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc double maxload, // Upperbound on the average chain length DWORD initsize, // Initial size of hash table. DWORD /*num_subtbls*/, // for compatiblity with CLKRHashTable bool fMultiKeys // Allow multiple identical keys? ) : #ifdef LOCK_INSTRUMENTATION m_Lock(_LockName()), #endif // LOCK_INSTRUMENTATION m_nTableLockType(static_cast(TableLock::LockType())), m_nBucketLockType(static_cast(BucketLock::LockType())), m_phtParent(NULL), // directly created, no owning table m_fMultiKeys(fMultiKeys) { #ifndef LOCK_INSTRUMENTATION STATIC_ASSERT(1 <= LK_DFLT_MAXLOAD && LK_DFLT_MAXLOAD <= NODES_PER_CLUMP); #endif // !LOCK_INSTRUMENTATION STATIC_ASSERT(0 <= NODE_BEGIN && NODE_BEGIN < NODES_PER_CLUMP); STATIC_ASSERT(!(0 <= NODE_END && NODE_END < NODES_PER_CLUMP)); IRTLVERIFY(LK_SUCCESS == _Initialize(pfnExtractKey, pfnCalcKeyHash, pfnEqualKeys, pfnAddRefRecord, pszName, maxload, initsize)); _InsertThisIntoGlobalList(); } // CLKRLinearHashTable::CLKRLinearHashTable // CLKRLinearHashTable -------------------------------------------------------- // Private Constructor for class CLKRLinearHashTable, used by CLKRHashTable. // ------------------------------------------------------------------------- CLKRLinearHashTable::CLKRLinearHashTable( LPCSTR pszName, // An identifier for debugging PFnExtractKey pfnExtractKey, // Extract key from record PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key PFnEqualKeys pfnEqualKeys, // Compare two keys PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc double maxload, // Upperbound on the average chain length DWORD initsize, // Initial size of hash table. CLKRHashTable* phtParent, // Owning table. bool fMultiKeys // Allow multiple identical keys? ) : #ifdef LOCK_INSTRUMENTATION m_Lock(_LockName()), #endif // LOCK_INSTRUMENTATION m_nTableLockType(static_cast(TableLock::LockType())), m_nBucketLockType(static_cast(BucketLock::LockType())), m_phtParent(phtParent), m_fMultiKeys(fMultiKeys) { IRTLASSERT(m_phtParent != NULL); IRTLVERIFY(LK_SUCCESS == _Initialize(pfnExtractKey, pfnCalcKeyHash, pfnEqualKeys, pfnAddRefRecord, pszName, maxload, initsize)); _InsertThisIntoGlobalList(); } // CLKRLinearHashTable::CLKRLinearHashTable // _Initialize ------------------------------------------------------------- // Do all the real work of constructing a CLKRLinearHashTable // ------------------------------------------------------------------------- LK_RETCODE CLKRLinearHashTable::_Initialize( PFnExtractKey pfnExtractKey, PFnCalcKeyHash pfnCalcKeyHash, PFnEqualKeys pfnEqualKeys, PFnAddRefRecord pfnAddRefRecord, LPCSTR pszName, double maxload, DWORD initsize) { m_dwSignature = SIGNATURE; m_dwBktAddrMask0 = 0; m_dwBktAddrMask1 = 0; m_iExpansionIdx = 0; m_paDirSegs = NULL; m_lkts = LK_MEDIUM_TABLESIZE; m_dwSegBits = 0; m_dwSegSize = 0; m_dwSegMask = 0; m_lkrcState = LK_UNUSABLE; m_MaxLoad = LK_DFLT_MAXLOAD; m_nLevel = 0; m_cDirSegs = 0; m_cRecords = 0; m_cActiveBuckets = 0; m_wBucketLockSpins= LOCK_USE_DEFAULT_SPINS; m_pfnExtractKey = pfnExtractKey; m_pfnCalcKeyHash = pfnCalcKeyHash; m_pfnEqualKeys = pfnEqualKeys; m_pfnAddRefRecord = pfnAddRefRecord; strncpy(m_szName, pszName, NAME_SIZE-1); m_szName[NAME_SIZE-1] = '\0'; IRTLASSERT(m_pfnExtractKey != NULL && m_pfnCalcKeyHash != NULL && m_pfnEqualKeys != NULL && m_pfnAddRefRecord != NULL); IRTLASSERT(s_fInitialized); if (!s_fInitialized) return (m_lkrcState = LK_NOT_INITIALIZED); if (m_pfnExtractKey == NULL || m_pfnCalcKeyHash == NULL || m_pfnEqualKeys == NULL || m_pfnAddRefRecord == NULL) return (m_lkrcState = LK_BAD_PARAMETERS); // TODO: better sanity check for ridiculous values? m_MaxLoad = (maxload <= 1.0) ? LK_DFLT_MAXLOAD : maxload; m_MaxLoad = min(m_MaxLoad, 10 * NODES_PER_CLUMP); // Choose the size of the segments according to the desired "size" of // the table, small, medium, or large. LK_TABLESIZE lkts; if (initsize == LK_SMALL_TABLESIZE) { lkts = LK_SMALL_TABLESIZE; initsize = CSmallSegment::INITSIZE; } else if (initsize == LK_MEDIUM_TABLESIZE) { lkts = LK_MEDIUM_TABLESIZE; initsize = CMediumSegment::INITSIZE; } else if (initsize == LK_LARGE_TABLESIZE) { lkts = LK_LARGE_TABLESIZE; initsize = CLargeSegment::INITSIZE; } // specified an explicit initial size else { // force Small::INITSIZE <= initsize <= MAX_DIRSIZE * Large::INITSIZE initsize = min(max(initsize, CSmallSegment::INITSIZE), (MAX_DIRSIZE >> CLargeSegment::SEGBITS) * CLargeSegment::INITSIZE); // Guess a table size if (initsize <= 8 * CSmallSegment::INITSIZE) lkts = LK_SMALL_TABLESIZE; else if (initsize >= CLargeSegment::INITSIZE) lkts = LK_LARGE_TABLESIZE; else lkts = LK_MEDIUM_TABLESIZE; } return _SetSegVars(lkts, initsize); } // CLKRLinearHashTable::_Initialize // CLKRHashTable ---------------------------------------------------------- // Constructor for class CLKRHashTable. // --------------------------------------------------------------------- CLKRHashTable::CLKRHashTable( LPCSTR pszName, // An identifier for debugging PFnExtractKey pfnExtractKey, // Extract key from record PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key PFnEqualKeys pfnEqualKeys, // Compare two keys PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc double maxload, // Bound on the average chain length DWORD initsize, // Initial size of hash table. DWORD num_subtbls, // Number of subordinate hash tables. bool fMultiKeys // Allow multiple identical keys? ) : m_dwSignature(SIGNATURE), m_cSubTables(0), m_palhtDir(NULL), m_pfnExtractKey(pfnExtractKey), m_pfnCalcKeyHash(pfnCalcKeyHash), m_lkrcState(LK_BAD_PARAMETERS) { strncpy(m_szName, pszName, NAME_SIZE-1); m_szName[NAME_SIZE-1] = '\0'; _InsertThisIntoGlobalList(); IRTLASSERT(pfnExtractKey != NULL && pfnCalcKeyHash != NULL && pfnEqualKeys != NULL && pfnAddRefRecord != NULL); if (pfnExtractKey == NULL || pfnCalcKeyHash == NULL || pfnEqualKeys == NULL || pfnAddRefRecord == NULL) return; if (!s_fInitialized) { m_lkrcState = LK_NOT_INITIALIZED; return; } LK_TABLESIZE lkts = NumSubTables(initsize, num_subtbls); #ifdef IRTLDEBUG int cBuckets = initsize; if (initsize == LK_SMALL_TABLESIZE) cBuckets = CSmallSegment::INITSIZE; else if (initsize == LK_MEDIUM_TABLESIZE) cBuckets = CMediumSegment::INITSIZE; else if (initsize == LK_LARGE_TABLESIZE) cBuckets = CLargeSegment::INITSIZE; IRTLTRACE(TEXT("CLKRHashTable: %s, %d subtables, initsize = %d, ") TEXT("total #buckets = %d\n"), ((lkts == LK_SMALL_TABLESIZE) ? "small" : (lkts == LK_MEDIUM_TABLESIZE) ? "medium" : "large"), num_subtbls, initsize, cBuckets * num_subtbls); #else // !IRTLDEBUG UNREFERENCED_PARAMETER(lkts); #endif // !IRTLDEBUG m_lkrcState = LK_ALLOC_FAIL; m_palhtDir = _AllocateSubTableArray(num_subtbls); if (m_palhtDir == NULL) return; else { m_cSubTables = num_subtbls; for (DWORD i = 0; i < m_cSubTables; i++) m_palhtDir[i] = NULL; } for (DWORD i = 0; i < m_cSubTables; i++) { m_palhtDir[i] = _AllocateSubTable(pszName, pfnExtractKey, pfnCalcKeyHash, pfnEqualKeys, pfnAddRefRecord, maxload, initsize, this, fMultiKeys); // Failed to allocate a subtable. Destroy everything allocated so far. if (m_palhtDir[i] == NULL || !m_palhtDir[i]->IsValid()) { for (DWORD j = i; j-- > 0; ) _FreeSubTable(m_palhtDir[j]); _FreeSubTableArray(m_palhtDir); m_cSubTables = 0; m_palhtDir = NULL; return; } } m_nSubTableMask = m_cSubTables - 1; // power of 2? if ((m_nSubTableMask & m_cSubTables) != 0) m_nSubTableMask = -1; m_lkrcState = LK_SUCCESS; // so IsValid/IsUsable won't fail } // CLKRHashTable::CLKRHashTable // ~CLKRLinearHashTable ------------------------------------------------------ // Destructor for class CLKRLinearHashTable //------------------------------------------------------------------------- CLKRLinearHashTable::~CLKRLinearHashTable() { // must acquire all locks before deleting to make sure // that no other threads are using the table WriteLock(); _Clear(false); WriteUnlock(); _RemoveThisFromGlobalList(); m_dwSignature = SIGNATURE_FREE; m_lkrcState = LK_UNUSABLE; // so IsUsable will fail } // CLKRLinearHashTable::~CLKRLinearHashTable // ~CLKRHashTable ------------------------------------------------------------ // Destructor for class CLKRHashTable //------------------------------------------------------------------------- CLKRHashTable::~CLKRHashTable() { // Must delete the subtables in forward order (unlike // delete[], which starts at the end and moves backwards) to // prevent possibility of deadlock by acquiring the subtable // locks in a different order from the rest of the code. for (DWORD i = 0; i < m_cSubTables; ++i) _FreeSubTable(m_palhtDir[i]); _FreeSubTableArray(m_palhtDir); _RemoveThisFromGlobalList(); m_dwSignature = SIGNATURE_FREE; m_lkrcState = LK_UNUSABLE; // so IsUsable will fail } // CLKRHashTable::~CLKRHashTable //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::NumSubTables // Synopsis: //------------------------------------------------------------------------ LK_TABLESIZE CLKRLinearHashTable::NumSubTables( DWORD&, DWORD&) { LK_TABLESIZE lkts = LK_MEDIUM_TABLESIZE; return lkts; } // CLKRLinearHashTable::NumSubTables //------------------------------------------------------------------------ // Function: CLKRHashTable::NumSubTables // Synopsis: //------------------------------------------------------------------------ LK_TABLESIZE CLKRHashTable::NumSubTables( DWORD& rinitsize, DWORD& rnum_subtbls) { LK_TABLESIZE lkts; // Establish the table size if (rinitsize == LK_SMALL_TABLESIZE || rinitsize == LK_MEDIUM_TABLESIZE || rinitsize == LK_LARGE_TABLESIZE) { lkts = static_cast(rinitsize); } else { if (rnum_subtbls != LK_DFLT_NUM_SUBTBLS) { rinitsize = (rinitsize - 1) / rnum_subtbls + 1; if (rinitsize <= CSmallSegment::SEGSIZE) lkts = LK_SMALL_TABLESIZE; else if (rinitsize >= CLargeSegment::SEGSIZE) lkts = LK_LARGE_TABLESIZE; else lkts = LK_MEDIUM_TABLESIZE; } else { lkts = LK_MEDIUM_TABLESIZE; } } // Choose a suitable number of subtables if (rnum_subtbls == LK_DFLT_NUM_SUBTBLS) { int nCPUs = NumProcessors(); switch (lkts) { case LK_SMALL_TABLESIZE: rnum_subtbls = max(1, nCPUs); break; case LK_MEDIUM_TABLESIZE: rnum_subtbls = 2 * nCPUs; break; case LK_LARGE_TABLESIZE: rnum_subtbls = 4 * nCPUs; break; } } rnum_subtbls = min(MAX_SUBTABLES, rnum_subtbls); return lkts; } // CLKRHashTable::NumSubTables //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_FindBucket // Synopsis: Find a bucket, given its signature. The bucket is locked // before returning. Assumes table is already locked, to avoid // race conditions. //------------------------------------------------------------------------ LOCK_FORCEINLINE CBucket* CLKRLinearHashTable::_FindBucket( DWORD dwSignature, bool fLockForWrite) const { IRTLASSERT(IsValid()); IRTLASSERT(m_dwBktAddrMask0 > 0); IRTLASSERT((m_dwBktAddrMask0 & (m_dwBktAddrMask0+1)) == 0); // 00011..111 IRTLASSERT(m_dwBktAddrMask0 == (1U << m_nLevel) - 1); IRTLASSERT(m_dwBktAddrMask1 == ((m_dwBktAddrMask0 << 1) | 1)); IRTLASSERT((m_dwBktAddrMask1 & (m_dwBktAddrMask1+1)) == 0); IRTLASSERT(m_iExpansionIdx <= m_dwBktAddrMask0); IRTLASSERT(2 < m_dwSegBits && m_dwSegBits < 20 && m_dwSegSize == (1U << m_dwSegBits) && m_dwSegMask == (m_dwSegSize - 1)); IRTLASSERT(IsReadLocked() || IsWriteLocked()); const DWORD dwBktAddr = _BucketAddress(dwSignature); IRTLASSERT(dwBktAddr < m_cActiveBuckets); CBucket* const pbkt = _Bucket(dwBktAddr); IRTLASSERT(pbkt != NULL); if (fLockForWrite) pbkt->WriteLock(); else pbkt->ReadLock(); return pbkt; } // CLKRLinearHashTable::_FindBucket //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_IsNodeCompact // Synopsis: validates that a node is correctly compacted //------------------------------------------------------------------------ int CLKRLinearHashTable::_IsNodeCompact( CBucket* const pbkt) const { CNodeClump* pncCurr; CNodeClump* pncPrev; bool fEmpty = pbkt->m_ncFirst.InvalidSignature(NODE_BEGIN); int cErrors = fEmpty ? !pbkt->m_ncFirst.IsLastClump() : 0; for (pncCurr = &pbkt->m_ncFirst, pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { int i; FOR_EACH_NODE(i) { if (fEmpty) { cErrors += (!pncCurr->InvalidSignature(i)); cErrors += (!pncCurr->IsEmptyNode(i)); } else if (pncCurr->InvalidSignature(i)) { fEmpty = true; cErrors += (!pncCurr->IsEmptyNode(i)); cErrors += (!pncCurr->IsLastClump()); } else // still in non-empty portion { cErrors += (pncCurr->InvalidSignature(i)); cErrors += (pncCurr->IsEmptyNode(i)); } } } return cErrors; } // CLKRLinearHashTable::_IsNodeCompact //------------------------------------------------------------------------ // Function: CLKRHashTable::_SubTable // Synopsis: //------------------------------------------------------------------------ LOCK_FORCEINLINE CLKRHashTable::SubTable* CLKRHashTable::_SubTable( DWORD dwSignature) const { IRTLASSERT(m_lkrcState == LK_SUCCESS && m_palhtDir != NULL && m_cSubTables > 0); const DWORD PRIME = 1048583UL; // used to scramble the hash sig DWORD index = dwSignature; index = (((index * PRIME + 12345) >> 16) | ((index * 69069 + 1) & 0xffff0000)); if (m_nSubTableMask >= 0) index &= m_nSubTableMask; else index %= m_cSubTables; return m_palhtDir[index]; } // CLKRHashTable::_SubTable //------------------------------------------------------------------------ // Function: CLKRHashTable::_SubTableIndex // Synopsis: //------------------------------------------------------------------------ int CLKRHashTable::_SubTableIndex( CLKRHashTable::SubTable* pst) const { int index = -1; for (int i = 0; i < (int) m_cSubTables; ++i) { if (pst == m_palhtDir[i]) { index = i; break; } } IRTLASSERT(index >= 0); return index; } // CLKRHashTable::_SubTableIndex //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_InsertRecord // Synopsis: Inserts a new record into the hash table. If this causes the // average chain length to exceed the upper bound, the table is // expanded by one bucket. // Output: LK_SUCCESS, if the record was inserted. // LK_KEY_EXISTS, if the record was not inserted (because a record // with the same key value already exists in the table, unless // fOverwrite==true). // LK_ALLOC_FAIL, if failed to allocate the required space // LK_UNUSABLE, if hash table not in usable state // LK_BAD_RECORD, if record is bad. // // TODO: honor m_fMultiKeys and allow multiple identical keys. // This will require keeping all identical signatures contiguously // within a bucket chain, and keeping all identical keys contigously // within that set of contigous signatures. With a good hash function, // there should not be identical signatures without also having // identical keys. Also, need to modify _DeleteNode. This modification // is needed for EqualRange and for hash_multiset and hash_multimap // to work. //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_InsertRecord( const void* pvRecord, // Pointer to the record to add to table DWORD dwSignature,// hash signature bool fOverwrite // overwrite record if key already present #ifdef LKR_STL_ITERATORS , Iterator* piterResult #endif // LKR_STL_ITERATORS ) { IRTLASSERT(IsUsable() && pvRecord != NULL && dwSignature != HASH_INVALID_SIGNATURE); // find the beginning of the correct bucket chain WriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); #ifdef LKR_STL_ITERATORS const DWORD dwBktAddr = _BucketAddress(dwSignature); IRTLASSERT(dwBktAddr < m_cActiveBuckets); #endif // LKR_STL_ITERATORS CBucket* const pbkt = _FindBucket(dwSignature, true); IRTLASSERT(pbkt != NULL); IRTLASSERT(pbkt->IsWriteLocked()); WriteUnlock(); // check that no record with the same key value exists // and save a pointer to the last element on the chain LK_RETCODE lkrc = LK_SUCCESS; CNodeClump* pncFree = NULL; int iFreePos = NODE_BEGIN - NODE_STEP; CNodeClump* pncPrev = NULL; bool fUpdate = false; const DWORD_PTR pnKey = _ExtractKey(pvRecord); // walk down the entire bucket chain, looking for matching hash // signatures and keys CNodeClump* pncCurr = &pbkt->m_ncFirst; do { IRTLASSERT(pncCurr != NULL) ; int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); pncFree = pncCurr; iFreePos = i; goto insert; } if (dwSignature == pncCurr->m_dwKeySigs[i] && (pvRecord == pncCurr->m_pvNode[i] || _EqualKeys(pnKey, _ExtractKey(pncCurr->m_pvNode[i])))) { if (fOverwrite) { // If we allow overwrites, this is the slot to do it to fUpdate = true; pncFree = pncCurr; iFreePos = i; goto insert; } else { // overwrites forbidden: return an error lkrc = LK_KEY_EXISTS; goto exit; } } } pncPrev = pncCurr; pncCurr = pncCurr->m_pncNext; } while (pncCurr != NULL); insert: if (pncFree != NULL) { pncCurr = pncFree; IRTLASSERT(0 <= iFreePos && iFreePos < NODES_PER_CLUMP); } else { // No free slots. Attach the new node to the end of the chain IRTLASSERT(iFreePos == NODE_BEGIN - NODE_STEP); pncCurr = _AllocateNodeClump(); if (pncCurr == NULL) { lkrc = LK_ALLOC_FAIL; goto exit; } IRTLASSERT(pncPrev != NULL && pncPrev->IsLastClump()); pncPrev->m_pncNext = pncCurr; iFreePos = NODE_BEGIN; } // Bump the new record's reference count upwards _AddRefRecord(pvRecord, +1); if (fUpdate) { // We're overwriting an existing record. Adjust the old record's // refcount downwards. (Doing ++new, --old in this order ensures // that the refcount won't briefly go to zero if new and old are // the same record.) IRTLASSERT(!pncCurr->IsEmptyAndInvalid(iFreePos)); _AddRefRecord(pncCurr->m_pvNode[iFreePos], -1); } else { IRTLASSERT(pncCurr->IsEmptyAndInvalid(iFreePos)); InterlockedIncrement(reinterpret_cast(&m_cRecords)); } pncCurr->m_dwKeySigs[iFreePos] = dwSignature; pncCurr->m_pvNode[iFreePos] = pvRecord; exit: pbkt->WriteUnlock(); if (lkrc == LK_SUCCESS) { #ifdef LKR_STL_ITERATORS // Don't call _Expand() if we're putting the result into an // iterator, as _Expand() tends to invalidate any other // iterators that might be in use. if (piterResult != NULL) { piterResult->m_plht = this; piterResult->m_pnc = pncCurr; piterResult->m_dwBucketAddr = dwBktAddr; piterResult->m_iNode = (short) iFreePos; // Add an extra reference on the record, as the one added by // _InsertRecord will be lost when the iterator's destructor // fires or its assignment operator is used piterResult->_AddRef(+1); } else #endif // LKR_STL_ITERATORS { // If the average load factor has grown too high, we grow the // table one bucket at a time. while (m_cRecords > m_MaxLoad * m_cActiveBuckets) { // If _Expand returns an error code (viz. LK_ALLOC_FAIL), it // just means that there isn't enough spare memory to expand // the table by one bucket. This is likely to cause problems // elsewhere soon, but this hashtable has not been corrupted. // If the call to _AllocateNodeClump above failed, then we do // have a real error that must be propagated back to the caller // because we were unable to insert the element at all. if (_Expand() != LK_SUCCESS) break; // expansion failed } } } return lkrc; } // CLKRLinearHashTable::_InsertRecord //------------------------------------------------------------------------ // Function: CLKRHashTable::InsertRecord // Synopsis: Thin wrapper for the corresponding method in CLKRLinearHashTable //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::InsertRecord( const void* pvRecord, bool fOverwrite /*=false*/) { if (!IsUsable()) return m_lkrcState; if (pvRecord == NULL) return LK_BAD_RECORD; LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord)); SubTable* const pst = _SubTable(hash_val); LK_RETCODE lk = pst->_InsertRecord(pvRecord, hash_val, fOverwrite); LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op return lk; } // CLKRHashTable::InsertRecord //------------------------------------------------------------------------- // Function: CLKRLinearHashTable::_DeleteKey // Synopsis: Deletes the record with the given key value from the hash // table (if it exists). // Returns: LK_SUCCESS, if record found and deleted. // LK_NO_SUCH_KEY, if no record with the given key value was found. // LK_UNUSABLE, if hash table not in usable state //------------------------------------------------------------------------- LK_RETCODE CLKRLinearHashTable::_DeleteKey( const DWORD_PTR pnKey, // Key value of the record, depends on key type DWORD dwSignature ) { IRTLASSERT(IsUsable()); LK_RETCODE lkrc = LK_NO_SUCH_KEY; // locate the beginning of the correct bucket chain WriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); CBucket* const pbkt = _FindBucket(dwSignature, true); IRTLASSERT(pbkt != NULL); IRTLASSERT(pbkt->IsWriteLocked()); WriteUnlock(); // scan down the bucket chain, looking for the victim for (CNodeClump* pncCurr = &pbkt->m_ncFirst, *pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); goto exit; } if (dwSignature != pncCurr->m_dwKeySigs[i]) continue; const DWORD_PTR pnKey2 = _ExtractKey(pncCurr->m_pvNode[i]); if (pnKey == pnKey2 || _EqualKeys(pnKey, pnKey2)) { IRTLVERIFY(_DeleteNode(pbkt, pncCurr, pncPrev, i)); lkrc = LK_SUCCESS; goto exit; } } } exit: pbkt->WriteUnlock(); if (lkrc == LK_SUCCESS) { // contract the table if necessary unsigned nContractedRecords = m_cRecords; // Hysteresis: add a fudge factor to allow a slightly lower density // in the subtable. This reduces the frequency of contractions and // expansions in a subtable that gets a lot of deletions and insertions nContractedRecords += nContractedRecords >> 4; // Always want to have at least m_dwSegSize buckets while (m_cActiveBuckets * m_MaxLoad > nContractedRecords && m_cActiveBuckets > m_dwSegSize) { // If _Contract returns an error code (viz. LK_ALLOC_FAIL), it // just means that there isn't enough spare memory to contract // the table by one bucket. This is likely to cause problems // elsewhere soon, but this hashtable has not been corrupted. if (_Contract() != LK_SUCCESS) break; } } return lkrc; } // CLKRLinearHashTable::_DeleteKey //------------------------------------------------------------------------ // Function: CLKRHashTable::DeleteKey // Synopsis: Thin wrapper for the corresponding method in CLKRLinearHashTable //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::DeleteKey( const DWORD_PTR pnKey) { if (!IsUsable()) return m_lkrcState; LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op DWORD hash_val = _CalcKeyHash(pnKey); SubTable* const pst = _SubTable(hash_val); LK_RETCODE lk = pst->_DeleteKey(pnKey, hash_val); LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op return lk; } // CLKRHashTable::DeleteKey //------------------------------------------------------------------------- // Function: CLKRLinearHashTable::_DeleteRecord // Synopsis: Deletes the specified record from the hash table (if it // exists). This is not the same thing as calling // DeleteKey(_ExtractKey(pvRecord)). If _DeleteKey were called for // a record that doesn't exist in the table, it could delete some // completely unrelated record that happened to have the same key. // Returns: LK_SUCCESS, if record found and deleted. // LK_NO_SUCH_KEY, if the record is not found in the table. // LK_UNUSABLE, if hash table not in usable state. //------------------------------------------------------------------------- LK_RETCODE CLKRLinearHashTable::_DeleteRecord( const void* pvRecord, // Pointer to the record to delete from the table DWORD dwSignature ) { IRTLASSERT(IsUsable() && pvRecord != NULL); LK_RETCODE lkrc = LK_NO_SUCH_KEY; // locate the beginning of the correct bucket chain WriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); CBucket* const pbkt = _FindBucket(dwSignature, true); IRTLASSERT(pbkt != NULL); IRTLASSERT(pbkt->IsWriteLocked()); WriteUnlock(); const DWORD_PTR pnKey = _ExtractKey(pvRecord); UNREFERENCED_PARAMETER(pnKey); IRTLASSERT(dwSignature == _CalcKeyHash(pnKey)); // scan down the bucket chain, looking for the victim for (CNodeClump* pncCurr = &pbkt->m_ncFirst, *pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); goto exit; } if (pncCurr->m_pvNode[i] == pvRecord) { IRTLASSERT(_EqualKeys(pnKey, _ExtractKey(pncCurr->m_pvNode[i]))); IRTLASSERT(dwSignature == pncCurr->m_dwKeySigs[i]); IRTLVERIFY(_DeleteNode(pbkt, pncCurr, pncPrev, i)); lkrc = LK_SUCCESS; goto exit; } } } exit: pbkt->WriteUnlock(); if (lkrc == LK_SUCCESS) { // contract the table if necessary unsigned nContractedRecords = m_cRecords; // Hysteresis: add a fudge factor to allow a slightly lower density // in the subtable. This reduces the frequency of contractions and // expansions in a subtable that gets a lot of deletions and insertions nContractedRecords += nContractedRecords >> 4; // Always want to have at least m_dwSegSize buckets while (m_cActiveBuckets * m_MaxLoad > nContractedRecords && m_cActiveBuckets > m_dwSegSize) { // If _Contract returns an error code (viz. LK_ALLOC_FAIL), it // just means that there isn't enough spare memory to contract // the table by one bucket. This is likely to cause problems // elsewhere soon, but this hashtable has not been corrupted. if (_Contract() != LK_SUCCESS) break; } } return lkrc; } // CLKRLinearHashTable::_DeleteRecord //------------------------------------------------------------------------ // Function: CLKRHashTable::DeleteRecord // Synopsis: Thin wrapper for the corresponding method in CLKRLinearHashTable //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::DeleteRecord( const void* pvRecord) { if (!IsUsable()) return m_lkrcState; if (pvRecord == NULL) return LK_BAD_RECORD; LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord)); SubTable* const pst = _SubTable(hash_val); LK_RETCODE lk = pst->_DeleteRecord(pvRecord, hash_val); LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op return lk; } // CLKRHashTable::DeleteRecord //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_DeleteNode // Synopsis: Deletes a node; removes the node clump if empty // Returns: true if successful // // TODO: Is the rpncPrev parameter really necessary? //------------------------------------------------------------------------ bool CLKRLinearHashTable::_DeleteNode( CBucket* pbkt, // bucket chain containing node CNodeClump*& rpnc, // actual node CNodeClump*& rpncPrev, // predecessor of actual node, or NULL int& riNode) // index within node { IRTLASSERT(pbkt != NULL && pbkt->IsWriteLocked()); IRTLASSERT(rpnc != NULL); IRTLASSERT(rpncPrev == NULL || rpncPrev->m_pncNext == rpnc); IRTLASSERT(0 <= riNode && riNode < NODES_PER_CLUMP); IRTLASSERT(!rpnc->IsEmptyAndInvalid(riNode)); #ifdef IRTLDEBUG // Check that the node clump really does belong to the bucket CNodeClump* pnc1 = &pbkt->m_ncFirst; while (pnc1 != NULL && pnc1 != rpnc) pnc1 = pnc1->m_pncNext; IRTLASSERT(pnc1 == rpnc); #endif // IRTLDEBUG // Release the reference to the record _AddRefRecord(rpnc->m_pvNode[riNode], -1); IRTLASSERT(0 == _IsNodeCompact(pbkt)); // TODO: honor m_fMultiKeys // Compact the nodeclump by moving the very last node back to the // newly freed slot CNodeClump* pnc2 = rpnc; int iNode2 = riNode; // Find the last nodeclump in the chain while (!pnc2->IsLastClump()) { pnc2 = pnc2->m_pncNext; iNode2 = NODE_BEGIN; } IRTLASSERT(0 <= iNode2 && iNode2 < NODES_PER_CLUMP); IRTLASSERT(!pnc2->IsEmptyAndInvalid(iNode2)); // Find the first empty slot in the nodeclump while (iNode2 != NODE_END && !pnc2->IsEmptySlot(iNode2)) { iNode2 += NODE_STEP; } // Back up to last non-empty slot iNode2 -= NODE_STEP; IRTLASSERT(0 <= iNode2 && iNode2 < NODES_PER_CLUMP && !pnc2->IsEmptyAndInvalid(iNode2)); IRTLASSERT(iNode2+NODE_STEP == NODE_END || pnc2->IsEmptyAndInvalid(iNode2+NODE_STEP)); #ifdef IRTLDEBUG // Check that all the remaining nodes are empty IRTLASSERT(pnc2->IsLastClump()); for (int iNode3 = iNode2 + NODE_STEP; iNode3 != NODE_END; iNode3 += NODE_STEP) { IRTLASSERT(pnc2->IsEmptyAndInvalid(iNode3)); } #endif // IRTLDEBUG // Move the last node's data back to the current node rpnc->m_pvNode[riNode] = pnc2->m_pvNode[iNode2]; rpnc->m_dwKeySigs[riNode] = pnc2->m_dwKeySigs[iNode2]; // Blank the old last node. // Correct even if (rpnc, riNode) == (pnc2, iNode2). pnc2->m_pvNode[iNode2] = NULL; pnc2->m_dwKeySigs[iNode2] = HASH_INVALID_SIGNATURE; IRTLASSERT(0 == _IsNodeCompact(pbkt)); // Back up riNode by one, so that the next iteration of the loop // calling _DeleteNode will end up pointing to the same spot. if (riNode != NODE_BEGIN) { riNode -= NODE_STEP; } else { // rewind rpnc and rpncPrev to previous node if (rpnc == &pbkt->m_ncFirst) { riNode = NODE_BEGIN - NODE_STEP; } else { riNode = NODE_END; rpnc = rpncPrev; if (rpnc == &pbkt->m_ncFirst) { rpncPrev = NULL; } else { for (rpncPrev = &pbkt->m_ncFirst; rpncPrev->m_pncNext != rpnc; rpncPrev = rpncPrev->m_pncNext) {} } } } // Is the last node clump now completely empty? Delete, if possible if (iNode2 == NODE_BEGIN && pnc2 != &pbkt->m_ncFirst) { // Find preceding nodeclump CNodeClump* pnc3 = &pbkt->m_ncFirst; while (pnc3->m_pncNext != pnc2) { pnc3 = pnc3->m_pncNext; IRTLASSERT(pnc3 != NULL); } pnc3->m_pncNext = NULL; #ifdef IRTLDEBUG pnc2->m_pncNext = NULL; // or dtor will ASSERT #endif // IRTLDEBUG _FreeNodeClump(pnc2); } IRTLASSERT(rpncPrev == NULL || rpncPrev->m_pncNext == rpnc); InterlockedDecrement(reinterpret_cast(&m_cRecords)); return true; } // CLKRLinearHashTable::_DeleteNode //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_FindKey // Synopsis: Locate the record associated with the given key value. // Returns: Pointer to the record, if it is found. // NULL, if the record is not found. // Returns: LK_SUCCESS, if record found (record is returned in *ppvRecord) // LK_BAD_RECORD, if ppvRecord is invalid // LK_NO_SUCH_KEY, if no record with the given key value was found. // LK_UNUSABLE, if hash table not in usable state // Note: the record is AddRef'd. You must decrement the reference count // when you are finished with the record (if you're implementing // refcounting semantics). //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_FindKey( const DWORD_PTR pnKey, // Key value of the record, depends on key type DWORD dwSignature,// hash signature const void** ppvRecord // resultant record #ifdef LKR_STL_ITERATORS , Iterator* piterResult #endif // LKR_STL_ITERATORS ) const { IRTLASSERT(IsUsable() && ppvRecord != NULL); *ppvRecord = NULL; LK_RETCODE lkrc = LK_NO_SUCH_KEY; int iNode = NODE_BEGIN - NODE_STEP; // locate the beginning of the correct bucket chain bool fReadLocked = _ReadOrWriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); #ifdef LKR_STL_ITERATORS const DWORD dwBktAddr = _BucketAddress(dwSignature); IRTLASSERT(dwBktAddr < m_cActiveBuckets); #endif // LKR_STL_ITERATORS CBucket* const pbkt = _FindBucket(dwSignature, false); IRTLASSERT(pbkt != NULL); IRTLASSERT(pbkt->IsReadLocked()); _ReadOrWriteUnlock(fReadLocked); // walk down the bucket chain for (CNodeClump* pncCurr = &pbkt->m_ncFirst; pncCurr != NULL; pncCurr = pncCurr->m_pncNext) { FOR_EACH_NODE(iNode) { if (pncCurr->IsEmptySlot(iNode)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(iNode)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); goto exit; } if (dwSignature != pncCurr->m_dwKeySigs[iNode]) continue; const DWORD_PTR pnKey2 = _ExtractKey(pncCurr->m_pvNode[iNode]); if (pnKey == pnKey2 || _EqualKeys(pnKey, pnKey2)) { *ppvRecord = pncCurr->m_pvNode[iNode]; lkrc = LK_SUCCESS; // bump the reference count before handing the record // back to the user. The user should decrement the // reference count when finished with this record. _AddRefRecord(*ppvRecord, +1); goto exit; } } } exit: pbkt->ReadUnlock(); #ifdef LKR_STL_ITERATORS if (piterResult != NULL && lkrc == LK_SUCCESS) { piterResult->m_plht = const_cast(this); piterResult->m_pnc = pncCurr; piterResult->m_dwBucketAddr = dwBktAddr; piterResult->m_iNode = (short) iNode; } #endif // LKR_STL_ITERATORS return lkrc; } // CLKRLinearHashTable::_FindKey //------------------------------------------------------------------------ // Function: CLKRHashTable::FindKey // Synopsis: Thin wrapper for the corresponding method in CLKRLinearHashTable //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::FindKey( const DWORD_PTR pnKey, const void** ppvRecord) const { if (!IsUsable()) return m_lkrcState; if (ppvRecord == NULL) return LK_BAD_RECORD; LKRHASH_GLOBAL_READ_LOCK(); // usu. no-op DWORD hash_val = _CalcKeyHash(pnKey); SubTable* const pst = _SubTable(hash_val); LK_RETCODE lkrc = pst->_FindKey(pnKey, hash_val, ppvRecord); LKRHASH_GLOBAL_READ_UNLOCK(); // usu. no-op return lkrc; } // CLKRHashTable::FindKey //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_FindRecord // Synopsis: Sees if the record is contained in the table // Returns: Pointer to the record, if it is found. // NULL, if the record is not found. // Returns: LK_SUCCESS, if record found // LK_BAD_RECORD, if pvRecord is invalid // LK_NO_SUCH_KEY, if the record was not found in the table // LK_UNUSABLE, if hash table not in usable state // Note: The record is *not* AddRef'd. //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_FindRecord( const void* pvRecord, // Pointer to the record to find in the table DWORD dwSignature // hash signature ) const { IRTLASSERT(IsUsable() && pvRecord != NULL); LK_RETCODE lkrc = LK_NO_SUCH_KEY; // locate the beginning of the correct bucket chain bool fReadLocked = _ReadOrWriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); CBucket* const pbkt = _FindBucket(dwSignature, false); IRTLASSERT(pbkt != NULL); IRTLASSERT(pbkt->IsReadLocked()); _ReadOrWriteUnlock(fReadLocked); const DWORD_PTR pnKey = _ExtractKey(pvRecord); UNREFERENCED_PARAMETER(pnKey); IRTLASSERT(dwSignature == _CalcKeyHash(pnKey)); // walk down the bucket chain for (CNodeClump* pncCurr = &pbkt->m_ncFirst; pncCurr != NULL; pncCurr = pncCurr->m_pncNext) { int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); goto exit; } if (pncCurr->m_pvNode[i] == pvRecord) { IRTLASSERT(dwSignature == pncCurr->m_dwKeySigs[i]); IRTLASSERT(_EqualKeys(pnKey, _ExtractKey(pncCurr->m_pvNode[i]))); lkrc = LK_SUCCESS; goto exit; } } } exit: pbkt->ReadUnlock(); return lkrc; } // CLKRLinearHashTable::_FindRecord //------------------------------------------------------------------------ // Function: CLKRHashTable::FindRecord // Synopsis: Thin wrapper for the corresponding method in CLKRLinearHashTable //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::FindRecord( const void* pvRecord) const { if (!IsUsable()) return m_lkrcState; if (pvRecord == NULL) return LK_BAD_RECORD; LKRHASH_GLOBAL_READ_LOCK(); // usu. no-op DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord)); SubTable* const pst = _SubTable(hash_val); LK_RETCODE lkrc = pst->_FindRecord(pvRecord, hash_val); LKRHASH_GLOBAL_READ_UNLOCK(); // usu. no-op return lkrc; } // CLKRHashTable::FindRecord #ifdef LKR_APPLY_IF //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::Apply // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRLinearHashTable::Apply( PFnRecordAction pfnAction, void* pvState, LK_LOCKTYPE lkl) { if (!IsUsable()) return static_cast(LK_UNUSABLE); LK_PREDICATE lkp = LKP_PERFORM; if (lkl == LKL_WRITELOCK) WriteLock(); else ReadLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); DWORD dw = _Apply(pfnAction, pvState, lkl, lkp); if (lkl == LKL_WRITELOCK) WriteUnlock(); else ReadUnlock(); return dw; } // CLKRLinearHashTable::Apply //------------------------------------------------------------------------ // Function: CLKRHashTable::Apply // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRHashTable::Apply( PFnRecordAction pfnAction, void* pvState, LK_LOCKTYPE lkl) { if (!IsUsable()) return static_cast(LK_UNUSABLE); DWORD dw = 0; LK_PREDICATE lkp = LKP_PERFORM; if (lkl == LKL_WRITELOCK) WriteLock(); else ReadLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (IsValid()) { for (DWORD i = 0; i < m_cSubTables; i++) { dw += m_palhtDir[i]->_Apply(pfnAction, pvState, lkl, lkp); if (lkp == LKP_ABORT || lkp == LKP_PERFORM_STOP || lkp == LKP_DELETE_STOP) break; } } if (lkl == LKL_WRITELOCK) WriteUnlock(); else ReadUnlock(); return dw; } // CLKRHashTable::Apply //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::ApplyIf // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRLinearHashTable::ApplyIf( PFnRecordPred pfnPredicate, PFnRecordAction pfnAction, void* pvState, LK_LOCKTYPE lkl) { if (!IsUsable()) return static_cast(LK_UNUSABLE); DWORD dw = 0; LK_PREDICATE lkp = LKP_PERFORM; if (lkl == LKL_WRITELOCK) WriteLock(); else ReadLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (IsValid()) { dw = _ApplyIf(pfnPredicate, pfnAction, pvState, lkl, lkp); } if (lkl == LKL_WRITELOCK) WriteUnlock(); else ReadUnlock(); return dw; } // CLKRLinearHashTable::ApplyIf //------------------------------------------------------------------------ // Function: CLKRHashTable::ApplyIf // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRHashTable::ApplyIf( PFnRecordPred pfnPredicate, PFnRecordAction pfnAction, void* pvState, LK_LOCKTYPE lkl) { if (!IsUsable()) return static_cast(LK_UNUSABLE); DWORD dw = 0; LK_PREDICATE lkp = LKP_PERFORM; if (lkl == LKL_WRITELOCK) WriteLock(); else ReadLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (IsValid()) { for (DWORD i = 0; i < m_cSubTables; i++) { dw += m_palhtDir[i]->_ApplyIf(pfnPredicate, pfnAction, pvState, lkl, lkp); if (lkp == LKP_ABORT || lkp == LKP_PERFORM_STOP || lkp == LKP_DELETE_STOP) break; } } if (lkl == LKL_WRITELOCK) WriteUnlock(); else ReadUnlock(); return dw; } // CLKRHashTable::ApplyIf //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::DeleteIf // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRLinearHashTable::DeleteIf( PFnRecordPred pfnPredicate, void* pvState) { if (!IsUsable()) return static_cast(LK_UNUSABLE); DWORD dw = 0; LK_PREDICATE lkp = LKP_PERFORM; WriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (IsValid()) dw = _DeleteIf(pfnPredicate, pvState, lkp); WriteUnlock(); return dw; } // CLKRLinearHashTable::DeleteIf //------------------------------------------------------------------------ // Function: CLKRHashTable::DeleteIf // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRHashTable::DeleteIf( PFnRecordPred pfnPredicate, void* pvState) { if (!IsUsable()) return static_cast(LK_UNUSABLE); DWORD dw = 0; LK_PREDICATE lkp = LKP_PERFORM; WriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (IsValid()) { for (DWORD i = 0; i < m_cSubTables; i++) { dw += m_palhtDir[i]->_DeleteIf(pfnPredicate, pvState, lkp); if (lkp == LKP_ABORT || lkp == LKP_PERFORM_STOP || lkp == LKP_DELETE_STOP) break; } } WriteUnlock(); return dw; } // CLKRHashTable::DeleteIf //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_Apply // Synopsis: // Returns: //------------------------------------------------------------------------ DWORD CLKRLinearHashTable::_Apply( PFnRecordAction pfnAction, void* pvState, LK_LOCKTYPE lkl, LK_PREDICATE& rlkp) { if (!IsUsable()) return static_cast(LK_UNUSABLE); IRTLASSERT(lkl == LKL_WRITELOCK ? IsWriteLocked() : IsReadLocked()); return _ApplyIf(_PredTrue, pfnAction, pvState, lkl, rlkp); } // CLKRLinearHashTable::_Apply //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_ApplyIf // Synopsis: // Returns: Number of successful actions //------------------------------------------------------------------------ DWORD CLKRLinearHashTable::_ApplyIf( PFnRecordPred pfnPredicate, PFnRecordAction pfnAction, void* pvState, LK_LOCKTYPE lkl, LK_PREDICATE& rlkp) { if (!IsUsable()) return static_cast(LK_UNUSABLE); IRTLASSERT(lkl == LKL_WRITELOCK ? IsWriteLocked() : IsReadLocked()); IRTLASSERT(pfnPredicate != NULL && pfnAction != NULL); if ((lkl == LKL_WRITELOCK ? !IsWriteLocked() : !IsReadLocked()) || pfnPredicate == NULL || pfnAction == NULL) return 0; DWORD cActions = 0; for (DWORD iBkt = 0; iBkt < m_cActiveBuckets; ++iBkt) { CBucket* const pbkt = _Bucket(iBkt); IRTLASSERT(pbkt != NULL); if (lkl == LKL_WRITELOCK) pbkt->WriteLock(); else pbkt->ReadLock(); for (CNodeClump* pncCurr = &pbkt->m_ncFirst, *pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); goto unlock; } else { rlkp = (*pfnPredicate)(pncCurr->m_pvNode[i], pvState); switch (rlkp) { case LKP_ABORT: if (lkl == LKL_WRITELOCK) pbkt->WriteUnlock(); else pbkt->ReadUnlock(); return cActions; break; case LKP_NO_ACTION: // nothing to do break; case LKP_DELETE: case LKP_DELETE_STOP: if (lkl != LKL_WRITELOCK) { pbkt->ReadUnlock(); return cActions; } // fall through case LKP_PERFORM: case LKP_PERFORM_STOP: { LK_ACTION lka; if (rlkp == LKP_DELETE || rlkp == LKP_DELETE_STOP) { IRTLVERIFY(_DeleteNode(pbkt, pncCurr, pncPrev, i)); ++cActions; lka = LKA_SUCCEEDED; } else { lka = (*pfnAction)(pncCurr->m_pvNode[i], pvState); switch (lka) { case LKA_ABORT: if (lkl == LKL_WRITELOCK) pbkt->WriteUnlock(); else pbkt->ReadUnlock(); return cActions; case LKA_FAILED: // nothing to do break; case LKA_SUCCEEDED: ++cActions; break; default: IRTLASSERT(! "Unknown LK_ACTION in ApplyIf"); break; } } if (rlkp == LKP_PERFORM_STOP || rlkp == LKP_DELETE_STOP) { if (lkl == LKL_WRITELOCK) pbkt->WriteUnlock(); else pbkt->ReadUnlock(); return cActions; } break; } default: IRTLASSERT(! "Unknown LK_PREDICATE in ApplyIf"); break; } } } } unlock: if (lkl == LKL_WRITELOCK) pbkt->WriteUnlock(); else pbkt->ReadUnlock(); } return cActions; } // CLKRLinearHashTable::_ApplyIf //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_DeleteIf // Synopsis: Deletes all records that match the predicate // Returns: Count of successful deletions //------------------------------------------------------------------------ DWORD CLKRLinearHashTable::_DeleteIf( PFnRecordPred pfnPredicate, void* pvState, LK_PREDICATE& rlkp) { if (!IsUsable()) return static_cast(LK_UNUSABLE); IRTLASSERT(IsWriteLocked()); IRTLASSERT(pfnPredicate != NULL); if (!IsWriteLocked() || pfnPredicate == NULL) return 0; DWORD cActions = 0; for (DWORD iBkt = 0; iBkt < m_cActiveBuckets; ++iBkt) { CBucket* const pbkt = _Bucket(iBkt); IRTLASSERT(pbkt != NULL); pbkt->WriteLock(); for (CNodeClump* pncCurr = &pbkt->m_ncFirst, *pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(0 == _IsNodeCompact(pbkt)); IRTLASSERT(pncCurr->IsLastClump()); goto unlock; } else { rlkp = (*pfnPredicate)(pncCurr->m_pvNode[i], pvState); switch (rlkp) { case LKP_ABORT: pbkt->WriteUnlock(); return cActions; break; case LKP_NO_ACTION: // nothing to do break; case LKP_PERFORM: case LKP_PERFORM_STOP: case LKP_DELETE: case LKP_DELETE_STOP: { IRTLVERIFY(_DeleteNode(pbkt, pncCurr, pncPrev, i)); ++cActions; if (rlkp == LKP_PERFORM_STOP || rlkp == LKP_DELETE_STOP) { pbkt->WriteUnlock(); return cActions; } break; } default: IRTLASSERT(! "Unknown LK_PREDICATE in DeleteIf"); break; } } } } unlock: pbkt->WriteUnlock(); } return cActions; } // CLKRLinearHashTable::_DeleteIf #endif // LKR_APPLY_IF //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::CheckTable // Synopsis: Verify that all records are in the right place and can be located. // Returns: 0 => hash table is consistent // >0 => that many misplaced records // <0 => otherwise invalid //------------------------------------------------------------------------ int CLKRLinearHashTable::CheckTable() const { if (!IsUsable()) return LK_UNUSABLE; bool fReadLocked = _ReadOrWriteLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (!IsValid()) { _ReadOrWriteUnlock(fReadLocked); return LK_UNUSABLE; } int cMisplaced = 0; DWORD cRecords = 0; int retcode = 0; // Check every bucket for (DWORD i = 0; i < m_cActiveBuckets; i++) { CBucket* const pbkt = _Bucket(i); IRTLASSERT(pbkt != NULL); retcode += !(pbkt != NULL); pbkt->ReadLock(); IRTLASSERT(0 == _IsNodeCompact(pbkt)); // Walk the bucket chain for (CNodeClump* pncCurr = &pbkt->m_ncFirst, *pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { int j; FOR_EACH_NODE(j) { if (pncCurr->IsEmptySlot(j)) { IRTLASSERT(pncCurr->IsLastClump()); retcode += !(pncCurr->IsLastClump()); for (int k = j; k != NODE_END; k += NODE_STEP) { IRTLASSERT(pncCurr->IsEmptyNode(k)); retcode += !pncCurr->IsEmptyNode(k); IRTLASSERT(pncCurr->InvalidSignature(k)); retcode += !pncCurr->InvalidSignature(k); } break; } if (!pncCurr->IsEmptySlot(j)) { ++cRecords; const DWORD_PTR pnKey = _ExtractKey(pncCurr->m_pvNode[j]); DWORD dwSignature = _CalcKeyHash(pnKey); IRTLASSERT(dwSignature != HASH_INVALID_SIGNATURE); retcode += !(dwSignature != HASH_INVALID_SIGNATURE); IRTLASSERT(dwSignature == pncCurr->m_dwKeySigs[j]); retcode += !(dwSignature == pncCurr->m_dwKeySigs[j]); DWORD address = _BucketAddress(dwSignature); IRTLASSERT(address == i); retcode += !(address == i); if (address != i || dwSignature != pncCurr->m_dwKeySigs[j]) cMisplaced++; } else // pncCurr->IsEmptySlot(j) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(j)); retcode += !pncCurr->IsEmptyAndInvalid(j); } } if (pncPrev != NULL) { IRTLASSERT(pncPrev->m_pncNext == pncCurr); retcode += !(pncPrev->m_pncNext == pncCurr); } } pbkt->ReadUnlock(); } if (cRecords != m_cRecords) ++retcode; IRTLASSERT(cRecords == m_cRecords); retcode += !(cRecords == m_cRecords); if (cMisplaced > 0) retcode = cMisplaced; IRTLASSERT(cMisplaced == 0); retcode += !(cMisplaced == 0); _ReadOrWriteUnlock(fReadLocked); return retcode; } // CLKRLinearHashTable::CheckTable //------------------------------------------------------------------------ // Function: CLKRHashTable::CheckTable // Synopsis: Verify that all records are in the right place and can be located. // Returns: 0 => hash table is consistent // >0 => that many misplaced records // <0 => otherwise invalid //------------------------------------------------------------------------ int CLKRHashTable::CheckTable() const { if (!IsUsable()) return LK_UNUSABLE; int retcode = 0; for (DWORD i = 0; i < m_cSubTables; i++) retcode += m_palhtDir[i]->CheckTable(); return retcode; } // CLKRHashTable::CheckTable //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_Clear // Synopsis: Remove all data from the table //------------------------------------------------------------------------ void CLKRLinearHashTable::_Clear( bool fShrinkDirectory) // Shrink to min size but don't destroy entirely? { if (!IsUsable()) return; IRTLASSERT(IsWriteLocked()); // If we're Clear()ing the table AND the table has no records, we // can return immediately. The dtor, however, must clean up completely. if (fShrinkDirectory && 0 == m_cRecords) return; #ifdef IRTLDEBUG DWORD cDeleted = 0; DWORD cOldRecords = m_cRecords; #endif // IRTLDEBUG for (DWORD iBkt = 0; iBkt < m_cActiveBuckets; ++iBkt) { CBucket* const pbkt = _Bucket(iBkt); IRTLASSERT(pbkt != NULL); pbkt->WriteLock(); IRTLASSERT(0 == _IsNodeCompact(pbkt)); for (CNodeClump* pncCurr = &pbkt->m_ncFirst, *pncPrev = NULL; pncCurr != NULL; ) { int i; FOR_EACH_NODE(i) { if (pncCurr->IsEmptySlot(i)) { IRTLASSERT(pncCurr->IsEmptyAndInvalid(i)); IRTLASSERT(pncCurr->IsLastClump()); break; } else { _AddRefRecord(pncCurr->m_pvNode[i], -1); pncCurr->m_pvNode[i] = NULL; pncCurr->m_dwKeySigs[i] = HASH_INVALID_SIGNATURE; m_cRecords--; #ifdef IRTLDEBUG ++cDeleted; #endif // IRTLDEBUG } } // for (i ... pncPrev = pncCurr; pncCurr = pncCurr->m_pncNext; pncPrev->m_pncNext = NULL; if (pncPrev != &pbkt->m_ncFirst) _FreeNodeClump(pncPrev); } // for (pncCurr ... pbkt->WriteUnlock(); } // for (iBkt ... IRTLASSERT(m_cRecords == 0 && cDeleted == cOldRecords); // delete all segments for (DWORD iSeg = 0; iSeg < m_cActiveBuckets; iSeg += m_dwSegSize) { _FreeSegment(_Segment(iSeg)); _Segment(iSeg) = NULL; } _FreeSegmentDirectory(); m_nLevel = m_cActiveBuckets = m_iExpansionIdx = 0; m_dwBktAddrMask0 = 1; m_dwBktAddrMask1 = (m_dwBktAddrMask0 << 1) | 1; // set directory of segments to minimum size if (fShrinkDirectory) { DWORD cInitialBuckets = 0; if (LK_SMALL_TABLESIZE == m_lkts) cInitialBuckets = CSmallSegment::INITSIZE; else if (LK_MEDIUM_TABLESIZE == m_lkts) cInitialBuckets = CMediumSegment::INITSIZE; else if (LK_LARGE_TABLESIZE == m_lkts) cInitialBuckets = CLargeSegment::INITSIZE; else IRTLASSERT(! "Unknown LK_TABLESIZE"); _SetSegVars(m_lkts, cInitialBuckets); } } // CLKRLinearHashTable::_Clear //------------------------------------------------------------------------ // Function: CLKRHashTable::Clear // Synopsis: Remove all data from the table //------------------------------------------------------------------------ void CLKRHashTable::Clear() { WriteLock(); for (DWORD i = 0; i < m_cSubTables; i++) m_palhtDir[i]->_Clear(true); WriteUnlock(); } // CLKRHashTable::Clear //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::GetStatistics // Synopsis: Gather statistics about the table //------------------------------------------------------------------------ CLKRHashTableStats CLKRLinearHashTable::GetStatistics() const { CLKRHashTableStats stats; if (!IsUsable()) return stats; if (m_paDirSegs != NULL) { stats.RecordCount = m_cRecords; stats.TableSize = m_cActiveBuckets; stats.SplitFactor = static_cast(m_iExpansionIdx) / (1ui64 << m_nLevel); stats.DirectorySize = m_cDirSegs; stats.NodeClumpSize = NODES_PER_CLUMP; stats.CBucketSize = sizeof(CBucket); #ifdef LOCK_INSTRUMENTATION stats.m_alsBucketsAvg.m_nContentions = 0; stats.m_alsBucketsAvg.m_nSleeps = 0; stats.m_alsBucketsAvg.m_nContentionSpins = 0; stats.m_alsBucketsAvg.m_nAverageSpins = 0; stats.m_alsBucketsAvg.m_nReadLocks = 0; stats.m_alsBucketsAvg.m_nWriteLocks = 0; stats.m_alsBucketsAvg.m_nItems = 0; #endif // LOCK_INSTRUMENTATION int empty = 0; int totacc = 0; int low_count = 0; int high_count = 0; int max_length = 0; for (DWORD i = 0; i < m_cActiveBuckets; i++) { int acc = 0; for (CNodeClump* pncCurr = &_Bucket(i)->m_ncFirst; pncCurr != NULL; pncCurr = pncCurr->m_pncNext) { int j; FOR_EACH_NODE(j) { if (!pncCurr->IsEmptySlot(j)) { acc++; totacc += acc; int iBucketIndex = stats.BucketIndex(acc); ++stats.m_aBucketLenHistogram[iBucketIndex]; } } } #ifdef LOCK_INSTRUMENTATION CLockStatistics ls = _Bucket(i)->LockStats(); stats.m_alsBucketsAvg.m_nContentions += ls.m_nContentions; stats.m_alsBucketsAvg.m_nSleeps += ls.m_nSleeps; stats.m_alsBucketsAvg.m_nContentionSpins += ls.m_nContentionSpins; stats.m_alsBucketsAvg.m_nAverageSpins += ls.m_nAverageSpins; stats.m_alsBucketsAvg.m_nReadLocks += ls.m_nReadLocks; stats.m_alsBucketsAvg.m_nWriteLocks += ls.m_nWriteLocks; stats.m_alsBucketsAvg.m_nItems ++; #endif // LOCK_INSTRUMENTATION max_length = max(max_length, acc); if (acc == 0) empty++; if (_H0(i) < m_iExpansionIdx) { low_count += acc; } else { high_count += acc; } } stats.LongestChain = max_length; stats.EmptySlots = empty; if (m_cActiveBuckets > 0) { if (m_cRecords > 0) { double x=static_cast(m_iExpansionIdx) /(1ui64 << m_nLevel); double alpha= static_cast(m_cRecords)/m_cActiveBuckets; double low_sl = 0.0; double high_sl = 0.0; stats.AvgSearchLength= static_cast(totacc) /m_cRecords; stats.ExpSearchLength = 1 + alpha * 0.25 * (2 + x - x*x); if (m_iExpansionIdx > 0) low_sl = static_cast(low_count) / (2.0 * m_iExpansionIdx); if (m_cActiveBuckets - 2 * m_iExpansionIdx > 0) high_sl = static_cast(high_count) / (m_cActiveBuckets - 2.0 * m_iExpansionIdx); stats.AvgUSearchLength = low_sl * x + high_sl * (1.0 - x); stats.ExpUSearchLength = alpha * 0.5 * (2 + x - x*x); } #ifdef LOCK_INSTRUMENTATION stats.m_alsBucketsAvg.m_nContentions /= m_cActiveBuckets; stats.m_alsBucketsAvg.m_nSleeps /= m_cActiveBuckets; stats.m_alsBucketsAvg.m_nContentionSpins /= m_cActiveBuckets; stats.m_alsBucketsAvg.m_nAverageSpins /= m_cActiveBuckets; stats.m_alsBucketsAvg.m_nReadLocks /= m_cActiveBuckets; stats.m_alsBucketsAvg.m_nWriteLocks /= m_cActiveBuckets; #endif // LOCK_INSTRUMENTATION } else { stats.AvgSearchLength = 0.0; stats.ExpSearchLength = 0.0; stats.AvgUSearchLength = 0.0; stats.ExpUSearchLength = 0.0; } } #ifdef LOCK_INSTRUMENTATION stats.m_gls = TableLock::GlobalStatistics(); CLockStatistics ls = _LockStats(); stats.m_alsTable.m_nContentions = ls.m_nContentions; stats.m_alsTable.m_nSleeps = ls.m_nSleeps; stats.m_alsTable.m_nContentionSpins = ls.m_nContentionSpins; stats.m_alsTable.m_nAverageSpins = ls.m_nAverageSpins; stats.m_alsTable.m_nReadLocks = ls.m_nReadLocks; stats.m_alsTable.m_nWriteLocks = ls.m_nWriteLocks; stats.m_alsTable.m_nItems = 1; #endif // LOCK_INSTRUMENTATION return stats; } // CLKRLinearHashTable::GetStatistics //------------------------------------------------------------------------ // Function: CLKRHashTable::GetStatistics // Synopsis: Gather statistics about the table //------------------------------------------------------------------------ CLKRHashTableStats CLKRHashTable::GetStatistics() const { CLKRHashTableStats hts; if (!IsUsable()) return hts; for (DWORD i = 0; i < m_cSubTables; i++) { CLKRHashTableStats stats = m_palhtDir[i]->GetStatistics(); hts.RecordCount += stats.RecordCount; hts.TableSize += stats.TableSize; hts.DirectorySize += stats.DirectorySize; hts.LongestChain = max(hts.LongestChain, stats.LongestChain); hts.EmptySlots += stats.EmptySlots; hts.SplitFactor += stats.SplitFactor; hts.AvgSearchLength += stats.AvgSearchLength; hts.ExpSearchLength += stats.ExpSearchLength; hts.AvgUSearchLength += stats.AvgUSearchLength; hts.ExpUSearchLength += stats.ExpUSearchLength; hts.NodeClumpSize = stats.NodeClumpSize; hts.CBucketSize = stats.CBucketSize; for (int j = 0; j < CLKRHashTableStats::MAX_BUCKETS; ++j) hts.m_aBucketLenHistogram[j] += stats.m_aBucketLenHistogram[j]; #ifdef LOCK_INSTRUMENTATION hts.m_alsTable.m_nContentions += stats.m_alsTable.m_nContentions; hts.m_alsTable.m_nSleeps += stats.m_alsTable.m_nSleeps; hts.m_alsTable.m_nContentionSpins += stats.m_alsTable.m_nContentionSpins; hts.m_alsTable.m_nAverageSpins += stats.m_alsTable.m_nAverageSpins; hts.m_alsTable.m_nReadLocks += stats.m_alsTable.m_nReadLocks; hts.m_alsTable.m_nWriteLocks += stats.m_alsTable.m_nWriteLocks; hts.m_alsBucketsAvg.m_nContentions += stats.m_alsBucketsAvg.m_nContentions; hts.m_alsBucketsAvg.m_nSleeps += stats.m_alsBucketsAvg.m_nSleeps; hts.m_alsBucketsAvg.m_nContentionSpins += stats.m_alsBucketsAvg.m_nContentionSpins; hts.m_alsBucketsAvg.m_nAverageSpins += stats.m_alsBucketsAvg.m_nAverageSpins; hts.m_alsBucketsAvg.m_nReadLocks += stats.m_alsBucketsAvg.m_nReadLocks; hts.m_alsBucketsAvg.m_nWriteLocks += stats.m_alsBucketsAvg.m_nWriteLocks; hts.m_alsBucketsAvg.m_nItems += stats.m_alsBucketsAvg.m_nItems; hts.m_gls = stats.m_gls; #endif // LOCK_INSTRUMENTATION } // Average out the subtables statistics. (Does this make sense // for all of these fields?) hts.DirectorySize /= m_cSubTables; hts.SplitFactor /= m_cSubTables; hts.AvgSearchLength /= m_cSubTables; hts.ExpSearchLength /= m_cSubTables; hts.AvgUSearchLength /= m_cSubTables; hts.ExpUSearchLength /= m_cSubTables; #ifdef LOCK_INSTRUMENTATION hts.m_alsTable.m_nContentions /= m_cSubTables; hts.m_alsTable.m_nSleeps /= m_cSubTables; hts.m_alsTable.m_nContentionSpins /= m_cSubTables; hts.m_alsTable.m_nAverageSpins /= m_cSubTables; hts.m_alsTable.m_nReadLocks /= m_cSubTables; hts.m_alsTable.m_nWriteLocks /= m_cSubTables; hts.m_alsTable.m_nItems = m_cSubTables; hts.m_alsBucketsAvg.m_nContentions /= m_cSubTables; hts.m_alsBucketsAvg.m_nSleeps /= m_cSubTables; hts.m_alsBucketsAvg.m_nContentionSpins /= m_cSubTables; hts.m_alsBucketsAvg.m_nAverageSpins /= m_cSubTables; hts.m_alsBucketsAvg.m_nReadLocks /= m_cSubTables; hts.m_alsBucketsAvg.m_nWriteLocks /= m_cSubTables; #endif // LOCK_INSTRUMENTATION return hts; } // CLKRHashTable::GetStatistics //----------------------------------------------------------------------- // Function: CLKRLinearHashTable::_SetSegVars // Synopsis: sets the size-specific segment variables //----------------------------------------------------------------------- LK_RETCODE CLKRLinearHashTable::_SetSegVars( LK_TABLESIZE lkts, DWORD cInitialBuckets) { switch (lkts) { case LK_SMALL_TABLESIZE: { m_lkts = LK_SMALL_TABLESIZE; m_dwSegBits = CSmallSegment::SEGBITS; m_dwSegSize = CSmallSegment::SEGSIZE; m_dwSegMask = CSmallSegment::SEGMASK; STATIC_ASSERT(CSmallSegment::SEGSIZE == (1U< 0); IRTLASSERT(m_nLevel == m_dwSegBits); IRTLASSERT(m_dwBktAddrMask0 == (1U << m_nLevel) - 1); IRTLASSERT(m_dwBktAddrMask1 == ((m_dwBktAddrMask0 << 1) | 1)); IRTLASSERT(m_dwSegBits > 0); IRTLASSERT(m_dwSegSize == (1U << m_dwSegBits)); IRTLASSERT(m_dwSegMask == (m_dwSegSize - 1)); IRTLASSERT(m_dwBktAddrMask0 == m_dwSegMask); // adjust m_dwBktAddrMask0 (== m_dwSegMask) to make it large // enough to distribute the buckets across the address space for (DWORD tmp = m_cActiveBuckets >> m_dwSegBits; tmp > 1; tmp >>= 1) { ++m_nLevel; m_dwBktAddrMask0 = (m_dwBktAddrMask0 << 1) | 1; } m_dwBktAddrMask1 = (m_dwBktAddrMask0 << 1) | 1; IRTLASSERT(_H1(m_cActiveBuckets) == m_cActiveBuckets); m_iExpansionIdx = m_cActiveBuckets & m_dwBktAddrMask0; // create and clear directory of segments DWORD cDirSegs = MIN_DIRSIZE; while (cDirSegs < (m_cActiveBuckets >> m_dwSegBits)) cDirSegs <<= 1; cDirSegs = min(cDirSegs, MAX_DIRSIZE); IRTLASSERT((cDirSegs << m_dwSegBits) >= m_cActiveBuckets); m_lkrcState = LK_ALLOC_FAIL; m_paDirSegs = _AllocateSegmentDirectory(cDirSegs); if (m_paDirSegs != NULL) { m_cDirSegs = cDirSegs; IRTLASSERT(m_cDirSegs >= MIN_DIRSIZE && (m_cDirSegs & (m_cDirSegs-1)) == 0); // == (1 << N) // create and initialize only the required segments DWORD dwMaxSegs = (m_cActiveBuckets + m_dwSegSize - 1) >> m_dwSegBits; IRTLASSERT(dwMaxSegs <= m_cDirSegs); IRTLTRACE(TEXT("InitSegDir: m_lkts = %d, m_cActiveBuckets = %lu, ") TEXT("m_dwSegSize = %lu, bits = %lu\n") TEXT("m_cDirSegs = %lu, dwMaxSegs = %lu, ") TEXT("segment total size = %lu bytes\n"), m_lkts, m_cActiveBuckets, m_dwSegSize, m_dwSegBits, m_cDirSegs, dwMaxSegs, m_dwSegSize * sizeof(CBucket)); m_lkrcState = LK_SUCCESS; // so IsValid/IsUsable won't fail for (DWORD i = 0; i < dwMaxSegs; i++) { CSegment* pSeg = _AllocateSegment(); if (pSeg != NULL) m_paDirSegs[i].m_pseg = pSeg; else { // problem: deallocate everything m_lkrcState = LK_ALLOC_FAIL; for (DWORD j = i; j-- > 0; ) { _FreeSegment(m_paDirSegs[j].m_pseg); m_paDirSegs[j].m_pseg = NULL; } _FreeSegmentDirectory(); break; } } } if (m_lkrcState != LK_SUCCESS) { m_paDirSegs = NULL; m_cDirSegs = m_cActiveBuckets = m_iExpansionIdx = 0; // Propagate error back up to parent (if it exists). This ensures // that all of the parent's public methods will start failing. if (NULL != m_phtParent) m_phtParent->m_lkrcState = m_lkrcState; } return m_lkrcState; } // CLKRLinearHashTable::_SetSegVars #include LONG g_cAllocDirEntry = 0; LONG g_cAllocNodeClump = 0; LONG g_cAllocSmallSegment = 0; LONG g_cAllocMediumSegment = 0; LONG g_cAllocLargeSegment = 0; extern "C" __declspec(dllexport) bool GetAllocCounters() { return true; } // #define LKR_RANDOM_MEMORY_FAILURES 1000 // 1..RAND_MAX (32767) // Memory allocation wrappers to allow us to simulate allocation // failures during testing //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_AllocateSegmentDirectory // Synopsis: //------------------------------------------------------------------------ CDirEntry* const CLKRLinearHashTable::_AllocateSegmentDirectory( size_t n) { #ifdef LKR_RANDOM_MEMORY_FAILURES if (rand() < LKR_RANDOM_MEMORY_FAILURES) return NULL; #endif // LKR_RANDOM_MEMORY_FAILURES // InterlockedIncrement(&g_cAllocDirEntry); CDirEntry* const paDirSegs = new CDirEntry [n]; #ifdef IRTLDEBUG for (size_t i = 0; i < n; ++i) IRTLASSERT(paDirSegs[i].m_pseg == NULL); #endif // IRTLDEBUG return paDirSegs; } // CLKRLinearHashTable::_AllocateSegmentDirectory //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_FreeSegmentDirectory // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::_FreeSegmentDirectory() { #ifdef IRTLDEBUG for (size_t i = 0; i < m_cDirSegs; ++i) IRTLASSERT(m_paDirSegs[i].m_pseg == NULL); #endif // IRTLDEBUG delete [] m_paDirSegs; m_paDirSegs = NULL; m_cDirSegs = 0; return true; } // CLKRLinearHashTable::_FreeSegmentDirectory //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_AllocateNodeClump // Synopsis: //------------------------------------------------------------------------ CNodeClump* const CLKRLinearHashTable::_AllocateNodeClump() { #ifdef LKR_RANDOM_MEMORY_FAILURES if (rand() < LKR_RANDOM_MEMORY_FAILURES) return NULL; #endif // LKR_RANDOM_MEMORY_FAILURES // InterlockedIncrement(&g_cAllocNodeClump); return new CNodeClump; } // CLKRLinearHashTable::_AllocateNodeClump //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_FreeNodeClump // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::_FreeNodeClump( CNodeClump* pnc) { delete pnc; return true; } // CLKRLinearHashTable::_FreeNodeClump //----------------------------------------------------------------------- // Function: CLKRLinearHashTable::_AllocateSegment // Synopsis: creates a new segment of the approriate size // Output: pointer to the new segment; NULL => failure //----------------------------------------------------------------------- CSegment* const CLKRLinearHashTable::_AllocateSegment( ) const { #ifdef LKR_RANDOM_MEMORY_FAILURES if (rand() < LKR_RANDOM_MEMORY_FAILURES) return NULL; #endif // LKR_RANDOM_MEMORY_FAILURES STATIC_ASSERT(offsetof(CSegment, m_bktSlots) + sizeof(CBucket) == offsetof(CSmallSegment, m_bktSlots2)); STATIC_ASSERT(offsetof(CSegment, m_bktSlots) + sizeof(CBucket) == offsetof(CMediumSegment, m_bktSlots2)); STATIC_ASSERT(offsetof(CSegment, m_bktSlots) + sizeof(CBucket) == offsetof(CLargeSegment, m_bktSlots2)); CSegment* pseg = NULL; switch (m_lkts) { case LK_SMALL_TABLESIZE: #ifdef LKRHASH_ALLOCATOR_NEW IRTLASSERT(CSmallSegment::sm_palloc != NULL); #endif // LKRHASH_ALLOCATOR_NEW // InterlockedIncrement(&g_cAllocSmallSegment); pseg = new CSmallSegment; break; default: IRTLASSERT(! "Unknown LK_TABLESIZE"); // fall-through case LK_MEDIUM_TABLESIZE: #ifdef LKRHASH_ALLOCATOR_NEW IRTLASSERT(CMediumSegment::sm_palloc != NULL); #endif // LKRHASH_ALLOCATOR_NEW // InterlockedIncrement(&g_cAllocMediumSegment); pseg = new CMediumSegment; break; case LK_LARGE_TABLESIZE: #ifdef LKRHASH_ALLOCATOR_NEW IRTLASSERT(CLargeSegment::sm_palloc != NULL); #endif // LKRHASH_ALLOCATOR_NEW // InterlockedIncrement(&g_cAllocLargeSegment); pseg = new CLargeSegment; break; } IRTLASSERT(pseg != NULL); if (pseg != NULL && BucketLock::PerLockSpin() == LOCK_INDIVIDUAL_SPIN) { for (DWORD i = 0; i < m_dwSegSize; ++i) pseg->Slot(i).SetSpinCount(m_wBucketLockSpins); } return pseg; } // CLKRLinearHashTable::_AllocateSegment //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_FreeSegment // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::_FreeSegment( CSegment* pseg) const { switch (m_lkts) { case LK_SMALL_TABLESIZE: delete static_cast(pseg); break; default: IRTLASSERT(! "Unknown LK_TABLESIZE"); // fall-through case LK_MEDIUM_TABLESIZE: delete static_cast(pseg); break; case LK_LARGE_TABLESIZE: delete static_cast(pseg); break; } return true; } // CLKRLinearHashTable::_FreeSegment //------------------------------------------------------------------------ // Function: CLKRHashTable::_AllocateSubTableArray // Synopsis: //------------------------------------------------------------------------ CLKRHashTable::SubTable** const CLKRHashTable::_AllocateSubTableArray( size_t n) { #ifdef LKR_RANDOM_MEMORY_FAILURES if (rand() < LKR_RANDOM_MEMORY_FAILURES) return NULL; #endif // LKR_RANDOM_MEMORY_FAILURES return new SubTable* [n]; } // CLKRHashTable::_AllocateSubTableArray //------------------------------------------------------------------------ // Function: CLKRHashTable::_FreeSubTableArray // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::_FreeSubTableArray( CLKRHashTable::SubTable** palht) { delete [] palht; return true; } // CLKRHashTable::_FreeSubTableArray //------------------------------------------------------------------------ // Function: CLKRHashTable::_AllocateSubTable // Synopsis: //------------------------------------------------------------------------ CLKRHashTable::SubTable* const CLKRHashTable::_AllocateSubTable( LPCSTR pszName, // An identifier for debugging PFnExtractKey pfnExtractKey, // Extract key from record PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key PFnEqualKeys pfnEqualKeys, // Compare two keys PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc double maxload, // Upperbound on average chain length DWORD initsize, // Initial size of hash table. CLKRHashTable* phtParent, // Owning table. bool fMultiKeys // Allow multiple identical keys? ) { #ifdef LKR_RANDOM_MEMORY_FAILURES if (rand() < LKR_RANDOM_MEMORY_FAILURES) return NULL; #endif // LKR_RANDOM_MEMORY_FAILURES return new SubTable(pszName, pfnExtractKey, pfnCalcKeyHash, pfnEqualKeys, pfnAddRefRecord, maxload, initsize, phtParent, fMultiKeys); } // CLKRHashTable::_AllocateSubTable //------------------------------------------------------------------------ // Function: CLKRHashTable::_FreeSubTable // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::_FreeSubTable( CLKRHashTable::SubTable* plht) { delete plht; return true; } // CLKRHashTable::_FreeSubTable //----------------------------------------------------------------------- // Function: CLKRLinearHashTable::_Expand // Synopsis: Expands the table by one bucket. Done by splitting the // bucket pointed to by m_iExpansionIdx. // Output: LK_SUCCESS, if expansion was successful. // LK_ALLOC_FAIL, if expansion failed due to lack of memory. //----------------------------------------------------------------------- LK_RETCODE CLKRLinearHashTable::_Expand() { if (m_cActiveBuckets >= MAX_DIRSIZE * m_dwSegSize - 1) return LK_ALLOC_FAIL; // table is not allowed to grow any more WriteLock(); // double segment directory size if necessary if (m_cActiveBuckets >= m_cDirSegs * m_dwSegSize) { IRTLASSERT(m_cDirSegs < MAX_DIRSIZE); DWORD cDirSegsNew = (m_cDirSegs == 0) ? MIN_DIRSIZE : m_cDirSegs << 1; CDirEntry* paDirSegsNew = _AllocateSegmentDirectory(cDirSegsNew); if (paDirSegsNew != NULL) { for (DWORD j = 0; j < m_cDirSegs; j++) { paDirSegsNew[j] = m_paDirSegs[j]; m_paDirSegs[j].m_pseg = NULL; } _FreeSegmentDirectory(); m_paDirSegs = paDirSegsNew; m_cDirSegs = cDirSegsNew; } else { WriteUnlock(); return LK_ALLOC_FAIL; // expansion failed } } // locate the new bucket, creating a new segment if necessary ++m_cActiveBuckets; DWORD dwOldBkt = m_iExpansionIdx; DWORD dwNewBkt = (1 << m_nLevel) | dwOldBkt; IRTLASSERT(dwOldBkt < m_cActiveBuckets); IRTLASSERT(dwNewBkt < m_cActiveBuckets); IRTLASSERT(_Segment(dwOldBkt) != NULL); CSegment* psegNew = _Segment(dwNewBkt); if (psegNew == NULL) { psegNew = _AllocateSegment(); if (psegNew == NULL) { --m_cActiveBuckets; WriteUnlock(); return LK_ALLOC_FAIL; // expansion failed } _Segment(dwNewBkt) = psegNew; } // prepare to relocate records to the new bucket CBucket* pbktOld = _Bucket(dwOldBkt); CBucket* pbktNew = _Bucket(dwNewBkt); // get locks on the two buckets involved pbktOld->WriteLock(); pbktNew->WriteLock(); // Now work out if we need to allocate any extra CNodeClumps. We do // this up front, before calling _SplitRecordSet, as it's hard to // gracefully recover from the depths of that routine should we run // out of memory. CNodeClump* pncFreeList = NULL; LK_RETCODE lkrc = LK_SUCCESS; // If the old bucket has more than one CNodeClump, there's a chance that // we'll need extra CNodeClumps in the new bucket too. If it doesn't, // we definitely won't. One CNodeClump is enough to prime the freelist. if (!pbktOld->m_ncFirst.IsLastClump()) { pncFreeList = _AllocateNodeClump(); if (pncFreeList == NULL) { lkrc = LK_ALLOC_FAIL; --m_cActiveBuckets; } } // adjust expansion pointer, level, and mask if (lkrc == LK_SUCCESS) { if (++m_iExpansionIdx == (1U << m_nLevel)) { ++m_nLevel; m_iExpansionIdx = 0; m_dwBktAddrMask0 = (m_dwBktAddrMask0 << 1) | 1; // m_dwBktAddrMask0 = 00011..111 IRTLASSERT((m_dwBktAddrMask0 & (m_dwBktAddrMask0+1)) == 0); m_dwBktAddrMask1 = (m_dwBktAddrMask0 << 1) | 1; IRTLASSERT((m_dwBktAddrMask1 & (m_dwBktAddrMask1+1)) == 0); } } DWORD iExpansionIdx = m_iExpansionIdx; // save to avoid race conditions DWORD dwBktAddrMask = m_dwBktAddrMask0; // ditto // Release the table lock before doing the actual relocation WriteUnlock(); if (lkrc == LK_SUCCESS) { lkrc = _SplitRecordSet(&pbktOld->m_ncFirst, &pbktNew->m_ncFirst, iExpansionIdx, dwBktAddrMask, dwNewBkt, pncFreeList); } pbktNew->WriteUnlock(); pbktOld->WriteUnlock(); return lkrc; } // CLKRLinearHashTable::_Expand //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_SplitRecordSet // Synopsis: Split records between the old and new buckets. //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_SplitRecordSet( CNodeClump* pncOldTarget, CNodeClump* pncNewTarget, DWORD iExpansionIdx, DWORD dwBktAddrMask, DWORD dwNewBkt, CNodeClump* pncFreeList // list of free nodes available for reuse ) { CNodeClump ncFirst = *pncOldTarget; // save head of old target chain CNodeClump* pncOldList = &ncFirst; CNodeClump* pncTmp; int iOldSlot = NODE_BEGIN; int iNewSlot = NODE_BEGIN; // clear target buckets pncOldTarget->Clear(); pncNewTarget->Clear(); // scan through the old bucket chain and decide where to move each record while (pncOldList != NULL) { int i; FOR_EACH_NODE(i) { // node already empty? if (pncOldList->IsEmptySlot(i)) { IRTLASSERT(pncOldList->IsEmptyAndInvalid(i)); continue; } // calculate bucket address of this node DWORD dwBkt = _H0(pncOldList->m_dwKeySigs[i], dwBktAddrMask); if (dwBkt < iExpansionIdx) dwBkt = _H1(pncOldList->m_dwKeySigs[i], dwBktAddrMask); // record to be moved to the new address? if (dwBkt == dwNewBkt) { // node in new bucket chain full? if (iNewSlot == NODE_END) { // the calling routine has passed in a FreeList adequate // for all needs IRTLASSERT(pncFreeList != NULL); pncTmp = pncFreeList; pncFreeList = pncFreeList->m_pncNext; pncTmp->Clear(); pncNewTarget->m_pncNext = pncTmp; pncNewTarget = pncTmp; iNewSlot = NODE_BEGIN; } pncNewTarget->m_dwKeySigs[iNewSlot] = pncOldList->m_dwKeySigs[i]; pncNewTarget->m_pvNode[iNewSlot] = pncOldList->m_pvNode[i]; iNewSlot += NODE_STEP; } // no, record stays in its current bucket chain else { // node in old bucket chain full? if (iOldSlot == NODE_END) { // the calling routine has passed in a FreeList adequate // for all needs IRTLASSERT(pncFreeList != NULL); pncTmp = pncFreeList; pncFreeList = pncFreeList->m_pncNext; pncTmp->Clear(); pncOldTarget->m_pncNext = pncTmp; pncOldTarget = pncTmp; iOldSlot = NODE_BEGIN; } pncOldTarget->m_dwKeySigs[iOldSlot] = pncOldList->m_dwKeySigs[i]; pncOldTarget->m_pvNode[iOldSlot] = pncOldList->m_pvNode[i]; iOldSlot += NODE_STEP; } // clear old slot pncOldList->m_dwKeySigs[i] = HASH_INVALID_SIGNATURE; pncOldList->m_pvNode[i] = NULL; } // keep walking down the original bucket chain pncTmp = pncOldList; pncOldList = pncOldList->m_pncNext; // ncFirst is a stack variable, not allocated on the heap if (pncTmp != &ncFirst) { pncTmp->m_pncNext = pncFreeList; pncFreeList = pncTmp; } } // delete any leftover nodes while (pncFreeList != NULL) { pncTmp = pncFreeList; pncFreeList = pncFreeList->m_pncNext; #ifdef IRTLDEBUG pncTmp->m_pncNext = NULL; // or ~CNodeClump will ASSERT #endif // IRTLDEBUG _FreeNodeClump(pncTmp); } #ifdef IRTLDEBUG ncFirst.m_pncNext = NULL; // or ~CNodeClump will ASSERT #endif // IRTLDEBUG return LK_SUCCESS; } // CLKRLinearHashTable::_SplitRecordSet //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_Contract // Synopsis: Contract the table by deleting the last bucket in the active // address space. Return the records to the "buddy" of the // deleted bucket. //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_Contract() { WriteLock(); IRTLASSERT(m_cActiveBuckets >= m_dwSegSize); // Always keep at least m_dwSegSize buckets in the table; // i.e., one segment's worth. if (m_cActiveBuckets <= m_dwSegSize) { WriteUnlock(); return LK_ALLOC_FAIL; } // update the state variables (expansion ptr, level and mask) if (m_iExpansionIdx > 0) --m_iExpansionIdx; else { --m_nLevel; m_iExpansionIdx = (1 << m_nLevel) - 1; IRTLASSERT(m_nLevel > 0 && m_iExpansionIdx > 0); m_dwBktAddrMask0 >>= 1; IRTLASSERT((m_dwBktAddrMask0 & (m_dwBktAddrMask0+1)) == 0); // 00011..111 m_dwBktAddrMask1 >>= 1; IRTLASSERT(m_dwBktAddrMask1 == ((m_dwBktAddrMask0 << 1) | 1)); IRTLASSERT((m_dwBktAddrMask1 & (m_dwBktAddrMask1+1)) == 0); } // The last bucket is the one that will be emptied CBucket* pbktLast = _Bucket(m_cActiveBuckets - 1); pbktLast->WriteLock(); // Decrement after calculating pbktLast, or _Bucket() will assert. --m_cActiveBuckets; // Where the nodes from pbktLast will end up CBucket* pbktNew = _Bucket(m_iExpansionIdx); pbktNew->WriteLock(); // Now we work out if we need to allocate any extra CNodeClumps. We do // this up front, before calling _MergeRecordSets, as it's hard to // gracefully recover from the depths of that routine should we run // out of memory. CNodeClump* pnc; int c = 0; // First, count the number of items in the old bucket for (pnc = &pbktLast->m_ncFirst; pnc != NULL; pnc = pnc->m_pncNext) { int i; FOR_EACH_NODE(i) { if (!pnc->IsEmptySlot(i)) { IRTLASSERT(!pnc->IsEmptyAndInvalid(i)); c++; } } } // Then, subtract off the number of empty slots in the new bucket for (pnc = &pbktNew->m_ncFirst; pnc != NULL; pnc = pnc->m_pncNext) { int i; FOR_EACH_NODE(i) { if (pnc->IsEmptySlot(i)) { IRTLASSERT(pnc->IsEmptyAndInvalid(i)); c--; } } } CNodeClump* pncFreeList = NULL; // list of nodes available for reuse LK_RETCODE lkrc = LK_SUCCESS; // Do we need to allocate CNodeClumps to accommodate the surplus items? if (c > 0) { pncFreeList = _AllocateNodeClump(); if (pncFreeList == NULL) lkrc = LK_ALLOC_FAIL; else if (c > NODES_PER_CLUMP) { // In the worst case, we need a 2-element freelist for // _MergeRecordSets. Two CNodeClumps always suffice since the // freelist will be augmented by the CNodeClumps from the old // bucket as they are processed. pnc = _AllocateNodeClump(); if (pnc == NULL) { _FreeNodeClump(pncFreeList); lkrc = LK_ALLOC_FAIL; } else pncFreeList->m_pncNext = pnc; } } // Abort if we couldn't allocate enough CNodeClumps if (lkrc != LK_SUCCESS) { // undo the changes to the state variables if (++m_iExpansionIdx == (1U << m_nLevel)) { ++m_nLevel; m_iExpansionIdx = 0; m_dwBktAddrMask0 = (m_dwBktAddrMask0 << 1) | 1; m_dwBktAddrMask1 = (m_dwBktAddrMask0 << 1) | 1; } ++m_cActiveBuckets; // Unlock the buckets and the table pbktLast->WriteUnlock(); pbktNew->WriteUnlock(); WriteUnlock(); return lkrc; } // Copy the chain of records from pbktLast CNodeClump ncOldFirst = pbktLast->m_ncFirst; // destroy pbktLast pbktLast->m_ncFirst.Clear(); pbktLast->WriteUnlock(); // remove segment, if empty if (_SegIndex(m_cActiveBuckets) == 0) { #ifdef IRTLDEBUG // double-check that the supposedly empty segment is really empty IRTLASSERT(_Segment(m_cActiveBuckets) != NULL); for (DWORD i = 0; i < m_dwSegSize; ++i) { CBucket* pbkt = &_Segment(m_cActiveBuckets)->Slot(i); IRTLASSERT(pbkt->IsWriteUnlocked() && pbkt->IsReadUnlocked()); IRTLASSERT(pbkt->m_ncFirst.IsLastClump()); int j; FOR_EACH_NODE(j) { IRTLASSERT(pbkt->m_ncFirst.IsEmptyAndInvalid(j)); } } #endif // IRTLDEBUG _FreeSegment(_Segment(m_cActiveBuckets)); _Segment(m_cActiveBuckets) = NULL; } // reduce directory of segments if possible if (m_cActiveBuckets <= (m_cDirSegs * m_dwSegSize) >> 1 && m_cDirSegs > MIN_DIRSIZE) { DWORD cDirSegsNew = m_cDirSegs >> 1; CDirEntry* paDirSegsNew = _AllocateSegmentDirectory(cDirSegsNew); // Memory allocation failure here does not require us to abort; it // just means that the directory of segments is larger than we'd like. if (paDirSegsNew != NULL) { for (DWORD j = 0; j < cDirSegsNew; j++) paDirSegsNew[j] = m_paDirSegs[j]; for (j = 0; j < m_cDirSegs; j++) m_paDirSegs[j].m_pseg = NULL; _FreeSegmentDirectory(); m_paDirSegs = paDirSegsNew; m_cDirSegs = cDirSegsNew; } } // release the table lock before doing the reorg WriteUnlock(); lkrc = _MergeRecordSets(pbktNew, &ncOldFirst, pncFreeList); pbktNew->WriteUnlock(); #ifdef IRTLDEBUG ncOldFirst.m_pncNext = NULL; // or ~CNodeClump will ASSERT #endif // IRTLDEBUG return lkrc; } // CLKRLinearHashTable::_Contract //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_MergeRecordSets // Synopsis: Merge two record sets. Copy the contents of pncOldList // into pbktNewTarget. //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_MergeRecordSets( CBucket* pbktNewTarget, CNodeClump* pncOldList, CNodeClump* pncFreeList ) { IRTLASSERT(pbktNewTarget != NULL && pncOldList != NULL); CNodeClump* pncTmp = NULL; CNodeClump* const pncOldFirst = pncOldList; CNodeClump* pncNewTarget = &pbktNewTarget->m_ncFirst; int iNewSlot; // find the first nodeclump in the new target bucket with an empty slot while (!pncNewTarget->IsLastClump()) { FOR_EACH_NODE(iNewSlot) { if (pncNewTarget->IsEmptySlot(iNewSlot)) break; } if (iNewSlot == NODE_END) pncNewTarget = pncNewTarget->m_pncNext; else break; } IRTLASSERT(pncNewTarget != NULL); // find the first empty slot in pncNewTarget; // if none, iNewSlot == NODE_END FOR_EACH_NODE(iNewSlot) { if (pncNewTarget->IsEmptySlot(iNewSlot)) { break; } } while (pncOldList != NULL) { int i; FOR_EACH_NODE(i) { if (!pncOldList->IsEmptySlot(i)) { // any empty slots left in pncNewTarget? if (iNewSlot == NODE_END) { // no, so walk down pncNewTarget until we find another // empty slot while (!pncNewTarget->IsLastClump()) { pncNewTarget = pncNewTarget->m_pncNext; FOR_EACH_NODE(iNewSlot) { if (pncNewTarget->IsEmptySlot(iNewSlot)) goto found_slot; } } // Oops, reached the last nodeclump in pncNewTarget // and it's full. Get a new nodeclump off the free // list, which is big enough to handle all needs. IRTLASSERT(pncNewTarget != NULL); IRTLASSERT(pncFreeList != NULL); pncTmp = pncFreeList; pncFreeList = pncFreeList->m_pncNext; pncTmp->Clear(); pncNewTarget->m_pncNext = pncTmp; pncNewTarget = pncTmp; iNewSlot = NODE_BEGIN; } found_slot: // We have an empty slot in pncNewTarget IRTLASSERT(0 <= iNewSlot && iNewSlot < NODES_PER_CLUMP && pncNewTarget != NULL && pncNewTarget->IsEmptyAndInvalid(iNewSlot)); // Let's copy the node from pncOldList pncNewTarget->m_dwKeySigs[iNewSlot] = pncOldList->m_dwKeySigs[i]; pncNewTarget->m_pvNode[iNewSlot] = pncOldList->m_pvNode[i]; // Clear old slot pncOldList->m_dwKeySigs[i] = HASH_INVALID_SIGNATURE; pncOldList->m_pvNode[i] = NULL; // find the next free slot in pncNewTarget while ((iNewSlot += NODE_STEP) != NODE_END) { if (pncNewTarget->IsEmptySlot(iNewSlot)) { break; } } } else // iNewSlot != NODE_END { IRTLASSERT(pncOldList->IsEmptyAndInvalid(i)); } } // Move into the next nodeclump in pncOldList pncTmp = pncOldList; pncOldList = pncOldList->m_pncNext; // Append to the free list. Don't put the first node of // pncOldList on the free list, as it's a stack variable. if (pncTmp != pncOldFirst) { pncTmp->m_pncNext = pncFreeList; pncFreeList = pncTmp; } } // delete any leftover nodes while (pncFreeList != NULL) { pncTmp = pncFreeList; pncFreeList = pncFreeList->m_pncNext; #ifdef IRTLDEBUG pncTmp->m_pncNext = NULL; // or ~CNodeClump will ASSERT #endif // IRTLDEBUG _FreeNodeClump(pncTmp); } return LK_SUCCESS; } // CLKRLinearHashTable::_MergeRecordSets #ifdef LKR_DEPRECATED_ITERATORS //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_InitializeIterator // Synopsis: Make the iterator point to the first record in the hash table. //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_InitializeIterator( CIterator* piter) { if (!IsUsable()) return LK_UNUSABLE; IRTLASSERT(piter != NULL); IRTLASSERT(piter->m_lkl == LKL_WRITELOCK ? IsWriteLocked() : IsReadLocked()); if (piter == NULL || piter->m_plht != NULL) return LK_BAD_ITERATOR; piter->m_plht = this; piter->m_dwBucketAddr = 0; CBucket* pbkt = _Bucket(piter->m_dwBucketAddr); IRTLASSERT(pbkt != NULL); if (piter->m_lkl == LKL_WRITELOCK) pbkt->WriteLock(); else pbkt->ReadLock(); piter->m_pnc = &pbkt->m_ncFirst; piter->m_iNode = NODE_BEGIN - NODE_STEP; // Let IncrementIterator do the hard work of finding the first // slot in use. return IncrementIterator(piter); } // CLKRLinearHashTable::_InitializeIterator //------------------------------------------------------------------------ // Function: CLKRHashTable::InitializeIterator // Synopsis: make the iterator point to the first record in the hash table //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::InitializeIterator( CIterator* piter) { if (!IsUsable()) return LK_UNUSABLE; IRTLASSERT(piter != NULL && piter->m_pht == NULL); if (piter == NULL || piter->m_pht != NULL) return LK_BAD_ITERATOR; // First, lock all the subtables if (piter->m_lkl == LKL_WRITELOCK) WriteLock(); else ReadLock(); // Must call IsValid inside a lock to ensure that none of the state // variables change while it's being evaluated IRTLASSERT(IsValid()); if (!IsValid()) return LK_UNUSABLE; piter->m_pht = this; piter->m_ist = -1; piter->m_plht = NULL; // Let IncrementIterator do the hard work of finding the first // valid node in the subtables. return IncrementIterator(piter); } // CLKRHashTable::InitializeIterator //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::IncrementIterator // Synopsis: move the iterator on to the next record in the hash table //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::IncrementIterator( CIterator* piter) { if (!IsUsable()) return LK_UNUSABLE; IRTLASSERT(piter != NULL); IRTLASSERT(piter->m_plht == this); IRTLASSERT(piter->m_lkl == LKL_WRITELOCK ? IsWriteLocked() : IsReadLocked()); IRTLASSERT(piter->m_dwBucketAddr < m_cActiveBuckets); IRTLASSERT(piter->m_pnc != NULL); IRTLASSERT((0 <= piter->m_iNode && piter->m_iNode < NODES_PER_CLUMP) || (NODE_BEGIN - NODE_STEP == piter->m_iNode)); if (piter == NULL || piter->m_plht != this) return LK_BAD_ITERATOR; const void* pvRecord = NULL; if (piter->m_iNode != NODE_BEGIN - NODE_STEP) { // Release the reference acquired in the previous call to // IncrementIterator pvRecord = piter->m_pnc->m_pvNode[piter->m_iNode]; _AddRefRecord(pvRecord, -1); } do { do { // find the next slot in the nodeclump that's in use while ((piter->m_iNode += NODE_STEP) != NODE_END) { pvRecord = piter->m_pnc->m_pvNode[piter->m_iNode]; if (pvRecord != NULL) { // Add a new reference _AddRefRecord(pvRecord, +1); return LK_SUCCESS; } else // pvRecord == NULL { #ifdef IRTLDEBUG // Check that all the remaining nodes are empty IRTLASSERT(piter->m_pnc->IsLastClump()); for (int i = piter->m_iNode; i != NODE_END; i += NODE_STEP) { IRTLASSERT(piter->m_pnc->IsEmptyAndInvalid(i)); } #endif // IRTLDEBUG break; // rest of nodeclump is empty } } // try the next nodeclump in the bucket chain piter->m_iNode = NODE_BEGIN - NODE_STEP; piter->m_pnc = piter->m_pnc->m_pncNext; } while (piter->m_pnc != NULL); // Exhausted this bucket chain. Unlock it. CBucket* pbkt = _Bucket(piter->m_dwBucketAddr); IRTLASSERT(pbkt != NULL); IRTLASSERT(piter->m_lkl == LKL_WRITELOCK ? pbkt->IsWriteLocked() : pbkt->IsReadLocked()); if (piter->m_lkl == LKL_WRITELOCK) pbkt->WriteUnlock(); else pbkt->ReadUnlock(); // Try the next bucket, if there is one if (++piter->m_dwBucketAddr < m_cActiveBuckets) { pbkt = _Bucket(piter->m_dwBucketAddr); IRTLASSERT(pbkt != NULL); if (piter->m_lkl == LKL_WRITELOCK) pbkt->WriteLock(); else pbkt->ReadLock(); piter->m_pnc = &pbkt->m_ncFirst; } } while (piter->m_dwBucketAddr < m_cActiveBuckets); // We have fallen off the end of the hashtable piter->m_iNode = NODE_BEGIN - NODE_STEP; piter->m_pnc = NULL; return LK_NO_MORE_ELEMENTS; } // CLKRLinearHashTable::IncrementIterator //------------------------------------------------------------------------ // Function: CLKRHashTable::IncrementIterator // Synopsis: move the iterator on to the next record in the hash table //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::IncrementIterator( CIterator* piter) { if (!IsUsable()) return LK_UNUSABLE; IRTLASSERT(piter != NULL); IRTLASSERT(piter->m_pht == this); IRTLASSERT(-1 <= piter->m_ist && piter->m_ist < static_cast(m_cSubTables)); if (piter == NULL || piter->m_pht != this) return LK_BAD_ITERATOR; // Table is already locked if (!IsValid()) return LK_UNUSABLE; LK_RETCODE lkrc; CLHTIterator* pBaseIter = static_cast(piter); for (;;) { // Do we have a valid iterator into a subtable? If not, get one. while (piter->m_plht == NULL) { while (++piter->m_ist < static_cast(m_cSubTables)) { lkrc = m_palhtDir[piter->m_ist]->_InitializeIterator(piter); if (lkrc == LK_SUCCESS) { IRTLASSERT(m_palhtDir[piter->m_ist] == piter->m_plht); return lkrc; } else if (lkrc == LK_NO_MORE_ELEMENTS) lkrc = piter->m_plht->_CloseIterator(pBaseIter); if (lkrc != LK_SUCCESS) return lkrc; } // There are no more subtables left. return LK_NO_MORE_ELEMENTS; } // We already have a valid iterator into a subtable. Increment it. lkrc = piter->m_plht->IncrementIterator(pBaseIter); if (lkrc == LK_SUCCESS) return lkrc; // We've exhausted that subtable. Move on. if (lkrc == LK_NO_MORE_ELEMENTS) lkrc = piter->m_plht->_CloseIterator(pBaseIter); if (lkrc != LK_SUCCESS) return lkrc; } } // CLKRHashTable::IncrementIterator //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_CloseIterator // Synopsis: release the resources held by the iterator //------------------------------------------------------------------------ LK_RETCODE CLKRLinearHashTable::_CloseIterator( CIterator* piter) { if (!IsUsable()) return LK_UNUSABLE; IRTLASSERT(piter != NULL); IRTLASSERT(piter->m_plht == this); IRTLASSERT(piter->m_lkl == LKL_WRITELOCK ? IsWriteLocked() : IsReadLocked()); IRTLASSERT(piter->m_dwBucketAddr <= m_cActiveBuckets); IRTLASSERT((0 <= piter->m_iNode && piter->m_iNode < NODES_PER_CLUMP) || (NODE_BEGIN - NODE_STEP == piter->m_iNode)); if (piter == NULL || piter->m_plht != this) return LK_BAD_ITERATOR; // Are we abandoning the iterator before the end of the table? // If so, need to unlock the bucket. if (piter->m_dwBucketAddr < m_cActiveBuckets) { CBucket* pbkt = _Bucket(piter->m_dwBucketAddr); IRTLASSERT(pbkt != NULL); IRTLASSERT(piter->m_lkl == LKL_WRITELOCK ? pbkt->IsWriteLocked() : pbkt->IsReadLocked()); if (0 <= piter->m_iNode && piter->m_iNode < NODES_PER_CLUMP) { IRTLASSERT(piter->m_pnc != NULL); const void* pvRecord = piter->m_pnc->m_pvNode[piter->m_iNode]; _AddRefRecord(pvRecord, -1); } if (piter->m_lkl == LKL_WRITELOCK) pbkt->WriteUnlock(); else pbkt->ReadUnlock(); } piter->m_plht = NULL; piter->m_pnc = NULL; return LK_SUCCESS; } // CLKRLinearHashTable::_CloseIterator //------------------------------------------------------------------------ // Function: CLKRHashTable::CloseIterator // Synopsis: release the resources held by the iterator //------------------------------------------------------------------------ LK_RETCODE CLKRHashTable::CloseIterator( CIterator* piter) { if (!IsUsable()) return LK_UNUSABLE; IRTLASSERT(piter != NULL); IRTLASSERT(piter->m_pht == this); IRTLASSERT(-1 <= piter->m_ist && piter->m_ist <= static_cast(m_cSubTables)); if (piter == NULL || piter->m_pht != this) return LK_BAD_ITERATOR; LK_RETCODE lkrc = LK_SUCCESS; if (!IsValid()) lkrc = LK_UNUSABLE; else { // Are we abandoning the iterator before we've reached the end? // If so, close the subtable iterator. if (piter->m_plht != NULL) { IRTLASSERT(piter->m_ist < static_cast(m_cSubTables)); CLHTIterator* pBaseIter = static_cast(piter); piter->m_plht->_CloseIterator(pBaseIter); } } // Unlock all the subtables if (piter->m_lkl == LKL_WRITELOCK) WriteUnlock(); else ReadUnlock(); piter->m_plht = NULL; piter->m_pht = NULL; piter->m_ist = -1; return lkrc; } // CLKRHashTable::CloseIterator #endif // LKR_DEPRECATED_ITERATORS #ifdef LKR_STL_ITERATORS //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::Begin // Synopsis: Make the iterator point to the first record in the hash table. //------------------------------------------------------------------------ CLKRLinearHashTable::Iterator CLKRLinearHashTable::Begin() { Iterator iter(this, &_Bucket(0)->m_ncFirst, 0, NODE_BEGIN - NODE_STEP); LKR_ITER_TRACE(_TEXT(" LKLH:Begin(it=%p, plht=%p)\n"), &iter, this); // Let Increment do the hard work of finding the first slot in use. iter._Increment(false); IRTLASSERT(iter.m_iNode != NODE_BEGIN - NODE_STEP); IRTLASSERT(iter == End() || _IsValidIterator(iter)); return iter; } // CLKRLinearHashTable::Begin //------------------------------------------------------------------------ // Function: CLKRLinearHashTable_Iterator::Increment() // Synopsis: move iterator to next valid record in table //------------------------------------------------------------------------ bool CLKRLinearHashTable_Iterator::_Increment( bool fDecrementOldValue) { IRTLASSERT(m_plht != NULL); IRTLASSERT(m_dwBucketAddr < m_plht->m_cActiveBuckets); IRTLASSERT(m_pnc != NULL); IRTLASSERT((0 <= m_iNode && m_iNode < NODES_PER_CLUMP) || (NODE_BEGIN - NODE_STEP == m_iNode)); // Release the reference acquired in the previous call to _Increment if (fDecrementOldValue) _AddRef(-1); do { do { // find the next slot in the nodeclump that's in use while ((m_iNode += NODE_STEP) != NODE_END) { const void* pvRecord = m_pnc->m_pvNode[m_iNode]; if (pvRecord != NULL) { IRTLASSERT(!m_pnc->InvalidSignature(m_iNode)); // Add a new reference _AddRef(+1); LKR_ITER_TRACE(_TEXT(" LKLH:++(this=%p, plht=%p, NC=%p, ") _TEXT("BA=%u, IN=%d, Rec=%p)\n"), this, m_plht, m_pnc, m_dwBucketAddr, m_iNode, pvRecord); return true; } else // pvRecord == NULL { #if 0 //// #ifdef IRTLDEBUG // Check that all the remaining nodes are empty IRTLASSERT(m_pnc->IsLastClump()); for (int i = m_iNode; i != NODE_END; i += NODE_STEP) { IRTLASSERT(m_pnc->IsEmptyAndInvalid(i)); } #endif // IRTLDEBUG break; // rest of nodeclump is empty } } // try the next nodeclump in the bucket chain m_iNode = NODE_BEGIN - NODE_STEP; m_pnc = m_pnc->m_pncNext; } while (m_pnc != NULL); // Try the next bucket, if there is one if (++m_dwBucketAddr < m_plht->m_cActiveBuckets) { CBucket* pbkt = m_plht->_Bucket(m_dwBucketAddr); IRTLASSERT(pbkt != NULL); m_pnc = &pbkt->m_ncFirst; } } while (m_dwBucketAddr < m_plht->m_cActiveBuckets); // We have fallen off the end of the hashtable. Set iterator equal // to end(), the empty iterator. LKR_ITER_TRACE(_TEXT(" LKLH:End(this=%p, plht=%p)\n"), this, m_plht); m_plht = NULL; m_pnc = NULL; m_dwBucketAddr = 0; m_iNode = 0; //// IRTLASSERT(this->operator==(Iterator())); // == end() return false; } // CLKRLinearHashTable_Iterator::_Increment() //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::Insert // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::Insert( const void* pvRecord, Iterator& riterResult, bool fOverwrite) { riterResult = End(); if (!IsUsable() || pvRecord == NULL) return false; bool fSuccess = (_InsertRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)), fOverwrite, &riterResult) == LK_SUCCESS); IRTLASSERT(riterResult.m_iNode != NODE_BEGIN - NODE_STEP); IRTLASSERT(fSuccess ? _IsValidIterator(riterResult) : riterResult == End()); return fSuccess; } // CLKRLinearHashTable::Insert() //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::_Erase // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::_Erase( Iterator& riter, DWORD dwSignature) { CNodeClump* pncCurr, *pncPrev; CBucket* const pbkt = riter.m_plht->_Bucket(riter.m_dwBucketAddr); LKR_ITER_TRACE(_TEXT(" LKLH:_Erase:pre(iter=%p, plht=%p, NC=%p, ") _TEXT("BA=%u, IN=%d, Sig=%x, Rec=%p)\n"), &riter, riter.m_plht, riter.m_pnc, riter.m_dwBucketAddr, riter.m_iNode, dwSignature, riter.m_pnc ? riter.m_pnc->m_pvNode[riter.m_iNode] : NULL); pbkt->WriteLock(); for (pncCurr = &pbkt->m_ncFirst, pncPrev = NULL; pncCurr != NULL; pncPrev = pncCurr, pncCurr = pncCurr->m_pncNext) { if (pncCurr == riter.m_pnc) break; } IRTLASSERT(pncCurr != NULL); // Release the iterator's reference on the record const void* pvRecord = riter.m_pnc->m_pvNode[riter.m_iNode]; IRTLASSERT(pvRecord != NULL); _AddRefRecord(pvRecord, -1); // _DeleteNode will leave iterator members pointing to the // preceding record int iNode = riter.m_iNode; IRTLVERIFY(_DeleteNode(pbkt, riter.m_pnc, pncPrev, iNode)); if (iNode == NODE_END) { LKR_ITER_TRACE(_TEXT("\t_Erase(BKT=%p, PNC=%p, PREV=%p, INODE=%d)\n"), pbkt, riter.m_pnc, pncPrev, iNode); } riter.m_iNode = (short) ((iNode == NODE_END) ? NODE_END-NODE_STEP : iNode); pbkt->WriteUnlock(); // Don't contract the table. Likely to invalidate the iterator, // if iterator is being used in a loop return true; } // CLKRLinearHashTable::_Erase() //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::Erase // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::Erase( Iterator& riter) { if (!IsUsable() || !_IsValidIterator(riter)) return false; DWORD dwSignature = _CalcKeyHash(_ExtractKey(riter.Record())); LKR_ITER_TRACE(_TEXT(" LKLH:Erase:pre(iter=%p, plht=%p, NC=%p, BA=%u, ") _TEXT("IN=%d, Sig=%x, Rec=%p)\n"), &riter, riter.m_plht, riter.m_pnc, riter.m_dwBucketAddr, riter.m_iNode, dwSignature, riter.m_pnc ? riter.m_pnc->m_pvNode[riter.m_iNode] : NULL); bool fSuccess = _Erase(riter, dwSignature); bool fIncrement = false; LKR_ITER_TRACE(_TEXT(" LKLH:Erase:post(iter=%p, plht=%p, NC=%p, BA=%u, ") _TEXT("IN=%d, Sig=%x, Rec=%p, Success=%s)\n"), &riter, riter.m_plht, riter.m_pnc, riter.m_dwBucketAddr, riter.m_iNode, dwSignature, riter.m_pnc ? riter.m_pnc->m_pvNode[riter.m_iNode] : NULL, (fSuccess ? "true" : "false")); // _Erase left riter pointing to the preceding record. // Move to next record. if (fSuccess) fIncrement = riter._Increment(false); IRTLASSERT(riter.m_iNode != NODE_BEGIN - NODE_STEP); IRTLASSERT(fIncrement ? _IsValidIterator(riter) : riter == End()); LKR_ITER_TRACE(_TEXT(" LKLH:Erase:post++(iter=%p, plht=%p, NC=%p, ") _TEXT("BA=%u, IN=%d, Sig=%x, Rec=%p)\n"), &riter, riter.m_plht, riter.m_pnc, riter.m_dwBucketAddr, riter.m_iNode, dwSignature, riter.m_pnc ? riter.m_pnc->m_pvNode[riter.m_iNode] : NULL); return fSuccess; } // CLKRLinearHashTable::Erase //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::Erase // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::Erase( Iterator& riterFirst, Iterator& riterLast) { LKR_ITER_TRACE(_TEXT(" LKHT:Erase2(%p, %p)\n"), &riterFirst, &riterLast); bool fSuccess; int cRecords = 0; do { LKR_ITER_TRACE(_TEXT("\n LKLH:Erase2(%d, %p)\n"), ++cRecords, &riterFirst); fSuccess = Erase(riterFirst); } while (fSuccess && riterFirst != End() && riterFirst != riterLast); LKR_ITER_TRACE(_TEXT(" LKLH:Erase2: fSuccess = %s\n"), (fSuccess ? "true" : "false")); return fSuccess; } // CLKRLinearHashTable::Erase //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::Find // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::Find( DWORD_PTR pnKey, Iterator& riterResult) { riterResult = End(); if (!IsUsable()) return false; const void* pvRecord = NULL; DWORD hash_val = _CalcKeyHash(pnKey); bool fFound = (_FindKey(pnKey, hash_val, &pvRecord, &riterResult) == LK_SUCCESS); IRTLASSERT(fFound ? _IsValidIterator(riterResult) && riterResult.Key() == pnKey : riterResult == End()); IRTLASSERT(riterResult.m_iNode != NODE_BEGIN - NODE_STEP); return fFound; } // CLKRLinearHashTable::Find //------------------------------------------------------------------------ // Function: CLKRLinearHashTable::EqualRange // Synopsis: //------------------------------------------------------------------------ bool CLKRLinearHashTable::EqualRange( DWORD_PTR pnKey, Iterator& riterFirst, Iterator& riterLast) { LKR_ITER_TRACE(_TEXT(" LKLH:EqualRange: Key=%p)\n"), (void*) pnKey); riterLast = End(); bool fFound = Find(pnKey, riterFirst); if (fFound) { riterLast = riterFirst; IRTLASSERT(riterLast != End()); do { riterLast._Increment(); } while (riterLast != End() && riterLast.Key() == pnKey); } IRTLASSERT(riterFirst.m_iNode != NODE_BEGIN - NODE_STEP); IRTLASSERT(fFound ? _IsValidIterator(riterFirst) : riterFirst == End()); IRTLASSERT(riterLast.m_iNode != NODE_BEGIN - NODE_STEP); IRTLASSERT(fFound || riterLast == End()); return fFound; } // CLKRLinearHashTable::EqualRange //------------------------------------------------------------------------ // Function: CLKRHashTable::Begin // Synopsis: Make the iterator point to the first record in the hash table. //------------------------------------------------------------------------ CLKRHashTable::Iterator CLKRHashTable::Begin() { Iterator iter(this, -1); LKR_ITER_TRACE(_TEXT(" LKHT:Begin(it=%p, pht=%p)\n"), &iter, this); // Let Increment do the hard work of finding the first slot in use. iter._Increment(false); IRTLASSERT(iter.m_ist != -1); IRTLASSERT(iter == End() || _IsValidIterator(iter)); return iter; } // CLKRHashTable::Begin //------------------------------------------------------------------------ // Function: CLKRHashTable_Iterator::_Increment() // Synopsis: move iterator to next valid record in table //------------------------------------------------------------------------ bool CLKRHashTable_Iterator::_Increment( bool fDecrementOldValue) { IRTLASSERT(m_pht != NULL); IRTLASSERT(-1 <= m_ist && m_ist < static_cast(m_pht->m_cSubTables)); for (;;) { // Do we have a valid iterator into a subtable? If not, get one. while (m_subiter.m_plht == NULL) { while (++m_ist < static_cast(m_pht->m_cSubTables)) { LKR_ITER_TRACE(_TEXT(" LKHT:++IST=%d\n"), m_ist); m_subiter = m_pht->m_palhtDir[m_ist]->Begin(); if (m_subiter.m_plht != NULL) { LKR_ITER_TRACE(_TEXT(" LKHT:++(this=%p, pht=%p, IST=%d, ") _TEXT("LHT=%p, NC=%p, ") _TEXT("BA=%u, IN=%d, Rec=%p)\n"), this, m_pht, m_ist, m_subiter.m_plht, m_subiter.m_pnc, m_subiter.m_dwBucketAddr, m_subiter.m_iNode, m_subiter.m_pnc->m_pvNode[m_subiter.m_iNode] ); return true; } } // There are no more subtables left. LKR_ITER_TRACE(_TEXT(" LKHT:End(this=%p, pht=%p)\n"), this, m_pht); m_pht = NULL; m_ist = 0; //// IRTLASSERT(this->operator==(Iterator())); // == end() return false; } // We already have a valid iterator into a subtable. Increment it. m_subiter._Increment(fDecrementOldValue); if (m_subiter.m_plht != NULL) { LKR_ITER_TRACE(_TEXT(" LKHT:++(this=%p, pht=%p, IST=%d, ") _TEXT("LHT=%p, NC=%p, BA=%u, IN=%d, Rec=%p)\n"), this, m_pht, m_ist, m_subiter.m_plht, m_subiter.m_pnc, m_subiter.m_dwBucketAddr, m_subiter.m_iNode, m_subiter.m_pnc->m_pvNode[m_subiter.m_iNode]); return true; } } } // CLKRHashTable_Iterator::_Increment() //------------------------------------------------------------------------ // Function: CLKRHashTable::Insert // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::Insert( const void* pvRecord, Iterator& riterResult, bool fOverwrite) { riterResult = End(); if (!IsUsable() || pvRecord == NULL) return false; DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord)); SubTable* const pst = _SubTable(hash_val); bool f = (pst->_InsertRecord(pvRecord, hash_val, fOverwrite, &riterResult.m_subiter) == LK_SUCCESS); if (f) { riterResult.m_pht = this; riterResult.m_ist = (short) _SubTableIndex(pst); } IRTLASSERT(riterResult.m_ist != -1); IRTLASSERT(f ? _IsValidIterator(riterResult) : riterResult == End()); return f; } // CLKRHashTable::Insert //------------------------------------------------------------------------ // Function: CLKRHashTable::Erase // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::Erase( Iterator& riter) { if (!IsUsable() || !_IsValidIterator(riter)) return false; DWORD dwSignature = _CalcKeyHash(_ExtractKey(riter.Record())); SubTable* const pst = _SubTable(dwSignature); IRTLASSERT(pst == riter.m_subiter.m_plht); if (pst != riter.m_subiter.m_plht) return false; LKR_ITER_TRACE(_TEXT(" LKHT:Erase:pre(iter=%p, pht=%p, ist=%d, plht=%p, ") _TEXT("NC=%p, BA=%u, IN=%d, Sig=%x, Rec=%p)\n"), &riter, riter.m_pht, riter.m_ist, riter.m_subiter.m_plht, riter.m_subiter.m_pnc, riter.m_subiter.m_dwBucketAddr, riter.m_subiter.m_iNode, dwSignature, (riter.m_subiter.m_pnc ? riter.Record() : NULL)); // _Erase left riter pointing to the preceding record. Move to // next record. bool fSuccess = pst->_Erase(riter.m_subiter, dwSignature); bool fIncrement = false; LKR_ITER_TRACE(_TEXT(" LKHT:Erase:post(iter=%p, pht=%p, ist=%d, plht=%p, ") _TEXT("NC=%p, BA=%u, IN=%d, Sig=%x, Rec=%p, Success=%s)\n"), &riter, riter.m_pht, riter.m_ist, riter.m_subiter.m_plht, riter.m_subiter.m_pnc, riter.m_subiter.m_dwBucketAddr, riter.m_subiter.m_iNode, dwSignature, ((riter.m_subiter.m_pnc && riter.m_subiter.m_iNode >= 0) ? riter.Record() : NULL), (fSuccess ? "true" : "false")); if (fSuccess) fIncrement = riter._Increment(false); IRTLASSERT(riter.m_ist != -1); IRTLASSERT(fIncrement ? _IsValidIterator(riter) : riter == End()); LKR_ITER_TRACE(_TEXT(" LKHT:Erase:post++(iter=%p, pht=%p, ist=%d, ") _TEXT("plht=%p, NC=%p, ") _TEXT("BA=%u, IN=%d, Sig=%x, Rec=%p)\n"), &riter, riter.m_pht, riter.m_ist, riter.m_subiter.m_plht, riter.m_subiter.m_pnc, riter.m_subiter.m_dwBucketAddr, riter.m_subiter.m_iNode, dwSignature, (riter.m_subiter.m_pnc ? riter.Record() : NULL)); return fSuccess; } // CLKRHashTable::Erase //------------------------------------------------------------------------ // Function: CLKRHashTable::Erase // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::Erase( Iterator& riterFirst, Iterator& riterLast) { LKR_ITER_TRACE(_TEXT(" LKHT:Erase2(%p, %p)\n"), &riterFirst, &riterLast); bool fSuccess; int cRecords = 0; do { LKR_ITER_TRACE(_TEXT("\n LKHT:Erase2(%d, %p)\n"), ++cRecords, &riterFirst); fSuccess = Erase(riterFirst); } while (fSuccess && riterFirst != End() && riterFirst != riterLast); LKR_ITER_TRACE(_TEXT(" LKHT:Erase2: fSuccess = %s\n"), (fSuccess ? "true" : "false")); return fSuccess; } // CLKRHashTable::Erase //------------------------------------------------------------------------ // Function: CLKRHashTable::Find // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::Find( DWORD_PTR pnKey, Iterator& riterResult) { riterResult = End(); if (!IsUsable()) return false; const void* pvRecord = NULL; DWORD hash_val = _CalcKeyHash(pnKey); SubTable* const pst = _SubTable(hash_val); bool fFound = (pst->_FindKey(pnKey, hash_val, &pvRecord, &riterResult.m_subiter) == LK_SUCCESS); if (fFound) { riterResult.m_pht = this; riterResult.m_ist = (short) _SubTableIndex(pst); } IRTLASSERT(riterResult.m_ist != -1); IRTLASSERT(fFound ? _IsValidIterator(riterResult) && riterResult.Key() == pnKey : riterResult == End()); return fFound; } // CLKRHashTable::Find //------------------------------------------------------------------------ // Function: CLKRHashTable::EqualRange // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::EqualRange( DWORD_PTR pnKey, Iterator& riterFirst, Iterator& riterLast) { LKR_ITER_TRACE(_TEXT(" LKHT:EqualRange: Key=%p)\n"), (void*) pnKey); riterLast = End(); bool fFound = Find(pnKey, riterFirst); if (fFound) { riterLast = riterFirst; IRTLASSERT(riterLast != End()); do { riterLast._Increment(); } while (riterLast != End() && riterLast.Key() == pnKey); } IRTLASSERT(riterFirst.m_ist != -1); IRTLASSERT(fFound ? _IsValidIterator(riterFirst) : riterFirst == End()); IRTLASSERT(riterLast.m_ist != -1); IRTLASSERT(fFound || riterLast == End()); return fFound; } // CLKRHashTable::EqualRange #endif // LKR_STL_ITERATORS //------------------------------------------------------------------------ // Function: CLKRHashTable::WriteLock // Synopsis: Lock all subtables for writing //------------------------------------------------------------------------ void CLKRHashTable::WriteLock() { for (DWORD i = 0; i < m_cSubTables; i++) { m_palhtDir[i]->WriteLock(); IRTLASSERT(m_palhtDir[i]->IsWriteLocked()); } } // CLKRHashTable::WriteLock //------------------------------------------------------------------------ // Function: CLKRHashTable::ReadLock // Synopsis: Lock all subtables for reading //------------------------------------------------------------------------ void CLKRHashTable::ReadLock() const { for (DWORD i = 0; i < m_cSubTables; i++) { m_palhtDir[i]->ReadLock(); IRTLASSERT(m_palhtDir[i]->IsReadLocked()); } } // CLKRHashTable::ReadLock //------------------------------------------------------------------------ // Function: CLKRHashTable::WriteUnlock // Synopsis: Unlock all subtables //------------------------------------------------------------------------ void CLKRHashTable::WriteUnlock() const { // unlock in reverse order: LIFO for (DWORD i = m_cSubTables; i-- > 0; ) { IRTLASSERT(m_palhtDir[i]->IsWriteLocked()); m_palhtDir[i]->WriteUnlock(); IRTLASSERT(m_palhtDir[i]->IsWriteUnlocked()); } } // CLKRHashTable::WriteUnlock //------------------------------------------------------------------------ // Function: CLKRHashTable::ReadUnlock // Synopsis: Unlock all subtables //------------------------------------------------------------------------ void CLKRHashTable::ReadUnlock() const { // unlock in reverse order: LIFO for (DWORD i = m_cSubTables; i-- > 0; ) { IRTLASSERT(m_palhtDir[i]->IsReadLocked()); m_palhtDir[i]->ReadUnlock(); IRTLASSERT(m_palhtDir[i]->IsReadUnlocked()); } } // CLKRHashTable::ReadUnlock //------------------------------------------------------------------------ // Function: CLKRHashTable::IsWriteLocked // Synopsis: Are all subtables write-locked? //------------------------------------------------------------------------ bool CLKRHashTable::IsWriteLocked() const { bool fLocked = (m_cSubTables > 0); for (DWORD i = 0; i < m_cSubTables; i++) { fLocked = fLocked && m_palhtDir[i]->IsWriteLocked(); } return fLocked; } // CLKRHashTable::IsWriteLocked //------------------------------------------------------------------------ // Function: CLKRHashTable::IsReadLocked // Synopsis: Are all subtables read-locked? //------------------------------------------------------------------------ bool CLKRHashTable::IsReadLocked() const { bool fLocked = (m_cSubTables > 0); for (DWORD i = 0; i < m_cSubTables; i++) { fLocked = fLocked && m_palhtDir[i]->IsReadLocked(); } return fLocked; } // CLKRHashTable::IsReadLocked //------------------------------------------------------------------------ // Function: CLKRHashTable::IsWriteUnlocked // Synopsis: Are all subtables write-unlocked? //------------------------------------------------------------------------ bool CLKRHashTable::IsWriteUnlocked() const { bool fUnlocked = (m_cSubTables > 0); for (DWORD i = 0; i < m_cSubTables; i++) { fUnlocked = fUnlocked && m_palhtDir[i]->IsWriteUnlocked(); } return fUnlocked; } // CLKRHashTable::IsWriteUnlocked //------------------------------------------------------------------------ // Function: CLKRHashTable::IsReadUnlocked // Synopsis: Are all subtables read-unlocked? //------------------------------------------------------------------------ bool CLKRHashTable::IsReadUnlocked() const { bool fUnlocked = (m_cSubTables > 0); for (DWORD i = 0; i < m_cSubTables; i++) { fUnlocked = fUnlocked && m_palhtDir[i]->IsReadUnlocked(); } return fUnlocked; } // CLKRHashTable::IsReadUnlocked //------------------------------------------------------------------------ // Function: CLKRHashTable::ConvertSharedToExclusive // Synopsis: Convert the read lock to a write lock //------------------------------------------------------------------------ void CLKRHashTable::ConvertSharedToExclusive() const { for (DWORD i = 0; i < m_cSubTables; i++) { m_palhtDir[i]->ConvertSharedToExclusive(); IRTLASSERT(m_palhtDir[i]->IsWriteLocked()); } } // CLKRHashTable::ConvertSharedToExclusive //------------------------------------------------------------------------ // Function: CLKRHashTable::ConvertExclusiveToShared // Synopsis: Convert the write lock to a read lock //------------------------------------------------------------------------ void CLKRHashTable::ConvertExclusiveToShared() const { for (DWORD i = 0; i < m_cSubTables; i++) { m_palhtDir[i]->ConvertExclusiveToShared(); IRTLASSERT(m_palhtDir[i]->IsReadLocked()); } } // CLKRHashTable::ConvertExclusiveToShared //------------------------------------------------------------------------ // Function: CLKRHashTable::Size // Synopsis: Number of elements in the table //------------------------------------------------------------------------ DWORD CLKRHashTable::Size() const { DWORD cSize = 0; for (DWORD i = 0; i < m_cSubTables; i++) cSize += m_palhtDir[i]->Size(); return cSize; } // CLKRHashTable::Size //------------------------------------------------------------------------ // Function: CLKRHashTable::MaxSize // Synopsis: Maximum possible number of elements in the table //------------------------------------------------------------------------ DWORD CLKRHashTable::MaxSize() const { return (m_cSubTables == 0) ? 0 : m_cSubTables * m_palhtDir[0]->MaxSize(); } // CLKRHashTable::MaxSize //------------------------------------------------------------------------ // Function: CLKRHashTable::IsValid // Synopsis: is the table valid? //------------------------------------------------------------------------ bool CLKRHashTable::IsValid() const { bool f = (m_lkrcState == LK_SUCCESS // serious internal failure? && (m_palhtDir != NULL && m_cSubTables > 0) && ValidSignature()); for (DWORD i = 0; f && i < m_cSubTables; i++) f = f && m_palhtDir[i]->IsValid(); if (!f) m_lkrcState = LK_UNUSABLE; return f; } // CLKRHashTable::IsValid //------------------------------------------------------------------------ // Function: CLKRHashTable::SetBucketLockSpinCount // Synopsis: //------------------------------------------------------------------------ void CLKRLinearHashTable::SetBucketLockSpinCount( WORD wSpins) { m_wBucketLockSpins = wSpins; if (BucketLock::PerLockSpin() != LOCK_INDIVIDUAL_SPIN) return; for (DWORD i = 0; i < m_cDirSegs; i++) { CSegment* pseg = m_paDirSegs[i].m_pseg; if (pseg != NULL) { for (DWORD j = 0; j < m_dwSegSize; ++j) { pseg->Slot(j).SetSpinCount(wSpins); } } } } // CLKRLinearHashTable::SetBucketLockSpinCount //------------------------------------------------------------------------ // Function: CLKRHashTable::SetBucketLockSpinCount // Synopsis: //------------------------------------------------------------------------ WORD CLKRLinearHashTable::GetBucketLockSpinCount() const { return m_wBucketLockSpins; } // CLKRLinearHashTable::GetBucketLockSpinCount //------------------------------------------------------------------------ // Function: CLKRHashTable::SetTableLockSpinCount // Synopsis: //------------------------------------------------------------------------ void CLKRHashTable::SetTableLockSpinCount( WORD wSpins) { for (DWORD i = 0; i < m_cSubTables; i++) m_palhtDir[i]->SetTableLockSpinCount(wSpins); } // CLKRHashTable::SetTableLockSpinCount //------------------------------------------------------------------------ // Function: CLKRHashTable::GetTableLockSpinCount // Synopsis: //------------------------------------------------------------------------ WORD CLKRHashTable::GetTableLockSpinCount() const { return ((m_cSubTables == 0) ? (WORD) LOCK_DEFAULT_SPINS : m_palhtDir[0]->GetTableLockSpinCount()); } // CLKRHashTable::GetTableLockSpinCount //------------------------------------------------------------------------ // Function: CLKRHashTable::SetBucketLockSpinCount // Synopsis: //------------------------------------------------------------------------ void CLKRHashTable::SetBucketLockSpinCount( WORD wSpins) { for (DWORD i = 0; i < m_cSubTables; i++) m_palhtDir[i]->SetBucketLockSpinCount(wSpins); } // CLKRHashTable::SetBucketLockSpinCount //------------------------------------------------------------------------ // Function: CLKRHashTable::GetBucketLockSpinCount // Synopsis: //------------------------------------------------------------------------ WORD CLKRHashTable::GetBucketLockSpinCount() const { return ((m_cSubTables == 0) ? (WORD) LOCK_DEFAULT_SPINS : m_palhtDir[0]->GetBucketLockSpinCount()); } // CLKRHashTable::GetBucketLockSpinCount //------------------------------------------------------------------------ // Function: CLKRHashTable::MultiKeys // Synopsis: //------------------------------------------------------------------------ bool CLKRHashTable::MultiKeys() const { return ((m_cSubTables == 0) ? false : m_palhtDir[0]->MultiKeys()); } // CLKRHashTable::MultiKeys #ifndef __LKRHASH_NO_NAMESPACE__ } #endif // !__LKRHASH_NO_NAMESPACE__