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 if (store.get(SCOPE_KEY) != null) {
60 throw new IllegalStateException("Weird context lifecycle");
61 }
62 LeakPresenceDetector.ResourceScope scope = new LeakPresenceDetector.ResourceScope(context.getDisplayName());
63 store.put(SCOPE_KEY, scope);
64
65 WithTransferableScope.SCOPE.set(scope);
66 }
67
68 @Override
69 public void beforeEach(ExtensionContext context) {
70 LeakPresenceDetector.ResourceScope outerScope;
71 ExtensionContext outerContext = context;
72 while (true) {
73 outerScope = (LeakPresenceDetector.ResourceScope)
74 outerContext.getStore(ExtensionContext.Namespace.GLOBAL).get(SCOPE_KEY);
75 if (outerScope != null) {
76 break;
77 }
78 outerContext = outerContext.getParent()
79 .orElseThrow(() -> new IllegalStateException("No resource scope found"));
80 }
81
82 LeakPresenceDetector.ResourceScope previousScope = WithTransferableScope.SCOPE.get();
83 WithTransferableScope.SCOPE.set(outerScope);
84 if (previousScope != null) {
85 context.getStore(ExtensionContext.Namespace.GLOBAL).put(PREVIOUS_SCOPE_KEY, previousScope);
86 }
87 }
88
89 @Override
90 public void afterEach(ExtensionContext context) {
91 LeakPresenceDetector.ResourceScope previousScope = (LeakPresenceDetector.ResourceScope)
92 context.getStore(ExtensionContext.Namespace.GLOBAL).get(PREVIOUS_SCOPE_KEY);
93 if (previousScope != null) {
94 WithTransferableScope.SCOPE.set(previousScope);
95 }
96 }
97
98 @Override
99 public void afterAll(ExtensionContext context) throws InterruptedException {
100 LeakPresenceDetector.ResourceScope scope =
101 (LeakPresenceDetector.ResourceScope) context.getStore(ExtensionContext.Namespace.GLOBAL).get(SCOPE_KEY);
102
103
104 long start = System.nanoTime();
105 while (scope.hasOpenResources() && System.nanoTime() - start < TimeUnit.SECONDS.toNanos(5)) {
106 TimeUnit.MILLISECONDS.sleep(100);
107 }
108
109 scope.close();
110 }
111
112 public static final class WithTransferableScope<T> extends LeakPresenceDetector<T> {
113 static final InheritableThreadLocal<ResourceScope> SCOPE = new InheritableThreadLocal<>();
114
115 @SuppressWarnings("unused")
116 public WithTransferableScope(Class<?> resourceType, int samplingInterval) {
117 super(resourceType);
118 }
119
120 @SuppressWarnings("unused")
121 public WithTransferableScope(Class<?> resourceType, int samplingInterval, long maxActive) {
122 super(resourceType);
123 }
124
125 @Override
126 protected ResourceScope currentScope() {
127 return Objects.requireNonNull(SCOPE.get(), "Resource created outside test?");
128 }
129 }
130 }