1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.util.test;
18
19 import io.netty.util.LeakPresenceDetector;
20 import org.junit.jupiter.api.extension.AfterAllCallback;
21 import org.junit.jupiter.api.extension.AfterEachCallback;
22 import org.junit.jupiter.api.extension.BeforeAllCallback;
23 import org.junit.jupiter.api.extension.BeforeEachCallback;
24 import org.junit.jupiter.api.extension.ConditionEvaluationResult;
25 import org.junit.jupiter.api.extension.ExtensionContext;
26 import org.junit.jupiter.api.extension.ExecutionCondition;
27
28 import java.util.Objects;
29 import java.util.concurrent.TimeUnit;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public final class LeakPresenceExtension
49 implements ExecutionCondition, BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
50
51 static final String LEAK_PRESENCE_DETECTION_DISABLED_PROPERTY =
52 "io.netty.test.leakPresenceDetection.disabled";
53
54 private static final Object SCOPE_KEY = new Object();
55 private static final Object PREVIOUS_SCOPE_KEY = new Object();
56
57 static {
58 if (!Boolean.getBoolean(LEAK_PRESENCE_DETECTION_DISABLED_PROPERTY)) {
59 System.setProperty("io.netty.customResourceLeakDetector", WithTransferableScope.class.getName());
60 }
61 }
62
63 @Override
64 public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
65 if (Boolean.getBoolean(LEAK_PRESENCE_DETECTION_DISABLED_PROPERTY)) {
66 return ConditionEvaluationResult.disabled(
67 "Leak presence detection disabled by " + LEAK_PRESENCE_DETECTION_DISABLED_PROPERTY);
68 }
69 return ConditionEvaluationResult.enabled("Leak presence detection enabled");
70 }
71
72 @Override
73 public void beforeAll(ExtensionContext context) {
74 ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.GLOBAL);
75 ScopeWrapper existingScope = (ScopeWrapper) store.get(SCOPE_KEY);
76 Class<?> testClass = context.getRequiredTestClass();
77 if (existingScope == null) {
78 ScopeWrapper scope = new ScopeWrapper(
79 new LeakPresenceDetector.ResourceScope(context.getDisplayName()), testClass);
80 store.put(SCOPE_KEY, scope);
81 WithTransferableScope.SCOPE.set(scope);
82 return;
83 }
84
85
86
87 if (!isOwnedBy(testClass, existingScope.owner)) {
88 throw new IllegalStateException("Weird context lifecycle");
89 }
90 WithTransferableScope.SCOPE.set(existingScope);
91 }
92
93 @Override
94 public void beforeEach(ExtensionContext context) {
95 ScopeWrapper outerScope;
96 ExtensionContext outerContext = context;
97 while (true) {
98 outerScope = (ScopeWrapper)
99 outerContext.getStore(ExtensionContext.Namespace.GLOBAL).get(SCOPE_KEY);
100 if (outerScope != null) {
101 break;
102 }
103 outerContext = outerContext.getParent()
104 .orElseThrow(() -> new IllegalStateException("No resource scope found"));
105 }
106
107 ScopeWrapper previousScope = WithTransferableScope.SCOPE.get();
108 WithTransferableScope.SCOPE.set(outerScope);
109 if (previousScope != null) {
110 context.getStore(ExtensionContext.Namespace.GLOBAL).put(PREVIOUS_SCOPE_KEY, previousScope);
111 }
112 }
113
114 @Override
115 public void afterEach(ExtensionContext context) {
116 ScopeWrapper previousScope = (ScopeWrapper)
117 context.getStore(ExtensionContext.Namespace.GLOBAL).get(PREVIOUS_SCOPE_KEY);
118 if (previousScope != null) {
119 WithTransferableScope.SCOPE.set(previousScope);
120 }
121 }
122
123 @Override
124 public void afterAll(ExtensionContext context) throws InterruptedException {
125 ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.GLOBAL);
126 ScopeWrapper scope = (ScopeWrapper) store.get(SCOPE_KEY);
127 if (scope == null) {
128 return;
129 }
130 if (scope.owner != context.getRequiredTestClass()) {
131 return;
132 }
133
134
135 long start = System.nanoTime();
136 while (scope.scope.hasOpenResources() && System.nanoTime() - start < TimeUnit.SECONDS.toNanos(5)) {
137 TimeUnit.MILLISECONDS.sleep(100);
138 }
139
140 scope.scope.close();
141 store.remove(SCOPE_KEY);
142 }
143
144
145
146
147
148
149
150
151 private static boolean isOwnedBy(Class<?> testClass, Class<?> owner) {
152 Class<?> current = testClass;
153 while (current != null) {
154 if (current == owner) {
155 return true;
156 }
157 current = current.getEnclosingClass();
158 }
159 return false;
160 }
161
162 public static final class WithTransferableScope<T> extends LeakPresenceDetector<T> {
163 static final InheritableThreadLocal<ScopeWrapper> SCOPE = new InheritableThreadLocal<>();
164
165 @SuppressWarnings("unused")
166 public WithTransferableScope(Class<?> resourceType, int samplingInterval) {
167 super(resourceType);
168 }
169
170 @SuppressWarnings("unused")
171 public WithTransferableScope(Class<?> resourceType, int samplingInterval, long maxActive) {
172 super(resourceType);
173 }
174
175 @Override
176 protected ResourceScope currentScope() {
177 return Objects.requireNonNull(SCOPE.get(), "Resource created outside test?").scope;
178 }
179 }
180
181
182
183
184 private static final class ScopeWrapper {
185 final LeakPresenceDetector.ResourceScope scope;
186
187
188
189
190
191
192 final Class<?> owner;
193
194 ScopeWrapper(LeakPresenceDetector.ResourceScope scope, Class<?> owner) {
195 this.scope = scope;
196 this.owner = owner;
197 }
198 }
199 }