diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 2bf38108..3e953733 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -24,5 +24,6 @@ jobs:
name: Verify
uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v3
with:
+ jdk-matrix: '[ "8", "17", "21" ]'
ff-maven: "3.9.5" # Maven version for fail-fast-build
maven-matrix: '[ "3.6.3", "3.8.8", "3.9.5" ]' # Maven versions matrix for verify builds
diff --git a/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java b/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java
index 9f4245c1..1f603fec 100644
--- a/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java
+++ b/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java
@@ -50,6 +50,10 @@
public class ExecJavaMojo
extends AbstractExecMojo
{
+ // Implementation note: Constants can be included in javadocs by {@value #MY_CONST}
+ private static final String THREAD_STOP_UNAVAILABLE =
+ "Thread.stop() is unavailable in this JRE version, cannot force-stop any threads";
+
@Component
private RepositorySystem repositorySystem;
@@ -171,6 +175,10 @@ public class ExecJavaMojo
* this to true
if you are invoking problematic code that you can't fix. An example is
* {@link java.util.Timer} which doesn't respond to interruption. To have Timer
fixed, vote for
* this bug.
+ *
+ * Note: In JDK 20+, the long deprecated {@link Thread#stop()} (since JDK 1.2) has been removed and will + * throw an {@link UnsupportedOperationException}. This will be handled gracefully, yielding a log warning + * {@value #THREAD_STOP_UNAVAILABLE} once and not trying to stop any further threads during the same execution. * * @since 1.1-beta-1 */ @@ -522,6 +530,7 @@ private void terminateThreads( ThreadGroup threadGroup ) thread.interrupt(); } // Now join with a timeout and call stop() (assuming flags are set right) + boolean threadStopIsAvailable = true; for ( Thread thread : threads ) { if ( !thread.isAlive() ) @@ -543,10 +552,18 @@ private void terminateThreads( ThreadGroup threadGroup ) continue; } uncooperativeThreads.add( thread ); // ensure we don't process again - if ( stopUnresponsiveDaemonThreads ) + if ( stopUnresponsiveDaemonThreads && threadStopIsAvailable ) { getLog().warn( "thread " + thread + " will be Thread.stop()'ed" ); - thread.stop(); + try + { + thread.stop(); + } + catch ( UnsupportedOperationException unsupportedOperationException ) + { + threadStopIsAvailable = false; + getLog().warn( THREAD_STOP_UNAVAILABLE ); + } } else { diff --git a/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java b/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java index f228b1e6..10037eee 100644 --- a/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java +++ b/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java @@ -51,6 +51,9 @@ public class ExecJavaMojoTest private static final File LOCAL_REPO = new File( "src/test/repository" ); + private static final int JAVA_VERSION_MAJOR = + Integer.parseInt( System.getProperty( "java.version" ).replaceFirst( "[.].*", "" ) ); + /* * This one won't work yet public void xxtestSimpleRunPropertiesAndArguments() throws MojoExecutionException, * Exception { File pom = new File( getBasedir(), "src/test/projects/project2/pom.xml" ); String output = execute( @@ -198,19 +201,36 @@ public void testWaitNonInterruptibleDaemonThreads() } /** - * See MEXEC-15. FIXME: this sometimes fail with - * unit.framework.ComparisonFailure: expected:<...> but was:<...3(f)> + * See MEXEC-15, + * GitHub-391. + *
+ * FIXME: This sometimes fails with {@code unit.framework.ComparisonFailure: expected:<...>; but was:<...3(f)>}. * * @throws Exception if any exception occurs */ public void testUncooperativeThread() throws Exception { + // FIXME: + // This will fail the test, because Assume is a JUnit 4 thing, but we are running in JUnit 3 mode, because + // AbstractMojoTestCase extends PlexusTestCase extends TestCase. The latter is a JUnit 3 compatibility class. + // If we would simply use JUnit 4 annotations, conditional ignores via Assume would just work correctly in + // Surefire and IDEs. We could than have two dedicated test cases, one for each JDK 20+ and one for older + // versions. In JUnit 5, we could even conveniently use @EnabledOnJre. + // Assume.assumeTrue( JAVA_VERSION_MAJOR < 20 ); + File pom = new File( getBasedir(), "src/test/projects/project10/pom.xml" ); String output = execute( pom, "java" ); // note: execute() will wait a little bit before returning the output, // thereby allowing the stop()'ed thread to output the final "(f)". - assertEquals( MainUncooperative.SUCCESS, output.trim() ); + if ( JAVA_VERSION_MAJOR < 20 ) + { + assertEquals( MainUncooperative.SUCCESS, output.trim() ); + } + else + { + assertEquals( MainUncooperative.INTERRUPTED_BUT_NOT_STOPPED, output.trim() ); + } } /** diff --git a/src/test/java/org/codehaus/mojo/exec/MainUncooperative.java b/src/test/java/org/codehaus/mojo/exec/MainUncooperative.java index ce41b3c6..956328d0 100644 --- a/src/test/java/org/codehaus/mojo/exec/MainUncooperative.java +++ b/src/test/java/org/codehaus/mojo/exec/MainUncooperative.java @@ -9,6 +9,9 @@ public class MainUncooperative { public static final String SUCCESS = "1(interrupted)(f)2(f)"; + // In JDK 20+, Thread::stop has been removed and just throws an UnsupportedOperationException + public static final String INTERRUPTED_BUT_NOT_STOPPED = "1(interrupted)(f)2"; + public static void main( String... args ) throws InterruptedException {