[GRADLE-3047] Process execution hangs on Windows if children processes are still alive Created: 17/Mar/14  Updated: 10/Feb/17  Resolved: 10/Feb/17

Status: Resolved
Project: Gradle
Affects Version/s: None
Fix Version/s: None

Type: Bug
Reporter: Gradle Forums Assignee: Unassigned
Resolution: Won't Fix Votes: 1


 Description   

I have a problem where a JavaExec task runs, creates the new JVM, that JVM launches child processes, the JVM then terminates, returns an exit value, and hangs forever.

Here is a very simple snippet to reproduce the problem:

task runNotepad(type: Exec) {
description 'Demonstrate the problem independent of java'
commandLine 'cmd', '/c', 'cmd /c start notepad.exe'
}

Gradle gets stuck while notepad is open. Even though the parent process of notepad.exe is terminated.

A working demo of the problem, including a gradle file to reproduce the issue, as well as an executable unit test to see exactly where in gradle it gets stuckare posted here
[1]https://gist.github.com/skazzyy/9536507

I've reproduced this with Gradle versions 1.8,1.9,1.10, and 1.11.
I've verified this is NOT a problem on Linux (tried both RedHat and CentOS)

I posted this issue, before learning more information, on stackoverflow here:
[2]http://stackoverflow.com/questions/21...

The long story:
I'm using gradle to drive automated system tests... some of those system tests are UI tests, and the poorly behaving ones sometimes leave processes hanging out there. This causes the gradle task to freeze, forever. I'm even okay with the gradle task FAILING, just not hanging indefinitely. I'm stuck running on Windows, because that's where the UI tests need to run.
----------------------------------------------------------------------------------------
[1] https://gist.github.com/skazzyy/9536507
[2] http://stackoverflow.com/questions/21918965/why-does-a-java-process-hang-from-gradle-when-sub-process-is-still-open



 Comments   
Comment by Gradle Forums [ 17/Mar/14 ]

We experienced the same after a Java or Windows7 fixpack update. I don't remember which.

The apparent behavior is that a DOS command executed from Java does not properly terminate to Java, if it has started another process.

My solution to the problem is the (Groovy) class below. I hope you can use it.

Cheers,
Jesper

package dk.jyskebank.tools.system.impl

import java.util.regex.Matcher
import java.io.File;
import java.util.regex.Pattern

import dk.jyskebank.tools.system.ExecResult;

