]> source.dussan.org Git - aspectj.git/commitdiff
Add IT reproducing JoinPointImpl thread-locals memory leak
authorAlexander Kriegisch <Alexander@Kriegisch.name>
Wed, 10 Apr 2024 09:03:13 +0000 (11:03 +0200)
committerAlexander Kriegisch <Alexander@Kriegisch.name>
Wed, 10 Apr 2024 09:23:00 +0000 (11:23 +0200)
Relates to #302.

Signed-off-by: Alexander Kriegisch <Alexander@Kriegisch.name>
tests/bugs1922/github_302/FirstAspect.aj [new file with mode: 0644]
tests/bugs1922/github_302/NestedAroundClosureMemoryLeakTest.java [new file with mode: 0644]
tests/bugs1922/github_302/SecondAspect.aj [new file with mode: 0644]
tests/bugs1922/github_302/Task.java [new file with mode: 0644]
tests/src/test/java/org/aspectj/systemtest/ajc1922/Bugs1922Tests.java
tests/src/test/resources/org/aspectj/systemtest/ajc1922/ajc1922.xml

diff --git a/tests/bugs1922/github_302/FirstAspect.aj b/tests/bugs1922/github_302/FirstAspect.aj
new file mode 100644 (file)
index 0000000..4d99110
--- /dev/null
@@ -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();
+  }
+}
diff --git a/tests/bugs1922/github_302/NestedAroundClosureMemoryLeakTest.java b/tests/bugs1922/github_302/NestedAroundClosureMemoryLeakTest.java
new file mode 100644 (file)
index 0000000..9b3b2f8
--- /dev/null
@@ -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);
+  }
+
+}
diff --git a/tests/bugs1922/github_302/SecondAspect.aj b/tests/bugs1922/github_302/SecondAspect.aj
new file mode 100644 (file)
index 0000000..5f088b6
--- /dev/null
@@ -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();
+  }
+}
diff --git a/tests/bugs1922/github_302/Task.java b/tests/bugs1922/github_302/Task.java
new file mode 100644 (file)
index 0000000..0d1ea64
--- /dev/null
@@ -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")
+  }
+}
index 06211a1796626cc9f2f148d82122a7d56c2fad7a..6e402fa11db2c30314ae72577745a8fb7f983a18 100644 (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() {
index d436032e2634c739361f87cf8a3b1aa07d326a6f..e10fd99753f9d8e7f8f0e24522ef89452a104b01 100644 (file)
                </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>