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 | */ |
40 | void 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 | */ |
121 | OBJECTHANDLE *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 | */ |
156 | OBJECTHANDLE *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 | */ |
205 | void 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 | */ |
236 | void 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 | */ |
281 | void 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 | */ |
345 | void 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 | */ |
509 | void 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 | */ |
627 | OBJECTHANDLE 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 | */ |
674 | void 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 | */ |
715 | OBJECTHANDLE 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 | */ |
768 | void 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 | */ |
828 | uint32_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 | */ |
863 | void 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 | |