summaryrefslogtreecommitdiff
path: root/src/gc/handletablecache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gc/handletablecache.cpp')
-rw-r--r--src/gc/handletablecache.cpp881
1 files changed, 881 insertions, 0 deletions
diff --git a/src/gc/handletablecache.cpp b/src/gc/handletablecache.cpp
new file mode 100644
index 0000000000..b2af40c829
--- /dev/null
+++ b/src/gc/handletablecache.cpp
@@ -0,0 +1,881 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*
+ * Generational GC handle manager. Handle Caching Routines.
+ *
+ * Implementation of handle table allocation cache.
+ *
+
+ *
+ */
+
+#include "common.h"
+
+#include "gcenv.h"
+
+#include "handletablepriv.h"
+
+/****************************************************************************
+ *
+ * RANDOM HELPERS
+ *
+ ****************************************************************************/
+
+/*
+ * SpinUntil
+ *
+ * Spins on a variable until its state matches a desired state.
+ *
+ * This routine will assert if it spins for a very long time.
+ *
+ */
+void SpinUntil(void *pCond, BOOL fNonZero)
+{
+ WRAPPER_NO_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // if we have to sleep then we will keep track of a sleep period
+ uint32_t dwThisSleepPeriod = 1; // first just give up our timeslice
+ uint32_t dwNextSleepPeriod = 10; // next try a real delay
+
+#ifdef _DEBUG
+ uint32_t dwTotalSlept = 0;
+ uint32_t dwNextComplain = 1000;
+#endif //_DEBUG
+
+ // on MP machines, allow ourselves some spin time before sleeping
+ uint32_t uNonSleepSpins = 8 * (g_SystemInfo.dwNumberOfProcessors - 1);
+
+ // spin until the specificed condition is met
+ while ((*(uintptr_t *)pCond != 0) != (fNonZero != 0))
+ {
+ // have we exhausted the non-sleep spin count?
+ if (!uNonSleepSpins)
+ {
+#ifdef _DEBUG
+ // yes, missed again - before sleeping, check our current sleep time
+ if (dwTotalSlept >= dwNextComplain)
+ {
+ //
+ // THIS SHOULD NOT NORMALLY HAPPEN
+ //
+ // The only time this assert can be ignored is if you have
+ // another thread intentionally suspended in a way that either
+ // directly or indirectly leaves a thread suspended in the
+ // handle table while the current thread (this assert) is
+ // running normally.
+ //
+ // Otherwise, this assert should be investigated as a bug.
+ //
+ _ASSERTE(FALSE);
+
+ // slow down the assert rate so people can investigate
+ dwNextComplain = 3 * dwNextComplain;
+ }
+
+ // now update our total sleep time
+ dwTotalSlept += dwThisSleepPeriod;
+#endif //_DEBUG
+
+ // sleep for a little while
+ GCToOSInterface::Sleep(dwThisSleepPeriod);
+
+ // now update our sleep period
+ dwThisSleepPeriod = dwNextSleepPeriod;
+
+ // now increase the next sleep period if it is still small
+ if (dwNextSleepPeriod < 1000)
+ dwNextSleepPeriod += 10;
+ }
+ else
+ {
+ // nope - just spin again
+ YieldProcessor(); // indicate to the processor that we are spining
+ uNonSleepSpins--;
+ }
+ }
+}
+
+
+/*
+ * ReadAndZeroCacheHandles
+ *
+ * Reads a set of handles from a bank in the handle cache, zeroing them as they are taken.
+ *
+ * This routine will assert if a requested handle is missing.
+ *
+ */
+OBJECTHANDLE *ReadAndZeroCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // set up to loop
+ OBJECTHANDLE *pLast = pDst + uCount;
+
+ // loop until we've copied all of them
+ while (pDst < pLast)
+ {
+ // this version assumes we have handles to read
+ _ASSERTE(*pSrc);
+
+ // copy the handle and zero it from the source
+ *pDst = *pSrc;
+ *pSrc = 0;
+
+ // set up for another handle
+ pDst++;
+ pSrc++;
+ }
+
+ // return the next unfilled slot after what we filled in
+ return pLast;
+}
+
+
+/*
+ * SyncReadAndZeroCacheHandles
+ *
+ * Reads a set of handles from a bank in the handle cache, zeroing them as they are taken.
+ *
+ * This routine will spin until all requested handles are obtained.
+ *
+ */
+OBJECTHANDLE *SyncReadAndZeroCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
+{
+ WRAPPER_NO_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // set up to loop
+ // we loop backwards since that is the order handles are added to the bank
+ // this is designed to reduce the chance that we will have to spin on a handle
+ OBJECTHANDLE *pBase = pDst;
+ pSrc += uCount;
+ pDst += uCount;
+
+ // remember the end of the array
+ OBJECTHANDLE *pLast = pDst;
+
+ // loop until we've copied all of them
+ while (pDst > pBase)
+ {
+ // advance to the next slot
+ pDst--;
+ pSrc--;
+
+ // this version spins if there is no handle to read
+ if (!*pSrc)
+ SpinUntil(pSrc, TRUE);
+
+ // copy the handle and zero it from the source
+ *pDst = *pSrc;
+ *pSrc = 0;
+ }
+
+ // return the next unfilled slot after what we filled in
+ return pLast;
+}
+
+
+/*
+ * WriteCacheHandles
+ *
+ * Writes a set of handles to a bank in the handle cache.
+ *
+ * This routine will assert if it is about to clobber an existing handle.
+ *
+ */
+void WriteCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // set up to loop
+ OBJECTHANDLE *pLimit = pSrc + uCount;
+
+ // loop until we've copied all of them
+ while (pSrc < pLimit)
+ {
+ // this version assumes we have space to store the handles
+ _ASSERTE(!*pDst);
+
+ // copy the handle
+ *pDst = *pSrc;
+
+ // set up for another handle
+ pDst++;
+ pSrc++;
+ }
+}
+
+
+/*
+ * SyncWriteCacheHandles
+ *
+ * Writes a set of handles to a bank in the handle cache.
+ *
+ * This routine will spin until lingering handles in the cache bank are gone.
+ *
+ */
+void SyncWriteCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
+{
+ WRAPPER_NO_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // set up to loop
+ // we loop backwards since that is the order handles are removed from the bank
+ // this is designed to reduce the chance that we will have to spin on a handle
+ OBJECTHANDLE *pBase = pSrc;
+ pSrc += uCount;
+ pDst += uCount;
+
+ // loop until we've copied all of them
+ while (pSrc > pBase)
+ {
+ // set up for another handle
+ pDst--;
+ pSrc--;
+
+ // this version spins if there is no handle to read
+ if (*pDst)
+ SpinUntil(pDst, FALSE);
+
+ // copy the handle
+ *pDst = *pSrc;
+ }
+}
+
+
+/*
+ * SyncTransferCacheHandles
+ *
+ * Transfers a set of handles from one bank of the handle cache to another,
+ * zeroing the source bank as the handles are removed.
+ *
+ * The routine will spin until all requested handles can be transferred.
+ *
+ * This routine is equivalent to SyncReadAndZeroCacheHandles + SyncWriteCacheHandles
+ *
+ */
+void SyncTransferCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
+{
+ WRAPPER_NO_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // set up to loop
+ // we loop backwards since that is the order handles are added to the bank
+ // this is designed to reduce the chance that we will have to spin on a handle
+ OBJECTHANDLE *pBase = pDst;
+ pSrc += uCount;
+ pDst += uCount;
+
+ // loop until we've copied all of them
+ while (pDst > pBase)
+ {
+ // advance to the next slot
+ pDst--;
+ pSrc--;
+
+ // this version spins if there is no handle to read or no place to write it
+ if (*pDst || !*pSrc)
+ {
+ SpinUntil(pSrc, TRUE);
+ SpinUntil(pDst, FALSE);
+ }
+
+ // copy the handle and zero it from the source
+ *pDst = *pSrc;
+ *pSrc = 0;
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+
+
+/****************************************************************************
+ *
+ * HANDLE CACHE
+ *
+ ****************************************************************************/
+
+/*
+ * TableFullRebalanceCache
+ *
+ * Rebalances a handle cache by transferring handles from the cache's
+ * free bank to its reserve bank. If the free bank does not provide
+ * enough handles to replenish the reserve bank, handles are allocated
+ * in bulk from the main handle table. If too many handles remain in
+ * the free bank, the extra handles are returned in bulk to the main
+ * handle table.
+ *
+ * This routine attempts to reduce fragmentation in the main handle
+ * table by sorting the handles according to table order, preferring to
+ * refill the reserve bank with lower handles while freeing higher ones.
+ * The sorting also allows the free routine to operate more efficiently,
+ * as it can optimize the case where handles near each other are freed.
+ *
+ */
+void TableFullRebalanceCache(HandleTable *pTable,
+ HandleTypeCache *pCache,
+ uint32_t uType,
+ int32_t lMinReserveIndex,
+ int32_t lMinFreeIndex,
+ OBJECTHANDLE *pExtraOutHandle,
+ OBJECTHANDLE extraInHandle)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // we need a temporary space to sort our free handles in
+ OBJECTHANDLE rgHandles[HANDLE_CACHE_TYPE_SIZE];
+
+ // set up a base handle pointer to keep track of where we are
+ OBJECTHANDLE *pHandleBase = rgHandles;
+
+ // do we have a spare incoming handle?
+ if (extraInHandle)
+ {
+ // remember the extra handle now
+ *pHandleBase = extraInHandle;
+ pHandleBase++;
+ }
+
+ // if there are handles in the reserve bank then gather them up
+ // (we don't need to wait on these since they are only put there by this
+ // function inside our own lock)
+ if (lMinReserveIndex > 0)
+ pHandleBase = ReadAndZeroCacheHandles(pHandleBase, pCache->rgReserveBank, (uint32_t)lMinReserveIndex);
+ else
+ lMinReserveIndex = 0;
+
+ // if there are handles in the free bank then gather them up
+ if (lMinFreeIndex < HANDLES_PER_CACHE_BANK)
+ {
+ // this may have underflowed
+ if (lMinFreeIndex < 0)
+ lMinFreeIndex = 0;
+
+ // here we need to wait for all pending freed handles to be written by other threads
+ pHandleBase = SyncReadAndZeroCacheHandles(pHandleBase,
+ pCache->rgFreeBank + lMinFreeIndex,
+ HANDLES_PER_CACHE_BANK - (uint32_t)lMinFreeIndex);
+ }
+
+ // compute the number of handles we have
+ uint32_t uHandleCount = (uint32_t) (pHandleBase - rgHandles);
+
+ // do we have enough handles for a balanced cache?
+ if (uHandleCount < REBALANCE_LOWATER_MARK)
+ {
+ // nope - allocate some more
+ uint32_t uAlloc = HANDLES_PER_CACHE_BANK - uHandleCount;
+
+ // if we have an extra outgoing handle then plan for that too
+ if (pExtraOutHandle)
+ uAlloc++;
+
+ {
+ // allocate the new handles - we intentionally don't check for success here
+ FAULT_NOT_FATAL();
+
+ uHandleCount += TableAllocBulkHandles(pTable, uType, pHandleBase, uAlloc);
+ }
+ }
+
+ // reset the base handle pointer
+ pHandleBase = rgHandles;
+
+ // by default the whole free bank is available
+ lMinFreeIndex = HANDLES_PER_CACHE_BANK;
+
+ // if we have handles left over then we need to do some more work
+ if (uHandleCount)
+ {
+ // do we have too many handles for a balanced cache?
+ if (uHandleCount > REBALANCE_HIWATER_MARK)
+ {
+ //
+ // sort the array by reverse handle order - this does two things:
+ // (1) combats handle fragmentation by preferring low-address handles to high ones
+ // (2) allows the free routine to run much more efficiently over the ones we free
+ //
+ QuickSort((uintptr_t *)pHandleBase, 0, uHandleCount - 1, CompareHandlesByFreeOrder);
+
+ // yup, we need to free some - calculate how many
+ uint32_t uFree = uHandleCount - HANDLES_PER_CACHE_BANK;
+
+ // free the handles - they are already 'prepared' (eg zeroed and sorted)
+ TableFreeBulkPreparedHandles(pTable, uType, pHandleBase, uFree);
+
+ // update our array base and length
+ uHandleCount -= uFree;
+ pHandleBase += uFree;
+ }
+
+ // if we have an extra outgoing handle then fill it now
+ if (pExtraOutHandle)
+ {
+ // account for the handle we're giving away
+ uHandleCount--;
+
+ // now give it away
+ *pExtraOutHandle = pHandleBase[uHandleCount];
+ }
+
+ // if we have more than a reserve bank of handles then put some in the free bank
+ if (uHandleCount > HANDLES_PER_CACHE_BANK)
+ {
+ // compute the number of extra handles we need to save away
+ uint32_t uStore = uHandleCount - HANDLES_PER_CACHE_BANK;
+
+ // compute the index to start writing the handles to
+ lMinFreeIndex = HANDLES_PER_CACHE_BANK - uStore;
+
+ // store the handles
+ // (we don't need to wait on these since we already waited while reading them)
+ WriteCacheHandles(pCache->rgFreeBank + lMinFreeIndex, pHandleBase, uStore);
+
+ // update our array base and length
+ uHandleCount -= uStore;
+ pHandleBase += uStore;
+ }
+ }
+
+ // update the write index for the free bank
+ // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
+ // AFTER THIS POINT THE FREE BANK IS LIVE AND COULD RECEIVE NEW HANDLES
+ Interlocked::Exchange(&pCache->lFreeIndex, lMinFreeIndex);
+
+ // now if we have any handles left, store them in the reserve bank
+ if (uHandleCount)
+ {
+ // store the handles
+ // (here we need to wait for all pending allocated handles to be taken
+ // before we set up new ones in their places)
+ SyncWriteCacheHandles(pCache->rgReserveBank, pHandleBase, uHandleCount);
+ }
+
+ // compute the index to start serving handles from
+ lMinReserveIndex = (int32_t)uHandleCount;
+
+ // update the read index for the reserve bank
+ // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
+ // AT THIS POINT THE RESERVE BANK IS LIVE AND HANDLES COULD BE ALLOCATED FROM IT
+ Interlocked::Exchange(&pCache->lReserveIndex, lMinReserveIndex);
+}
+
+
+/*
+ * TableQuickRebalanceCache
+ *
+ * Rebalances a handle cache by transferring handles from the cache's free bank
+ * to its reserve bank. If the free bank does not provide enough handles to
+ * replenish the reserve bank or too many handles remain in the free bank, the
+ * routine just punts and calls TableFullRebalanceCache.
+ *
+ */
+void TableQuickRebalanceCache(HandleTable *pTable,
+ HandleTypeCache *pCache,
+ uint32_t uType,
+ int32_t lMinReserveIndex,
+ int32_t lMinFreeIndex,
+ OBJECTHANDLE *pExtraOutHandle,
+ OBJECTHANDLE extraInHandle)
+{
+ WRAPPER_NO_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // clamp the min free index to be non-negative
+ if (lMinFreeIndex < 0)
+ lMinFreeIndex = 0;
+
+ // clamp the min reserve index to be non-negative
+ if (lMinReserveIndex < 0)
+ lMinReserveIndex = 0;
+
+ // compute the number of slots in the free bank taken by handles
+ uint32_t uFreeAvail = HANDLES_PER_CACHE_BANK - (uint32_t)lMinFreeIndex;
+
+ // compute the number of handles we have to fiddle with
+ uint32_t uHandleCount = (uint32_t)lMinReserveIndex + uFreeAvail + (extraInHandle != 0);
+
+ // can we rebalance these handles in place?
+ if ((uHandleCount < REBALANCE_LOWATER_MARK) ||
+ (uHandleCount > REBALANCE_HIWATER_MARK))
+ {
+ // nope - perform a full rebalance of the handle cache
+ TableFullRebalanceCache(pTable, pCache, uType, lMinReserveIndex, lMinFreeIndex,
+ pExtraOutHandle, extraInHandle);
+
+ // all done
+ return;
+ }
+
+ // compute the number of empty slots in the reserve bank
+ uint32_t uEmptyReserve = HANDLES_PER_CACHE_BANK - lMinReserveIndex;
+
+ // we want to transfer as many handles as we can from the free bank
+ uint32_t uTransfer = uFreeAvail;
+
+ // but only as many as we have room to store in the reserve bank
+ if (uTransfer > uEmptyReserve)
+ uTransfer = uEmptyReserve;
+
+ // transfer the handles
+ SyncTransferCacheHandles(pCache->rgReserveBank + lMinReserveIndex,
+ pCache->rgFreeBank + lMinFreeIndex,
+ uTransfer);
+
+ // adjust the free and reserve indices to reflect the transfer
+ lMinFreeIndex += uTransfer;
+ lMinReserveIndex += uTransfer;
+
+ // do we have an extra incoming handle to store?
+ if (extraInHandle)
+ {
+ //
+ // Workaround: For code size reasons, we don't handle all cases here.
+ // We assume an extra IN handle means a cache overflow during a free.
+ //
+ // After the rebalance above, the reserve bank should be full, and
+ // there may be a few handles sitting in the free bank. The HIWATER
+ // check above guarantees that we have room to store the handle.
+ //
+ _ASSERTE(!pExtraOutHandle);
+
+ // store the handle in the next available free bank slot
+ pCache->rgFreeBank[--lMinFreeIndex] = extraInHandle;
+ }
+ else if (pExtraOutHandle) // do we have an extra outgoing handle to satisfy?
+ {
+ //
+ // For code size reasons, we don't handle all cases here.
+ // We assume an extra OUT handle means a cache underflow during an alloc.
+ //
+ // After the rebalance above, the free bank should be empty, and
+ // the reserve bank may not be fully populated. The LOWATER check above
+ // guarantees that the reserve bank has at least one handle we can steal.
+ //
+
+ // take the handle from the reserve bank and update the reserve index
+ *pExtraOutHandle = pCache->rgReserveBank[--lMinReserveIndex];
+
+ // zero the cache slot we chose
+ pCache->rgReserveBank[lMinReserveIndex] = NULL;
+ }
+
+ // update the write index for the free bank
+ // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
+ // AFTER THIS POINT THE FREE BANK IS LIVE AND COULD RECEIVE NEW HANDLES
+ Interlocked::Exchange(&pCache->lFreeIndex, lMinFreeIndex);
+
+ // update the read index for the reserve bank
+ // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
+ // AT THIS POINT THE RESERVE BANK IS LIVE AND HANDLES COULD BE ALLOCATED FROM IT
+ Interlocked::Exchange(&pCache->lReserveIndex, lMinReserveIndex);
+}
+
+
+/*
+ * TableCacheMissOnAlloc
+ *
+ * Gets a single handle of the specified type from the handle table,
+ * making the assumption that the reserve cache for that type was
+ * recently emptied. This routine acquires the handle manager lock and
+ * attempts to get a handle from the reserve cache again. If this second
+ * get operation also fails, the handle is allocated by means of a cache
+ * rebalance.
+ *
+ */
+OBJECTHANDLE TableCacheMissOnAlloc(HandleTable *pTable, HandleTypeCache *pCache, uint32_t uType)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // assume we get no handle
+ OBJECTHANDLE handle = NULL;
+
+ // acquire the handle manager lock
+ CrstHolder ch(&pTable->Lock);
+
+ // try again to take a handle (somebody else may have rebalanced)
+ int32_t lReserveIndex = Interlocked::Decrement(&pCache->lReserveIndex);
+
+ // are we still waiting for handles?
+ if (lReserveIndex < 0)
+ {
+ // yup, suspend free list usage...
+ int32_t lFreeIndex = Interlocked::Exchange(&pCache->lFreeIndex, 0);
+
+ // ...and rebalance the cache...
+ TableQuickRebalanceCache(pTable, pCache, uType, lReserveIndex, lFreeIndex, &handle, NULL);
+ }
+ else
+ {
+ // somebody else rebalanced the cache for us - take the handle
+ handle = pCache->rgReserveBank[lReserveIndex];
+
+ // zero the handle slot
+ pCache->rgReserveBank[lReserveIndex] = 0;
+ }
+
+ // return the handle we got
+ return handle;
+}
+
+
+/*
+ * TableCacheMissOnFree
+ *
+ * Returns a single handle of the specified type to the handle table,
+ * making the assumption that the free cache for that type was recently
+ * filled. This routine acquires the handle manager lock and attempts
+ * to store the handle in the free cache again. If this second store
+ * operation also fails, the handle is freed by means of a cache
+ * rebalance.
+ *
+ */
+void TableCacheMissOnFree(HandleTable *pTable, HandleTypeCache *pCache, uint32_t uType, OBJECTHANDLE handle)
+{
+ WRAPPER_NO_CONTRACT;
+
+ /*
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ */
+
+ // acquire the handle manager lock
+ CrstHolder ch(&pTable->Lock);
+
+ // try again to take a slot (somebody else may have rebalanced)
+ int32_t lFreeIndex = Interlocked::Decrement(&pCache->lFreeIndex);
+
+ // are we still waiting for free slots?
+ if (lFreeIndex < 0)
+ {
+ // yup, suspend reserve list usage...
+ int32_t lReserveIndex = Interlocked::Exchange(&pCache->lReserveIndex, 0);
+
+ // ...and rebalance the cache...
+ TableQuickRebalanceCache(pTable, pCache, uType, lReserveIndex, lFreeIndex, NULL, handle);
+ }
+ else
+ {
+ // somebody else rebalanced the cache for us - free the handle
+ pCache->rgFreeBank[lFreeIndex] = handle;
+ }
+}
+
+
+/*
+ * TableAllocSingleHandleFromCache
+ *
+ * Gets a single handle of the specified type from the handle table by
+ * trying to fetch it from the reserve cache for that handle type. If the
+ * reserve cache is empty, this routine calls TableCacheMissOnAlloc.
+ *
+ */
+OBJECTHANDLE TableAllocSingleHandleFromCache(HandleTable *pTable, uint32_t uType)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // we use this in two places
+ OBJECTHANDLE handle;
+
+ // first try to get a handle from the quick cache
+ if (pTable->rgQuickCache[uType])
+ {
+ // try to grab the handle we saw
+ handle = Interlocked::ExchangePointer(pTable->rgQuickCache + uType, (OBJECTHANDLE)NULL);
+
+ // if it worked then we're done
+ if (handle)
+ return handle;
+ }
+
+ // ok, get the main handle cache for this type
+ HandleTypeCache *pCache = pTable->rgMainCache + uType;
+
+ // try to take a handle from the main cache
+ int32_t lReserveIndex = Interlocked::Decrement(&pCache->lReserveIndex);
+
+ // did we underflow?
+ if (lReserveIndex < 0)
+ {
+ // yep - the cache is out of handles
+ return TableCacheMissOnAlloc(pTable, pCache, uType);
+ }
+
+ // get our handle
+ handle = pCache->rgReserveBank[lReserveIndex];
+
+ // zero the handle slot
+ pCache->rgReserveBank[lReserveIndex] = 0;
+
+ // sanity
+ _ASSERTE(handle);
+
+ // return our handle
+ return handle;
+}
+
+
+/*
+ * TableFreeSingleHandleToCache
+ *
+ * Returns a single handle of the specified type to the handle table
+ * by trying to store it in the free cache for that handle type. If the
+ * free cache is full, this routine calls TableCacheMissOnFree.
+ *
+ */
+void TableFreeSingleHandleToCache(HandleTable *pTable, uint32_t uType, OBJECTHANDLE handle)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ SO_TOLERANT;
+ CAN_TAKE_LOCK; // because of TableCacheMissOnFree
+ }
+ CONTRACTL_END;
+
+#ifdef DEBUG_DestroyedHandleValue
+ *(_UNCHECKED_OBJECTREF *)handle = DEBUG_DestroyedHandleValue;
+#else
+ // zero the handle's object pointer
+ *(_UNCHECKED_OBJECTREF *)handle = NULL;
+#endif
+
+ // if this handle type has user data then clear it - AFTER the referent is cleared!
+ if (TypeHasUserData(pTable, uType))
+ HandleQuickSetUserData(handle, 0L);
+
+ // is there room in the quick cache?
+ if (!pTable->rgQuickCache[uType])
+ {
+ // yup - try to stuff our handle in the slot we saw
+ handle = Interlocked::ExchangePointer(&pTable->rgQuickCache[uType], handle);
+
+ // if we didn't end up with another handle then we're done
+ if (!handle)
+ return;
+ }
+
+ // ok, get the main handle cache for this type
+ HandleTypeCache *pCache = pTable->rgMainCache + uType;
+
+ // try to take a free slot from the main cache
+ int32_t lFreeIndex = Interlocked::Decrement(&pCache->lFreeIndex);
+
+ // did we underflow?
+ if (lFreeIndex < 0)
+ {
+ // yep - we're out of free slots
+ TableCacheMissOnFree(pTable, pCache, uType, handle);
+ return;
+ }
+
+ // we got a slot - save the handle in the free bank
+ pCache->rgFreeBank[lFreeIndex] = handle;
+}
+
+
+/*
+ * TableAllocHandlesFromCache
+ *
+ * Allocates multiple handles of the specified type by repeatedly
+ * calling TableAllocSingleHandleFromCache.
+ *
+ */
+uint32_t TableAllocHandlesFromCache(HandleTable *pTable, uint32_t uType, OBJECTHANDLE *pHandleBase, uint32_t uCount)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // loop until we have satisfied all the handles we need to allocate
+ uint32_t uSatisfied = 0;
+ while (uSatisfied < uCount)
+ {
+ // get a handle from the cache
+ OBJECTHANDLE handle = TableAllocSingleHandleFromCache(pTable, uType);
+
+ // if we can't get any more then bail out
+ if (!handle)
+ break;
+
+ // store the handle in the caller's array
+ *pHandleBase = handle;
+
+ // on to the next one
+ uSatisfied++;
+ pHandleBase++;
+ }
+
+ // return the number of handles we allocated
+ return uSatisfied;
+}
+
+
+/*
+ * TableFreeHandlesToCache
+ *
+ * Frees multiple handles of the specified type by repeatedly
+ * calling TableFreeSingleHandleToCache.
+ *
+ */
+void TableFreeHandlesToCache(HandleTable *pTable, uint32_t uType, const OBJECTHANDLE *pHandleBase, uint32_t uCount)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // loop until we have freed all the handles
+ while (uCount)
+ {
+ // get the next handle to free
+ OBJECTHANDLE handle = *pHandleBase;
+
+ // advance our state
+ uCount--;
+ pHandleBase++;
+
+ // sanity
+ _ASSERTE(handle);
+
+ // return the handle to the cache
+ TableFreeSingleHandleToCache(pTable, uType, handle);
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+