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.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
39
40 @UnstableApi
41 public final class LeakDetection {
42 static volatile int leakDetectionEnabled;
43
44
45
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
69
70
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
78 LEAK_DETECTION_ENABLED_UPDATER.getAndAddAcquire(1);
79 }
80 }
81 return new CallbackRemover(callback);
82 }
83
84
85
86
87
88
89
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)) {
115 synchronized (CALLBACKS) {
116 CALLBACKS.compute(callback, (k, v) -> {
117 assert v != null;
118 if (v.equals(INTEGER_ONE)) {
119
120 LEAK_DETECTION_ENABLED_UPDATER.getAndAddRelease(-1);
121 return null;
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 }