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