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 trueif 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, intlong)}.
177      */

178     @Deprecated
179     public ResourceLeakDetector(Class<?> resourceType) {
180         this(simpleClassName(resourceType));
181     }
182
183     /**
184      * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, intlong)}.
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, intlong)}
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, intlong)}
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, intlong)}.
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(thisnew 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(thisnull);
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(thisnull);
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