diff --git a/its/core-it-support/core-it-plugins/maven-it-plugin-active-collection/src/main/java/org/apache/maven/plugin/coreit/CheckThreadSafetyMojo.java b/its/core-it-support/core-it-plugins/maven-it-plugin-active-collection/src/main/java/org/apache/maven/plugin/coreit/CheckThreadSafetyMojo.java index f8f4441bd7cf..0152a85569f1 100644 --- a/its/core-it-support/core-it-plugins/maven-it-plugin-active-collection/src/main/java/org/apache/maven/plugin/coreit/CheckThreadSafetyMojo.java +++ b/its/core-it-support/core-it-plugins/maven-it-plugin-active-collection/src/main/java/org/apache/maven/plugin/coreit/CheckThreadSafetyMojo.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Properties; import java.util.Vector; +import java.util.concurrent.CountDownLatch; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -33,6 +34,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugin.logging.Log; /** * Checks the thread-safe retrieval of components from active component collections. @@ -42,6 +44,7 @@ @Mojo(name = "check-thread-safety", defaultPhase = LifecyclePhase.VALIDATE) public class CheckThreadSafetyMojo extends AbstractMojo { + private static final String MAVEN_CORE_IT_LOG = "[MAVEN-CORE-IT-LOG] "; /** * Project base directory used for manual path alignment. */ @@ -70,60 +73,45 @@ public class CheckThreadSafetyMojo extends AbstractMojo { * Runs this mojo. * * @throws MojoExecutionException If the output file could not be created. + * + * @implNote threads need to use different realms to trigger changes of the collections. */ public void execute() throws MojoExecutionException { - Properties componentProperties = new Properties(); - - getLog().info("[MAVEN-CORE-IT-LOG] Testing concurrent component access"); - - ClassLoader pluginRealm = getClass().getClassLoader(); - ClassLoader coreRealm = MojoExecutionException.class.getClassLoader(); - - final Map map = componentMap; - final List list = componentList; - final List go = new Vector(); - final List exceptions = new Vector(); - - Thread[] threads = new Thread[2]; + getLog().info(MAVEN_CORE_IT_LOG + "Testing concurrent component access"); + final Thread[] threads = new Thread[2]; + final List exceptions = new Vector<>(); + final CountDownLatch startLatch = new CountDownLatch(1); for (int i = 0; i < threads.length; i++) { - // NOTE: The threads need to use different realms to trigger changes of the collections - final ClassLoader cl = (i % 2) == 0 ? pluginRealm : coreRealm; - threads[i] = new Thread() { - private final ClassLoader tccl = cl; - - public void run() { - getLog().info("[MAVEN-CORE-IT-LOG] Thread " + this + " uses " + tccl); - Thread.currentThread().setContextClassLoader(tccl); - while (go.isEmpty()) { - // wait for start - } - for (int j = 0; j < 10 * 1000; j++) { - try { - for (Object o : map.values()) { - o.toString(); - } - for (Object aList : list) { - aList.toString(); - } - } catch (Exception e) { - getLog().warn("[MAVEN-CORE-IT-LOG] Thread " + this + " encountered concurrency issue", e); - exceptions.add(e); - } - } - } - }; + threads[i] = new Thread(new CheckThreadSafetyTask( + (i % 2) == 0 ? getClass().getClassLoader() : MojoExecutionException.class.getClassLoader(), + startLatch, + componentMap, + componentList, + exceptions, + getLog() + )); threads[i].start(); } - go.add(null); + startLatch.countDown(); // signal all threads to start + joinOrInterrupt(threads); + storeComponentProperties(exceptions); + getLog().info(MAVEN_CORE_IT_LOG + "Created output file " + outputFile); + } + + private void joinOrInterrupt(Thread[] threads) { for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { - getLog().warn("[MAVEN-CORE-IT-LOG] Interrupted while joining " + thread); + Thread.currentThread().interrupt(); + getLog().warn(MAVEN_CORE_IT_LOG + "Interrupted while joining " + thread); } } + } + private void storeComponentProperties( List exceptions) throws MojoExecutionException { + final Properties componentProperties= new Properties(); componentProperties.setProperty("components", Integer.toString(componentList.size())); componentProperties.setProperty("exceptions", Integer.toString(exceptions.size())); @@ -131,25 +119,51 @@ public void run() { outputFile = new File(basedir, outputFile.getPath()); } - getLog().info("[MAVEN-CORE-IT-LOG] Creating output file " + outputFile); - - OutputStream out = null; - try { + getLog().info(MAVEN_CORE_IT_LOG + "Creating output file " + outputFile); + try (OutputStream out = new FileOutputStream(outputFile)) { outputFile.getParentFile().mkdirs(); - out = new FileOutputStream(outputFile); componentProperties.store(out, "MAVEN-CORE-IT-LOG"); } catch (IOException e) { throw new MojoExecutionException("Output file could not be created: " + outputFile, e); - } finally { - if (out != null) { + } + } + + private record CheckThreadSafetyTask( + ClassLoader tccl, + CountDownLatch startLatch, + Map map, + List list, + List exceptions, + Log log + ) implements Runnable { + + @Override + public void run() { + log.info(MAVEN_CORE_IT_LOG + "Thread " + Thread.currentThread() + " uses " + tccl); + Thread.currentThread().setContextClassLoader(tccl); + try { + startLatch.await(); // wait for the start signal + checkThreadSafety(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn(MAVEN_CORE_IT_LOG + "Thread " + Thread.currentThread() + " was interrupted while waiting"); + } + } + + private void checkThreadSafety() { + for (int j = 0; j < 10 * 1000; j++) { try { - out.close(); - } catch (IOException e) { - // just ignore + for (Object o : map.values()) { + o.toString(); + } + for (Object aList : list) { + aList.toString(); + } + } catch (Exception e) { + log.warn(MAVEN_CORE_IT_LOG + "Thread " + Thread.currentThread() + " encountered concurrency issue", e); + exceptions.add(e); } } } - - getLog().info("[MAVEN-CORE-IT-LOG] Created output file " + outputFile); } -} +} \ No newline at end of file