/**

  • Runs script on Windows 7 where CMD does not appear to return until all child processes have completed.
    *
  • For WAS6 startServer.bat this means that the script will launch the server, and then hang.
  • This code works around this by looking for a special marker in the output, which is used to
  • communicate exit value - and the order to kill the process.
    */
    class Win7ScriptRunner {
    private static final int PROCESS_OK = 0
    private static final String TERMINATION_MARKER = "#### WIN7 PROCESS EXIT %ERRORLEVEL% ####"
    private static final Pattern TERMINATION_MARKER_PATTERN = Pattern.compile("(?s)#### WIN7 PROCESS EXIT (-?\d+) ####.*")
    private boolean processSucceeded = true
    private int exitValue = 0
    private boolean terminatedByMarker = false
    private boolean echoOutput = false

/**

  • Executes DOS script with magic marker exit handling.
  • Note that stdout and stderr output is merged.
    *
  • @param processCurrentDirectory directory to run process in.
  • @param args command arguments.
  • @param messageOnError message to print if script failed.
    *
  • @return execution result.
    */
    public static ExecResult executeShellCommandAbortOnError(File processCurrentDirectory, List<String> args, String messageOnError) {
    return new Win7ScriptRunner().executeScriptAbortOnError(processCurrentDirectory, args, messageOnError)
    }

/**

  • Executes DOS script with magic marker exit handling.
    *
  • @param processCurrentDirectory directory to run process in.
  • @param args command arguments.
  • @param messageOnError message to print if script failed.
    *
  • @return integer result from script
    */
    public static int executeShellCommandIntResult(File processCurrentDirectory, List<String> args, String messageOnError) {
    return new Win7ScriptRunner().executeScriptIntResult(processCurrentDirectory, args, messageOnError)
    }

private int executeScriptIntResult(File processCurrentDirectory, List<String> args, String messageOnError) {
echoOutput = true
executeScriptAbortOnError(processCurrentDirectory, args, messageOnError)
return exitValue
}

public ExecResult executeScriptAbortOnError(File processCurrentDirectory, List<String> args, String messageOnError) {
List<String> cmdWrapped = getWindowsShellExecutionPrefix() + args + [ "&", "echo ${TERMINATION_MARKER}"]
File wrapperScript = makeWrapperScript(args)
Process proc = new ProcessBuilder(wrapperScript.getAbsolutePath()).directory(processCurrentDirectory).redirectErrorStream(true).start()
InputStreamReader isr = new InputStreamReader(new BufferedInputStream(proc.getInputStream()))
String output = consumeProcessOutputLookingForTermintationMarker(isr)
if (terminatedByMarker) {
proc.destroy()
} else {
recordExitValue(proc.exitValue())
}
wrapperScript.delete()
return new ExecResult(processSucceeded, output, "", cmdWrapped)
}

private File makeWrapperScript(List<String> args) {
File wrapper = File.createTempFile("win7.wrapper.", ".bat")
wrapper << """@echo off
@call ${args.join(' ')}
@echo ${TERMINATION_MARKER}
"""
return wrapper
}

private String consumeProcessOutputLookingForTermintationMarker(Reader reader) {
StringBuilder sb = new StringBuilder()
char[] buffer = new char[1024]
String builtString = ""
while (true) {
int readCount = reader.read(buffer, 0, buffer.length)
if (readCount == -1){
return builtString
}
sb.append(buffer, 0, readCount)
builtString = sb.toString()

String tailString = getTailLinePreservingNewline(builtString)
Matcher m = TERMINATION_MARKER_PATTERN.matcher(tailString)
if (m.matches()) {
recordExitValue(Integer.parseInt(m[0][1]))
terminatedByMarker = true
return builtString.replace(tailString, "")
}
if (echoOutput) {
System.out.print(new String(buffer, 0, readCount))
}
}

return sb.toString()
}

private void recordExitValue(int exitValue) {
this.exitValue = exitValue
processSucceeded = (exitValue == PROCESS_OK)
}

private String getTailLinePreservingNewline(String str) {
String tailString = str.split("\n").last()
if (str.endsWith("\n"))

{ tailString += "\n" } return tailString }

private getWindowsShellExecutionPrefix() {
return [ System.getenv('ComSpec'), "/c" ]
}

public static final void main(String[] args) {
int res = executeShellCommandIntResult(new File(System.getProperty("user.dir")), args as List, "Failed to run script: ${args}")
System.exit(res)
}
}

package dk.jyskebank.tools.system

/**

  • Represents execution result.
  • The boolean value of the object itself reflects the execution success/failure.
    */
    class ExecResult {
    final boolean executionResult
    final String stdOutput
    final String errOutput
    final List<String> command

ExecResult(boolean executionResult, String stdOutput, String errOutput, List<String> command) {
this.executionResult = executionResult
this.stdOutput = stdOutput
this.errOutput = errOutput
this.command = command
}

boolean asBoolean() {
return executionResult
}
}

Comment by Gradle Forums [ 17/Mar/14 ]

Is it possible to get a JIRA created with regards to this issue such that I can submit a pull request to gradle?

Comment by Benjamin Muschko [ 15/Nov/16 ]

As announced on the Gradle blog we are planning to completely migrate issues from JIRA to GitHub.

We intend to prioritize issues that are actionable and impactful while working more closely with the community. Many of our JIRA issues are inactionable or irrelevant. We would like to request your help to ensure we can appropriately prioritize JIRA issues you’ve contributed to.

Please confirm that you still advocate for your JIRA issue before December 10th, 2016 by:

  • Checking that your issues contain requisite context, impact, behaviors, and examples as described in our published guidelines.
  • Leave a comment on the JIRA issue or open a new GitHub issue confirming that the above is complete.

We look forward to collaborating with you more closely on GitHub. Thank you for your contribution to Gradle!

Comment by Benjamin Muschko [ 10/Feb/17 ]

Thanks again for reporting this issue. We haven't heard back from you after our inquiry from November 15th. We are closing this issue now. Please create an issue on GitHub if you still feel passionate about getting it resolved.

Generated at Wed Jun 30 12:38:27 CDT 2021 using Jira 8.4.2#804003-sha1:d21414fc212e3af190e92c2d2ac41299b89402cf.