Andreas Pabst created SUREFIRE-2135:
---------------------------------------

             Summary: On Unix-like OS and JDK 18+ Surefire kills the forked JVM 
if PpidChecker is active and argLine is set to -Dfile.encoding=UTF-16
                 Key: SUREFIRE-2135
                 URL: https://issues.apache.org/jira/browse/SUREFIRE-2135
             Project: Maven Surefire
          Issue Type: Bug
          Components: process forking
    Affects Versions: 3.0.0-M7, 3.0.0-M6, 3.0.0-M5, 3.0.0-M4
         Environment: Unix-like OS, JDK 18 or higher
            Reporter: Andreas Pabst


This breaks the continuous build on Java 18 and 19 because the integration test 
{{ConsoleOutputIT#properNewlinesAndEncodingWithDifferentEncoding}} fails.
h3. Steps to reproduce

A minimum project to reproduce the issue requires a unit test and the following 
surefire configuration:
{code:xml}
  <build>
     <plugins>
        <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
           <version>3.0.0-M7</version>
               <configuration>
               <enableProcessChecker>all</enableProcessChecker>
           </configuration>
        </plugin>
     </plugins>
  </build>
{code}
The following output will be produced upon executing {{{}mvn test{}}}:
{noformat}
[ERROR] Failed to execute goal 
org.apache.maven.plugins:maven-surefire-plugin:3.0.0-M7:test (default-test) on 
project myproject:
[ERROR] Please refer to 
/home/ogapa/code/maven-samples/utf16/target/surefire-reports for the individual 
test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, 
[date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] The forked VM terminated without properly saying goodbye. VM crash or 
System.exit called?
{noformat}
with the following stacktrace
{noformat}
org.apache.maven.surefire.booter.SurefireBooterForkException: Error occurred in 
starting fork, check output in log
        at 
org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:635)
        at 
org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:311)
        at 
org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:268)
        at 
org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1334)
        at 
org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:1167)
        at 
org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:931)
        at 
org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
        at 
org.apache.maven.lifecycle.internal.MojoExecutor.doExecute2(MojoExecutor.java:370)
        at 
org.apache.maven.lifecycle.internal.MojoExecutor.doExecute(MojoExecutor.java:351)
        at 
org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:215)
        at 
org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:171)
        at 
org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:163)
        at 
org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
        at 
org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
        at 
org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
        at 
org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:294)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
        at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:105)
        at org.apache.maven.cli.MavenCli.execute(MavenCli.java:960)
        at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:293)
        at org.apache.maven.cli.MavenCli.main(MavenCli.java:196)
        at 
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
        at java.base/java.lang.reflect.Method.invoke(Method.java:577)
        at 
org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:282)
        at 
org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:225)
        at 
org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:406)
        at 
org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:347)
{noformat}
All versions between 3.0.0-M4 and 3.0.0-M7 are affected. I could not reproduce 
it with versions before 3.0.0-M4.
h3. Root cause analysis

In the log of the failed test the following line gives an indication as to what 
the cause is:
{noformat}
[ERROR] Surefire is going to kill self fork JVM. Maven process died.
{noformat}
_Note: There is a race condition. If the test is fast enough (< 20 ms), the 
build may actually be successful despite the attempted killing of the self fork 
JVM. The exit code of the mvn process is still 1 though, not 0._

The above output is produced by the {{ForkedBooter}} class if 
{{PpidChecker#isProcessAlive}} returns false but the ping scheduler is not shut 
down yet.

Debugging into {{PpidChecker}} I found the reason why 
{{PpidChecker#isProcessAlive}} returns false:

The {{PpidChecker}} uses the command {{ps -o etime,pid <PID>}} to check if the 
elapsed time is still increasing, but if {{file.encoding}} is set to UTF-16, it 
fails to parse the {{ps}} output. Parsing fails because the 
{{java.util.Scanner}} it uses for that job is configured with 
{{Charset#defaultCharset}}, meaning it expects the {{InputStream}} of the 
{{ps}} output to produce UTF-16. It is however UTF-8 on basically all Unix-like 
systems.
h3. So, how did this code work before Java 18?

It didn't. The only reason this didn't surface before is that there was another 
bug that prevented the {{PpidChecker}} from even getting that far. In Java 
versions < 18 the {{ProcessBuilder#start}} call fails if {{file.encoding}} is 
set to UTF-16, with an exception message saying "java.io.IOException: Cannot 
run program "/bin/sh": error=2, No such file or directory" which leads to 
{{PpidChecker#isProcessAlive}} throwing an exception. That exception leads to 
the process check being aborted, but is then swallowed in the {{ForkedBooter}} 
class.

The {{ProcessBuilder}} bug was apparently fixed in Java 18 which led to this 
problem surfacing.
h3. Suggestion of a fix

In Java 17 the additional system property {{native.encoding}} was 
[introduced|https://bugs.openjdk.org/browse/JDK-8266075]. Since we are dealing 
with the output of a native OS command, this is the correct encoding to use for 
this particular use case - if it is present.

In case the system property is missing, we can use UTF-8 as a reasonable 
default since almost all modern Unix-like systems use UTF-8 as a system 
encoding by default.

So, when configuring the {{java.util.Scanner}} it uses to parse the output of 
the {{ps}} command, I suggest the {{PpidChecker}} should configure it based on 
the system property {{native.encoding}} and fall back to UTF-8 if the system 
property is absent, i.e. in Java versions before 17.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to