From 6fae009079da7825154ce0e1a51c10a9abf16e90 Mon Sep 17 00:00:00 2001 From: Alexander Kriegisch Date: Sat, 11 Nov 2023 10:19:59 +0100 Subject: [PATCH] [#391] Cope with Thread::stop being unavailable in JDK 20+ (#393) * [#391] Cope with Thread::stop being unavailable in JDK 20+ In JDK 20+, the long deprecated Thread.stop() (since JDK 1.2) has been removed and will throw an UnsupportedOperationException. This will be handled gracefully when using option 'stopUnresponsiveDaemonThreads', yielding a log warning "Thread.stop() is unavailable in this JRE version, cannot force-stop any threads" once and not trying to stop any further threads during the same execution. Tests and documentation have been adjusted accordingly. Closes #391. * [#391] Add JDK 21 to CI build Because #391 introduces special handling of the fact that in JDK 20+ there is no more Thread::stop, we should also run the tests on JDK 21. --- .github/workflows/maven.yml | 1 + .../org/codehaus/mojo/exec/ExecJavaMojo.java | 21 +++++++++++++-- .../codehaus/mojo/exec/ExecJavaMojoTest.java | 26 ++++++++++++++++--- .../codehaus/mojo/exec/MainUncooperative.java | 3 +++ 4 files changed, 46 insertions(+), 5 deletions(-) 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 {