Test "same class woven concurrently in parallel-capable classloader" reproduces #279. Originally taken and modified from: https://gitlab.com/urisimchoni/aspectj-parallel-issue Co-authored-by: Uri Simchoni <urisimchoni@gmail.com> Signed-off-by: Alexander Kriegisch <Alexander@Kriegisch.name>tags/V1_9_21_1
@@ -0,0 +1,44 @@ | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.TimeUnit; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
/** | |||
* https://github.com/eclipse-aspectj/aspectj/issues/279 | |||
*/ | |||
public class Application { | |||
public static AtomicInteger HELLO_COUNT = new AtomicInteger(0); | |||
public static AtomicInteger ASPECT_COUNT = new AtomicInteger(0); | |||
private static final int ROUNDS = 25; | |||
private static final int THREAD_COUNT = 2; | |||
private static final int TOTAL_COUNT = ROUNDS * THREAD_COUNT; | |||
private static final String CLASS_TO_LOAD = "GreeterImpl"; | |||
public static void main(String[] args) throws Exception { | |||
for (int round = 0; round < ROUNDS; round++) { | |||
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT); | |||
ParallelCapableClassLoader cl = new ParallelCapableClassLoader(Application.class.getClassLoader(), CLASS_TO_LOAD); | |||
for (int i = 0; i < THREAD_COUNT; i++) { | |||
executor.submit(() -> { | |||
try { | |||
Class<?> myClass = Class.forName(CLASS_TO_LOAD, true, cl); | |||
Greeter greeter = (Greeter) myClass.getConstructor(new Class<?>[] {}).newInstance(); | |||
greeter.hello(); | |||
HELLO_COUNT.incrementAndGet(); | |||
} | |||
catch (Exception e) { | |||
throw new RuntimeException(e); | |||
} | |||
}); | |||
} | |||
executor.shutdown(); | |||
executor.awaitTermination(60, TimeUnit.SECONDS); | |||
} | |||
assert HELLO_COUNT.get() == TOTAL_COUNT | |||
: String.format("Hello count should be %s, but is %s", TOTAL_COUNT, HELLO_COUNT.get()); | |||
assert ASPECT_COUNT.get() == TOTAL_COUNT | |||
: String.format("Aspect count should be %s, but is %s", TOTAL_COUNT, ASPECT_COUNT.get()); | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
public interface Greeter { | |||
String hello(); | |||
} |
@@ -0,0 +1,6 @@ | |||
public class GreeterImpl implements Greeter { | |||
@Override | |||
public String hello() { | |||
return "World!"; | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
import org.aspectj.lang.ProceedingJoinPoint; | |||
import org.aspectj.lang.annotation.Around; | |||
import org.aspectj.lang.annotation.Aspect; | |||
@Aspect | |||
public class HelloInterceptor { | |||
@Around("execution(public String Greeter.hello())") | |||
public Object interceptHello(ProceedingJoinPoint pjp) throws Throwable { | |||
Application.ASPECT_COUNT.incrementAndGet(); | |||
return pjp.proceed(); | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
public class ParallelCapableClassLoader extends ClassLoader { | |||
private final ClassLoader delegate; | |||
private final String classNameToHandle; | |||
static { | |||
if (!ClassLoader.registerAsParallelCapable()) | |||
throw new RuntimeException("Failed to register " + ParallelCapableClassLoader.class.getName() + " as parallel-capable"); | |||
} | |||
public ParallelCapableClassLoader(ClassLoader delegate, String classNameToHandle) { | |||
this.delegate = delegate; | |||
this.classNameToHandle = classNameToHandle; | |||
} | |||
@Override | |||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | |||
Class<?> c = this.findLoadedClass(name); | |||
if (c == null && name.equals(classNameToHandle)) { | |||
byte[] bytes = getClassBytes(name); | |||
try { | |||
c = defineClass(name, bytes, 0, bytes.length); | |||
} | |||
catch (LinkageError e) { | |||
c = findLoadedClass(name); | |||
if (c == null) | |||
throw e; | |||
} | |||
} | |||
if (c == null) | |||
c = delegate.loadClass(name); | |||
if (resolve) | |||
this.resolveClass(c); | |||
return c; | |||
} | |||
private byte[] getClassBytes(String name) { | |||
String classFilePath = name.replace('.', File.separatorChar) + ".class"; | |||
try (InputStream inputStream = delegate.getResourceAsStream(classFilePath)) { | |||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
int bytesRead; | |||
byte[] buffer = new byte[4096]; | |||
while ((bytesRead = inputStream.read(buffer)) != -1) { | |||
outputStream.write(buffer, 0, bytesRead); | |||
} | |||
return outputStream.toByteArray(); | |||
} | |||
catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
<!-- ajc-ant script, not to be used from Ant commant line - see AntSpec --> | |||
<project name="ltw"> | |||
<target name="same class woven concurrently in parallel-capable classloader"> | |||
<copy file="${aj.root}/tests/bugs1921/github_279/aop.xml" tofile="${aj.sandbox}/META-INF/aop.xml"/> | |||
<java fork="yes" classname="Application" failonerror="yes"> | |||
<classpath refid="aj.path"/> | |||
<jvmarg value="-ea"/> | |||
<!-- use META-INF/aop.xml style --> | |||
<jvmarg value="-javaagent:${aj.root}/lib/test/loadtime5.jar"/> | |||
<!--<jvmarg value="${aj.addOpensKey}"/>--> | |||
<!--<jvmarg value="${aj.addOpensValue}"/>--> | |||
</java> | |||
</target> | |||
</project> |
@@ -0,0 +1,5 @@ | |||
<aspectj> | |||
<aspects> | |||
<aspect name="HelloInterceptor"/> | |||
</aspects> | |||
</aspectj> |
@@ -15,8 +15,8 @@ import org.aspectj.testing.XMLBasedAjcTestCase; | |||
*/ | |||
public class Bugs1921Tests extends XMLBasedAjcTestCase { | |||
public void testDummy() { | |||
//runTest("dummy"); | |||
public void testGitHub_279() { | |||
runTest("same class woven concurrently in parallel-capable classloader"); | |||
} | |||
public static Test suite() { |
@@ -333,4 +333,12 @@ | |||
</run> | |||
</ajc-test> | |||
<!-- https://github.com/eclipse-aspectj/aspectj/issues/279 --> | |||
<ajc-test dir="bugs1921/github_279" vm="8" title="same class woven concurrently in parallel-capable classloader"> | |||
<compile files="Application.java Greeter.java GreeterImpl.java ParallelCapableClassLoader.java" options="-8"/> | |||
<compile files="HelloInterceptor.java" options="-8 -Xlint:ignore"/> | |||
<!-- Problem only reproduces in forked JVM with java agent, hence Ant build --> | |||
<ant file="ant.xml" target="same class woven concurrently in parallel-capable classloader" verbose="true"/> | |||
</ajc-test> | |||
</suite> |