View Javadoc
1   /*
2    * Copyright 2021 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    *   https://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  package io.netty5.buffer.api.internal;
17  
18  import io.netty5.buffer.api.LeakInfo;
19  import io.netty5.buffer.api.LoggingLeakCallback;
20  import io.netty5.buffer.api.MemoryManager;
21  import io.netty5.util.SafeCloseable;
22  import io.netty5.util.internal.SystemPropertyUtil;
23  import io.netty5.util.internal.UnstableApi;
24  
25  import java.lang.invoke.MethodHandles;
26  import java.lang.invoke.VarHandle;
27  import java.util.Collection;
28  import java.util.IdentityHashMap;
29  import java.util.Iterator;
30  import java.util.Map;
31  import java.util.concurrent.atomic.AtomicBoolean;
32  import java.util.function.Consumer;
33  import java.util.stream.Stream;
34  
35  import static java.util.Objects.requireNonNull;
36  
37  /**
38   * Utility class for the leak detection parts that are static and shared system-wide.
39   */
40  @UnstableApi
41  public final class LeakDetection {
42      static volatile int leakDetectionEnabled;
43  
44      // Protected by synchronizing on the instance.
45      // This field is only accessed when leak are detected, or when callbacks are installed or removed.
46      private static final Map<Consumer<LeakInfo>, Integer> CALLBACKS = new IdentityHashMap<>();
47      private static final Integer INTEGER_ONE = 1;
48  
49      private static final VarHandle LEAK_DETECTION_ENABLED_UPDATER;
50      static {
51          int enabled = SystemPropertyUtil.getBoolean("io.netty5.buffer.leakDetectionEnabled", false) ? 1 : 0;
52          try {
53              LEAK_DETECTION_ENABLED_UPDATER = MethodHandles.lookup().findStaticVarHandle(
54                      LeakDetection.class, "leakDetectionEnabled", int.class);
55          } catch (Exception e) {
56              throw new ExceptionInInitializerError(e);
57          }
58          if (enabled > 0) {
59              CALLBACKS.put(LoggingLeakCallback.getInstance(), 1);
60          }
61          leakDetectionEnabled = enabled;
62      }
63  
64      private LeakDetection() {
65      }
66  
67      /**
68       * Internal API for {@link MemoryManager#onLeakDetected(Consumer)}.
69       *
70       * @see MemoryManager#onLeakDetected(Consumer)
71       */
72      public static SafeCloseable onLeakDetected(Consumer<LeakInfo> callback) {
73          requireNonNull(callback, "callback");
74          synchronized (CALLBACKS) {
75              Integer newValue = CALLBACKS.compute(callback, (k, v) -> v == null ? INTEGER_ONE : v + 1);
76              if (newValue.equals(INTEGER_ONE)) {
77                  // This callback was not already in the map, so we need to increment the leak-detection-enabled counter.
78                  LEAK_DETECTION_ENABLED_UPDATER.getAndAddAcquire(1);
79              }
80          }
81          return new CallbackRemover(callback);
82      }
83  
84      /**
85       * Called when a leak is detected. This method will inform all registered
86       * {@linkplain MemoryManager#onLeakDetected(Consumer) on-leak-detected} callbacks.
87       *
88       * @param tracer The life-cycle trace of the leaked object.
89       * @param leakedObjectDescription A human-readable description of the leaked object, that can be used for logging.
90       */
91      public static void reportLeak(LifecycleTracer tracer, String leakedObjectDescription) {
92          requireNonNull(tracer, "tracer");
93          requireNonNull(leakedObjectDescription, "leakedObjectDescription");
94          synchronized (CALLBACKS) {
95              if (!CALLBACKS.isEmpty()) {
96                  LeakInfo info = new InternalLeakInfo(tracer, leakedObjectDescription);
97                  for (Consumer<LeakInfo> callback : CALLBACKS.keySet()) {
98                      callback.accept(info);
99                  }
100             }
101         }
102     }
103 
104     private static final class CallbackRemover extends AtomicBoolean implements SafeCloseable {
105         private static final long serialVersionUID = -7883321389305330790L;
106         private final Consumer<LeakInfo> callback;
107 
108         CallbackRemover(Consumer<LeakInfo> callback) {
109             this.callback = callback;
110         }
111 
112         @Override
113         public void close() {
114             if (!getAndSet(true)) { // Close can only be called once, per remover-object.
115                 synchronized (CALLBACKS) {
116                     CALLBACKS.compute(callback, (k, v) -> {
117                         assert v != null; // This should not be possible with the getAndSet guard above.
118                         if (v.equals(INTEGER_ONE)) {
119                             // The specific callback was removed, so reduce the leak-detection-enabled counter.
120                             LEAK_DETECTION_ENABLED_UPDATER.getAndAddRelease(-1);
121                             return null; // And then remove the mapping.
122                         }
123                         return v - 1;
124                     });
125                 }
126             }
127         }
128     }
129 
130     private static final class InternalLeakInfo implements LeakInfo {
131         private final LifecycleTracer tracer;
132         private final String leakedObjectDescription;
133         private Collection<TracePoint> cachedTrace;
134 
135         InternalLeakInfo(LifecycleTracer tracer, String leakedObjectDescription) {
136             this.tracer = tracer;
137             this.leakedObjectDescription = leakedObjectDescription;
138         }
139 
140         @Override
141         public Iterator<TracePoint> iterator() {
142             return getTracePoints().iterator();
143         }
144 
145         @Override
146         public Stream<TracePoint> stream() {
147             return getTracePoints().stream();
148         }
149 
150         @Override
151         public String objectDescription() {
152             return leakedObjectDescription;
153         }
154 
155         private Collection<TracePoint> getTracePoints() {
156             if (cachedTrace == null) {
157                 cachedTrace = tracer.collectTraces();
158             }
159             return cachedTrace;
160         }
161     }
162 }