1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.util;
17
18 import io.netty.util.concurrent.FastThreadLocal;
19 import io.netty.util.concurrent.FastThreadLocalThread;
20 import io.netty.util.internal.ObjectPool;
21 import io.netty.util.internal.PlatformDependent;
22 import io.netty.util.internal.SystemPropertyUtil;
23 import io.netty.util.internal.UnstableApi;
24 import io.netty.util.internal.logging.InternalLogger;
25 import io.netty.util.internal.logging.InternalLoggerFactory;
26 import org.jctools.queues.MessagePassingQueue;
27 import org.jetbrains.annotations.VisibleForTesting;
28
29 import java.util.ArrayDeque;
30 import java.util.Objects;
31 import java.util.Queue;
32 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
33
34 import static io.netty.util.internal.PlatformDependent.newFixedMpmcQueue;
35 import static io.netty.util.internal.PlatformDependent.newMpscQueue;
36 import static java.lang.Math.max;
37 import static java.lang.Math.min;
38
39
40
41
42
43
44 public abstract class Recycler<T> {
45 private static final InternalLogger logger = InternalLoggerFactory.getInstance(Recycler.class);
46
47
48
49
50
51 private static final class LocalPoolHandle<T> extends EnhancedHandle<T> {
52 private final UnguardedLocalPool<T> pool;
53
54 private LocalPoolHandle(UnguardedLocalPool<T> pool) {
55 this.pool = pool;
56 }
57
58 @Override
59 public void recycle(T object) {
60 UnguardedLocalPool<T> pool = this.pool;
61 if (pool != null) {
62 pool.release(object);
63 }
64 }
65
66 @Override
67 public void unguardedRecycle(final Object object) {
68 UnguardedLocalPool<T> pool = this.pool;
69 if (pool != null) {
70 pool.release((T) object);
71 }
72 }
73 }
74
75 private static final EnhancedHandle<?> NOOP_HANDLE = new LocalPoolHandle<>(null);
76 private static final UnguardedLocalPool<?> NOOP_LOCAL_POOL = new UnguardedLocalPool<>(0);
77 private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024;
78 private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
79 private static final int RATIO;
80 private static final int DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD;
81 private static final boolean BLOCKING_POOL;
82 private static final boolean BATCH_FAST_TL_ONLY;
83
84 static {
85
86
87
88 int maxCapacityPerThread = SystemPropertyUtil.getInt("io.netty.recycler.maxCapacityPerThread",
89 SystemPropertyUtil.getInt("io.netty.recycler.maxCapacity", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD));
90 if (maxCapacityPerThread < 0) {
91 maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
92 }
93
94 DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
95 DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD = SystemPropertyUtil.getInt("io.netty.recycler.chunkSize", 32);
96
97
98
99
100 RATIO = max(0, SystemPropertyUtil.getInt("io.netty.recycler.ratio", 8));
101
102 BLOCKING_POOL = SystemPropertyUtil.getBoolean("io.netty.recycler.blocking", false);
103 BATCH_FAST_TL_ONLY = SystemPropertyUtil.getBoolean("io.netty.recycler.batchFastThreadLocalOnly", true);
104
105 if (logger.isDebugEnabled()) {
106 if (DEFAULT_MAX_CAPACITY_PER_THREAD == 0) {
107 logger.debug("-Dio.netty.recycler.maxCapacityPerThread: disabled");
108 logger.debug("-Dio.netty.recycler.ratio: disabled");
109 logger.debug("-Dio.netty.recycler.chunkSize: disabled");
110 logger.debug("-Dio.netty.recycler.blocking: disabled");
111 logger.debug("-Dio.netty.recycler.batchFastThreadLocalOnly: disabled");
112 } else {
113 logger.debug("-Dio.netty.recycler.maxCapacityPerThread: {}", DEFAULT_MAX_CAPACITY_PER_THREAD);
114 logger.debug("-Dio.netty.recycler.ratio: {}", RATIO);
115 logger.debug("-Dio.netty.recycler.chunkSize: {}", DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD);
116 logger.debug("-Dio.netty.recycler.blocking: {}", BLOCKING_POOL);
117 logger.debug("-Dio.netty.recycler.batchFastThreadLocalOnly: {}", BATCH_FAST_TL_ONLY);
118 }
119 }
120 }
121
122 private final LocalPool<?, T> localPool;
123 private final FastThreadLocal<LocalPool<?, T>> threadLocalPool;
124
125
126
127
128
129
130
131
132
133
134
135
136 protected Recycler(int maxCapacity, boolean unguarded) {
137 if (maxCapacity <= 0) {
138 maxCapacity = 0;
139 } else {
140 maxCapacity = max(4, maxCapacity);
141 }
142 threadLocalPool = null;
143 if (maxCapacity == 0) {
144 localPool = (LocalPool<?, T>) NOOP_LOCAL_POOL;
145 } else {
146 localPool = unguarded? new UnguardedLocalPool<>(maxCapacity) : new GuardedLocalPool<>(maxCapacity);
147 }
148 }
149
150
151
152
153
154
155
156
157 protected Recycler(boolean unguarded) {
158 this(DEFAULT_MAX_CAPACITY_PER_THREAD, RATIO, DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD, unguarded);
159 }
160
161
162
163
164
165
166
167
168
169
170
171 protected Recycler(Thread owner, boolean unguarded) {
172 this(DEFAULT_MAX_CAPACITY_PER_THREAD, RATIO, DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD, owner, unguarded);
173 }
174
175 protected Recycler(int maxCapacityPerThread) {
176 this(maxCapacityPerThread, RATIO, DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD);
177 }
178
179 protected Recycler() {
180 this(DEFAULT_MAX_CAPACITY_PER_THREAD);
181 }
182
183
184
185
186
187
188 protected Recycler(int chunksSize, int maxCapacityPerThread, boolean unguarded) {
189 this(maxCapacityPerThread, RATIO, chunksSize, unguarded);
190 }
191
192
193
194
195
196
197
198
199
200 protected Recycler(int chunkSize, int maxCapacityPerThread, Thread owner, boolean unguarded) {
201 this(maxCapacityPerThread, RATIO, chunkSize, owner, unguarded);
202 }
203
204
205
206
207
208 @Deprecated
209 @SuppressWarnings("unused")
210 protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor) {
211 this(maxCapacityPerThread, RATIO, DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD);
212 }
213
214
215
216
217
218 @Deprecated
219 @SuppressWarnings("unused")
220 protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor,
221 int ratio, int maxDelayedQueuesPerThread) {
222 this(maxCapacityPerThread, ratio, DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD);
223 }
224
225
226
227
228
229 @Deprecated
230 @SuppressWarnings("unused")
231 protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor,
232 int ratio, int maxDelayedQueuesPerThread, int delayedQueueRatio) {
233 this(maxCapacityPerThread, ratio, DEFAULT_QUEUE_CHUNK_SIZE_PER_THREAD);
234 }
235
236 protected Recycler(int maxCapacityPerThread, int interval, int chunkSize) {
237 this(maxCapacityPerThread, interval, chunkSize, true, null, false);
238 }
239
240
241
242
243
244
245 protected Recycler(int maxCapacityPerThread, int interval, int chunkSize, boolean unguarded) {
246 this(maxCapacityPerThread, interval, chunkSize, true, null, unguarded);
247 }
248
249
250
251
252
253
254 protected Recycler(int maxCapacityPerThread, int interval, int chunkSize, Thread owner, boolean unguarded) {
255 this(maxCapacityPerThread, interval, chunkSize, false, owner, unguarded);
256 }
257
258 @SuppressWarnings("unchecked")
259 private Recycler(int maxCapacityPerThread, int ratio, int chunkSize, boolean useThreadLocalStorage,
260 Thread owner, boolean unguarded) {
261 final int interval = max(0, ratio);
262 if (maxCapacityPerThread <= 0) {
263 maxCapacityPerThread = 0;
264 chunkSize = 0;
265 } else {
266 maxCapacityPerThread = max(4, maxCapacityPerThread);
267 chunkSize = max(2, min(chunkSize, maxCapacityPerThread >> 1));
268 }
269 if (maxCapacityPerThread > 0 && useThreadLocalStorage) {
270 final int finalMaxCapacityPerThread = maxCapacityPerThread;
271 final int finalChunkSize = chunkSize;
272 threadLocalPool = new FastThreadLocal<LocalPool<?, T>>() {
273 @Override
274 protected LocalPool<?, T> initialValue() {
275 return unguarded? new UnguardedLocalPool<>(finalMaxCapacityPerThread, interval, finalChunkSize) :
276 new GuardedLocalPool<>(finalMaxCapacityPerThread, interval, finalChunkSize);
277 }
278
279 @Override
280 protected void onRemoval(LocalPool<?, T> value) throws Exception {
281 super.onRemoval(value);
282 MessagePassingQueue<?> handles = value.pooledHandles;
283 value.pooledHandles = null;
284 value.owner = null;
285 if (handles != null) {
286 handles.clear();
287 }
288 }
289 };
290 localPool = null;
291 } else {
292 threadLocalPool = null;
293 if (maxCapacityPerThread == 0) {
294 localPool = (LocalPool<?, T>) NOOP_LOCAL_POOL;
295 } else {
296 Objects.requireNonNull(owner, "owner");
297 localPool = unguarded? new UnguardedLocalPool<>(owner, maxCapacityPerThread, interval, chunkSize) :
298 new GuardedLocalPool<>(owner, maxCapacityPerThread, interval, chunkSize);
299 }
300 }
301 }
302
303 @SuppressWarnings("unchecked")
304 public final T get() {
305 if (localPool != null) {
306 return localPool.getWith(this);
307 } else {
308 if (!FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals()) {
309 return newObject((Handle<T>) NOOP_HANDLE);
310 }
311 return threadLocalPool.get().getWith(this);
312 }
313 }
314
315
316
317
318
319
320
321
322
323 public static void unpinOwner(Recycler<?> recycler) {
324 if (recycler.localPool != null) {
325 recycler.localPool.owner = null;
326 }
327 }
328
329
330
331
332 @Deprecated
333 public final boolean recycle(T o, Handle<T> handle) {
334 if (handle == NOOP_HANDLE) {
335 return false;
336 }
337
338 handle.recycle(o);
339 return true;
340 }
341
342 @VisibleForTesting
343 final int threadLocalSize() {
344 if (localPool != null) {
345 return localPool.size();
346 } else {
347 if (!FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals()) {
348 return 0;
349 }
350 final LocalPool<?, T> pool = threadLocalPool.getIfExists();
351 if (pool == null) {
352 return 0;
353 }
354 return pool.size();
355 }
356 }
357
358
359
360
361 protected abstract T newObject(Handle<T> handle);
362
363 @SuppressWarnings("ClassNameSameAsAncestorName")
364 public interface Handle<T> extends ObjectPool.Handle<T> { }
365
366 @UnstableApi
367 public abstract static class EnhancedHandle<T> implements Handle<T> {
368
369 public abstract void unguardedRecycle(Object object);
370
371 private EnhancedHandle() {
372 }
373 }
374
375 private static final class DefaultHandle<T> extends EnhancedHandle<T> {
376 private static final int STATE_CLAIMED = 0;
377 private static final int STATE_AVAILABLE = 1;
378 private static final AtomicIntegerFieldUpdater<DefaultHandle<?>> STATE_UPDATER;
379 static {
380 AtomicIntegerFieldUpdater<?> updater = AtomicIntegerFieldUpdater.newUpdater(DefaultHandle.class, "state");
381
382 STATE_UPDATER = (AtomicIntegerFieldUpdater<DefaultHandle<?>>) updater;
383 }
384
385 private volatile int state;
386 private final GuardedLocalPool<T> localPool;
387 private T value;
388
389 DefaultHandle(GuardedLocalPool<T> localPool) {
390 this.localPool = localPool;
391 }
392
393 @Override
394 public void recycle(Object object) {
395 if (object != value) {
396 throw new IllegalArgumentException("object does not belong to handle");
397 }
398 toAvailable();
399 localPool.release(this);
400 }
401
402 @Override
403 public void unguardedRecycle(Object object) {
404 if (object != value) {
405 throw new IllegalArgumentException("object does not belong to handle");
406 }
407 unguardedToAvailable();
408 localPool.release(this);
409 }
410
411 T claim() {
412 assert state == STATE_AVAILABLE;
413 STATE_UPDATER.lazySet(this, STATE_CLAIMED);
414 return value;
415 }
416
417 void set(T value) {
418 this.value = value;
419 }
420
421 private void toAvailable() {
422 int prev = STATE_UPDATER.getAndSet(this, STATE_AVAILABLE);
423 if (prev == STATE_AVAILABLE) {
424 throw new IllegalStateException("Object has been recycled already.");
425 }
426 }
427
428 private void unguardedToAvailable() {
429 int prev = state;
430 if (prev == STATE_AVAILABLE) {
431 throw new IllegalStateException("Object has been recycled already.");
432 }
433 STATE_UPDATER.lazySet(this, STATE_AVAILABLE);
434 }
435 }
436
437 private static final class GuardedLocalPool<T> extends LocalPool<DefaultHandle<T>, T> {
438 static {
439
440 int ignore = DefaultHandle.STATE_AVAILABLE;
441 }
442
443 GuardedLocalPool(int maxCapacity) {
444 super(maxCapacity);
445 }
446
447 GuardedLocalPool(Thread owner, int maxCapacity, int ratioInterval, int chunkSize) {
448 super(owner, maxCapacity, ratioInterval, chunkSize);
449 }
450
451 GuardedLocalPool(int maxCapacity, int ratioInterval, int chunkSize) {
452 super(maxCapacity, ratioInterval, chunkSize);
453 }
454
455 @Override
456 public T getWith(Recycler<T> recycler) {
457 DefaultHandle<T> handle = acquire();
458 T obj;
459 if (handle == null) {
460 handle = canAllocatePooled()? new DefaultHandle<>(this) : null;
461 if (handle != null) {
462 obj = recycler.newObject(handle);
463 handle.set(obj);
464 } else {
465 obj = recycler.newObject((Handle<T>) NOOP_HANDLE);
466 }
467 } else {
468 obj = handle.claim();
469 }
470 return obj;
471 }
472 }
473
474 private static final class UnguardedLocalPool<T> extends LocalPool<T, T> {
475 private final EnhancedHandle<T> handle;
476
477 UnguardedLocalPool(int maxCapacity) {
478 super(maxCapacity);
479 handle = maxCapacity == 0? null : new LocalPoolHandle<>(this);
480 }
481
482 UnguardedLocalPool(Thread owner, int maxCapacity, int ratioInterval, int chunkSize) {
483 super(owner, maxCapacity, ratioInterval, chunkSize);
484 handle = new LocalPoolHandle<>(this);
485 }
486
487 UnguardedLocalPool(int maxCapacity, int ratioInterval, int chunkSize) {
488 super(maxCapacity, ratioInterval, chunkSize);
489 handle = new LocalPoolHandle<>(this);
490 }
491
492 @Override
493 public T getWith(Recycler<T> recycler) {
494 T obj = acquire();
495 if (obj == null) {
496 obj = recycler.newObject(canAllocatePooled()? handle : (Handle<T>) NOOP_HANDLE);
497 }
498 return obj;
499 }
500 }
501
502 private abstract static class LocalPool<H, T> {
503 private final int ratioInterval;
504 private final H[] batch;
505 private int batchSize;
506 private Thread owner;
507 private MessagePassingQueue<H> pooledHandles;
508 private int ratioCounter;
509
510 LocalPool(int maxCapacity) {
511
512
513
514 this.ratioInterval = maxCapacity == 0? -1 : 0;
515 this.owner = null;
516 batch = null;
517 batchSize = 0;
518 pooledHandles = createExternalMcPool(maxCapacity);
519 ratioCounter = 0;
520 }
521
522 @SuppressWarnings("unchecked")
523 LocalPool(Thread owner, int maxCapacity, int ratioInterval, int chunkSize) {
524 this.ratioInterval = ratioInterval;
525 this.owner = owner;
526 batch = owner != null? (H[]) new Object[chunkSize] : null;
527 batchSize = 0;
528 pooledHandles = createExternalScPool(chunkSize, maxCapacity);
529 ratioCounter = ratioInterval;
530 }
531
532 private static <H> MessagePassingQueue<H> createExternalMcPool(int maxCapacity) {
533 if (maxCapacity == 0) {
534 return null;
535 }
536 if (BLOCKING_POOL) {
537 return new BlockingMessageQueue<>(maxCapacity);
538 }
539 return (MessagePassingQueue<H>) newFixedMpmcQueue(maxCapacity);
540 }
541
542 private static <H> MessagePassingQueue<H> createExternalScPool(int chunkSize, int maxCapacity) {
543 if (maxCapacity == 0) {
544 return null;
545 }
546 if (BLOCKING_POOL) {
547 return new BlockingMessageQueue<>(maxCapacity);
548 }
549 return (MessagePassingQueue<H>) newMpscQueue(chunkSize, maxCapacity);
550 }
551
552 LocalPool(int maxCapacity, int ratioInterval, int chunkSize) {
553 this(!BATCH_FAST_TL_ONLY || FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals()
554 ? Thread.currentThread() : null, maxCapacity, ratioInterval, chunkSize);
555 }
556
557 protected final H acquire() {
558 int size = batchSize;
559 if (size == 0) {
560
561 final MessagePassingQueue<H> handles = pooledHandles;
562 if (handles == null) {
563 return null;
564 }
565 return handles.relaxedPoll();
566 }
567 int top = size - 1;
568 final H h = batch[top];
569 batchSize = top;
570 batch[top] = null;
571 return h;
572 }
573
574 protected final void release(H handle) {
575 Thread owner = this.owner;
576 if (owner != null && Thread.currentThread() == owner && batchSize < batch.length) {
577 batch[batchSize] = handle;
578 batchSize++;
579 } else if (owner != null && isTerminated(owner)) {
580 pooledHandles = null;
581 this.owner = null;
582 } else {
583 MessagePassingQueue<H> handles = pooledHandles;
584 if (handles != null) {
585 handles.relaxedOffer(handle);
586 }
587 }
588 }
589
590 private static boolean isTerminated(Thread owner) {
591
592
593 return PlatformDependent.isJ9Jvm()? !owner.isAlive() : owner.getState() == Thread.State.TERMINATED;
594 }
595
596 boolean canAllocatePooled() {
597 if (ratioInterval < 0) {
598 return false;
599 }
600 if (ratioInterval == 0) {
601 return true;
602 }
603 if (++ratioCounter >= ratioInterval) {
604 ratioCounter = 0;
605 return true;
606 }
607 return false;
608 }
609
610 abstract T getWith(Recycler<T> recycler);
611
612 int size() {
613 MessagePassingQueue<H> handles = pooledHandles;
614 final int externalSize = handles != null? handles.size() : 0;
615 return externalSize + (batch != null? batchSize : 0);
616 }
617 }
618
619
620
621
622
623
624
625 private static final class BlockingMessageQueue<T> implements MessagePassingQueue<T> {
626 private final Queue<T> deque;
627 private final int maxCapacity;
628
629 BlockingMessageQueue(int maxCapacity) {
630 this.maxCapacity = maxCapacity;
631
632
633
634
635
636
637
638
639
640 deque = new ArrayDeque<T>();
641 }
642
643 @Override
644 public synchronized boolean offer(T e) {
645 if (deque.size() == maxCapacity) {
646 return false;
647 }
648 return deque.offer(e);
649 }
650
651 @Override
652 public synchronized T poll() {
653 return deque.poll();
654 }
655
656 @Override
657 public synchronized T peek() {
658 return deque.peek();
659 }
660
661 @Override
662 public synchronized int size() {
663 return deque.size();
664 }
665
666 @Override
667 public synchronized void clear() {
668 deque.clear();
669 }
670
671 @Override
672 public synchronized boolean isEmpty() {
673 return deque.isEmpty();
674 }
675
676 @Override
677 public int capacity() {
678 return maxCapacity;
679 }
680
681 @Override
682 public boolean relaxedOffer(T e) {
683 return offer(e);
684 }
685
686 @Override
687 public T relaxedPoll() {
688 return poll();
689 }
690
691 @Override
692 public T relaxedPeek() {
693 return peek();
694 }
695
696 @Override
697 public int drain(Consumer<T> c, int limit) {
698 T obj;
699 int i = 0;
700 for (; i < limit && (obj = poll()) != null; i++) {
701 c.accept(obj);
702 }
703 return i;
704 }
705
706 @Override
707 public int fill(Supplier<T> s, int limit) {
708 throw new UnsupportedOperationException();
709 }
710
711 @Override
712 public int drain(Consumer<T> c) {
713 throw new UnsupportedOperationException();
714 }
715
716 @Override
717 public int fill(Supplier<T> s) {
718 throw new UnsupportedOperationException();
719 }
720
721 @Override
722 public void drain(Consumer<T> c, WaitStrategy wait, ExitCondition exit) {
723 throw new UnsupportedOperationException();
724 }
725
726 @Override
727 public void fill(Supplier<T> s, WaitStrategy wait, ExitCondition exit) {
728 throw new UnsupportedOperationException();
729 }
730 }
731 }