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.