summaryrefslogtreecommitdiff
path: root/src/gc/handletablecache.cpp
blob: aaf3370bd61e4957b27424862ea8b302860752fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
// 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"

#ifdef Sleep // TODO(segilles)
#undef Sleep
#endif // Sleep

#include "env/gcenv.os.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);
    }
}

/*--------------------------------------------------------------------------*/