1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
35
36 @UnstableApi
37 public abstract class LifecycleTracer {
38 static final boolean lifecycleTracingEnabled =
39 SystemPropertyUtil.getBoolean("io.netty5.buffer.lifecycleTracingEnabled", false);
40
41
42
43
44
45
46
47
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
58
59 public abstract void allocate();
60
61
62
63
64
65
66 public abstract void acquire(int acquires);
67
68
69
70
71
72
73 public abstract void drop(int acquires);
74
75
76
77
78
79
80 public abstract void close(int acquires);
81
82
83
84
85
86
87
88 public abstract void touch(Object hint);
89
90
91
92
93
94
95
96
97
98 public abstract <I extends Resource<I>, T extends ResourceSupport<I, T>> Owned<T> send(Owned<T> instance);
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 public abstract void splitTo(LifecycleTracer splitTracer);
114
115
116
117
118
119
120
121
122
123 public abstract <E extends Throwable> E attachTrace(E throwable);
124
125
126
127
128
129
130
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
420
421 SEND_FROM,
422
423
424
425 RECEIVED_AT,
426
427
428
429 SPLIT_FROM,
430
431
432
433 SPLIT_TO,
434
435
436
437 HINT,
438 }
439 }