Skip to content

Commit

Permalink
[#391] Cope with Thread::stop being unavailable in JDK 20+ (#393)
Browse files Browse the repository at this point in the history
* [#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.
  • Loading branch information
kriegaex committed Nov 11, 2023
1 parent 6fcb15b commit 6fae009
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 19 additions & 2 deletions src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -171,6 +175,10 @@ public class ExecJavaMojo
* this to <code>true</code> 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 <code>Timer</code> fixed, vote for
* <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6336543">this bug</a>.
* <p>
* <b>Note:</b> 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
*/
Expand Down Expand Up @@ -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() )
Expand All @@ -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
{
Expand Down
26 changes: 23 additions & 3 deletions src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -198,19 +201,36 @@ public void testWaitNonInterruptibleDaemonThreads()
}

/**
* See <a href="http://jira.codehaus.org/browse/MEXEC-15">MEXEC-15</a>. FIXME: this sometimes fail with
* unit.framework.ComparisonFailure: expected:&lt;...&gt; but was:&lt;...3(f)&gt;
* See <a href="http://jira.codehaus.org/browse/MEXEC-15">MEXEC-15</a>,
* <a href="https://github.com/mojohaus/exec-maven-plugin/issues/391">GitHub-391</a>.
* <p>
* 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() );
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/org/codehaus/mojo/exec/MainUncooperative.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down

0 comments on commit 6fae009

Please sign in to comment.