Skip to content

Commit 856db5d

Browse files
committed
Add IT reproducing JoinPointImpl thread-locals memory leak
Relates to #302. Signed-off-by: Alexander Kriegisch <[email protected]>
1 parent e54ae56 commit 856db5d

File tree

6 files changed

+146
-2
lines changed

6 files changed

+146
-2
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import org.aspectj.lang.ProceedingJoinPoint;
2+
import org.aspectj.lang.annotation.Around;
3+
import org.aspectj.lang.annotation.Aspect;
4+
5+
@Aspect
6+
public class FirstAspect {
7+
@Around("execution(* toIntercept())")
8+
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
9+
//System.out.println(getClass().getSimpleName());
10+
return joinPoint.proceed();
11+
}
12+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import java.lang.reflect.Field;
2+
import java.util.ArrayList;
3+
import java.util.List;
4+
import java.util.Set;
5+
import java.util.concurrent.ExecutorService;
6+
import java.util.concurrent.Executors;
7+
8+
public class NestedAroundClosureMemoryLeakTest {
9+
10+
private static final int NUM_THREAD_POOLS = 4;
11+
private static final int THREAD_POOL_SIZE = 3;
12+
13+
public static void main(String[] args) throws Exception {
14+
testNoMemoryLeak_ThreadLocalCleared();
15+
}
16+
17+
/**
18+
* Tests that the thread-locals of the spawned threads are either null or contain all null elements
19+
*/
20+
public static void testNoMemoryLeak_ThreadLocalCleared() throws Exception {
21+
List<ExecutorService> executorServices = createExecutorServicesWithFixedThreadPools();
22+
try {
23+
executeTasks(executorServices);
24+
25+
Field mapField = Thread.class.getDeclaredField("threadLocals");
26+
mapField.setAccessible(true);
27+
Set<Thread> threads = Thread.getAllStackTraces().keySet();
28+
System.out.println("Number of pool threads = " + threads.stream().filter(thread -> thread.getName().contains("pool")).count());
29+
30+
threads.stream()
31+
.filter(thread -> thread.getName().contains("pool"))
32+
.forEach(thread -> {
33+
try {
34+
Object threadLocals = mapField.get(thread);
35+
if (threadLocals != null) {
36+
Field tableField = threadLocals.getClass().getDeclaredField("table");
37+
tableField.setAccessible(true);
38+
Object[] threadLocalTable = (Object[]) tableField.get(threadLocals);
39+
if (threadLocalTable != null) {
40+
for (Object entry : threadLocalTable) {
41+
if (entry == null)
42+
continue;
43+
Field entryValueField = entry.getClass().getDeclaredField("value");
44+
entryValueField.setAccessible(true);
45+
throw new RuntimeException(
46+
"All thread-locals should be null, but found entry with value " + entryValueField.get(entry)
47+
);
48+
}
49+
}
50+
}
51+
}
52+
catch (RuntimeException rte) {
53+
throw rte;
54+
}
55+
catch (Exception e) {
56+
throw new RuntimeException(e);
57+
}
58+
});
59+
60+
System.out.println("Test passed - all thread-locals are null");
61+
}
62+
finally {
63+
for (ExecutorService executorService : executorServices)
64+
executorService.shutdown();
65+
}
66+
}
67+
68+
private static List<ExecutorService> createExecutorServicesWithFixedThreadPools() {
69+
List<ExecutorService> executorServiceList = new ArrayList<>(NestedAroundClosureMemoryLeakTest.NUM_THREAD_POOLS);
70+
for (int i = 0; i < NestedAroundClosureMemoryLeakTest.NUM_THREAD_POOLS; i++)
71+
executorServiceList.add(Executors.newFixedThreadPool(THREAD_POOL_SIZE));
72+
return executorServiceList;
73+
}
74+
75+
private static void executeTasks(List<ExecutorService> executorServices) throws Exception {
76+
for (ExecutorService executorService : executorServices) {
77+
for (int i = 0; i < THREAD_POOL_SIZE * 2; i++)
78+
new Task(executorService).doSomething();
79+
}
80+
System.out.println("Finished executing tasks");
81+
82+
// Sleep to take a memory dump
83+
// Thread.sleep(500000);
84+
}
85+
86+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import org.aspectj.lang.ProceedingJoinPoint;
2+
import org.aspectj.lang.annotation.Around;
3+
import org.aspectj.lang.annotation.Aspect;
4+
5+
@Aspect
6+
public class SecondAspect {
7+
@Around("execution(* toIntercept())")
8+
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
9+
//System.out.println(getClass().getSimpleName());
10+
return joinPoint.proceed();
11+
}
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import java.util.concurrent.ExecutionException;
2+
import java.util.concurrent.ExecutorService;
3+
import java.util.concurrent.Future;
4+
5+
public class Task {
6+
final ExecutorService taskManager;
7+
8+
public Task(final ExecutorService executorService) {
9+
taskManager = executorService;
10+
}
11+
12+
public void doSomething() throws ExecutionException, InterruptedException {
13+
Future<?> future = taskManager.submit(Task::toIntercept);
14+
future.get();
15+
}
16+
17+
public static void toIntercept() {
18+
//System.out.println("Executing task")
19+
}
20+
}

tests/src/test/java/org/aspectj/systemtest/ajc1922/Bugs1922Tests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616
public class Bugs1922Tests extends XMLBasedAjcTestCase {
1717

18-
public void testDummy() {
19-
//runTest("dummy");
18+
public void testGitHub_302() {
19+
runTest("thread-local around closure index is removed after innermost proceed");
2020
}
2121

2222
public static Test suite() {

tests/src/test/resources/org/aspectj/systemtest/ajc1922/ajc1922.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,18 @@
196196
</run>
197197
</ajc-test>
198198

199+
<!-- https://github.com/eclipse-aspectj/aspectj/issues/302 -->
200+
<ajc-test dir="bugs1922/github_302" title="thread-local around closure index is removed after innermost proceed">
201+
<compile files="NestedAroundClosureMemoryLeakTest.java Task.java FirstAspect.aj SecondAspect.aj" options="-1.8 -XnoInline"/>
202+
<run class="NestedAroundClosureMemoryLeakTest" vmargs="--add-opens java.base/java.lang=ALL-UNNAMED">
203+
<stdout>
204+
<line text="Finished executing tasks"/>
205+
<line text="Number of pool threads = 12"/>
206+
<line text="Test passed - all thread-locals are null"/>
207+
</stdout>
208+
<!-- No RuntimeException on stderr-->
209+
<stderr/>
210+
</run>
211+
</ajc-test>
212+
199213
</suite>

0 commit comments

Comments
 (0)