Browse Source

Add IT reproducing JoinPointImpl thread-locals memory leak

Relates to #302.

Signed-off-by: Alexander Kriegisch <Alexander@Kriegisch.name>
pull/222/head
Alexander Kriegisch 2 weeks ago
parent
commit
856db5d97f

+ 12
- 0
tests/bugs1922/github_302/FirstAspect.aj View File

@@ -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
- 0
tests/bugs1922/github_302/NestedAroundClosureMemoryLeakTest.java View File

@@ -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
- 0
tests/bugs1922/github_302/SecondAspect.aj View File

@@ -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
- 0
tests/bugs1922/github_302/Task.java View File

@@ -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")
}
}

+ 2
- 2
tests/src/test/java/org/aspectj/systemtest/ajc1922/Bugs1922Tests.java View File

@@ -15,8 +15,8 @@ import org.aspectj.testing.XMLBasedAjcTestCase;
*/
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() {

+ 14
- 0
tests/src/test/resources/org/aspectj/systemtest/ajc1922/ajc1922.xml View File

@@ -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>

Loading…
Cancel
Save