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
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()); | |||||
} | |||||
} |
public interface Greeter { | |||||
String hello(); | |||||
} |
public class GreeterImpl implements Greeter { | |||||
@Override | |||||
public String hello() { | |||||
return "World!"; | |||||
} | |||||
} |
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(); | |||||
} | |||||
} |
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); | |||||
} | |||||
} | |||||
} |
<!-- 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> |
<aspectj> | |||||
<aspects> | |||||
<aspect name="HelloInterceptor"/> | |||||
</aspects> | |||||
</aspectj> |
*/ | */ | ||||
public class Bugs1921Tests extends 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() { | public static Test suite() { |
</run> | </run> | ||||
</ajc-test> | </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> | </suite> |