Class AdaptivePoolingAllocator.ThreadLocalSizeClassedChunkCache

java.lang.Object
io.netty.buffer.AdaptivePoolingAllocator.SizeClassedChunkCache
io.netty.buffer.AdaptivePoolingAllocator.ThreadLocalSizeClassedChunkCache
All Implemented Interfaces:
AdaptivePoolingAllocator.ChunkCache
Enclosing class:
AdaptivePoolingAllocator

static final class AdaptivePoolingAllocator.ThreadLocalSizeClassedChunkCache extends AdaptivePoolingAllocator.SizeClassedChunkCache
Ring buffer cache for thread-local chunk reuse (SPSC — only the owner thread accesses it).

Logical layout after purge:

   head                          tail
   v                             v
   [..., notEmpty, notEmpty, ..., empty, empty, ..., null, ...]
        |--- notEmptyCount ---|--- emptyCount --|
        |------------ count ------------------|
 

Physical layout when the ring wraps:

   0         tail          head          length
   v         v             v             v
   [...tail] [  unused  ]  [head................]
             ^             |--- content wraps ---|
             wrap point
 

scanForCapacity — O(1) fast path takes from head while notEmptyCount > 0:

   before: notEmptyCount=2, count=5
   [NE, NE, E, E, E, _, _, _]
    ^head            ^tail

   after: returns NE, notEmptyCount=1, count=4
   [_,  NE, E, E, E, _, _, _]
        ^head        ^tail
 
Fallback when notEmptyCount == 0: linear scan of the empty zone for chunks that gained capacity from external segment returns.

offerChunk — write at tail, grow (double + linearize) if full:

   before: count=4
   [_,  NE, E, E, E, _, _, _]
        ^head        ^tail

   after: count=5
   [_,  NE, E, E, E, X, _, _]
        ^head           ^tail
 

runPurgeScan (every AdaptivePoolingAllocator.CHUNK_PURGE_POLLS_THREAD_LOCAL polls) — two passes. Pass 1: age idle chunks (full → epoch++, non-full → epoch=0), evict past threshold, compact survivors (nulls stale slots inline). Pass 2: partition hasCap to front / noCap to back, then three-way Dutch-flag within hasCap into [epoch=0 | 0<epoch<threshold | epoch>=threshold]. Chunks with epoch>=threshold are placed at the back of hasCap so scan doesn't reach them — they age to threshold+1 and get evicted. Never selects — selection is always scanForCapacity.

Case 1 — no eviction, an empty chunk gained capacity externally (common):

   before (E* gained capacity since last purge):
   [NE, NE, E*, E, _, _, _, _]
    ^head            ^tail
    notEmptyCount=2

   pass 1: age idle chunks. None past threshold. No compaction needed.
   pass 2 (partition): E* now has capacity → placed in notEmpty zone.

   after:
   [NE, NE, E*, E, _, _, _, _]
    ^head            ^tail
    notEmptyCount=3
 

Case 2 — eviction (uncommon, burst wind-down):

   before (ring wraps, IDLE* = idle past threshold):
   [E, NE, _,  IDLE*, NE, E, E, NE]
          ^tail ^head

   pass 1: IDLE* evicted (markToDeallocate), survivors compacted, stale slots nulled.
   [_, _, _,  NE, E, E, NE, E]
     ^tail    ^head
              |--- kept=6 ---|

   pass 2 (partition): [epoch=0 hasCap | 0<epoch<T hasCap | epoch>=T hasCap | noCap].
   [_, _, _,  NE, NE, E, E, E]
     ^tail    ^head
              notEmptyCount=2, count=6
 
Idle chunks (remainingCapacity == capacity) age via purgeEpoch and are evicted past threshold, but at least purgeRetentionFloor chunks are always retained.