1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*
6 * Generational GC handle manager. Handle Caching Routines.
7 *
8 * Implementation of handle table allocation cache.
9 *
10
11 *
12 */
13
14#include "common.h"
15
16#include "gcenv.h"
17
18#ifdef Sleep // TODO(segilles)
19#undef Sleep
20#endif // Sleep
21
22#include "env/gcenv.os.h"
23
24#include "handletablepriv.h"
25
26/****************************************************************************
27 *
28 * RANDOM HELPERS
29 *
30 ****************************************************************************/
31
32/*
33 * SpinUntil
34 *
35 * Spins on a variable until its state matches a desired state.
36 *
37 * This routine will assert if it spins for a very long time.
38 *
39 */
40void SpinUntil(void *pCond, BOOL fNonZero)
41{
42 WRAPPER_NO_CONTRACT;
43
44 /*
45 NOTHROW;
46 GC_NOTRIGGER;
47 MODE_ANY;
48 */
49
50 // if we have to sleep then we will keep track of a sleep period
51 uint32_t dwThisSleepPeriod = 1; // first just give up our timeslice
52 uint32_t dwNextSleepPeriod = 10; // next try a real delay
53
54#ifdef _DEBUG
55 uint32_t dwTotalSlept = 0;
56 uint32_t dwNextComplain = 1000;
57#endif //_DEBUG
58
59 // on MP machines, allow ourselves some spin time before sleeping
60 static uint32_t uNonSleepSpins = 8 * (GCToOSInterface::GetCurrentProcessCpuCount() - 1);
61
62 // spin until the specificed condition is met
63 while ((*(uintptr_t *)pCond != 0) != (fNonZero != 0))
64 {
65 // have we exhausted the non-sleep spin count?
66 if (!uNonSleepSpins)
67 {
68#ifdef _DEBUG
69 // yes, missed again - before sleeping, check our current sleep time
70 if (dwTotalSlept >= dwNextComplain)
71 {
72 //
73 // THIS SHOULD NOT NORMALLY HAPPEN
74 //
75 // The only time this assert can be ignored is if you have
76 // another thread intentionally suspended in a way that either
77 // directly or indirectly leaves a thread suspended in the
78 // handle table while the current thread (this assert) is
79 // running normally.
80 //
81 // Otherwise, this assert should be investigated as a bug.
82 //
83 _ASSERTE(FALSE);
84
85 // slow down the assert rate so people can investigate
86 dwNextComplain = 3 * dwNextComplain;
87 }
88
89 // now update our total sleep time
90 dwTotalSlept += dwThisSleepPeriod;
91#endif //_DEBUG
92
93 // sleep for a little while
94 GCToOSInterface::Sleep(dwThisSleepPeriod);
95
96 // now update our sleep period
97 dwThisSleepPeriod = dwNextSleepPeriod;
98
99 // now increase the next sleep period if it is still small
100 if (dwNextSleepPeriod < 1000)
101 dwNextSleepPeriod += 10;
102 }
103 else
104 {
105 // nope - just spin again
106 YieldProcessor(); // indicate to the processor that we are spining
107 uNonSleepSpins--;
108 }
109 }
110}
111
112
113/*
114 * ReadAndZeroCacheHandles
115 *
116 * Reads a set of handles from a bank in the handle cache, zeroing them as they are taken.
117 *
118 * This routine will assert if a requested handle is missing.
119 *
120 */
121OBJECTHANDLE *ReadAndZeroCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
122{
123 LIMITED_METHOD_CONTRACT;
124
125 // set up to loop
126 OBJECTHANDLE *pLast = pDst + uCount;
127
128 // loop until we've copied all of them
129 while (pDst < pLast)
130 {
131 // this version assumes we have handles to read
132 _ASSERTE(*pSrc);
133
134 // copy the handle and zero it from the source
135 *pDst = *pSrc;
136 *pSrc = 0;
137
138 // set up for another handle
139 pDst++;
140 pSrc++;
141 }
142
143 // return the next unfilled slot after what we filled in
144 return pLast;
145}
146
147
148/*
149 * SyncReadAndZeroCacheHandles
150 *
151 * Reads a set of handles from a bank in the handle cache, zeroing them as they are taken.
152 *
153 * This routine will spin until all requested handles are obtained.
154 *
155 */
156OBJECTHANDLE *SyncReadAndZeroCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
157{
158 WRAPPER_NO_CONTRACT;
159
160 /*
161 NOTHROW;
162 GC_NOTRIGGER;
163 MODE_ANY;
164 */
165
166 // set up to loop
167 // we loop backwards since that is the order handles are added to the bank
168 // this is designed to reduce the chance that we will have to spin on a handle
169 OBJECTHANDLE *pBase = pDst;
170 pSrc += uCount;
171 pDst += uCount;
172
173 // remember the end of the array
174 OBJECTHANDLE *pLast = pDst;
175
176 // loop until we've copied all of them
177 while (pDst > pBase)
178 {
179 // advance to the next slot
180 pDst--;
181 pSrc--;
182
183 // this version spins if there is no handle to read
184 if (!*pSrc)
185 SpinUntil(pSrc, TRUE);
186
187 // copy the handle and zero it from the source
188 *pDst = *pSrc;
189 *pSrc = 0;
190 }
191
192 // return the next unfilled slot after what we filled in
193 return pLast;
194}
195
196
197/*
198 * WriteCacheHandles
199 *
200 * Writes a set of handles to a bank in the handle cache.
201 *
202 * This routine will assert if it is about to clobber an existing handle.
203 *
204 */
205void WriteCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
206{
207 LIMITED_METHOD_CONTRACT;
208
209 // set up to loop
210 OBJECTHANDLE *pLimit = pSrc + uCount;
211
212 // loop until we've copied all of them
213 while (pSrc < pLimit)
214 {
215 // this version assumes we have space to store the handles
216 _ASSERTE(!*pDst);
217
218 // copy the handle
219 *pDst = *pSrc;
220
221 // set up for another handle
222 pDst++;
223 pSrc++;
224 }
225}
226
227
228/*
229 * SyncWriteCacheHandles
230 *
231 * Writes a set of handles to a bank in the handle cache.
232 *
233 * This routine will spin until lingering handles in the cache bank are gone.
234 *
235 */
236void SyncWriteCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
237{
238 WRAPPER_NO_CONTRACT;
239
240 /*
241 NOTHROW;
242 GC_NOTRIGGER;
243 MODE_ANY;
244 */
245
246 // set up to loop
247 // we loop backwards since that is the order handles are removed from the bank
248 // this is designed to reduce the chance that we will have to spin on a handle
249 OBJECTHANDLE *pBase = pSrc;
250 pSrc += uCount;
251 pDst += uCount;
252
253 // loop until we've copied all of them
254 while (pSrc > pBase)
255 {
256 // set up for another handle
257 pDst--;
258 pSrc--;
259
260 // this version spins if there is no handle to read
261 if (*pDst)
262 SpinUntil(pDst, FALSE);
263
264 // copy the handle
265 *pDst = *pSrc;
266 }
267}
268
269
270/*
271 * SyncTransferCacheHandles
272 *
273 * Transfers a set of handles from one bank of the handle cache to another,
274 * zeroing the source bank as the handles are removed.
275 *
276 * The routine will spin until all requested handles can be transferred.
277 *
278 * This routine is equivalent to SyncReadAndZeroCacheHandles + SyncWriteCacheHandles
279 *
280 */
281void SyncTransferCacheHandles(OBJECTHANDLE *pDst, OBJECTHANDLE *pSrc, uint32_t uCount)
282{
283 WRAPPER_NO_CONTRACT;
284
285 /*
286 NOTHROW;
287 GC_NOTRIGGER;
288 MODE_ANY;
289 */
290
291 // set up to loop
292 // we loop backwards since that is the order handles are added to the bank
293 // this is designed to reduce the chance that we will have to spin on a handle
294 OBJECTHANDLE *pBase = pDst;
295 pSrc += uCount;
296 pDst += uCount;
297
298 // loop until we've copied all of them
299 while (pDst > pBase)
300 {
301 // advance to the next slot
302 pDst--;
303 pSrc--;
304
305 // this version spins if there is no handle to read or no place to write it
306 if (*pDst || !*pSrc)
307 {
308 SpinUntil(pSrc, TRUE);
309 SpinUntil(pDst, FALSE);
310 }
311
312 // copy the handle and zero it from the source
313 *pDst = *pSrc;
314 *pSrc = 0;
315 }
316}
317
318/*--------------------------------------------------------------------------*/
319
320
321
322/****************************************************************************
323 *
324 * HANDLE CACHE
325 *
326 ****************************************************************************/
327
328/*
329 * TableFullRebalanceCache
330 *
331 * Rebalances a handle cache by transferring handles from the cache's
332 * free bank to its reserve bank. If the free bank does not provide
333 * enough handles to replenish the reserve bank, handles are allocated
334 * in bulk from the main handle table. If too many handles remain in
335 * the free bank, the extra handles are returned in bulk to the main
336 * handle table.
337 *
338 * This routine attempts to reduce fragmentation in the main handle
339 * table by sorting the handles according to table order, preferring to
340 * refill the reserve bank with lower handles while freeing higher ones.
341 * The sorting also allows the free routine to operate more efficiently,
342 * as it can optimize the case where handles near each other are freed.
343 *
344 */
345void TableFullRebalanceCache(HandleTable *pTable,
346 HandleTypeCache *pCache,
347 uint32_t uType,
348 int32_t lMinReserveIndex,
349 int32_t lMinFreeIndex,
350 OBJECTHANDLE *pExtraOutHandle,
351 OBJECTHANDLE extraInHandle)
352{
353 LIMITED_METHOD_CONTRACT;
354
355 /*
356 NOTHROW;
357 GC_NOTRIGGER;
358 MODE_ANY;
359 */
360
361 // we need a temporary space to sort our free handles in
362 OBJECTHANDLE rgHandles[HANDLE_CACHE_TYPE_SIZE];
363
364 // set up a base handle pointer to keep track of where we are
365 OBJECTHANDLE *pHandleBase = rgHandles;
366
367 // do we have a spare incoming handle?
368 if (extraInHandle)
369 {
370 // remember the extra handle now
371 *pHandleBase = extraInHandle;
372 pHandleBase++;
373 }
374
375 // if there are handles in the reserve bank then gather them up
376 // (we don't need to wait on these since they are only put there by this
377 // function inside our own lock)
378 if (lMinReserveIndex > 0)
379 pHandleBase = ReadAndZeroCacheHandles(pHandleBase, pCache->rgReserveBank, (uint32_t)lMinReserveIndex);
380 else
381 lMinReserveIndex = 0;
382
383 // if there are handles in the free bank then gather them up
384 if (lMinFreeIndex < HANDLES_PER_CACHE_BANK)
385 {
386 // this may have underflowed
387 if (lMinFreeIndex < 0)
388 lMinFreeIndex = 0;
389
390 // here we need to wait for all pending freed handles to be written by other threads
391 pHandleBase = SyncReadAndZeroCacheHandles(pHandleBase,
392 pCache->rgFreeBank + lMinFreeIndex,
393 HANDLES_PER_CACHE_BANK - (uint32_t)lMinFreeIndex);
394 }
395
396 // compute the number of handles we have
397 uint32_t uHandleCount = (uint32_t) (pHandleBase - rgHandles);
398
399 // do we have enough handles for a balanced cache?
400 if (uHandleCount < REBALANCE_LOWATER_MARK)
401 {
402 // nope - allocate some more
403 uint32_t uAlloc = HANDLES_PER_CACHE_BANK - uHandleCount;
404
405 // if we have an extra outgoing handle then plan for that too
406 if (pExtraOutHandle)
407 uAlloc++;
408
409 {
410 // allocate the new handles - we intentionally don't check for success here
411 FAULT_NOT_FATAL();
412
413 uHandleCount += TableAllocBulkHandles(pTable, uType, pHandleBase, uAlloc);
414 }
415 }
416
417 // reset the base handle pointer
418 pHandleBase = rgHandles;
419
420 // by default the whole free bank is available
421 lMinFreeIndex = HANDLES_PER_CACHE_BANK;
422
423 // if we have handles left over then we need to do some more work
424 if (uHandleCount)
425 {
426 // do we have too many handles for a balanced cache?
427 if (uHandleCount > REBALANCE_HIWATER_MARK)
428 {
429 //
430 // sort the array by reverse handle order - this does two things:
431 // (1) combats handle fragmentation by preferring low-address handles to high ones
432 // (2) allows the free routine to run much more efficiently over the ones we free
433 //
434 QuickSort((uintptr_t *)pHandleBase, 0, uHandleCount - 1, CompareHandlesByFreeOrder);
435
436 // yup, we need to free some - calculate how many
437 uint32_t uFree = uHandleCount - HANDLES_PER_CACHE_BANK;
438
439 // free the handles - they are already 'prepared' (eg zeroed and sorted)
440 TableFreeBulkPreparedHandles(pTable, uType, pHandleBase, uFree);
441
442 // update our array base and length
443 uHandleCount -= uFree;
444 pHandleBase += uFree;
445 }
446
447 // if we have an extra outgoing handle then fill it now
448 if (pExtraOutHandle)
449 {
450 // account for the handle we're giving away
451 uHandleCount--;
452
453 // now give it away
454 *pExtraOutHandle = pHandleBase[uHandleCount];
455 }
456
457 // if we have more than a reserve bank of handles then put some in the free bank
458 if (uHandleCount > HANDLES_PER_CACHE_BANK)
459 {
460 // compute the number of extra handles we need to save away
461 uint32_t uStore = uHandleCount - HANDLES_PER_CACHE_BANK;
462
463 // compute the index to start writing the handles to
464 lMinFreeIndex = HANDLES_PER_CACHE_BANK - uStore;
465
466 // store the handles
467 // (we don't need to wait on these since we already waited while reading them)
468 WriteCacheHandles(pCache->rgFreeBank + lMinFreeIndex, pHandleBase, uStore);
469
470 // update our array base and length
471 uHandleCount -= uStore;
472 pHandleBase += uStore;
473 }
474 }
475
476 // update the write index for the free bank
477 // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
478 // AFTER THIS POINT THE FREE BANK IS LIVE AND COULD RECEIVE NEW HANDLES
479 Interlocked::Exchange(&pCache->lFreeIndex, lMinFreeIndex);
480
481 // now if we have any handles left, store them in the reserve bank
482 if (uHandleCount)
483 {
484 // store the handles
485 // (here we need to wait for all pending allocated handles to be taken
486 // before we set up new ones in their places)
487 SyncWriteCacheHandles(pCache->rgReserveBank, pHandleBase, uHandleCount);
488 }
489
490 // compute the index to start serving handles from
491 lMinReserveIndex = (int32_t)uHandleCount;
492
493 // update the read index for the reserve bank
494 // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
495 // AT THIS POINT THE RESERVE BANK IS LIVE AND HANDLES COULD BE ALLOCATED FROM IT
496 Interlocked::Exchange(&pCache->lReserveIndex, lMinReserveIndex);
497}
498
499
500/*
501 * TableQuickRebalanceCache
502 *
503 * Rebalances a handle cache by transferring handles from the cache's free bank
504 * to its reserve bank. If the free bank does not provide enough handles to
505 * replenish the reserve bank or too many handles remain in the free bank, the
506 * routine just punts and calls TableFullRebalanceCache.
507 *
508 */
509void TableQuickRebalanceCache(HandleTable *pTable,
510 HandleTypeCache *pCache,
511 uint32_t uType,
512 int32_t lMinReserveIndex,
513 int32_t lMinFreeIndex,
514 OBJECTHANDLE *pExtraOutHandle,
515 OBJECTHANDLE extraInHandle)
516{
517 WRAPPER_NO_CONTRACT;
518
519 /*
520 NOTHROW;
521 GC_NOTRIGGER;
522 MODE_ANY;
523 */
524
525 // clamp the min free index to be non-negative
526 if (lMinFreeIndex < 0)
527 lMinFreeIndex = 0;
528
529 // clamp the min reserve index to be non-negative
530 if (lMinReserveIndex < 0)
531 lMinReserveIndex = 0;
532
533 // compute the number of slots in the free bank taken by handles
534 uint32_t uFreeAvail = HANDLES_PER_CACHE_BANK - (uint32_t)lMinFreeIndex;
535
536 // compute the number of handles we have to fiddle with
537 uint32_t uHandleCount = (uint32_t)lMinReserveIndex + uFreeAvail + (extraInHandle != 0);
538
539 // can we rebalance these handles in place?
540 if ((uHandleCount < REBALANCE_LOWATER_MARK) ||
541 (uHandleCount > REBALANCE_HIWATER_MARK))
542 {
543 // nope - perform a full rebalance of the handle cache
544 TableFullRebalanceCache(pTable, pCache, uType, lMinReserveIndex, lMinFreeIndex,
545 pExtraOutHandle, extraInHandle);
546
547 // all done
548 return;
549 }
550
551 // compute the number of empty slots in the reserve bank
552 uint32_t uEmptyReserve = HANDLES_PER_CACHE_BANK - lMinReserveIndex;
553
554 // we want to transfer as many handles as we can from the free bank
555 uint32_t uTransfer = uFreeAvail;
556
557 // but only as many as we have room to store in the reserve bank
558 if (uTransfer > uEmptyReserve)
559 uTransfer = uEmptyReserve;
560
561 // transfer the handles
562 SyncTransferCacheHandles(pCache->rgReserveBank + lMinReserveIndex,
563 pCache->rgFreeBank + lMinFreeIndex,
564 uTransfer);
565
566 // adjust the free and reserve indices to reflect the transfer
567 lMinFreeIndex += uTransfer;
568 lMinReserveIndex += uTransfer;
569
570 // do we have an extra incoming handle to store?
571 if (extraInHandle)
572 {
573 //
574 // Workaround: For code size reasons, we don't handle all cases here.
575 // We assume an extra IN handle means a cache overflow during a free.
576 //
577 // After the rebalance above, the reserve bank should be full, and
578 // there may be a few handles sitting in the free bank. The HIWATER
579 // check above guarantees that we have room to store the handle.
580 //
581 _ASSERTE(!pExtraOutHandle);
582
583 // store the handle in the next available free bank slot
584 pCache->rgFreeBank[--lMinFreeIndex] = extraInHandle;
585 }
586 else if (pExtraOutHandle) // do we have an extra outgoing handle to satisfy?
587 {
588 //
589 // For code size reasons, we don't handle all cases here.
590 // We assume an extra OUT handle means a cache underflow during an alloc.
591 //
592 // After the rebalance above, the free bank should be empty, and
593 // the reserve bank may not be fully populated. The LOWATER check above
594 // guarantees that the reserve bank has at least one handle we can steal.
595 //
596
597 // take the handle from the reserve bank and update the reserve index
598 *pExtraOutHandle = pCache->rgReserveBank[--lMinReserveIndex];
599
600 // zero the cache slot we chose
601 pCache->rgReserveBank[lMinReserveIndex] = NULL;
602 }
603
604 // update the write index for the free bank
605 // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
606 // AFTER THIS POINT THE FREE BANK IS LIVE AND COULD RECEIVE NEW HANDLES
607 Interlocked::Exchange(&pCache->lFreeIndex, lMinFreeIndex);
608
609 // update the read index for the reserve bank
610 // NOTE: we use an interlocked exchange here to guarantee relative store order on MP
611 // AT THIS POINT THE RESERVE BANK IS LIVE AND HANDLES COULD BE ALLOCATED FROM IT
612 Interlocked::Exchange(&pCache->lReserveIndex, lMinReserveIndex);
613}
614
615
616/*
617 * TableCacheMissOnAlloc
618 *
619 * Gets a single handle of the specified type from the handle table,
620 * making the assumption that the reserve cache for that type was
621 * recently emptied. This routine acquires the handle manager lock and
622 * attempts to get a handle from the reserve cache again. If this second
623 * get operation also fails, the handle is allocated by means of a cache
624 * rebalance.
625 *
626 */
627OBJECTHANDLE TableCacheMissOnAlloc(HandleTable *pTable, HandleTypeCache *pCache, uint32_t uType)
628{
629 WRAPPER_NO_CONTRACT;
630
631 // assume we get no handle
632 OBJECTHANDLE handle = NULL;
633
634 // acquire the handle manager lock
635 CrstHolder ch(&pTable->Lock);
636
637 // try again to take a handle (somebody else may have rebalanced)
638 int32_t lReserveIndex = Interlocked::Decrement(&pCache->lReserveIndex);
639
640 // are we still waiting for handles?
641 if (lReserveIndex < 0)
642 {
643 // yup, suspend free list usage...
644 int32_t lFreeIndex = Interlocked::Exchange(&pCache->lFreeIndex, 0);
645
646 // ...and rebalance the cache...
647 TableQuickRebalanceCache(pTable, pCache, uType, lReserveIndex, lFreeIndex, &handle, NULL);
648 }
649 else
650 {
651 // somebody else rebalanced the cache for us - take the handle
652 handle = pCache->rgReserveBank[lReserveIndex];
653
654 // zero the handle slot
655 pCache->rgReserveBank[lReserveIndex] = 0;
656 }
657
658 // return the handle we got
659 return handle;
660}
661
662
663/*
664 * TableCacheMissOnFree
665 *
666 * Returns a single handle of the specified type to the handle table,
667 * making the assumption that the free cache for that type was recently
668 * filled. This routine acquires the handle manager lock and attempts
669 * to store the handle in the free cache again. If this second store
670 * operation also fails, the handle is freed by means of a cache
671 * rebalance.
672 *
673 */
674void TableCacheMissOnFree(HandleTable *pTable, HandleTypeCache *pCache, uint32_t uType, OBJECTHANDLE handle)
675{
676 WRAPPER_NO_CONTRACT;
677
678 /*
679 NOTHROW;
680 GC_NOTRIGGER;
681 MODE_ANY;
682 */
683
684 // acquire the handle manager lock
685 CrstHolder ch(&pTable->Lock);
686
687 // try again to take a slot (somebody else may have rebalanced)
688 int32_t lFreeIndex = Interlocked::Decrement(&pCache->lFreeIndex);
689
690 // are we still waiting for free slots?
691 if (lFreeIndex < 0)
692 {
693 // yup, suspend reserve list usage...
694 int32_t lReserveIndex = Interlocked::Exchange(&pCache->lReserveIndex, 0);
695
696 // ...and rebalance the cache...
697 TableQuickRebalanceCache(pTable, pCache, uType, lReserveIndex, lFreeIndex, NULL, handle);
698 }
699 else
700 {
701 // somebody else rebalanced the cache for us - free the handle
702 pCache->rgFreeBank[lFreeIndex] = handle;
703 }
704}
705
706
707/*
708 * TableAllocSingleHandleFromCache
709 *
710 * Gets a single handle of the specified type from the handle table by
711 * trying to fetch it from the reserve cache for that handle type. If the
712 * reserve cache is empty, this routine calls TableCacheMissOnAlloc.
713 *
714 */
715OBJECTHANDLE TableAllocSingleHandleFromCache(HandleTable *pTable, uint32_t uType)
716{
717 WRAPPER_NO_CONTRACT;
718
719 // we use this in two places
720 OBJECTHANDLE handle;
721
722 // first try to get a handle from the quick cache
723 if (pTable->rgQuickCache[uType])
724 {
725 // try to grab the handle we saw
726 handle = Interlocked::ExchangePointer(pTable->rgQuickCache + uType, (OBJECTHANDLE)NULL);
727
728 // if it worked then we're done
729 if (handle)
730 return handle;
731 }
732
733 // ok, get the main handle cache for this type
734 HandleTypeCache *pCache = pTable->rgMainCache + uType;
735
736 // try to take a handle from the main cache
737 int32_t lReserveIndex = Interlocked::Decrement(&pCache->lReserveIndex);
738
739 // did we underflow?
740 if (lReserveIndex < 0)
741 {
742 // yep - the cache is out of handles
743 return TableCacheMissOnAlloc(pTable, pCache, uType);
744 }
745
746 // get our handle
747 handle = pCache->rgReserveBank[lReserveIndex];
748
749 // zero the handle slot
750 pCache->rgReserveBank[lReserveIndex] = 0;
751
752 // sanity
753 _ASSERTE(handle);
754
755 // return our handle
756 return handle;
757}
758
759
760/*
761 * TableFreeSingleHandleToCache
762 *
763 * Returns a single handle of the specified type to the handle table
764 * by trying to store it in the free cache for that handle type. If the
765 * free cache is full, this routine calls TableCacheMissOnFree.
766 *
767 */
768void TableFreeSingleHandleToCache(HandleTable *pTable, uint32_t uType, OBJECTHANDLE handle)
769{
770 CONTRACTL
771 {
772 NOTHROW;
773 GC_NOTRIGGER;
774 MODE_ANY;
775 SO_TOLERANT;
776 CAN_TAKE_LOCK; // because of TableCacheMissOnFree
777 }
778 CONTRACTL_END;
779
780#ifdef DEBUG_DestroyedHandleValue
781 *(_UNCHECKED_OBJECTREF *)handle = DEBUG_DestroyedHandleValue;
782#else
783 // zero the handle's object pointer
784 *(_UNCHECKED_OBJECTREF *)handle = NULL;
785#endif
786
787 // if this handle type has user data then clear it - AFTER the referent is cleared!
788 if (TypeHasUserData(pTable, uType))
789 HandleQuickSetUserData(handle, 0L);
790
791 // is there room in the quick cache?
792 if (!pTable->rgQuickCache[uType])
793 {
794 // yup - try to stuff our handle in the slot we saw
795 handle = Interlocked::ExchangePointer(&pTable->rgQuickCache[uType], handle);
796
797 // if we didn't end up with another handle then we're done
798 if (!handle)
799 return;
800 }
801
802 // ok, get the main handle cache for this type
803 HandleTypeCache *pCache = pTable->rgMainCache + uType;
804
805 // try to take a free slot from the main cache
806 int32_t lFreeIndex = Interlocked::Decrement(&pCache->lFreeIndex);
807
808 // did we underflow?
809 if (lFreeIndex < 0)
810 {
811 // yep - we're out of free slots
812 TableCacheMissOnFree(pTable, pCache, uType, handle);
813 return;
814 }
815
816 // we got a slot - save the handle in the free bank
817 pCache->rgFreeBank[lFreeIndex] = handle;
818}
819
820
821/*
822 * TableAllocHandlesFromCache
823 *
824 * Allocates multiple handles of the specified type by repeatedly
825 * calling TableAllocSingleHandleFromCache.
826 *
827 */
828uint32_t TableAllocHandlesFromCache(HandleTable *pTable, uint32_t uType, OBJECTHANDLE *pHandleBase, uint32_t uCount)
829{
830 WRAPPER_NO_CONTRACT;
831
832 // loop until we have satisfied all the handles we need to allocate
833 uint32_t uSatisfied = 0;
834 while (uSatisfied < uCount)
835 {
836 // get a handle from the cache
837 OBJECTHANDLE handle = TableAllocSingleHandleFromCache(pTable, uType);
838
839 // if we can't get any more then bail out
840 if (!handle)
841 break;
842
843 // store the handle in the caller's array
844 *pHandleBase = handle;
845
846 // on to the next one
847 uSatisfied++;
848 pHandleBase++;
849 }
850
851 // return the number of handles we allocated
852 return uSatisfied;
853}
854
855
856/*
857 * TableFreeHandlesToCache
858 *
859 * Frees multiple handles of the specified type by repeatedly
860 * calling TableFreeSingleHandleToCache.
861 *
862 */
863void TableFreeHandlesToCache(HandleTable *pTable, uint32_t uType, const OBJECTHANDLE *pHandleBase, uint32_t uCount)
864{
865 WRAPPER_NO_CONTRACT;
866
867 // loop until we have freed all the handles
868 while (uCount)
869 {
870 // get the next handle to free
871 OBJECTHANDLE handle = *pHandleBase;
872
873 // advance our state
874 uCount--;
875 pHandleBase++;
876
877 // sanity
878 _ASSERTE(handle);
879
880 // return the handle to the cache
881 TableFreeSingleHandleToCache(pTable, uType, handle);
882 }
883}
884
885/*--------------------------------------------------------------------------*/
886
887
888