1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.buffer;
17
18 import io.netty.util.ByteProcessor;
19 import io.netty.util.CharsetUtil;
20 import io.netty.util.IllegalReferenceCountException;
21 import io.netty.util.NettyRuntime;
22 import io.netty.util.Recycler;
23 import io.netty.util.Recycler.EnhancedHandle;
24 import io.netty.util.concurrent.ConcurrentSkipListIntObjMultimap;
25 import io.netty.util.concurrent.ConcurrentSkipListIntObjMultimap.IntEntry;
26 import io.netty.util.concurrent.FastThreadLocal;
27 import io.netty.util.concurrent.FastThreadLocalThread;
28 import io.netty.util.concurrent.MpscIntQueue;
29 import io.netty.util.internal.MathUtil;
30 import io.netty.util.internal.ObjectUtil;
31 import io.netty.util.internal.PlatformDependent;
32 import io.netty.util.internal.RefCnt;
33 import io.netty.util.internal.SystemPropertyUtil;
34 import io.netty.util.internal.ThreadExecutorMap;
35 import io.netty.util.internal.UnstableApi;
36
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.nio.channels.ClosedChannelException;
43 import java.nio.channels.FileChannel;
44 import java.nio.channels.GatheringByteChannel;
45 import java.nio.channels.ScatteringByteChannel;
46 import java.nio.charset.Charset;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Iterator;
50 import java.util.Queue;
51 import java.util.concurrent.atomic.AtomicInteger;
52 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
53 import java.util.concurrent.atomic.AtomicLong;
54 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
55 import java.util.concurrent.atomic.LongAdder;
56 import java.util.concurrent.locks.StampedLock;
57 import java.util.function.IntConsumer;
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 @UnstableApi
85 final class AdaptivePoolingAllocator {
86 private static final int LOW_MEM_THRESHOLD = 512 * 1024 * 1024;
87 private static final boolean IS_LOW_MEM = SystemPropertyUtil.getBoolean(
88 "io.netty.allocator.lowMemory",
89 Runtime.getRuntime().maxMemory() <= LOW_MEM_THRESHOLD);
90
91
92
93
94
95 private static final boolean DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM = SystemPropertyUtil.getBoolean(
96 "io.netty.allocator.disableThreadLocalMagazinesOnLowMemory", true);
97
98
99
100
101
102
103
104
105 static final int MIN_CHUNK_SIZE = 128 * 1024;
106 private static final int EXPANSION_ATTEMPTS = 3;
107 private static final int INITIAL_MAGAZINES = 1;
108 private static final int RETIRE_CAPACITY = 256;
109 private static final int MAX_STRIPES = IS_LOW_MEM ? 1 : NettyRuntime.availableProcessors() * 2;
110 private static final int BUFS_PER_CHUNK = 8;
111
112
113
114
115
116
117 private static final int MAX_CHUNK_SIZE = IS_LOW_MEM ?
118 2 * 1024 * 1024 :
119 8 * 1024 * 1024;
120 private static final int MAX_POOLED_BUF_SIZE = MAX_CHUNK_SIZE / BUFS_PER_CHUNK;
121
122
123
124
125
126
127 static final int CHUNK_REUSE_QUEUE = Math.max(2, SystemPropertyUtil.getInt(
128 "io.netty.allocator.chunkReuseQueueCapacity", NettyRuntime.availableProcessors() * 2));
129
130 static final long CHUNK_PURGE_POLLS_THREAD_LOCAL = Math.max(1, SystemPropertyUtil.getLong(
131 "io.netty.allocator.chunkPurgePollsThreadLocal", 16L));
132
133 static final long CHUNK_PURGE_POLLS_SHARED = Math.max(1, SystemPropertyUtil.getLong(
134 "io.netty.allocator.chunkPurgePollsShared", 128L));
135
136 static final int CHUNK_PURGE_THRESHOLD = Math.max(1, SystemPropertyUtil.getInt(
137 "io.netty.allocator.chunkPurgeThreshold", 3));
138
139
140
141
142
143
144
145
146 static final int THREAD_LOCAL_CACHE_MAX_BYTES = Math.max(1, SystemPropertyUtil.getInt(
147 "io.netty.allocator.threadLocalChunkCacheMaxBytes", 8 * 1024 * 1024));
148
149
150
151
152
153
154
155 static final int THREAD_LOCAL_CACHE_MIN_BYTES = Math.min(THREAD_LOCAL_CACHE_MAX_BYTES,
156 Math.max(1, SystemPropertyUtil.getInt(
157 "io.netty.allocator.threadLocalChunkCacheMinBytes",
158 THREAD_LOCAL_CACHE_MAX_BYTES / 2)));
159
160
161
162
163
164 private static final int MAGAZINE_BUFFER_QUEUE_CAPACITY = SystemPropertyUtil.getInt(
165 "io.netty.allocator.magazineBufferQueueCapacity", 1024);
166
167
168
169
170
171
172
173
174
175
176
177
178
179 private static final int[] SIZE_CLASSES = {
180 32,
181 64,
182 128,
183 256,
184 512,
185 640,
186 1024,
187 1152,
188 2048,
189 2304,
190 4096,
191 4352,
192 8192,
193 8704,
194 16384,
195 16896,
196 };
197
198 private static final int SIZE_CLASSES_COUNT = SIZE_CLASSES.length;
199 private static final byte[] SIZE_INDEXES = new byte[SIZE_CLASSES[SIZE_CLASSES_COUNT - 1] / 32 + 1];
200
201 static {
202 if (MAGAZINE_BUFFER_QUEUE_CAPACITY < 2) {
203 throw new IllegalArgumentException("MAGAZINE_BUFFER_QUEUE_CAPACITY: " + MAGAZINE_BUFFER_QUEUE_CAPACITY
204 + " (expected: >= " + 2 + ')');
205 }
206 int lastIndex = 0;
207 for (int i = 0; i < SIZE_CLASSES_COUNT; i++) {
208 int sizeClass = SIZE_CLASSES[i];
209
210 assert (sizeClass & 31) == 0 : "Size class must be a multiple of 32";
211 int sizeIndex = sizeIndexOf(sizeClass);
212 Arrays.fill(SIZE_INDEXES, lastIndex + 1, sizeIndex + 1, (byte) i);
213 lastIndex = sizeIndex;
214 }
215 }
216
217 private final ChunkAllocator chunkAllocator;
218 private final ChunkRegistry chunkRegistry;
219 private final MagazineGroup[] sizeClassedMagazineGroups;
220 private final MagazineGroup largeBufferMagazineGroup;
221 private final FastThreadLocal<MagazineGroup[]> threadLocalGroup;
222
223 AdaptivePoolingAllocator(ChunkAllocator chunkAllocator, boolean useCacheForNonEventLoopThreads) {
224 this.chunkAllocator = ObjectUtil.checkNotNull(chunkAllocator, "chunkAllocator");
225 chunkRegistry = new ChunkRegistry();
226 sizeClassedMagazineGroups = createMagazineGroupSizeClasses(this, false);
227 largeBufferMagazineGroup = new MagazineGroup(
228 this, chunkAllocator, new BuddyChunkManagementStrategy(), false);
229
230 boolean disableThreadLocalGroups = IS_LOW_MEM && DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM;
231 threadLocalGroup = disableThreadLocalGroups ? null : new FastThreadLocal<MagazineGroup[]>() {
232 @Override
233 protected MagazineGroup[] initialValue() {
234 if (useCacheForNonEventLoopThreads || ThreadExecutorMap.currentExecutor() != null) {
235 return createMagazineGroupSizeClasses(AdaptivePoolingAllocator.this, true);
236 }
237 return null;
238 }
239
240 @Override
241 protected void onRemoval(final MagazineGroup[] groups) throws Exception {
242 if (groups != null) {
243 for (MagazineGroup group : groups) {
244 group.free();
245 }
246 }
247 }
248 };
249 }
250
251 private static MagazineGroup[] createMagazineGroupSizeClasses(
252 AdaptivePoolingAllocator allocator, boolean isThreadLocal) {
253 MagazineGroup[] groups = new MagazineGroup[SIZE_CLASSES.length];
254 for (int i = 0; i < SIZE_CLASSES.length; i++) {
255 int segmentSize = SIZE_CLASSES[i];
256 groups[i] = new MagazineGroup(allocator, allocator.chunkAllocator,
257 new SizeClassChunkManagementStrategy(segmentSize), isThreadLocal);
258 }
259 return groups;
260 }
261
262 ByteBuf allocate(int size, int maxCapacity) {
263 return allocate(size, maxCapacity, Thread.currentThread(), null);
264 }
265
266 private AdaptiveByteBuf allocate(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
267 AdaptiveByteBuf allocated = null;
268 if (size <= MAX_POOLED_BUF_SIZE) {
269 final int index = sizeClassIndexOf(size);
270 MagazineGroup[] magazineGroups;
271 if (!FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals() ||
272 IS_LOW_MEM ||
273 (magazineGroups = threadLocalGroup.get()) == null) {
274 magazineGroups = sizeClassedMagazineGroups;
275 }
276 if (index < magazineGroups.length) {
277 allocated = magazineGroups[index].allocate(size, maxCapacity, currentThread, buf);
278 } else if (!IS_LOW_MEM) {
279 allocated = largeBufferMagazineGroup.allocate(size, maxCapacity, currentThread, buf);
280 }
281 }
282 if (allocated == null) {
283 allocated = allocateFallback(size, maxCapacity, currentThread, buf);
284 }
285 return allocated;
286 }
287
288 private static int sizeIndexOf(final int size) {
289
290 return size + 31 >> 5;
291 }
292
293 static int sizeClassIndexOf(int size) {
294 int sizeIndex = sizeIndexOf(size);
295 if (sizeIndex < SIZE_INDEXES.length) {
296 return SIZE_INDEXES[sizeIndex];
297 }
298 return SIZE_CLASSES_COUNT;
299 }
300
301 static int[] getSizeClasses() {
302 return SIZE_CLASSES.clone();
303 }
304
305 private AdaptiveByteBuf allocateFallback(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
306
307 Magazine magazine;
308 if (buf != null) {
309 Chunk chunk = buf.chunk;
310 if (chunk == null || chunk == Magazine.MAGAZINE_FREED || (magazine = chunk.currentMagazine()) == null) {
311 magazine = getFallbackMagazine(currentThread);
312 }
313 } else {
314 magazine = getFallbackMagazine(currentThread);
315 buf = magazine.newBuffer();
316 }
317
318 AbstractByteBuf innerChunk = chunkAllocator.allocate(size, maxCapacity);
319 Chunk chunk = new Chunk(innerChunk, magazine, false);
320 chunkRegistry.add(chunk);
321 try {
322 boolean success = chunk.readInitInto(buf, size, size, maxCapacity);
323 assert success : "Failed to initialize ByteBuf with dedicated chunk";
324 } finally {
325
326
327
328 chunk.release();
329 }
330 return buf;
331 }
332
333 private Magazine getFallbackMagazine(Thread currentThread) {
334 Magazine[] mags = largeBufferMagazineGroup.magazines;
335 return mags[(int) currentThread.getId() & mags.length - 1];
336 }
337
338
339
340
341 void reallocate(int size, int maxCapacity, AdaptiveByteBuf into) {
342 AdaptiveByteBuf result = allocate(size, maxCapacity, Thread.currentThread(), into);
343 assert result == into : "Re-allocation created separate buffer instance";
344 }
345
346 long usedMemory() {
347 return chunkRegistry.totalCapacity();
348 }
349
350
351
352
353 @SuppressWarnings({"FinalizeDeclaration", "deprecation"})
354 @Override
355 protected void finalize() throws Throwable {
356 try {
357 free();
358 } finally {
359 super.finalize();
360 }
361 }
362
363 private void free() {
364 largeBufferMagazineGroup.free();
365 }
366
367 private static final class MagazineGroup {
368 private final AdaptivePoolingAllocator allocator;
369 private final ChunkAllocator chunkAllocator;
370 private final ChunkManagementStrategy chunkManagementStrategy;
371 private final ChunkCache chunkCache;
372 private final StampedLock magazineExpandLock;
373 private final Magazine threadLocalMagazine;
374 private Thread ownerThread;
375 private volatile Magazine[] magazines;
376 private volatile boolean freed;
377
378 MagazineGroup(AdaptivePoolingAllocator allocator,
379 ChunkAllocator chunkAllocator,
380 ChunkManagementStrategy chunkManagementStrategy,
381 boolean isThreadLocal) {
382 this.allocator = allocator;
383 this.chunkAllocator = chunkAllocator;
384 this.chunkManagementStrategy = chunkManagementStrategy;
385 chunkCache = chunkManagementStrategy.createChunkCache(isThreadLocal);
386 if (isThreadLocal) {
387 ownerThread = Thread.currentThread();
388 magazineExpandLock = null;
389 threadLocalMagazine = new Magazine(this, false, chunkManagementStrategy.createController(this));
390 } else {
391 ownerThread = null;
392 magazineExpandLock = new StampedLock();
393 threadLocalMagazine = null;
394 Magazine[] mags = new Magazine[INITIAL_MAGAZINES];
395 for (int i = 0; i < mags.length; i++) {
396 mags[i] = new Magazine(this, true, chunkManagementStrategy.createController(this));
397 }
398 magazines = mags;
399 }
400 }
401
402 public AdaptiveByteBuf allocate(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
403 boolean reallocate = buf != null;
404
405
406 Magazine tlMag = threadLocalMagazine;
407 if (tlMag != null) {
408 if (buf == null) {
409 buf = tlMag.newBuffer();
410 }
411 boolean allocated = tlMag.tryAllocate(size, maxCapacity, buf, reallocate);
412 assert allocated : "Allocation of threadLocalMagazine must always succeed";
413 return buf;
414 }
415
416
417 long threadId = currentThread.getId();
418 Magazine[] mags;
419 int expansions = 0;
420 do {
421 mags = magazines;
422 int mask = mags.length - 1;
423 int index = (int) (threadId & mask);
424 for (int i = 0, m = mags.length << 1; i < m; i++) {
425 Magazine mag = mags[index + i & mask];
426 if (buf == null) {
427 buf = mag.newBuffer();
428 }
429 if (mag.tryAllocate(size, maxCapacity, buf, reallocate)) {
430
431 return buf;
432 }
433 }
434 expansions++;
435 } while (expansions <= EXPANSION_ATTEMPTS && tryExpandMagazines(mags.length));
436
437
438 if (!reallocate && buf != null) {
439 buf.release();
440 }
441 return null;
442 }
443
444 private boolean tryExpandMagazines(int currentLength) {
445 if (currentLength >= MAX_STRIPES) {
446 return true;
447 }
448 final Magazine[] mags;
449 long writeLock = magazineExpandLock.tryWriteLock();
450 if (writeLock != 0) {
451 try {
452 mags = magazines;
453 if (mags.length >= MAX_STRIPES || mags.length > currentLength || freed) {
454 return true;
455 }
456 Magazine[] expanded = new Magazine[mags.length * 2];
457 for (int i = 0, l = expanded.length; i < l; i++) {
458 expanded[i] = new Magazine(this, true, chunkManagementStrategy.createController(this));
459 }
460 magazines = expanded;
461 } finally {
462 magazineExpandLock.unlockWrite(writeLock);
463 }
464 for (Magazine magazine : mags) {
465 magazine.free();
466 }
467 }
468 return true;
469 }
470
471 Chunk pollChunk(int size) {
472 return chunkCache.pollChunk(size);
473 }
474
475 boolean offerChunk(Chunk chunk) {
476 if (freed) {
477 return false;
478 }
479
480 if (chunk.hasUnprocessedFreelistEntries()) {
481 chunk.processFreelistEntries();
482 }
483 boolean isAdded = chunkCache.offerChunk(chunk);
484
485 if (freed && isAdded) {
486
487 freeChunkReuseQueue(ownerThread);
488 }
489 return isAdded;
490 }
491
492 private void free() {
493 freed = true;
494 Thread ownerThread = this.ownerThread;
495 if (threadLocalMagazine != null) {
496 this.ownerThread = null;
497 threadLocalMagazine.free();
498 } else {
499 long stamp = magazineExpandLock.writeLock();
500 try {
501 Magazine[] mags = magazines;
502 for (Magazine magazine : mags) {
503 magazine.free();
504 }
505 } finally {
506 magazineExpandLock.unlockWrite(stamp);
507 }
508 }
509 freeChunkReuseQueue(ownerThread);
510 }
511
512 private void freeChunkReuseQueue(Thread ownerThread) {
513 if (ownerThread != null && chunkCache instanceof ThreadLocalSizeClassedChunkCache) {
514 ThreadLocalSizeClassedChunkCache tlCache = (ThreadLocalSizeClassedChunkCache) chunkCache;
515 int mask = tlCache.chunks.length - 1;
516 for (int i = 0; i < tlCache.count; i++) {
517 SizeClassedChunk chunk = tlCache.chunks[(tlCache.head + i) & mask];
518 assert ownerThread == chunk.ownerThread;
519 chunk.ownerThread = null;
520 }
521 }
522 chunkCache.free();
523 }
524 }
525
526 interface ChunkCache {
527 Chunk pollChunk(int size);
528
529 boolean offerChunk(Chunk chunk);
530
531 void free();
532
533 boolean isEmpty();
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 abstract static class SizeClassedChunkCache implements ChunkCache {
565 static SizeClassedChunkCache create(boolean isThreadLocal, int chunkSize) {
566 return isThreadLocal ? new ThreadLocalSizeClassedChunkCache(chunkSize) :
567 new SharedSizeClassedChunkCache();
568 }
569
570 @Override
571 public abstract SizeClassedChunk pollChunk(int size);
572
573
574 abstract SizeClassedChunk forcePurge();
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 static final class ThreadLocalSizeClassedChunkCache extends SizeClassedChunkCache {
667 SizeClassedChunk[] chunks;
668 int head;
669 int tail;
670 int count;
671 int notEmptyCount;
672 private long purgeBudget;
673 final int maxCachedChunks;
674 final int purgeRetentionFloor;
675
676 ThreadLocalSizeClassedChunkCache(int chunkSize) {
677 chunks = new SizeClassedChunk[8];
678 purgeBudget = CHUNK_PURGE_POLLS_THREAD_LOCAL;
679 maxCachedChunks = Math.max(1, THREAD_LOCAL_CACHE_MAX_BYTES / chunkSize);
680 purgeRetentionFloor = Math.min(maxCachedChunks,
681 Math.max(1, THREAD_LOCAL_CACHE_MIN_BYTES / chunkSize));
682 }
683
684 @Override
685 SizeClassedChunk forcePurge() {
686 purgeBudget = 1;
687 return pollChunk(0);
688 }
689
690 @Override
691 public SizeClassedChunk pollChunk(int size) {
692 if (--purgeBudget == 0) {
693 runPurgeScan();
694 }
695 return scanForCapacity();
696 }
697
698 private SizeClassedChunk scanForCapacity() {
699 if (notEmptyCount > 0) {
700 SizeClassedChunk chunk = chunks[head];
701 assert chunk.hasRemainingCapacity();
702 chunk.purgeEpoch = 0;
703 chunks[head] = null;
704 head = (head + 1) & (chunks.length - 1);
705 count--;
706 notEmptyCount--;
707 return chunk;
708 }
709 return scanForCapacityFallback();
710 }
711
712 private SizeClassedChunk scanForCapacityFallback() {
713 int mask = chunks.length - 1;
714 int emptyCount = count - notEmptyCount;
715 int pos = (head + notEmptyCount) & mask;
716 for (int i = 0; i < emptyCount; i++) {
717 SizeClassedChunk chunk = chunks[pos];
718 if (chunk.hasRemainingCapacity()) {
719 chunk.purgeEpoch = 0;
720 int lastIdx = (tail - 1) & mask;
721 chunks[pos] = chunks[lastIdx];
722 chunks[lastIdx] = null;
723 tail = lastIdx;
724 count--;
725 return chunk;
726 }
727 pos = (pos + 1) & mask;
728 }
729 return null;
730 }
731
732 private void runPurgeScan() {
733 int mask = chunks.length - 1;
734 int kept = 0;
735 int survivors = count;
736 for (int i = 0; i < count; i++) {
737 int readIdx = (head + i) & mask;
738 SizeClassedChunk chunk = chunks[readIdx];
739 if (chunk.purgeEpoch > 0) {
740 assert chunk.hasFullCapacity();
741 chunk.purgeEpoch++;
742 if (chunk.purgeEpoch > CHUNK_PURGE_THRESHOLD && survivors > purgeRetentionFloor) {
743 chunk.markToDeallocate();
744 chunks[readIdx] = null;
745 survivors--;
746 continue;
747 }
748 } else if (chunk.hasFullCapacity()) {
749 chunk.purgeEpoch = 1;
750 }
751 int writeIdx = (head + kept) & mask;
752 if (writeIdx != readIdx) {
753 chunks[writeIdx] = chunk;
754 chunks[readIdx] = null;
755 }
756 kept++;
757 }
758 tail = (head + kept) & mask;
759 count = kept;
760 partition(kept);
761 purgeBudget = CHUNK_PURGE_POLLS_THREAD_LOCAL;
762 }
763
764 private void partition(int size) {
765 int mask = chunks.length - 1;
766
767 int lo = 0;
768 int hi = size - 1;
769 while (lo <= hi) {
770 int loIdx = (head + lo) & mask;
771 if (chunks[loIdx].hasRemainingCapacity()) {
772 lo++;
773 } else {
774 int hiIdx = (head + hi) & mask;
775 SizeClassedChunk tmp = chunks[loIdx];
776 chunks[loIdx] = chunks[hiIdx];
777 chunks[hiIdx] = tmp;
778 hi--;
779 }
780 }
781 notEmptyCount = lo;
782
783
784
785
786
787
788
789
790
791
792
793 int elo = 0;
794 int emid = 0;
795 int ehi = lo - 1;
796 while (emid <= ehi) {
797 int emidIdx = (head + emid) & mask;
798 SizeClassedChunk c = chunks[emidIdx];
799 if (c.purgeEpoch == 0) {
800 if (elo != emid) {
801 int eloIdx = (head + elo) & mask;
802 chunks[emidIdx] = chunks[eloIdx];
803 chunks[eloIdx] = c;
804 }
805 elo++;
806 emid++;
807 } else if (c.purgeEpoch < CHUNK_PURGE_THRESHOLD) {
808 emid++;
809 } else {
810 int ehiIdx = (head + ehi) & mask;
811 chunks[emidIdx] = chunks[ehiIdx];
812 chunks[ehiIdx] = c;
813 ehi--;
814 }
815 }
816 }
817
818 @Override
819 public boolean offerChunk(Chunk chunk) {
820 if (count >= maxCachedChunks) {
821 return false;
822 }
823 if (count == chunks.length) {
824 SizeClassedChunk[] newChunks = new SizeClassedChunk[chunks.length * 2];
825 for (int i = 0; i < count; i++) {
826 newChunks[i] = chunks[(head + i) & (chunks.length - 1)];
827 }
828 chunks = newChunks;
829 head = 0;
830 tail = count;
831 }
832 chunks[tail] = (SizeClassedChunk) chunk;
833 tail = (tail + 1) & (chunks.length - 1);
834 count++;
835 return true;
836 }
837
838 @Override
839 public String toString() {
840 int mask = chunks.length - 1;
841 StringBuilder sb = new StringBuilder();
842 sb.append("ThreadLocalCache[head=").append(head)
843 .append(", tail=").append(tail)
844 .append(", count=").append(count)
845 .append(", notEmpty=").append(notEmptyCount)
846 .append(", length=").append(chunks.length)
847 .append("]\n ");
848 for (int i = 0; i < count; i++) {
849 if (i > 0) {
850 sb.append(", ");
851 }
852 if (i == notEmptyCount) {
853 sb.append("| ");
854 }
855 SizeClassedChunk c = chunks[(head + i) & mask];
856 String region = i < notEmptyCount ? "notEmpty" : "empty";
857 String actual = c == null ? "null" :
858 c.hasRemainingCapacity() ? "hasCap" : "noCap";
859 sb.append('[').append(region).append(':').append(actual)
860 .append(",ep=").append(c == null ? -1 : c.purgeEpoch).append(']');
861 }
862 return sb.toString();
863 }
864
865 @Override
866 public void free() {
867 int mask = chunks.length - 1;
868 for (int i = 0; i < count; i++) {
869 int idx = (head + i) & mask;
870 chunks[idx].markToDeallocate();
871 chunks[idx] = null;
872 }
873 head = 0;
874 tail = 0;
875 count = 0;
876 notEmptyCount = 0;
877 }
878
879 @Override
880 public boolean isEmpty() {
881 return count == 0;
882 }
883 }
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917 static final class SharedSizeClassedChunkCache extends SizeClassedChunkCache {
918
919
920 private static final int SHARED_CACHE_CAPACITY = Math.max(16, CHUNK_REUSE_QUEUE * 2);
921 private final Queue<SizeClassedChunk> queue;
922 private final AtomicLong purgeBudget;
923 private final ArrayList<SizeClassedChunk> deferredBuffer = new ArrayList<>();
924 private long purgeGeneration;
925 private final AtomicLong scanGeneration = new AtomicLong();
926
927 SharedSizeClassedChunkCache() {
928 queue = PlatformDependent.newFixedMpmcQueue(SHARED_CACHE_CAPACITY);
929 purgeBudget = new AtomicLong(CHUNK_PURGE_POLLS_SHARED);
930 }
931
932 @Override
933 SizeClassedChunk forcePurge() {
934 purgeBudget.set(1);
935 return pollChunk(0);
936 }
937
938 @Override
939 public SizeClassedChunk pollChunk(int size) {
940 long budget = purgeBudget.decrementAndGet();
941 if (budget == 0) {
942 runPurgeScan();
943 }
944 return scanForCapacity();
945 }
946
947 private SizeClassedChunk scanForCapacity() {
948 SizeClassedChunk first = queue.poll();
949 if (first == null) {
950 return null;
951 }
952 if (first.purgeEpoch == 0 && first.hasRemainingCapacity()) {
953 return first;
954 }
955 long generation = scanGeneration.incrementAndGet();
956 first.lastScanGeneration = generation;
957 if (first.hasRemainingCapacity()) {
958 return scanForCapacitySlow(generation, first);
959 }
960 offerOrDeallocate(first);
961 return scanForCapacitySlow(generation, null);
962 }
963
964 private SizeClassedChunk scanForCapacitySlow(long generation, SizeClassedChunk fallback) {
965 SizeClassedChunk chunk;
966 while ((chunk = queue.poll()) != null) {
967 if (chunk.lastScanGeneration >= generation) {
968 offerOrDeallocate(chunk);
969 break;
970 }
971 if (chunk.hasRemainingCapacity()) {
972 if (chunk.purgeEpoch == 0) {
973 if (fallback != null) {
974 offerOrDeallocate(fallback);
975 }
976 return chunk;
977 }
978 if (fallback == null) {
979 fallback = chunk;
980 continue;
981 }
982 }
983 chunk.lastScanGeneration = generation;
984 offerOrDeallocate(chunk);
985 }
986 if (fallback != null) {
987 fallback.purgeEpoch = 0;
988 return fallback;
989 }
990 return null;
991 }
992
993 private boolean offerOrDeallocate(SizeClassedChunk chunk) {
994 if (!queue.offer(chunk)) {
995 chunk.markToDeallocate();
996 return false;
997 }
998 return true;
999 }
1000
1001 private boolean offerOrDeallocate(SizeClassedChunk chunk, long generation) {
1002 chunk.lastPurgeGeneration = generation;
1003 return offerOrDeallocate(chunk);
1004 }
1005
1006 private void runPurgeScan() {
1007 long generation = ++purgeGeneration;
1008 int retained = 0;
1009 ArrayList<SizeClassedChunk> deferred = deferredBuffer;
1010 SizeClassedChunk chunk;
1011 while ((chunk = queue.poll()) != null) {
1012 if (chunk.lastPurgeGeneration == generation) {
1013 offerOrDeallocate(chunk, generation);
1014 break;
1015 }
1016 retained++;
1017 if (chunk.hasFullCapacity()) {
1018 chunk.purgeEpoch++;
1019 if (chunk.purgeEpoch > CHUNK_PURGE_THRESHOLD) {
1020 deferred.add(chunk);
1021 continue;
1022 }
1023 } else {
1024 chunk.purgeEpoch = 0;
1025 }
1026 int remaining = chunk.remainingCapacity();
1027 if (remaining > 0) {
1028 if (!offerOrDeallocate(chunk, generation)) {
1029 retained--;
1030 }
1031 } else {
1032 deferred.add(chunk);
1033 }
1034 }
1035 for (int i = 0, size = deferred.size(); i < size; i++) {
1036 chunk = deferred.get(i);
1037 if (chunk.purgeEpoch > CHUNK_PURGE_THRESHOLD && retained > CHUNK_REUSE_QUEUE) {
1038 chunk.markToDeallocate();
1039 retained--;
1040 } else {
1041 if (!offerOrDeallocate(chunk, generation)) {
1042 retained--;
1043 }
1044 }
1045 }
1046 deferred.clear();
1047 purgeBudget.lazySet(CHUNK_PURGE_POLLS_SHARED);
1048 }
1049
1050 @Override
1051 public boolean offerChunk(Chunk chunk) {
1052 return queue.offer((SizeClassedChunk) chunk);
1053 }
1054
1055 @Override
1056 public void free() {
1057 SizeClassedChunk chunk;
1058 while ((chunk = queue.poll()) != null) {
1059 chunk.markToDeallocate();
1060 }
1061 }
1062
1063 @Override
1064 public boolean isEmpty() {
1065 return queue.isEmpty();
1066 }
1067 }
1068
1069 private static final class ConcurrentSkipListChunkCache implements ChunkCache {
1070 private final ConcurrentSkipListIntObjMultimap<Chunk> chunks;
1071
1072 private ConcurrentSkipListChunkCache() {
1073 chunks = new ConcurrentSkipListIntObjMultimap<>(-1);
1074 }
1075
1076 @Override
1077 public Chunk pollChunk(int size) {
1078 if (chunks.isEmpty()) {
1079 return null;
1080 }
1081 IntEntry<Chunk> entry = chunks.pollCeilingEntry(size);
1082 if (entry != null) {
1083 Chunk chunk = entry.getValue();
1084 if (chunk.hasUnprocessedFreelistEntries()) {
1085 chunk.processFreelistEntries();
1086 }
1087 return chunk;
1088 }
1089
1090 Chunk bestChunk = null;
1091 int bestRemainingCapacity = 0;
1092 Iterator<IntEntry<Chunk>> itr = chunks.iterator();
1093 while (itr.hasNext()) {
1094 entry = itr.next();
1095 final Chunk chunk;
1096 if (entry != null && (chunk = entry.getValue()).hasUnprocessedFreelistEntries()) {
1097 if (!chunks.remove(entry.getKey(), entry.getValue())) {
1098 continue;
1099 }
1100 chunk.processFreelistEntries();
1101 int remainingCapacity = chunk.remainingCapacity();
1102 if (remainingCapacity >= size &&
1103 (bestChunk == null || remainingCapacity > bestRemainingCapacity)) {
1104 if (bestChunk != null) {
1105 chunks.put(bestRemainingCapacity, bestChunk);
1106 }
1107 bestChunk = chunk;
1108 bestRemainingCapacity = remainingCapacity;
1109 } else {
1110 chunks.put(remainingCapacity, chunk);
1111 }
1112 }
1113 }
1114
1115 return bestChunk;
1116 }
1117
1118 @Override
1119 public boolean offerChunk(Chunk chunk) {
1120 chunks.put(chunk.remainingCapacity(), chunk);
1121
1122 int size = chunks.size();
1123 while (size > CHUNK_REUSE_QUEUE) {
1124 int key = -1;
1125 Chunk toDeallocate = null;
1126 for (IntEntry<Chunk> entry : chunks) {
1127 Chunk candidate = entry.getValue();
1128 if (candidate != null && RefCnt.refCnt(candidate.refCnt) == 1) {
1129 toDeallocate = candidate;
1130 key = entry.getKey();
1131 break;
1132 }
1133 }
1134 if (toDeallocate == null) {
1135 break;
1136 }
1137 if (chunks.remove(key, toDeallocate)) {
1138 toDeallocate.markToDeallocate();
1139 }
1140 size = chunks.size();
1141 }
1142 return true;
1143 }
1144
1145 @Override
1146 public void free() {
1147 for (IntEntry<Chunk> entry : chunks) {
1148 Chunk chunk = entry.getValue();
1149 if (chunk != null && chunks.remove(entry.getKey(), chunk)) {
1150 chunk.markToDeallocate();
1151 }
1152 }
1153 }
1154
1155 @Override
1156 public boolean isEmpty() {
1157 return chunks.isEmpty();
1158 }
1159 }
1160
1161 private interface ChunkManagementStrategy {
1162 ChunkController createController(MagazineGroup group);
1163
1164 ChunkCache createChunkCache(boolean isThreadLocal);
1165 }
1166
1167 private interface ChunkController {
1168
1169
1170
1171 int computeBufferCapacity(int requestedSize, int maxCapacity, boolean isReallocation);
1172
1173
1174
1175
1176 Chunk newChunkAllocation(int promptingSize, Magazine magazine);
1177 }
1178
1179 private static final class SizeClassChunkManagementStrategy implements ChunkManagementStrategy {
1180
1181
1182
1183 private static final int MIN_SEGMENTS_PER_CHUNK = 32;
1184 private final int segmentSize;
1185 private final int chunkSize;
1186
1187 private SizeClassChunkManagementStrategy(int segmentSize) {
1188 this.segmentSize = ObjectUtil.checkPositive(segmentSize, "segmentSize");
1189 chunkSize = Math.max(MIN_CHUNK_SIZE, segmentSize * MIN_SEGMENTS_PER_CHUNK);
1190 }
1191
1192 @Override
1193 public ChunkController createController(MagazineGroup group) {
1194 return new SizeClassChunkController(group, segmentSize, chunkSize);
1195 }
1196
1197 @Override
1198 public ChunkCache createChunkCache(boolean isThreadLocal) {
1199 return SizeClassedChunkCache.create(isThreadLocal, chunkSize);
1200 }
1201 }
1202
1203 private static final class SizeClassChunkController implements ChunkController {
1204
1205 private final ChunkAllocator chunkAllocator;
1206 private final int segmentSize;
1207 private final int chunkSize;
1208 private final ChunkRegistry chunkRegistry;
1209
1210 private SizeClassChunkController(MagazineGroup group, int segmentSize, int chunkSize) {
1211 chunkAllocator = group.chunkAllocator;
1212 this.segmentSize = segmentSize;
1213 this.chunkSize = chunkSize;
1214 chunkRegistry = group.allocator.chunkRegistry;
1215 }
1216
1217 private MpscIntQueue createEmptyFreeList() {
1218 return MpscIntQueue.create(chunkSize / segmentSize, SizeClassedChunk.FREE_LIST_EMPTY);
1219 }
1220
1221 private MpscIntQueue createFreeList() {
1222 final int segmentsCount = chunkSize / segmentSize;
1223 final MpscIntQueue freeList = MpscIntQueue.create(segmentsCount, SizeClassedChunk.FREE_LIST_EMPTY);
1224 int segmentOffset = 0;
1225 for (int i = 0; i < segmentsCount; i++) {
1226 freeList.offer(segmentOffset);
1227 segmentOffset += segmentSize;
1228 }
1229 return freeList;
1230 }
1231
1232 private IntStack createLocalFreeList() {
1233 final int segmentsCount = chunkSize / segmentSize;
1234 int segmentOffset = chunkSize;
1235 int[] offsets = new int[segmentsCount];
1236 for (int i = 0; i < segmentsCount; i++) {
1237 segmentOffset -= segmentSize;
1238 offsets[i] = segmentOffset;
1239 }
1240 return new IntStack(offsets);
1241 }
1242
1243 @Override
1244 public int computeBufferCapacity(
1245 int requestedSize, int maxCapacity, boolean isReallocation) {
1246 return Math.min(segmentSize, maxCapacity);
1247 }
1248
1249 @Override
1250 public Chunk newChunkAllocation(int promptingSize, Magazine magazine) {
1251 AbstractByteBuf chunkBuffer = chunkAllocator.allocate(chunkSize, chunkSize);
1252 assert chunkBuffer.capacity() == chunkSize;
1253 SizeClassedChunk chunk = new SizeClassedChunk(chunkBuffer, magazine, this);
1254 chunkRegistry.add(chunk);
1255 return chunk;
1256 }
1257 }
1258
1259 private static final class BuddyChunkManagementStrategy implements ChunkManagementStrategy {
1260 private final AtomicInteger maxChunkSize = new AtomicInteger();
1261
1262 @Override
1263 public ChunkController createController(MagazineGroup group) {
1264 return new BuddyChunkController(group, maxChunkSize);
1265 }
1266
1267 @Override
1268 public ChunkCache createChunkCache(boolean isThreadLocal) {
1269 return new ConcurrentSkipListChunkCache();
1270 }
1271 }
1272
1273 private static final class BuddyChunkController implements ChunkController {
1274 private final ChunkAllocator chunkAllocator;
1275 private final ChunkRegistry chunkRegistry;
1276 private final AtomicInteger maxChunkSize;
1277
1278 BuddyChunkController(MagazineGroup group, AtomicInteger maxChunkSize) {
1279 chunkAllocator = group.chunkAllocator;
1280 chunkRegistry = group.allocator.chunkRegistry;
1281 this.maxChunkSize = maxChunkSize;
1282 }
1283
1284 @Override
1285 public int computeBufferCapacity(int requestedSize, int maxCapacity, boolean isReallocation) {
1286 return MathUtil.safeFindNextPositivePowerOfTwo(requestedSize);
1287 }
1288
1289 @Override
1290 public Chunk newChunkAllocation(int promptingSize, Magazine magazine) {
1291 int maxChunkSize = this.maxChunkSize.get();
1292 int proposedChunkSize = MathUtil.safeFindNextPositivePowerOfTwo(BUFS_PER_CHUNK * promptingSize);
1293 int chunkSize = Math.min(MAX_CHUNK_SIZE, Math.max(maxChunkSize, proposedChunkSize));
1294 if (chunkSize > maxChunkSize) {
1295
1296 this.maxChunkSize.set(chunkSize);
1297 }
1298 BuddyChunk chunk = new BuddyChunk(chunkAllocator.allocate(chunkSize, chunkSize), magazine);
1299 chunkRegistry.add(chunk);
1300 return chunk;
1301 }
1302 }
1303
1304 private static final class Magazine {
1305 private static final AtomicReferenceFieldUpdater<Magazine, Chunk> NEXT_IN_LINE;
1306
1307 static {
1308 NEXT_IN_LINE = AtomicReferenceFieldUpdater.newUpdater(Magazine.class, Chunk.class, "nextInLine");
1309 }
1310
1311 private static final Chunk MAGAZINE_FREED = new Chunk();
1312
1313 private static final class AdaptiveRecycler extends Recycler<AdaptiveByteBuf> {
1314
1315 private AdaptiveRecycler(boolean unguarded) {
1316
1317 super(unguarded);
1318 }
1319
1320 private AdaptiveRecycler(int maxCapacity, boolean unguarded) {
1321
1322 super(maxCapacity, unguarded);
1323 }
1324
1325 @Override
1326 protected AdaptiveByteBuf newObject(final Handle<AdaptiveByteBuf> handle) {
1327 return new AdaptiveByteBuf((EnhancedHandle<AdaptiveByteBuf>) handle);
1328 }
1329
1330 public static AdaptiveRecycler threadLocal() {
1331 return new AdaptiveRecycler(true);
1332 }
1333
1334 public static AdaptiveRecycler sharedWith(int maxCapacity) {
1335 return new AdaptiveRecycler(maxCapacity, true);
1336 }
1337 }
1338
1339 private static final AdaptiveRecycler EVENT_LOOP_LOCAL_BUFFER_POOL = AdaptiveRecycler.threadLocal();
1340
1341 private Chunk current;
1342 @SuppressWarnings("unused")
1343 private volatile Chunk nextInLine;
1344 private final MagazineGroup group;
1345 private final ChunkController chunkController;
1346 private final StampedLock allocationLock;
1347 private final AdaptiveRecycler recycler;
1348
1349 Magazine(MagazineGroup group, boolean shareable, ChunkController chunkController) {
1350 this.group = group;
1351 this.chunkController = chunkController;
1352
1353 if (shareable) {
1354
1355 allocationLock = new StampedLock();
1356 recycler = AdaptiveRecycler.sharedWith(MAGAZINE_BUFFER_QUEUE_CAPACITY);
1357 } else {
1358 allocationLock = null;
1359 recycler = null;
1360 }
1361 }
1362
1363 public boolean tryAllocate(int size, int maxCapacity, AdaptiveByteBuf buf, boolean reallocate) {
1364 if (allocationLock == null) {
1365
1366 return allocate(size, maxCapacity, buf, reallocate);
1367 }
1368
1369
1370 long writeLock = allocationLock.tryWriteLock();
1371 if (writeLock != 0) {
1372 try {
1373 return allocate(size, maxCapacity, buf, reallocate);
1374 } finally {
1375 allocationLock.unlockWrite(writeLock);
1376 }
1377 }
1378 return allocateWithoutLock(size, maxCapacity, buf);
1379 }
1380
1381 private boolean allocateWithoutLock(int size, int maxCapacity, AdaptiveByteBuf buf) {
1382 Chunk curr = NEXT_IN_LINE.getAndSet(this, null);
1383 if (curr == MAGAZINE_FREED) {
1384
1385 restoreMagazineFreed();
1386 return false;
1387 }
1388 if (curr == null) {
1389 curr = group.pollChunk(size);
1390 if (curr == null) {
1391 return false;
1392 }
1393 curr.attachToMagazine(this);
1394 }
1395 boolean allocated = false;
1396 int remainingCapacity = curr.remainingCapacity();
1397 int startingCapacity = chunkController.computeBufferCapacity(
1398 size, maxCapacity, true );
1399 if (remainingCapacity >= size &&
1400 curr.readInitInto(buf, size, Math.min(remainingCapacity, startingCapacity), maxCapacity)) {
1401 allocated = true;
1402 remainingCapacity = curr.remainingCapacity();
1403 }
1404 try {
1405 if (remainingCapacity >= RETIRE_CAPACITY) {
1406 transferToNextInLineOrRelease(curr);
1407 curr = null;
1408 }
1409 } finally {
1410 if (curr != null) {
1411 curr.releaseFromMagazine();
1412 }
1413 }
1414 return allocated;
1415 }
1416
1417 private boolean allocate(int size, int maxCapacity, AdaptiveByteBuf buf, boolean reallocate) {
1418 int startingCapacity = chunkController.computeBufferCapacity(size, maxCapacity, reallocate);
1419 Chunk curr = current;
1420 if (curr != null) {
1421 boolean success = curr.readInitInto(buf, size, startingCapacity, maxCapacity);
1422 int remainingCapacity = curr.remainingCapacity();
1423 if (!success && remainingCapacity > 0) {
1424 current = null;
1425 transferToNextInLineOrRelease(curr);
1426 } else if (remainingCapacity == 0) {
1427 current = null;
1428 curr.releaseFromMagazine();
1429 }
1430 if (success) {
1431 return true;
1432 }
1433 }
1434
1435 assert current == null;
1436
1437
1438
1439
1440
1441
1442
1443
1444 curr = NEXT_IN_LINE.getAndSet(this, null);
1445 if (curr != null) {
1446 if (curr == MAGAZINE_FREED) {
1447
1448 restoreMagazineFreed();
1449 return false;
1450 }
1451
1452 int remainingCapacity = curr.remainingCapacity();
1453 if (remainingCapacity > startingCapacity &&
1454 curr.readInitInto(buf, size, startingCapacity, maxCapacity)) {
1455
1456 current = curr;
1457 return true;
1458 }
1459
1460 try {
1461 if (remainingCapacity >= size) {
1462
1463
1464 return curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
1465 }
1466 } finally {
1467
1468
1469 curr.releaseFromMagazine();
1470 }
1471 }
1472
1473
1474 curr = group.pollChunk(size);
1475 if (curr == null) {
1476 curr = chunkController.newChunkAllocation(size, this);
1477 } else {
1478 curr.attachToMagazine(this);
1479
1480 int remainingCapacity = curr.remainingCapacity();
1481 if (remainingCapacity == 0 || remainingCapacity < size) {
1482
1483 if (remainingCapacity < RETIRE_CAPACITY) {
1484 curr.releaseFromMagazine();
1485 } else {
1486
1487
1488 transferToNextInLineOrRelease(curr);
1489 }
1490 curr = chunkController.newChunkAllocation(size, this);
1491 }
1492 }
1493
1494 current = curr;
1495 boolean success;
1496 try {
1497 int remainingCapacity = curr.remainingCapacity();
1498 assert remainingCapacity >= size;
1499 if (remainingCapacity > startingCapacity) {
1500 success = curr.readInitInto(buf, size, startingCapacity, maxCapacity);
1501 curr = null;
1502 } else {
1503 success = curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
1504 }
1505 } finally {
1506 if (curr != null) {
1507
1508
1509 curr.releaseFromMagazine();
1510 current = null;
1511 }
1512 }
1513 return success;
1514 }
1515
1516 private void restoreMagazineFreed() {
1517 Chunk next = NEXT_IN_LINE.getAndSet(this, MAGAZINE_FREED);
1518 if (next != null && next != MAGAZINE_FREED) {
1519
1520 next.releaseFromMagazine();
1521 }
1522 }
1523
1524 private void transferToNextInLineOrRelease(Chunk chunk) {
1525 if (NEXT_IN_LINE.compareAndSet(this, null, chunk)) {
1526 return;
1527 }
1528
1529 Chunk nextChunk = NEXT_IN_LINE.get(this);
1530 if (nextChunk != null && nextChunk != MAGAZINE_FREED
1531 && chunk.remainingCapacity() > nextChunk.remainingCapacity()) {
1532 if (NEXT_IN_LINE.compareAndSet(this, nextChunk, chunk)) {
1533 nextChunk.releaseFromMagazine();
1534 return;
1535 }
1536 }
1537
1538
1539
1540
1541 chunk.releaseFromMagazine();
1542 }
1543
1544 void free() {
1545
1546 restoreMagazineFreed();
1547 long stamp = allocationLock != null ? allocationLock.writeLock() : 0;
1548 try {
1549 if (current != null) {
1550 current.releaseFromMagazine();
1551 current = null;
1552 }
1553 } finally {
1554 if (allocationLock != null) {
1555 allocationLock.unlockWrite(stamp);
1556 }
1557 }
1558 }
1559
1560 public AdaptiveByteBuf newBuffer() {
1561 AdaptiveRecycler recycler = this.recycler;
1562 AdaptiveByteBuf buf = recycler == null ? EVENT_LOOP_LOCAL_BUFFER_POOL.get() : recycler.get();
1563 buf.resetRefCnt();
1564 buf.discardMarks();
1565 return buf;
1566 }
1567
1568 boolean offerToQueue(Chunk chunk) {
1569 return group.offerChunk(chunk);
1570 }
1571 }
1572
1573 private static final class ChunkRegistry {
1574 private final LongAdder totalCapacity = new LongAdder();
1575
1576 public long totalCapacity() {
1577 return totalCapacity.sum();
1578 }
1579
1580 public void add(Chunk chunk) {
1581 totalCapacity.add(chunk.capacity());
1582 }
1583
1584 public void remove(Chunk chunk) {
1585 totalCapacity.add(-chunk.capacity());
1586 }
1587 }
1588
1589 static class Chunk implements ChunkInfo {
1590 protected final AbstractByteBuf delegate;
1591 protected Magazine magazine;
1592 private final AdaptivePoolingAllocator allocator;
1593
1594
1595 private final RefCnt refCnt = new RefCnt();
1596 private final int capacity;
1597 private final boolean pooled;
1598 protected int allocatedBytes;
1599
1600 Chunk() {
1601
1602 delegate = null;
1603 magazine = null;
1604 allocator = null;
1605 capacity = 0;
1606 pooled = false;
1607 }
1608
1609 Chunk(AbstractByteBuf delegate, Magazine magazine, boolean pooled) {
1610 this.delegate = delegate;
1611 this.pooled = pooled;
1612 capacity = delegate.capacity();
1613 attachToMagazine(magazine);
1614
1615
1616 allocator = magazine.group.allocator;
1617
1618 if (PlatformDependent.isJfrEnabled() && AllocateChunkEvent.isEventEnabled()) {
1619 AllocateChunkEvent event = new AllocateChunkEvent();
1620 if (event.shouldCommit()) {
1621 event.fill(this, AdaptiveByteBufAllocator.class);
1622 event.pooled = pooled;
1623 event.threadLocal = magazine.allocationLock == null;
1624 event.commit();
1625 }
1626 }
1627 }
1628
1629 Magazine currentMagazine() {
1630 return magazine;
1631 }
1632
1633 void detachFromMagazine() {
1634 if (magazine != null) {
1635 magazine = null;
1636 }
1637 }
1638
1639 void attachToMagazine(Magazine magazine) {
1640 assert this.magazine == null;
1641 this.magazine = magazine;
1642 }
1643
1644
1645
1646
1647 void releaseFromMagazine() {
1648
1649
1650 Magazine mag = magazine;
1651 detachFromMagazine();
1652 if (!mag.offerToQueue(this)) {
1653 markToDeallocate();
1654 }
1655 }
1656
1657
1658
1659
1660 void releaseSegment(int ignoredSegmentId, int size) {
1661 release();
1662 }
1663
1664 void markToDeallocate() {
1665 release();
1666 }
1667
1668 private void retain() {
1669 RefCnt.retain(refCnt);
1670 }
1671
1672 protected boolean release() {
1673 boolean deallocate = RefCnt.release(refCnt);
1674 if (deallocate) {
1675 deallocate();
1676 }
1677 return deallocate;
1678 }
1679
1680 protected void deallocate() {
1681 onRelease();
1682 allocator.chunkRegistry.remove(this);
1683 delegate.release();
1684 }
1685
1686 private void onRelease() {
1687 if (PlatformDependent.isJfrEnabled() && FreeChunkEvent.isEventEnabled()) {
1688 FreeChunkEvent event = new FreeChunkEvent();
1689 if (event.shouldCommit()) {
1690 event.fill(this, AdaptiveByteBufAllocator.class);
1691 event.pooled = pooled;
1692 event.commit();
1693 }
1694 }
1695 }
1696
1697 public boolean readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1698 int startIndex = allocatedBytes;
1699 allocatedBytes = startIndex + startingCapacity;
1700 Chunk chunk = this;
1701 chunk.retain();
1702 try {
1703 buf.init(delegate, chunk, 0, 0, startIndex, size, startingCapacity, maxCapacity);
1704 chunk = null;
1705 } finally {
1706 if (chunk != null) {
1707
1708
1709
1710 allocatedBytes = startIndex;
1711 chunk.release();
1712 }
1713 }
1714 return true;
1715 }
1716
1717 public int remainingCapacity() {
1718 return capacity - allocatedBytes;
1719 }
1720
1721 public boolean hasUnprocessedFreelistEntries() {
1722 return false;
1723 }
1724
1725 public void processFreelistEntries() {
1726 }
1727
1728 @Override
1729 public int capacity() {
1730 return capacity;
1731 }
1732
1733 @Override
1734 public boolean isDirect() {
1735 return delegate.isDirect();
1736 }
1737
1738 @Override
1739 public long memoryAddress() {
1740 return delegate._memoryAddress();
1741 }
1742 }
1743
1744 private static final class IntStack {
1745
1746 private final int[] stack;
1747 private int top;
1748
1749 IntStack(int[] initialValues) {
1750 stack = initialValues;
1751 top = initialValues.length - 1;
1752 }
1753
1754 public boolean isEmpty() {
1755 return top == -1;
1756 }
1757
1758 public int pop() {
1759 final int last = stack[top];
1760 top--;
1761 return last;
1762 }
1763
1764 public void push(int value) {
1765 stack[top + 1] = value;
1766 top++;
1767 }
1768
1769 public int size() {
1770 return top + 1;
1771 }
1772 }
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793 static class SizeClassedChunk extends Chunk {
1794 private static final int FREE_LIST_EMPTY = -1;
1795 private static final int AVAILABLE = -1;
1796
1797
1798 private static final int DEALLOCATED = Integer.MIN_VALUE;
1799 private static final AtomicIntegerFieldUpdater<SizeClassedChunk> STATE =
1800 AtomicIntegerFieldUpdater.newUpdater(SizeClassedChunk.class, "state");
1801 private volatile int state;
1802 private final int segments;
1803 private final int segmentSize;
1804 private final MpscIntQueue externalFreeList;
1805 private final IntStack localFreeList;
1806 private Thread ownerThread;
1807 int purgeEpoch;
1808 long lastPurgeGeneration;
1809 long lastScanGeneration;
1810
1811 SizeClassedChunk(AbstractByteBuf delegate, Magazine magazine,
1812 SizeClassChunkController controller) {
1813 super(delegate, magazine, true);
1814 segmentSize = controller.segmentSize;
1815 segments = controller.chunkSize / segmentSize;
1816 STATE.lazySet(this, AVAILABLE);
1817 ownerThread = magazine.group.ownerThread;
1818 if (ownerThread == null) {
1819 externalFreeList = controller.createFreeList();
1820 localFreeList = null;
1821 } else {
1822 externalFreeList = controller.createEmptyFreeList();
1823 localFreeList = controller.createLocalFreeList();
1824 }
1825 }
1826
1827 @Override
1828 public boolean readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1829 assert state == AVAILABLE;
1830 final int startIndex = nextAvailableSegmentOffset();
1831 if (startIndex == FREE_LIST_EMPTY) {
1832 return false;
1833 }
1834 allocatedBytes += segmentSize;
1835 try {
1836 buf.init(delegate, this, 0, 0, startIndex, size, startingCapacity, maxCapacity);
1837 } catch (Throwable t) {
1838 allocatedBytes -= segmentSize;
1839 releaseSegmentOffsetIntoFreeList(startIndex);
1840 throw t;
1841 }
1842 return true;
1843 }
1844
1845 private int nextAvailableSegmentOffset() {
1846 final int startIndex;
1847 IntStack localFreeList = this.localFreeList;
1848 if (localFreeList != null) {
1849 assert Thread.currentThread() == ownerThread;
1850 if (localFreeList.isEmpty()) {
1851 startIndex = externalFreeList.poll();
1852 } else {
1853 startIndex = localFreeList.pop();
1854 }
1855 } else {
1856 startIndex = externalFreeList.poll();
1857 }
1858 return startIndex;
1859 }
1860
1861
1862
1863
1864 public boolean hasRemainingCapacity() {
1865 int remaining = super.remainingCapacity();
1866 if (remaining > 0) {
1867 return true;
1868 }
1869 if (localFreeList != null) {
1870 return !localFreeList.isEmpty();
1871 }
1872 return !externalFreeList.isEmpty();
1873 }
1874
1875 boolean hasFullCapacity() {
1876 int free = externalFreeList.size();
1877 IntStack local = localFreeList;
1878 if (local != null) {
1879 free += local.size();
1880 }
1881 return free == segments;
1882 }
1883
1884 @Override
1885 public int remainingCapacity() {
1886 int remaining = super.remainingCapacity();
1887 return remaining > segmentSize ? remaining : updateRemainingCapacity(remaining);
1888 }
1889
1890 private int updateRemainingCapacity(int snapshotted) {
1891 int freeSegments = externalFreeList.size();
1892 IntStack localFreeList = this.localFreeList;
1893 if (localFreeList != null) {
1894 freeSegments += localFreeList.size();
1895 }
1896 int updated = freeSegments * segmentSize;
1897 if (updated != snapshotted) {
1898 allocatedBytes = capacity() - updated;
1899 }
1900 return updated;
1901 }
1902
1903 private void releaseSegmentOffsetIntoFreeList(int startIndex) {
1904 IntStack localFreeList = this.localFreeList;
1905 if (localFreeList != null && Thread.currentThread() == ownerThread) {
1906 localFreeList.push(startIndex);
1907 } else {
1908 boolean segmentReturned = externalFreeList.offer(startIndex);
1909 assert segmentReturned : "Unable to return segment " + startIndex + " to free list";
1910 }
1911 }
1912
1913 @Override
1914 void releaseSegment(int startIndex, int size) {
1915 IntStack localFreeList = this.localFreeList;
1916 if (localFreeList != null && Thread.currentThread() == ownerThread) {
1917 localFreeList.push(startIndex);
1918 int state = this.state;
1919 if (state != AVAILABLE) {
1920 updateStateOnLocalReleaseSegment(state, localFreeList);
1921 }
1922 } else {
1923 boolean segmentReturned = externalFreeList.offer(startIndex);
1924 assert segmentReturned;
1925
1926 int state = this.state;
1927 if (state != AVAILABLE) {
1928 deallocateIfNeeded(state);
1929 }
1930 }
1931 }
1932
1933 private void updateStateOnLocalReleaseSegment(int previousLocalSize, IntStack localFreeList) {
1934 int newLocalSize = localFreeList.size();
1935 boolean alwaysTrue = STATE.compareAndSet(this, previousLocalSize, newLocalSize);
1936 assert alwaysTrue : "this shouldn't happen unless double release in the local free list";
1937 deallocateIfNeeded(newLocalSize);
1938 }
1939
1940 private void deallocateIfNeeded(int localSize) {
1941
1942 int totalFreeSegments = localSize + externalFreeList.size();
1943 if (totalFreeSegments == segments && STATE.compareAndSet(this, localSize, DEALLOCATED)) {
1944 deallocate();
1945 }
1946 }
1947
1948 @Override
1949 void markToDeallocate() {
1950 IntStack localFreeList = this.localFreeList;
1951 int localSize = localFreeList != null ? localFreeList.size() : 0;
1952 STATE.set(this, localSize);
1953 deallocateIfNeeded(localSize);
1954 }
1955 }
1956
1957 private static final class BuddyChunk extends Chunk implements IntConsumer {
1958 private static final int MIN_BUDDY_SIZE = 32768;
1959 private static final byte IS_CLAIMED = (byte) (1 << 7);
1960 private static final byte HAS_CLAIMED_CHILDREN = 1 << 6;
1961 private static final byte SHIFT_MASK = ~(IS_CLAIMED | HAS_CLAIMED_CHILDREN);
1962 private static final int PACK_OFFSET_MASK = 0xFFFF;
1963 private static final int PACK_SIZE_SHIFT = Integer.SIZE - Integer.numberOfLeadingZeros(PACK_OFFSET_MASK);
1964
1965 private final MpscIntQueue freeList;
1966
1967 private final byte[] buddies;
1968 private final int freeListCapacity;
1969
1970 BuddyChunk(AbstractByteBuf delegate, Magazine magazine) {
1971 super(delegate, magazine, true);
1972 freeListCapacity = delegate.capacity() / MIN_BUDDY_SIZE;
1973 int maxShift = Integer.numberOfTrailingZeros(freeListCapacity);
1974 assert maxShift <= 30;
1975 freeList = MpscIntQueue.create(freeListCapacity, -1);
1976 buddies = new byte[freeListCapacity << 1];
1977
1978
1979 int index = 1;
1980 int runLength = 1;
1981 int currentRun = 0;
1982 while (maxShift > 0) {
1983 buddies[index++] = (byte) maxShift;
1984 if (++currentRun == runLength) {
1985 currentRun = 0;
1986 runLength <<= 1;
1987 maxShift--;
1988 }
1989 }
1990 }
1991
1992 @Override
1993 public boolean readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1994 if (!freeList.isEmpty()) {
1995 freeList.drain(freeListCapacity, this);
1996 }
1997 int startIndex = chooseFirstFreeBuddy(1, startingCapacity, 0);
1998 if (startIndex == -1) {
1999 return false;
2000 }
2001 Chunk chunk = this;
2002 chunk.retain();
2003 try {
2004 buf.init(delegate, this, 0, 0, startIndex, size, startingCapacity, maxCapacity);
2005 allocatedBytes += startingCapacity;
2006 chunk = null;
2007 } finally {
2008 if (chunk != null) {
2009 unreserveMatchingBuddy(1, startingCapacity, startIndex, 0);
2010
2011
2012 chunk.release();
2013 }
2014 }
2015 return true;
2016 }
2017
2018 @Override
2019 public void accept(int packed) {
2020
2021 int size = unpackSize(packed);
2022 int offset = unpackOffset(packed);
2023 unreserveMatchingBuddy(1, size, offset, 0);
2024 allocatedBytes -= size;
2025 }
2026
2027 private static int unpackSize(int packed) {
2028 return MIN_BUDDY_SIZE << (packed >> PACK_SIZE_SHIFT);
2029 }
2030
2031 private static int unpackOffset(int packed) {
2032 return (packed & PACK_OFFSET_MASK) * MIN_BUDDY_SIZE;
2033 }
2034
2035 @Override
2036 void releaseSegment(int startingIndex, int size) {
2037 int packedOffset = startingIndex / MIN_BUDDY_SIZE;
2038 int packedSize = Integer.numberOfTrailingZeros(size / MIN_BUDDY_SIZE) << PACK_SIZE_SHIFT;
2039 int packed = packedOffset | packedSize;
2040 freeList.offer(packed);
2041 release();
2042 }
2043
2044 @Override
2045 public int remainingCapacity() {
2046 int capacityInFreeList = 0;
2047 if (!freeList.isEmpty()) {
2048 capacityInFreeList = freeList.weakPeekReduce(freeListCapacity, 0,
2049 (sum, entry) -> sum + unpackSize(entry));
2050 }
2051 return super.remainingCapacity() + capacityInFreeList;
2052 }
2053
2054 @Override
2055 public boolean hasUnprocessedFreelistEntries() {
2056 return !freeList.isEmpty();
2057 }
2058
2059 @Override
2060 public void processFreelistEntries() {
2061 freeList.drain(freeListCapacity, this);
2062 }
2063
2064
2065
2066
2067 private int chooseFirstFreeBuddy(int index, int size, int currOffset) {
2068 byte[] buddies = this.buddies;
2069 while (index < buddies.length) {
2070 byte buddy = buddies[index];
2071 int currValue = MIN_BUDDY_SIZE << (buddy & SHIFT_MASK);
2072 if (currValue < size || (buddy & IS_CLAIMED) == IS_CLAIMED) {
2073 return -1;
2074 }
2075 if (currValue == size && (buddy & HAS_CLAIMED_CHILDREN) == 0) {
2076 buddies[index] |= IS_CLAIMED;
2077 return currOffset;
2078 }
2079 int found = chooseFirstFreeBuddy(index << 1, size, currOffset);
2080 if (found != -1) {
2081 buddies[index] |= HAS_CLAIMED_CHILDREN;
2082 return found;
2083 }
2084 index = (index << 1) + 1;
2085 currOffset += currValue >> 1;
2086 }
2087 return -1;
2088 }
2089
2090
2091
2092
2093 private boolean unreserveMatchingBuddy(int index, int size, int offset, int currOffset) {
2094 byte[] buddies = this.buddies;
2095 if (buddies.length <= index) {
2096 return false;
2097 }
2098 byte buddy = buddies[index];
2099 int currSize = MIN_BUDDY_SIZE << (buddy & SHIFT_MASK);
2100
2101 if (currSize == size) {
2102
2103 if (currOffset == offset) {
2104 buddies[index] &= SHIFT_MASK;
2105 return false;
2106 }
2107 throw new IllegalStateException("The intended segment was not found at index " +
2108 index + ", for size " + size + " and offset " + offset);
2109 }
2110
2111
2112 boolean claims;
2113 int siblingIndex;
2114 if (offset < currOffset + (currSize >> 1)) {
2115
2116 claims = unreserveMatchingBuddy(index << 1, size, offset, currOffset);
2117 siblingIndex = (index << 1) + 1;
2118 } else {
2119
2120 claims = unreserveMatchingBuddy((index << 1) + 1, size, offset, currOffset + (currSize >> 1));
2121 siblingIndex = index << 1;
2122 }
2123 if (!claims) {
2124
2125 byte sibling = buddies[siblingIndex];
2126 if ((sibling & SHIFT_MASK) == sibling) {
2127
2128 buddies[index] &= SHIFT_MASK;
2129 return false;
2130 }
2131 }
2132 return true;
2133 }
2134
2135 @Override
2136 public String toString() {
2137 int capacity = delegate.capacity();
2138 int remaining = capacity - allocatedBytes;
2139 return "BuddyChunk[capacity: " + capacity +
2140 ", remaining: " + remaining +
2141 ", free list: " + freeList.size() + ']';
2142 }
2143 }
2144
2145 static final class AdaptiveByteBuf extends AbstractReferenceCountedByteBuf {
2146
2147 private final EnhancedHandle<AdaptiveByteBuf> handle;
2148
2149
2150 private int startIndex;
2151 private AbstractByteBuf rootParent;
2152 Chunk chunk;
2153 private int length;
2154 private int maxFastCapacity;
2155 private ByteBuffer tmpNioBuf;
2156 private boolean hasArray;
2157 private boolean hasMemoryAddress;
2158
2159 AdaptiveByteBuf(EnhancedHandle<AdaptiveByteBuf> recyclerHandle) {
2160 super(0);
2161 handle = ObjectUtil.checkNotNull(recyclerHandle, "recyclerHandle");
2162 }
2163
2164 void init(AbstractByteBuf unwrapped, Chunk wrapped, int readerIndex, int writerIndex,
2165 int startIndex, int size, int capacity, int maxCapacity) {
2166 this.startIndex = startIndex;
2167 chunk = wrapped;
2168 length = size;
2169 maxFastCapacity = capacity;
2170 maxCapacity(maxCapacity);
2171 setIndex0(readerIndex, writerIndex);
2172 hasArray = unwrapped.hasArray();
2173 hasMemoryAddress = unwrapped.hasMemoryAddress();
2174 rootParent = unwrapped;
2175 tmpNioBuf = null;
2176
2177 if (PlatformDependent.isJfrEnabled() && AllocateBufferEvent.isEventEnabled()) {
2178 AllocateBufferEvent event = new AllocateBufferEvent();
2179 if (event.shouldCommit()) {
2180 event.fill(this, AdaptiveByteBufAllocator.class);
2181 event.chunkPooled = wrapped.pooled;
2182 Magazine m = wrapped.magazine;
2183 event.chunkThreadLocal = m != null && m.allocationLock == null;
2184 event.commit();
2185 }
2186 }
2187 }
2188
2189 private AbstractByteBuf rootParent() {
2190 final AbstractByteBuf rootParent = this.rootParent;
2191 if (rootParent != null) {
2192 return rootParent;
2193 }
2194 throw new IllegalReferenceCountException();
2195 }
2196
2197 @Override
2198 public int capacity() {
2199 return length;
2200 }
2201
2202 @Override
2203 public int maxFastWritableBytes() {
2204 return Math.min(maxFastCapacity, maxCapacity()) - writerIndex;
2205 }
2206
2207 @Override
2208 public ByteBuf capacity(int newCapacity) {
2209 checkNewCapacity(newCapacity);
2210 if (length <= newCapacity && newCapacity <= maxFastCapacity) {
2211 length = newCapacity;
2212 return this;
2213 }
2214 if (newCapacity < capacity()) {
2215 length = newCapacity;
2216 trimIndicesToCapacity(newCapacity);
2217 return this;
2218 }
2219
2220 if (PlatformDependent.isJfrEnabled() && ReallocateBufferEvent.isEventEnabled()) {
2221 ReallocateBufferEvent event = new ReallocateBufferEvent();
2222 if (event.shouldCommit()) {
2223 event.fill(this, AdaptiveByteBufAllocator.class);
2224 event.newCapacity = newCapacity;
2225 event.commit();
2226 }
2227 }
2228
2229
2230 Chunk chunk = this.chunk;
2231 AdaptivePoolingAllocator allocator = chunk.allocator;
2232 int readerIndex = this.readerIndex;
2233 int writerIndex = this.writerIndex;
2234 int baseOldRootIndex = startIndex;
2235 int oldLength = length;
2236 int oldCapacity = maxFastCapacity;
2237 AbstractByteBuf oldRoot = rootParent();
2238 allocator.reallocate(newCapacity, maxCapacity(), this);
2239 oldRoot.getBytes(baseOldRootIndex, this, 0, oldLength);
2240 chunk.releaseSegment(baseOldRootIndex, oldCapacity);
2241 assert oldCapacity < maxFastCapacity && newCapacity <= maxFastCapacity :
2242 "Capacity increase failed";
2243 this.readerIndex = readerIndex;
2244 this.writerIndex = writerIndex;
2245 return this;
2246 }
2247
2248 @Override
2249 public ByteBufAllocator alloc() {
2250 return rootParent().alloc();
2251 }
2252
2253 @SuppressWarnings("deprecation")
2254 @Override
2255 public ByteOrder order() {
2256 return rootParent().order();
2257 }
2258
2259 @Override
2260 public ByteBuf unwrap() {
2261 return null;
2262 }
2263
2264 @Override
2265 public boolean isDirect() {
2266 return rootParent().isDirect();
2267 }
2268
2269 @Override
2270 public int arrayOffset() {
2271 return idx(rootParent().arrayOffset());
2272 }
2273
2274 @Override
2275 public boolean hasMemoryAddress() {
2276 return hasMemoryAddress;
2277 }
2278
2279 @Override
2280 public long memoryAddress() {
2281 ensureAccessible();
2282 return _memoryAddress();
2283 }
2284
2285 @Override
2286 long _memoryAddress() {
2287 AbstractByteBuf root = rootParent;
2288 return root != null ? root._memoryAddress() + startIndex : 0L;
2289 }
2290
2291 @Override
2292 boolean _isDirect() {
2293 AbstractByteBuf root = rootParent;
2294 return root != null && root.isDirect();
2295 }
2296
2297 @Override
2298 public ByteBuffer nioBuffer(int index, int length) {
2299 checkIndex(index, length);
2300 return rootParent().nioBuffer(idx(index), length);
2301 }
2302
2303 @Override
2304 public ByteBuffer internalNioBuffer(int index, int length) {
2305 checkIndex(index, length);
2306 return (ByteBuffer) internalNioBuffer().position(index).limit(index + length);
2307 }
2308
2309 private ByteBuffer internalNioBuffer() {
2310 if (tmpNioBuf == null) {
2311 tmpNioBuf = rootParent().nioBuffer(startIndex, maxFastCapacity);
2312 }
2313 return (ByteBuffer) tmpNioBuf.clear();
2314 }
2315
2316 @Override
2317 public ByteBuffer[] nioBuffers(int index, int length) {
2318 checkIndex(index, length);
2319 return rootParent().nioBuffers(idx(index), length);
2320 }
2321
2322 @Override
2323 public boolean hasArray() {
2324 return hasArray;
2325 }
2326
2327 @Override
2328 public byte[] array() {
2329 ensureAccessible();
2330 return rootParent().array();
2331 }
2332
2333 @Override
2334 public ByteBuf copy(int index, int length) {
2335 checkIndex(index, length);
2336 return rootParent().copy(idx(index), length);
2337 }
2338
2339 @Override
2340 public int nioBufferCount() {
2341 return rootParent().nioBufferCount();
2342 }
2343
2344 @Override
2345 protected byte _getByte(int index) {
2346 return rootParent()._getByte(idx(index));
2347 }
2348
2349 @Override
2350 protected short _getShort(int index) {
2351 return rootParent()._getShort(idx(index));
2352 }
2353
2354 @Override
2355 protected short _getShortLE(int index) {
2356 return rootParent()._getShortLE(idx(index));
2357 }
2358
2359 @Override
2360 protected int _getUnsignedMedium(int index) {
2361 return rootParent()._getUnsignedMedium(idx(index));
2362 }
2363
2364 @Override
2365 protected int _getUnsignedMediumLE(int index) {
2366 return rootParent()._getUnsignedMediumLE(idx(index));
2367 }
2368
2369 @Override
2370 protected int _getInt(int index) {
2371 return rootParent()._getInt(idx(index));
2372 }
2373
2374 @Override
2375 protected int _getIntLE(int index) {
2376 return rootParent()._getIntLE(idx(index));
2377 }
2378
2379 @Override
2380 protected long _getLong(int index) {
2381 return rootParent()._getLong(idx(index));
2382 }
2383
2384 @Override
2385 protected long _getLongLE(int index) {
2386 return rootParent()._getLongLE(idx(index));
2387 }
2388
2389 @Override
2390 public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
2391 checkIndex(index, length);
2392 rootParent().getBytes(idx(index), dst, dstIndex, length);
2393 return this;
2394 }
2395
2396 @Override
2397 public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
2398 checkIndex(index, length);
2399 rootParent().getBytes(idx(index), dst, dstIndex, length);
2400 return this;
2401 }
2402
2403 @Override
2404 public ByteBuf getBytes(int index, ByteBuffer dst) {
2405 checkIndex(index, dst.remaining());
2406 rootParent().getBytes(idx(index), dst);
2407 return this;
2408 }
2409
2410 @Override
2411 protected void _setByte(int index, int value) {
2412 rootParent()._setByte(idx(index), value);
2413 }
2414
2415 @Override
2416 protected void _setShort(int index, int value) {
2417 rootParent()._setShort(idx(index), value);
2418 }
2419
2420 @Override
2421 protected void _setShortLE(int index, int value) {
2422 rootParent()._setShortLE(idx(index), value);
2423 }
2424
2425 @Override
2426 protected void _setMedium(int index, int value) {
2427 rootParent()._setMedium(idx(index), value);
2428 }
2429
2430 @Override
2431 protected void _setMediumLE(int index, int value) {
2432 rootParent()._setMediumLE(idx(index), value);
2433 }
2434
2435 @Override
2436 protected void _setInt(int index, int value) {
2437 rootParent()._setInt(idx(index), value);
2438 }
2439
2440 @Override
2441 protected void _setIntLE(int index, int value) {
2442 rootParent()._setIntLE(idx(index), value);
2443 }
2444
2445 @Override
2446 protected void _setLong(int index, long value) {
2447 rootParent()._setLong(idx(index), value);
2448 }
2449
2450 @Override
2451 protected void _setLongLE(int index, long value) {
2452 rootParent()._setLongLE(idx(index), value);
2453 }
2454
2455 @Override
2456 public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
2457 checkIndex(index, length);
2458 if (tmpNioBuf == null && PlatformDependent.javaVersion() >= 13) {
2459 ByteBuffer dstBuffer = rootParent()._internalNioBuffer();
2460 PlatformDependent.absolutePut(dstBuffer, idx(index), src, srcIndex, length);
2461 } else {
2462 ByteBuffer tmp = (ByteBuffer) internalNioBuffer().clear().position(index);
2463 tmp.put(src, srcIndex, length);
2464 }
2465 return this;
2466 }
2467
2468 @Override
2469 public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
2470 checkIndex(index, length);
2471 if (src instanceof AdaptiveByteBuf && PlatformDependent.javaVersion() >= 16) {
2472 AdaptiveByteBuf srcBuf = (AdaptiveByteBuf) src;
2473 srcBuf.checkIndex(srcIndex, length);
2474 ByteBuffer dstBuffer = rootParent()._internalNioBuffer();
2475 ByteBuffer srcBuffer = srcBuf.rootParent()._internalNioBuffer();
2476 PlatformDependent.absolutePut(dstBuffer, idx(index), srcBuffer, srcBuf.idx(srcIndex), length);
2477 } else {
2478 ByteBuffer tmp = internalNioBuffer();
2479 tmp.position(index);
2480 tmp.put(src.nioBuffer(srcIndex, length));
2481 }
2482 return this;
2483 }
2484
2485 @Override
2486 public ByteBuf setBytes(int index, ByteBuffer src) {
2487 int length = src.remaining();
2488 checkIndex(index, length);
2489 ByteBuffer tmp = internalNioBuffer();
2490 if (PlatformDependent.javaVersion() >= 16) {
2491 int offset = src.position();
2492 PlatformDependent.absolutePut(tmp, index, src, offset, length);
2493 src.position(offset + length);
2494 } else {
2495 tmp.position(index);
2496 tmp.put(src);
2497 }
2498 return this;
2499 }
2500
2501 @Override
2502 public ByteBuf getBytes(int index, OutputStream out, int length)
2503 throws IOException {
2504 checkIndex(index, length);
2505 if (length != 0) {
2506 ByteBuffer tmp = internalNioBuffer();
2507 ByteBufUtil.readBytes(alloc(), tmp.hasArray() ? tmp : tmp.duplicate(), index, length, out);
2508 }
2509 return this;
2510 }
2511
2512 @Override
2513 public int getBytes(int index, GatheringByteChannel out, int length)
2514 throws IOException {
2515 ByteBuffer buf = internalNioBuffer().duplicate();
2516 buf.clear().position(index).limit(index + length);
2517 return out.write(buf);
2518 }
2519
2520 @Override
2521 public int getBytes(int index, FileChannel out, long position, int length)
2522 throws IOException {
2523 ByteBuffer buf = internalNioBuffer().duplicate();
2524 buf.clear().position(index).limit(index + length);
2525 return out.write(buf, position);
2526 }
2527
2528 @Override
2529 public int setBytes(int index, InputStream in, int length)
2530 throws IOException {
2531 checkIndex(index, length);
2532 final AbstractByteBuf rootParent = rootParent();
2533 if (rootParent.hasArray()) {
2534 return rootParent.setBytes(idx(index), in, length);
2535 }
2536 byte[] tmp = ByteBufUtil.threadLocalTempArray(length);
2537 int readBytes = in.read(tmp, 0, length);
2538 if (readBytes <= 0) {
2539 return readBytes;
2540 }
2541 setBytes(index, tmp, 0, readBytes);
2542 return readBytes;
2543 }
2544
2545 @Override
2546 public int setBytes(int index, ScatteringByteChannel in, int length)
2547 throws IOException {
2548 try {
2549 return in.read(internalNioBuffer(index, length));
2550 } catch (ClosedChannelException ignored) {
2551 return -1;
2552 }
2553 }
2554
2555 @Override
2556 public int setBytes(int index, FileChannel in, long position, int length)
2557 throws IOException {
2558 try {
2559 return in.read(internalNioBuffer(index, length), position);
2560 } catch (ClosedChannelException ignored) {
2561 return -1;
2562 }
2563 }
2564
2565 @Override
2566 public int setCharSequence(int index, CharSequence sequence, Charset charset) {
2567 return setCharSequence0(index, sequence, charset, false);
2568 }
2569
2570 private int setCharSequence0(int index, CharSequence sequence, Charset charset, boolean expand) {
2571 if (charset.equals(CharsetUtil.UTF_8)) {
2572 int length = ByteBufUtil.utf8MaxBytes(sequence);
2573 if (expand) {
2574 ensureWritable0(length);
2575 checkIndex0(index, length);
2576 } else {
2577 checkIndex(index, length);
2578 }
2579 return ByteBufUtil.writeUtf8(this, index, length, sequence, sequence.length());
2580 }
2581 if (charset.equals(CharsetUtil.US_ASCII) || charset.equals(CharsetUtil.ISO_8859_1)) {
2582 int length = sequence.length();
2583 if (expand) {
2584 ensureWritable0(length);
2585 checkIndex0(index, length);
2586 } else {
2587 checkIndex(index, length);
2588 }
2589 return ByteBufUtil.writeAscii(this, index, sequence, length);
2590 }
2591 byte[] bytes = sequence.toString().getBytes(charset);
2592 if (expand) {
2593 ensureWritable0(bytes.length);
2594
2595 }
2596 setBytes(index, bytes);
2597 return bytes.length;
2598 }
2599
2600 @Override
2601 public int writeCharSequence(CharSequence sequence, Charset charset) {
2602 int written = setCharSequence0(writerIndex, sequence, charset, true);
2603 writerIndex += written;
2604 return written;
2605 }
2606
2607 @Override
2608 public int forEachByte(int index, int length, ByteProcessor processor) {
2609 checkIndex(index, length);
2610 int ret = rootParent().forEachByte(idx(index), length, processor);
2611 return forEachResult(ret);
2612 }
2613
2614 @Override
2615 public int forEachByteDesc(int index, int length, ByteProcessor processor) {
2616 checkIndex(index, length);
2617 int ret = rootParent().forEachByteDesc(idx(index), length, processor);
2618 return forEachResult(ret);
2619 }
2620
2621 @Override
2622 public ByteBuf setZero(int index, int length) {
2623 checkIndex(index, length);
2624 rootParent().setZero(idx(index), length);
2625 return this;
2626 }
2627
2628 @Override
2629 public ByteBuf writeZero(int length) {
2630 ensureWritable(length);
2631 rootParent().setZero(idx(writerIndex), length);
2632 writerIndex += length;
2633 return this;
2634 }
2635
2636 private int forEachResult(int ret) {
2637 if (ret < startIndex) {
2638 return -1;
2639 }
2640 return ret - startIndex;
2641 }
2642
2643 @Override
2644 public boolean isContiguous() {
2645 return rootParent().isContiguous();
2646 }
2647
2648 private int idx(int index) {
2649 return index + startIndex;
2650 }
2651
2652 @Override
2653 protected void deallocate() {
2654 if (PlatformDependent.isJfrEnabled() && FreeBufferEvent.isEventEnabled()) {
2655 FreeBufferEvent event = new FreeBufferEvent();
2656 if (event.shouldCommit()) {
2657 event.fill(this, AdaptiveByteBufAllocator.class);
2658 event.commit();
2659 }
2660 }
2661
2662 if (chunk != null) {
2663 chunk.releaseSegment(startIndex, maxFastCapacity);
2664 }
2665 tmpNioBuf = null;
2666 chunk = null;
2667 rootParent = null;
2668 handle.unguardedRecycle(this);
2669 }
2670 }
2671
2672
2673
2674
2675 interface ChunkAllocator {
2676
2677
2678
2679
2680
2681
2682
2683 AbstractByteBuf allocate(int initialCapacity, int maxCapacity);
2684 }
2685 }