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.Buffer;
19  import io.netty5.buffer.api.LeakInfo;
20  import io.netty5.buffer.api.LeakInfo.TracePoint;
21  import io.netty5.buffer.api.Owned;
22  import io.netty5.util.Resource;
23  import io.netty5.util.internal.SystemPropertyUtil;
24  import io.netty5.util.internal.UnstableApi;
25  
26  import java.util.ArrayDeque;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Set;
30  import java.util.function.Function;
31  import java.util.stream.Stream;
32  
33  /**
34   * Instances of this class record life cycle events of resources, to help debug life-cycle errors.
35   */
36  @UnstableApi
37  public abstract class LifecycleTracer {
38      static final boolean lifecycleTracingEnabled =
39              SystemPropertyUtil.getBoolean("io.netty5.buffer.lifecycleTracingEnabled", false);
40  
41      /**
42       * Get a tracer for a newly allocated resource.
43       * <p>
44       * <strong>Note:</strong> this call is itself not traced.
45       * Instead, it is customary to immediately call {@link #allocate()} on the returned tracer.
46       *
47       * @return A new tracer for a resource.
48       */
49      public static LifecycleTracer get() {
50          if (!lifecycleTracingEnabled && LeakDetection.leakDetectionEnabled == 0) {
51              return NoOpTracer.INSTANCE;
52          }
53          return new StackTracer();
54      }
55  
56      /**
57       * Add to the trace log that the object has been allocated.
58       */
59      public abstract void allocate();
60  
61      /**
62       * Add to the trace log that the object has been acquired, in other words the reference count has been incremented.
63       *
64       * @param acquires The new current number of acquires on the traced object.
65       */
66      public abstract void acquire(int acquires);
67  
68      /**
69       * Add to the trace log that the object has been dropped.
70       *
71       * @param acquires The new current number of acquires on the traced object.
72       */
73      public abstract void drop(int acquires);
74  
75      /**
76       * Add to the trace log that the object has been closed, in other words, the reference count has been decremented.
77       *
78       * @param acquires The new current number of acquires on the traced object.
79       */
80      public abstract void close(int acquires);
81  
82      /**
83       * Add the hint object to the trace log.
84       * The hint objects can be inspected later if a lifecycle related exception is thrown, or if the object leaks.
85       *
86       * @param hint The hint object to attach to the trace log.
87       */
88      public abstract void touch(Object hint);
89  
90      /**
91       * Add to the trace log that the object is being sent.
92       *
93       * @param <I> The resource interface for the object.
94       * @param <T> The concrete type of the object.
95       * @param instance The owned instance being sent.
96       * @return An {@link Owned} instance that may trace the reception of the object.
97       */
98      public abstract <I extends Resource<I>, T extends ResourceSupport<I, T>> Owned<T> send(Owned<T> instance);
99  
100     /**
101      * Attach a trace to both life-cycles, that a single life-cycle has been split into two.
102      *
103      * Such branches happen when two views are created to share a single underlying resource.
104      * The most prominent example of this is the {@link Buffer#split()} method, where a buffer is broken into two that
105      * each covers a non-overlapping region of the original memory.
106      *
107      * This method is called on the originating, or "parent" tracer, while the newly allocated "child" is given as an
108      * argument.
109      *
110      * @param splitTracer The tracer for the life-cycle that was branched from the life-cycle represented by this
111      *                   tracer.
112      */
113     public abstract void splitTo(LifecycleTracer splitTracer);
114 
115     /**
116      * Attach a life cycle trace log to the given exception.
117      *
118      * @param throwable The exception that will receive the trace log in the form of
119      * {@linkplain Throwable#addSuppressed(Throwable) suppressed exceptions}.
120      * @param <E> The concrete exception type.
121      * @return The same exception instance, that can then be thrown.
122      */
123     public abstract <E extends Throwable> E attachTrace(E throwable);
124 
125     /**
126      * Return the life-cycle trace as an ordered {@link Collection} of {@link TracePoint}s.
127      * The trace points are ordered chronologically in the collection, with earlier events before later ones.
128      * The returned collection is not modifiable.
129      *
130      * @return A collection of trace points.
131      */
132     public abstract Collection<TracePoint> collectTraces();
133 
134     private static final class NoOpTracer extends LifecycleTracer {
135         private static final NoOpTracer INSTANCE = new NoOpTracer();
136 
137         @Override
138         public void allocate() {
139         }
140 
141         @Override
142         public void acquire(int acquires) {
143         }
144 
145         @Override
146         public void drop(int acquires) {
147         }
148 
149         @Override
150         public void close(int acquires) {
151         }
152 
153         @Override
154         public void touch(Object hint) {
155         }
156 
157         @Override
158         public <I extends Resource<I>, T extends ResourceSupport<I, T>> Owned<T> send(Owned<T> instance) {
159             return instance;
160         }
161 
162         @Override
163         public void splitTo(LifecycleTracer splitTracer) {
164         }
165 
166         @Override
167         public <E extends Throwable> E attachTrace(E throwable) {
168             return throwable;
169         }
170 
171         @Override
172         public Collection<TracePoint> collectTraces() {
173             return Collections.emptyList();
174         }
175     }
176 
177     private static final class StackTracer extends LifecycleTracer {
178         private static final int MAX_TRACE_POINTS = Math.min(SystemPropertyUtil.getInt(
179                 "io.netty5.buffer.api.internal.LifecycleTracer.MAX_TRACE_POINTS", 50), 1000);
180         private static final StackWalker WALKER;
181         static {
182             int depth = Trace.TRACE_LIFECYCLE_DEPTH;
183             WALKER = depth > 0 && lifecycleTracingEnabled ? StackWalker.getInstance(Set.of(), depth + 2) : null;
184         }
185 
186         private final ArrayDeque<Trace> traces = new ArrayDeque<>();
187         private boolean dropped;
188 
189         @Override
190         public void allocate() {
191             addTrace(walk(new Trace(TraceType.ALLOCATE, 0)));
192         }
193 
194         @Override
195         public void acquire(int acquires) {
196             addTrace(walk(new Trace(TraceType.ACQUIRE, acquires)));
197         }
198 
199         @Override
200         public void drop(int acquires) {
201             dropped = true;
202             addTrace(walk(new Trace(TraceType.DROP, acquires)));
203         }
204 
205         @Override
206         public void close(int acquires) {
207             if (!dropped) {
208                 addTrace(walk(new Trace(TraceType.CLOSE, acquires)));
209             }
210         }
211 
212         @Override
213         public void touch(Object hint) {
214             Trace trace = new Trace(TraceType.TOUCH);
215             trace.attachmentType = AttachmentType.HINT;
216             trace.attachment = hint;
217             addTrace(walk(trace));
218         }
219 
220         @Override
221         public <I extends Resource<I>, T extends ResourceSupport<I, T>> Owned<T> send(Owned<T> instance) {
222             Trace sendTrace = new Trace(TraceType.SEND);
223             sendTrace.attachmentType = AttachmentType.RECEIVED_AT;
224             addTrace(walk(sendTrace));
225             return drop -> {
226                 sendTrace.attachment = walk(new Trace(TraceType.RECEIVE));
227                 return instance.transferOwnership(drop);
228             };
229         }
230 
231         @Override
232         public void splitTo(LifecycleTracer splitTracer) {
233             Trace splitParent = new Trace(TraceType.SPLIT);
234             Trace splitChild = new Trace(TraceType.SPLIT);
235             splitParent.attachmentType = AttachmentType.SPLIT_TO;
236             splitParent.attachment = splitChild;
237             splitChild.attachmentType = AttachmentType.SPLIT_FROM;
238             splitChild.attachment = splitParent;
239             addTrace(walk(splitParent));
240             if (splitTracer instanceof StackTracer) {
241                 StackTracer tracer = (StackTracer) splitTracer;
242                 tracer.addTrace(walk(splitChild));
243             }
244         }
245 
246         @Override
247         public <E extends Throwable> E attachTrace(E throwable) {
248             synchronized (traces) {
249                 long timestamp = System.nanoTime();
250                 for (Trace trace : traces) {
251                     trace.attach(throwable, timestamp);
252                 }
253             }
254             return throwable;
255         }
256 
257         @Override
258         public Collection<TracePoint> collectTraces() {
259             return Collections.unmodifiableCollection(Collections.synchronizedCollection(traces));
260         }
261 
262         Trace walk(Trace trace) {
263             if (WALKER != null) {
264                 WALKER.walk(trace);
265             }
266             return trace;
267         }
268 
269         void addTrace(Trace trace) {
270             synchronized (traces) {
271                 if (traces.size() == MAX_TRACE_POINTS) {
272                     traces.pollFirst();
273                 }
274                 traces.addLast(trace);
275             }
276         }
277     }
278 
279     static final class Trace implements Function<Stream<StackWalker.StackFrame>, Trace>, LeakInfo.TracePoint {
280         private static final int TRACE_LIFECYCLE_DEPTH;
281         public static final StackTraceElement[] EMPTY_TRACE = new StackTraceElement[0];
282 
283         static {
284             int traceDefault = 50;
285             TRACE_LIFECYCLE_DEPTH = Math.max(Integer.getInteger(
286                     "io.netty5.buffer.api.internal.LifecycleTracer.TRACE_LIFECYCLE_DEPTH", traceDefault), 0);
287         }
288 
289         final TraceType type;
290         final int acquires;
291         final long timestamp;
292         volatile AttachmentType attachmentType;
293         volatile Object attachment;
294         StackWalker.StackFrame[] frames;
295 
296         Trace(TraceType type) {
297             this(type, Integer.MIN_VALUE);
298         }
299 
300         Trace(TraceType type, int acquires) {
301             this.type = type;
302             this.acquires = acquires;
303             timestamp = System.nanoTime();
304         }
305 
306         @Override
307         public Trace apply(Stream<StackWalker.StackFrame> frames) {
308             this.frames = frames.skip(2).limit(TRACE_LIFECYCLE_DEPTH + 1).toArray(StackWalker.StackFrame[]::new);
309             return this;
310         }
311 
312         public <E extends Throwable> void attach(E throwable, long timestamp) {
313             Traceback exception = getTraceback(timestamp, true);
314             throwable.addSuppressed(exception);
315         }
316 
317         @Override
318         public Throwable traceback() {
319             return getTraceback(System.nanoTime(), true);
320         }
321 
322         private Traceback getTraceback(long timestamp, boolean recurse) {
323             String message = type.name();
324             Trace associatedTrace = getAssociatedTrace();
325             message = explainAttachment(message, associatedTrace);
326             if (acquires != Integer.MIN_VALUE) {
327                 message += " (current acquires = " + acquires + ')';
328             }
329             message += " T" + (this.timestamp - timestamp) / 1000 + "us.";
330             Traceback exception = new Traceback(message);
331             StackTraceElement[] stackTrace = framesToStackTrace();
332             exception.setStackTrace(stackTrace);
333             if (associatedTrace != null && recurse) {
334                 exception.addSuppressed(associatedTrace.getTraceback(timestamp, false));
335             }
336             return exception;
337         }
338 
339         private Trace getAssociatedTrace() {
340             Object associated = attachment;
341             if (associated instanceof Trace) {
342                 return (Trace) associated;
343             }
344             return null;
345         }
346 
347         private String explainAttachment(String message, Trace associatedTrace) {
348             AttachmentType type = attachmentType;
349             if (type == null) {
350                 return message;
351             }
352             switch (type) {
353             case RECEIVED_AT:
354                 if (associatedTrace == null) {
355                     message += " (sent but not received)";
356                 } else {
357                     message += " (sent and received)";
358                 }
359                 break;
360             case SEND_FROM:
361                 message += " (from a send)";
362                 break;
363             case SPLIT_TO:
364                 message += " (split into two)";
365                 break;
366             case SPLIT_FROM:
367                 message += " (split from other object)";
368                 break;
369             case HINT:
370                 message += " (" + attachment + ')';
371                 break;
372             }
373             return message;
374         }
375 
376         private StackTraceElement[] framesToStackTrace() {
377             if (frames == null) {
378                 return EMPTY_TRACE;
379             }
380             StackTraceElement[] stackTrace = new StackTraceElement[frames.length];
381             for (int i = 0; i < frames.length; i++) {
382                 stackTrace[i] = frames[i].toStackTraceElement();
383             }
384             return stackTrace;
385         }
386 
387         @Override
388         public Object hint() {
389             return attachmentType == AttachmentType.HINT? attachment : null;
390         }
391     }
392 
393     private static final class Traceback extends Throwable {
394         private static final long serialVersionUID = 941453986194634605L;
395 
396         Traceback(String message) {
397             super(message);
398         }
399 
400         @Override
401         public Throwable fillInStackTrace() {
402             return this;
403         }
404     }
405 
406     private enum TraceType {
407         ALLOCATE,
408         ACQUIRE,
409         CLOSE,
410         DROP,
411         SEND,
412         RECEIVE,
413         TOUCH,
414         SPLIT,
415     }
416 
417     private enum AttachmentType {
418         /**
419          * Tracer of sending object.
420          */
421         SEND_FROM,
422         /**
423          * Tracer of object that was received, after being sent.
424          */
425         RECEIVED_AT,
426         /**
427          * Tracer of origin object of a split.
428          */
429         SPLIT_FROM,
430         /**
431          * Tracer of object split from this traced object.
432          */
433         SPLIT_TO,
434         /**
435          * Object is a hint from a {@link Resource#touch(Object)} call.
436          */
437         HINT,
438     }
439 }