1 /*
2 * Copyright 2013 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17 package io.netty.util;
18
19 import io.netty.util.internal.EmptyArrays;
20 import io.netty.util.internal.ObjectUtil;
21 import io.netty.util.internal.PlatformDependent;
22 import io.netty.util.internal.SystemPropertyUtil;
23 import io.netty.util.internal.logging.InternalLogger;
24 import io.netty.util.internal.logging.InternalLoggerFactory;
25
26 import java.lang.ref.WeakReference;
27 import java.lang.ref.ReferenceQueue;
28 import java.lang.reflect.Method;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.Set;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
36 import java.util.concurrent.atomic.AtomicReference;
37 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
38
39 import static io.netty.util.internal.StringUtil.EMPTY_STRING;
40 import static io.netty.util.internal.StringUtil.NEWLINE;
41 import static io.netty.util.internal.StringUtil.simpleClassName;
42
43 public class ResourceLeakDetector<T> {
44
45 private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel";
46 private static final String PROP_LEVEL = "io.netty.leakDetection.level";
47 private static final Level DEFAULT_LEVEL = Level.SIMPLE;
48
49 private static final String PROP_TARGET_RECORDS = "io.netty.leakDetection.targetRecords";
50 private static final int DEFAULT_TARGET_RECORDS = 4;
51
52 private static final String PROP_SAMPLING_INTERVAL = "io.netty.leakDetection.samplingInterval";
53 // There is a minor performance benefit in TLR if this is a power of 2.
54 private static final int DEFAULT_SAMPLING_INTERVAL = 128;
55
56 private static final int TARGET_RECORDS;
57 static final int SAMPLING_INTERVAL;
58
59 /**
60 * Represents the level of resource leak detection.
61 */
62 public enum Level {
63 /**
64 * Disables resource leak detection.
65 */
66 DISABLED,
67 /**
68 * Enables simplistic sampling resource leak detection which reports there is a leak or not,
69 * at the cost of small overhead (default).
70 */
71 SIMPLE,
72 /**
73 * Enables advanced sampling resource leak detection which reports where the leaked object was accessed
74 * recently at the cost of high overhead.
75 */
76 ADVANCED,
77 /**
78 * Enables paranoid resource leak detection which reports where the leaked object was accessed recently,
79 * at the cost of the highest possible overhead (for testing purposes only).
80 */
81 PARANOID;
82
83 /**
84 * Returns level based on string value. Accepts also string that represents ordinal number of enum.
85 *
86 * @param levelStr - level string : DISABLED, SIMPLE, ADVANCED, PARANOID. Ignores case.
87 * @return corresponding level or SIMPLE level in case of no match.
88 */
89 static Level parseLevel(String levelStr) {
90 String trimmedLevelStr = levelStr.trim();
91 for (Level l : values()) {
92 if (trimmedLevelStr.equalsIgnoreCase(l.name()) || trimmedLevelStr.equals(String.valueOf(l.ordinal()))) {
93 return l;
94 }
95 }
96 return DEFAULT_LEVEL;
97 }
98 }
99
100 private static Level level;
101
102 private static final InternalLogger logger = InternalLoggerFactory.getInstance(ResourceLeakDetector.class);
103
104 static {
105 final boolean disabled;
106 if (SystemPropertyUtil.get("io.netty.noResourceLeakDetection") != null) {
107 disabled = SystemPropertyUtil.getBoolean("io.netty.noResourceLeakDetection", false);
108 logger.debug("-Dio.netty.noResourceLeakDetection: {}", disabled);
109 logger.warn(
110 "-Dio.netty.noResourceLeakDetection is deprecated. Use '-D{}={}' instead.",
111 PROP_LEVEL, DEFAULT_LEVEL.name().toLowerCase());
112 } else {
113 disabled = false;
114 }
115
116 Level defaultLevel = disabled? Level.DISABLED : DEFAULT_LEVEL;
117
118 // First read old property name
119 String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name());
120
121 // If new property name is present, use it
122 levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr);
123 Level level = Level.parseLevel(levelStr);
124
125 TARGET_RECORDS = SystemPropertyUtil.getInt(PROP_TARGET_RECORDS, DEFAULT_TARGET_RECORDS);
126 SAMPLING_INTERVAL = SystemPropertyUtil.getInt(PROP_SAMPLING_INTERVAL, DEFAULT_SAMPLING_INTERVAL);
127
128 ResourceLeakDetector.level = level;
129 if (logger.isDebugEnabled()) {
130 logger.debug("-D{}: {}", PROP_LEVEL, level.name().toLowerCase());
131 logger.debug("-D{}: {}", PROP_TARGET_RECORDS, TARGET_RECORDS);
132 }
133 }
134
135 /**
136 * @deprecated Use {@link #setLevel(Level)} instead.
137 */
138 @Deprecated
139 public static void setEnabled(boolean enabled) {
140 setLevel(enabled? Level.SIMPLE : Level.DISABLED);
141 }
142
143 /**
144 * Returns {@code true} if resource leak detection is enabled.
145 */
146 public static boolean isEnabled() {
147 return getLevel().ordinal() > Level.DISABLED.ordinal();
148 }
149
150 /**
151 * Sets the resource leak detection level.
152 */
153 public static void setLevel(Level level) {
154 ResourceLeakDetector.level = ObjectUtil.checkNotNull(level, "level");
155 }
156
157 /**
158 * Returns the current resource leak detection level.
159 */
160 public static Level getLevel() {
161 return level;
162 }
163
164 /** the collection of active resources */
165 private final Set<DefaultResourceLeak<?>> allLeaks =
166 Collections.newSetFromMap(new ConcurrentHashMap<DefaultResourceLeak<?>, Boolean>());
167
168 private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
169 private final Set<String> reportedLeaks =
170 Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
171
172 private final String resourceType;
173 private final int samplingInterval;
174
175 /**
176 * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
177 */
178 @Deprecated
179 public ResourceLeakDetector(Class<?> resourceType) {
180 this(simpleClassName(resourceType));
181 }
182
183 /**
184 * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
185 */
186 @Deprecated
187 public ResourceLeakDetector(String resourceType) {
188 this(resourceType, DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE);
189 }
190
191 /**
192 * @deprecated Use {@link ResourceLeakDetector#ResourceLeakDetector(Class, int)}.
193 * <p>
194 * This should not be used directly by users of {@link ResourceLeakDetector}.
195 * Please use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class)}
196 * or {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}
197 *
198 * @param maxActive This is deprecated and will be ignored.
199 */
200 @Deprecated
201 public ResourceLeakDetector(Class<?> resourceType, int samplingInterval, long maxActive) {
202 this(resourceType, samplingInterval);
203 }
204
205 /**
206 * This should not be used directly by users of {@link ResourceLeakDetector}.
207 * Please use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class)}
208 * or {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}
209 */
210 @SuppressWarnings("deprecation")
211 public ResourceLeakDetector(Class<?> resourceType, int samplingInterval) {
212 this(simpleClassName(resourceType), samplingInterval, Long.MAX_VALUE);
213 }
214
215 /**
216 * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
217 * <p>
218 * @param maxActive This is deprecated and will be ignored.
219 */
220 @Deprecated
221 public ResourceLeakDetector(String resourceType, int samplingInterval, long maxActive) {
222 this.resourceType = ObjectUtil.checkNotNull(resourceType, "resourceType");
223 this.samplingInterval = samplingInterval;
224 }
225
226 /**
227 * Creates a new {@link ResourceLeak} which is expected to be closed via {@link ResourceLeak#close()} when the
228 * related resource is deallocated.
229 *
230 * @return the {@link ResourceLeak} or {@code null}
231 * @deprecated use {@link #track(Object)}
232 */
233 @Deprecated
234 public final ResourceLeak open(T obj) {
235 return track0(obj);
236 }
237
238 /**
239 * Creates a new {@link ResourceLeakTracker} which is expected to be closed via
240 * {@link ResourceLeakTracker#close(Object)} when the related resource is deallocated.
241 *
242 * @return the {@link ResourceLeakTracker} or {@code null}
243 */
244 @SuppressWarnings("unchecked")
245 public final ResourceLeakTracker<T> track(T obj) {
246 return track0(obj);
247 }
248
249 @SuppressWarnings("unchecked")
250 private DefaultResourceLeak track0(T obj) {
251 Level level = ResourceLeakDetector.level;
252 if (level == Level.DISABLED) {
253 return null;
254 }
255
256 if (level.ordinal() < Level.PARANOID.ordinal()) {
257 if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {
258 reportLeak();
259 return new DefaultResourceLeak(obj, refQueue, allLeaks);
260 }
261 return null;
262 }
263 reportLeak();
264 return new DefaultResourceLeak(obj, refQueue, allLeaks);
265 }
266
267 private void clearRefQueue() {
268 for (;;) {
269 DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
270 if (ref == null) {
271 break;
272 }
273 ref.dispose();
274 }
275 }
276
277 /**
278 * When the return value is {@code true}, {@link #reportTracedLeak} and {@link #reportUntracedLeak}
279 * will be called once a leak is detected, otherwise not.
280 *
281 * @return {@code true} to enable leak reporting.
282 */
283 protected boolean needReport() {
284 return logger.isErrorEnabled();
285 }
286
287 private void reportLeak() {
288 if (!needReport()) {
289 clearRefQueue();
290 return;
291 }
292
293 // Detect and report previous leaks.
294 for (;;) {
295 DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
296 if (ref == null) {
297 break;
298 }
299
300 if (!ref.dispose()) {
301 continue;
302 }
303
304 String records = ref.toString();
305 if (reportedLeaks.add(records)) {
306 if (records.isEmpty()) {
307 reportUntracedLeak(resourceType);
308 } else {
309 reportTracedLeak(resourceType, records);
310 }
311 }
312 }
313 }
314
315 /**
316 * This method is called when a traced leak is detected. It can be overridden for tracking how many times leaks
317 * have been detected.
318 */
319 protected void reportTracedLeak(String resourceType, String records) {
320 logger.error(
321 "LEAK: {}.release() was not called before it's garbage-collected. " +
322 "See https://netty.io/wiki/reference-counted-objects.html for more information.{}",
323 resourceType, records);
324 }
325
326 /**
327 * This method is called when an untraced leak is detected. It can be overridden for tracking how many times leaks
328 * have been detected.
329 */
330 protected void reportUntracedLeak(String resourceType) {
331 logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
332 "Enable advanced leak reporting to find out where the leak occurred. " +
333 "To enable advanced leak reporting, " +
334 "specify the JVM option '-D{}={}' or call {}.setLevel() " +
335 "See https://netty.io/wiki/reference-counted-objects.html for more information.",
336 resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
337 }
338
339 /**
340 * @deprecated This method will no longer be invoked by {@link ResourceLeakDetector}.
341 */
342 @Deprecated
343 protected void reportInstancesLeak(String resourceType) {
344 }
345
346 @SuppressWarnings("deprecation")
347 private static final class DefaultResourceLeak<T>
348 extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
349
350 @SuppressWarnings("unchecked") // generics and updaters do not mix.
351 private static final AtomicReferenceFieldUpdater<DefaultResourceLeak<?>, TraceRecord> headUpdater =
352 (AtomicReferenceFieldUpdater)
353 AtomicReferenceFieldUpdater.newUpdater(DefaultResourceLeak.class, TraceRecord.class, "head");
354
355 @SuppressWarnings("unchecked") // generics and updaters do not mix.
356 private static final AtomicIntegerFieldUpdater<DefaultResourceLeak<?>> droppedRecordsUpdater =
357 (AtomicIntegerFieldUpdater)
358 AtomicIntegerFieldUpdater.newUpdater(DefaultResourceLeak.class, "droppedRecords");
359
360 @SuppressWarnings("unused")
361 private volatile TraceRecord head;
362 @SuppressWarnings("unused")
363 private volatile int droppedRecords;
364
365 private final Set<DefaultResourceLeak<?>> allLeaks;
366 private final int trackedHash;
367
368 DefaultResourceLeak(
369 Object referent,
370 ReferenceQueue<Object> refQueue,
371 Set<DefaultResourceLeak<?>> allLeaks) {
372 super(referent, refQueue);
373
374 assert referent != null;
375
376 // Store the hash of the tracked object to later assert it in the close(...) method.
377 // It's important that we not store a reference to the referent as this would disallow it from
378 // be collected via the WeakReference.
379 trackedHash = System.identityHashCode(referent);
380 allLeaks.add(this);
381 // Create a new Record so we always have the creation stacktrace included.
382 headUpdater.set(this, new TraceRecord(TraceRecord.BOTTOM));
383 this.allLeaks = allLeaks;
384 }
385
386 @Override
387 public void record() {
388 record0(null);
389 }
390
391 @Override
392 public void record(Object hint) {
393 record0(hint);
394 }
395
396 /**
397 * This method works by exponentially backing off as more records are present in the stack. Each record has a
398 * 1 / 2^n chance of dropping the top most record and replacing it with itself. This has a number of convenient
399 * properties:
400 *
401 * <ol>
402 * <li> The current record is always recorded. This is due to the compare and swap dropping the top most
403 * record, rather than the to-be-pushed record.
404 * <li> The very last access will always be recorded. This comes as a property of 1.
405 * <li> It is possible to retain more records than the target, based upon the probability distribution.
406 * <li> It is easy to keep a precise record of the number of elements in the stack, since each element has to
407 * know how tall the stack is.
408 * </ol>
409 *
410 * In this particular implementation, there are also some advantages. A thread local random is used to decide
411 * if something should be recorded. This means that if there is a deterministic access pattern, it is now
412 * possible to see what other accesses occur, rather than always dropping them. Second, after
413 * {@link #TARGET_RECORDS} accesses, backoff occurs. This matches typical access patterns,
414 * where there are either a high number of accesses (i.e. a cached buffer), or low (an ephemeral buffer), but
415 * not many in between.
416 *
417 * The use of atomics avoids serializing a high number of accesses, when most of the records will be thrown
418 * away. High contention only happens when there are very few existing records, which is only likely when the
419 * object isn't shared! If this is a problem, the loop can be aborted and the record dropped, because another
420 * thread won the race.
421 */
422 private void record0(Object hint) {
423 // Check TARGET_RECORDS > 0 here to avoid similar check before remove from and add to lastRecords
424 if (TARGET_RECORDS > 0) {
425 TraceRecord oldHead;
426 TraceRecord prevHead;
427 TraceRecord newHead;
428 boolean dropped;
429 do {
430 if ((prevHead = oldHead = headUpdater.get(this)) == null) {
431 // already closed.
432 return;
433 }
434 final int numElements = oldHead.pos + 1;
435 if (numElements >= TARGET_RECORDS) {
436 final int backOffFactor = Math.min(numElements - TARGET_RECORDS, 30);
437 if (dropped = PlatformDependent.threadLocalRandom().nextInt(1 << backOffFactor) != 0) {
438 prevHead = oldHead.next;
439 }
440 } else {
441 dropped = false;
442 }
443 newHead = hint != null ? new TraceRecord(prevHead, hint) : new TraceRecord(prevHead);
444 } while (!headUpdater.compareAndSet(this, oldHead, newHead));
445 if (dropped) {
446 droppedRecordsUpdater.incrementAndGet(this);
447 }
448 }
449 }
450
451 boolean dispose() {
452 clear();
453 return allLeaks.remove(this);
454 }
455
456 @Override
457 public boolean close() {
458 if (allLeaks.remove(this)) {
459 // Call clear so the reference is not even enqueued.
460 clear();
461 headUpdater.set(this, null);
462 return true;
463 }
464 return false;
465 }
466
467 @Override
468 public boolean close(T trackedObject) {
469 // Ensure that the object that was tracked is the same as the one that was passed to close(...).
470 assert trackedHash == System.identityHashCode(trackedObject);
471
472 try {
473 return close();
474 } finally {
475 // This method will do `synchronized(trackedObject)` and we should be sure this will not cause deadlock.
476 // It should not, because somewhere up the callstack should be a (successful) `trackedObject.release`,
477 // therefore it is unreasonable that anyone else, anywhere, is holding a lock on the trackedObject.
478 // (Unreasonable but possible, unfortunately.)
479 reachabilityFence0(trackedObject);
480 }
481 }
482
483 /**
484 * Ensures that the object referenced by the given reference remains
485 * <a href="package-summary.html#reachability"><em>strongly reachable</em></a>,
486 * regardless of any prior actions of the program that might otherwise cause
487 * the object to become unreachable; thus, the referenced object is not
488 * reclaimable by garbage collection at least until after the invocation of
489 * this method.
490 *
491 * <p> Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable.
492 * see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8
493 * The Java 9 method Reference.reachabilityFence offers a solution to this problem.
494 *
495 * <p> This method is always implemented as a synchronization on {@code ref}, not as
496 * {@code Reference.reachabilityFence} for consistency across platforms and to allow building on JDK 6-8.
497 * <b>It is the caller's responsibility to ensure that this synchronization will not cause deadlock.</b>
498 *
499 * @param ref the reference. If {@code null}, this method has no effect.
500 * @see java.lang.ref.Reference#reachabilityFence
501 */
502 private static void reachabilityFence0(Object ref) {
503 if (ref != null) {
504 synchronized (ref) {
505 // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521
506 }
507 }
508 }
509
510 @Override
511 public String toString() {
512 TraceRecord oldHead = headUpdater.getAndSet(this, null);
513 if (oldHead == null) {
514 // Already closed
515 return EMPTY_STRING;
516 }
517
518 final int dropped = droppedRecordsUpdater.get(this);
519 int duped = 0;
520
521 int present = oldHead.pos + 1;
522 // Guess about 2 kilobytes per stack trace
523 StringBuilder buf = new StringBuilder(present * 2048).append(NEWLINE);
524 buf.append("Recent access records: ").append(NEWLINE);
525
526 int i = 1;
527 Set<String> seen = new HashSet<String>(present);
528 for (; oldHead != TraceRecord.BOTTOM; oldHead = oldHead.next) {
529 String s = oldHead.toString();
530 if (seen.add(s)) {
531 if (oldHead.next == TraceRecord.BOTTOM) {
532 buf.append("Created at:").append(NEWLINE).append(s);
533 } else {
534 buf.append('#').append(i++).append(':').append(NEWLINE).append(s);
535 }
536 } else {
537 duped++;
538 }
539 }
540
541 if (duped > 0) {
542 buf.append(": ")
543 .append(duped)
544 .append(" leak records were discarded because they were duplicates")
545 .append(NEWLINE);
546 }
547
548 if (dropped > 0) {
549 buf.append(": ")
550 .append(dropped)
551 .append(" leak records were discarded because the leak record count is targeted to ")
552 .append(TARGET_RECORDS)
553 .append(". Use system property ")
554 .append(PROP_TARGET_RECORDS)
555 .append(" to increase the limit.")
556 .append(NEWLINE);
557 }
558
559 buf.setLength(buf.length() - NEWLINE.length());
560 return buf.toString();
561 }
562 }
563
564 private static final AtomicReference<String[]> excludedMethods =
565 new AtomicReference<String[]>(EmptyArrays.EMPTY_STRINGS);
566
567 public static void addExclusions(Class clz, String ... methodNames) {
568 Set<String> nameSet = new HashSet<String>(Arrays.asList(methodNames));
569 // Use loop rather than lookup. This avoids knowing the parameters, and doesn't have to handle
570 // NoSuchMethodException.
571 for (Method method : clz.getDeclaredMethods()) {
572 if (nameSet.remove(method.getName()) && nameSet.isEmpty()) {
573 break;
574 }
575 }
576 if (!nameSet.isEmpty()) {
577 throw new IllegalArgumentException("Can't find '" + nameSet + "' in " + clz.getName());
578 }
579 String[] oldMethods;
580 String[] newMethods;
581 do {
582 oldMethods = excludedMethods.get();
583 newMethods = Arrays.copyOf(oldMethods, oldMethods.length + 2 * methodNames.length);
584 for (int i = 0; i < methodNames.length; i++) {
585 newMethods[oldMethods.length + i * 2] = clz.getName();
586 newMethods[oldMethods.length + i * 2 + 1] = methodNames[i];
587 }
588 } while (!excludedMethods.compareAndSet(oldMethods, newMethods));
589 }
590
591 private static final class TraceRecord extends Throwable {
592 private static final long serialVersionUID = 6065153674892850720L;
593
594 private static final TraceRecord BOTTOM = new TraceRecord();
595
596 private final String hintString;
597 private final TraceRecord next;
598 private final int pos;
599
600 TraceRecord(TraceRecord next, Object hint) {
601 // This needs to be generated even if toString() is never called as it may change later on.
602 hintString = hint instanceof ResourceLeakHint ? ((ResourceLeakHint) hint).toHintString() : hint.toString();
603 this.next = next;
604 this.pos = next.pos + 1;
605 }
606
607 TraceRecord(TraceRecord next) {
608 hintString = null;
609 this.next = next;
610 this.pos = next.pos + 1;
611 }
612
613 // Used to terminate the stack
614 private TraceRecord() {
615 hintString = null;
616 next = null;
617 pos = -1;
618 }
619
620 @Override
621 public String toString() {
622 StringBuilder buf = new StringBuilder(2048);
623 if (hintString != null) {
624 buf.append("\tHint: ").append(hintString).append(NEWLINE);
625 }
626
627 // Append the stack trace.
628 StackTraceElement[] array = getStackTrace();
629 // Skip the first three elements.
630 out: for (int i = 3; i < array.length; i++) {
631 StackTraceElement element = array[i];
632 // Strip the noisy stack trace elements.
633 String[] exclusions = excludedMethods.get();
634 for (int k = 0; k < exclusions.length; k += 2) {
635 if (exclusions[k].equals(element.getClassName())
636 && exclusions[k + 1].equals(element.getMethodName())) {
637 continue out;
638 }
639 }
640
641 buf.append('\t');
642 buf.append(element.toString());
643 buf.append(NEWLINE);
644 }
645 return buf.toString();
646 }
647 }
648 }
649