Skip to content

Commit

Permalink
Add IT reproducing JoinPointImpl thread-locals memory leak
Browse files Browse the repository at this point in the history
Relates to #302.

Signed-off-by: Alexander Kriegisch <[email protected]>
  • Loading branch information
kriegaex committed Apr 10, 2024
1 parent e54ae56 commit 856db5d
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 2 deletions.
12 changes: 12 additions & 0 deletions tests/bugs1922/github_302/FirstAspect.aj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class FirstAspect {
@Around("execution(* toIntercept())")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//System.out.println(getClass().getSimpleName());
return joinPoint.proceed();
}
}
86 changes: 86 additions & 0 deletions tests/bugs1922/github_302/NestedAroundClosureMemoryLeakTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NestedAroundClosureMemoryLeakTest {

private static final int NUM_THREAD_POOLS = 4;
private static final int THREAD_POOL_SIZE = 3;

public static void main(String[] args) throws Exception {
testNoMemoryLeak_ThreadLocalCleared();
}

/**
* Tests that the thread-locals of the spawned threads are either null or contain all null elements
*/
public static void testNoMemoryLeak_ThreadLocalCleared() throws Exception {
List<ExecutorService> executorServices = createExecutorServicesWithFixedThreadPools();
try {
executeTasks(executorServices);

Field mapField = Thread.class.getDeclaredField("threadLocals");
mapField.setAccessible(true);
Set<Thread> threads = Thread.getAllStackTraces().keySet();
System.out.println("Number of pool threads = " + threads.stream().filter(thread -> thread.getName().contains("pool")).count());

threads.stream()
.filter(thread -> thread.getName().contains("pool"))
.forEach(thread -> {
try {
Object threadLocals = mapField.get(thread);
if (threadLocals != null) {
Field tableField = threadLocals.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object[] threadLocalTable = (Object[]) tableField.get(threadLocals);
if (threadLocalTable != null) {
for (Object entry : threadLocalTable) {
if (entry == null)
continue;
Field entryValueField = entry.getClass().getDeclaredField("value");
entryValueField.setAccessible(true);
throw new RuntimeException(
"All thread-locals should be null, but found entry with value " + entryValueField.get(entry)
);
}
}
}
}
catch (RuntimeException rte) {
throw rte;
}
catch (Exception e) {
throw new RuntimeException(e);
}
});

System.out.println("Test passed - all thread-locals are null");
}
finally {
for (ExecutorService executorService : executorServices)
executorService.shutdown();
}
}

private static List<ExecutorService> createExecutorServicesWithFixedThreadPools() {
List<ExecutorService> executorServiceList = new ArrayList<>(NestedAroundClosureMemoryLeakTest.NUM_THREAD_POOLS);
for (int i = 0; i < NestedAroundClosureMemoryLeakTest.NUM_THREAD_POOLS; i++)
executorServiceList.add(Executors.newFixedThreadPool(THREAD_POOL_SIZE));
return executorServiceList;
}

private static void executeTasks(List<ExecutorService> executorServices) throws Exception {
for (ExecutorService executorService : executorServices) {
for (int i = 0; i < THREAD_POOL_SIZE * 2; i++)
new Task(executorService).doSomething();
}
System.out.println("Finished executing tasks");

// Sleep to take a memory dump
// Thread.sleep(500000);
}

}
12 changes: 12 additions & 0 deletions tests/bugs1922/github_302/SecondAspect.aj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class SecondAspect {
@Around("execution(* toIntercept())")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//System.out.println(getClass().getSimpleName());
return joinPoint.proceed();
}
}
20 changes: 20 additions & 0 deletions tests/bugs1922/github_302/Task.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

public class Task {
final ExecutorService taskManager;

public Task(final ExecutorService executorService) {
taskManager = executorService;
}

public void doSomething() throws ExecutionException, InterruptedException {
Future<?> future = taskManager.submit(Task::toIntercept);
future.get();
}

public static void toIntercept() {
//System.out.println("Executing task")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
public class Bugs1922Tests extends XMLBasedAjcTestCase {

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

public static Test suite() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,18 @@
</run>
</ajc-test>

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

</suite>

0 comments on commit 856db5d

Please sign in to comment.