Skip to content

Commit 6fae009

Browse files
authored
[#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.
1 parent 6fcb15b commit 6fae009

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

.github/workflows/maven.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ jobs:
2424
name: Verify
2525
uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v3
2626
with:
27+
jdk-matrix: '[ "8", "17", "21" ]'
2728
ff-maven: "3.9.5" # Maven version for fail-fast-build
2829
maven-matrix: '[ "3.6.3", "3.8.8", "3.9.5" ]' # Maven versions matrix for verify builds

src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
public class ExecJavaMojo
5151
extends AbstractExecMojo
5252
{
53+
// Implementation note: Constants can be included in javadocs by {@value #MY_CONST}
54+
private static final String THREAD_STOP_UNAVAILABLE =
55+
"Thread.stop() is unavailable in this JRE version, cannot force-stop any threads";
56+
5357
@Component
5458
private RepositorySystem repositorySystem;
5559

@@ -171,6 +175,10 @@ public class ExecJavaMojo
171175
* this to <code>true</code> if you are invoking problematic code that you can't fix. An example is
172176
* {@link java.util.Timer} which doesn't respond to interruption. To have <code>Timer</code> fixed, vote for
173177
* <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6336543">this bug</a>.
178+
* <p>
179+
* <b>Note:</b> In JDK 20+, the long deprecated {@link Thread#stop()} (since JDK 1.2) has been removed and will
180+
* throw an {@link UnsupportedOperationException}. This will be handled gracefully, yielding a log warning
181+
* {@value #THREAD_STOP_UNAVAILABLE} once and not trying to stop any further threads during the same execution.
174182
*
175183
* @since 1.1-beta-1
176184
*/
@@ -522,6 +530,7 @@ private void terminateThreads( ThreadGroup threadGroup )
522530
thread.interrupt();
523531
}
524532
// Now join with a timeout and call stop() (assuming flags are set right)
533+
boolean threadStopIsAvailable = true;
525534
for ( Thread thread : threads )
526535
{
527536
if ( !thread.isAlive() )
@@ -543,10 +552,18 @@ private void terminateThreads( ThreadGroup threadGroup )
543552
continue;
544553
}
545554
uncooperativeThreads.add( thread ); // ensure we don't process again
546-
if ( stopUnresponsiveDaemonThreads )
555+
if ( stopUnresponsiveDaemonThreads && threadStopIsAvailable )
547556
{
548557
getLog().warn( "thread " + thread + " will be Thread.stop()'ed" );
549-
thread.stop();
558+
try
559+
{
560+
thread.stop();
561+
}
562+
catch ( UnsupportedOperationException unsupportedOperationException )
563+
{
564+
threadStopIsAvailable = false;
565+
getLog().warn( THREAD_STOP_UNAVAILABLE );
566+
}
550567
}
551568
else
552569
{

src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public class ExecJavaMojoTest
5151

5252
private static final File LOCAL_REPO = new File( "src/test/repository" );
5353

54+
private static final int JAVA_VERSION_MAJOR =
55+
Integer.parseInt( System.getProperty( "java.version" ).replaceFirst( "[.].*", "" ) );
56+
5457
/*
5558
* This one won't work yet public void xxtestSimpleRunPropertiesAndArguments() throws MojoExecutionException,
5659
* Exception { File pom = new File( getBasedir(), "src/test/projects/project2/pom.xml" ); String output = execute(
@@ -198,19 +201,36 @@ public void testWaitNonInterruptibleDaemonThreads()
198201
}
199202

200203
/**
201-
* See <a href="http://jira.codehaus.org/browse/MEXEC-15">MEXEC-15</a>. FIXME: this sometimes fail with
202-
* unit.framework.ComparisonFailure: expected:&lt;...&gt; but was:&lt;...3(f)&gt;
204+
* See <a href="http://jira.codehaus.org/browse/MEXEC-15">MEXEC-15</a>,
205+
* <a href="https://github.com/mojohaus/exec-maven-plugin/issues/391">GitHub-391</a>.
206+
* <p>
207+
* FIXME: This sometimes fails with {@code unit.framework.ComparisonFailure: expected:<...>; but was:<...3(f)>}.
203208
*
204209
* @throws Exception if any exception occurs
205210
*/
206211
public void testUncooperativeThread()
207212
throws Exception
208213
{
214+
// FIXME:
215+
// This will fail the test, because Assume is a JUnit 4 thing, but we are running in JUnit 3 mode, because
216+
// AbstractMojoTestCase extends PlexusTestCase extends TestCase. The latter is a JUnit 3 compatibility class.
217+
// If we would simply use JUnit 4 annotations, conditional ignores via Assume would just work correctly in
218+
// Surefire and IDEs. We could than have two dedicated test cases, one for each JDK 20+ and one for older
219+
// versions. In JUnit 5, we could even conveniently use @EnabledOnJre.
220+
// Assume.assumeTrue( JAVA_VERSION_MAJOR < 20 );
221+
209222
File pom = new File( getBasedir(), "src/test/projects/project10/pom.xml" );
210223
String output = execute( pom, "java" );
211224
// note: execute() will wait a little bit before returning the output,
212225
// thereby allowing the stop()'ed thread to output the final "(f)".
213-
assertEquals( MainUncooperative.SUCCESS, output.trim() );
226+
if ( JAVA_VERSION_MAJOR < 20 )
227+
{
228+
assertEquals( MainUncooperative.SUCCESS, output.trim() );
229+
}
230+
else
231+
{
232+
assertEquals( MainUncooperative.INTERRUPTED_BUT_NOT_STOPPED, output.trim() );
233+
}
214234
}
215235

216236
/**

src/test/java/org/codehaus/mojo/exec/MainUncooperative.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public class MainUncooperative
99
{
1010
public static final String SUCCESS = "1(interrupted)(f)2(f)";
1111

12+
// In JDK 20+, Thread::stop has been removed and just throws an UnsupportedOperationException
13+
public static final String INTERRUPTED_BUT_NOT_STOPPED = "1(interrupted)(f)2";
14+
1215
public static void main( String... args )
1316
throws InterruptedException
1417
{

0 commit comments

Comments
 (0)