Cmd.exe hangs unexpectedly depending on where the file I'm using is located

This must be one of the strangest things I've ever seen. Consider the following Java program:

import java.io.IOException; public class StrangeError { public static void main(String[] args) { try { Process process = new ProcessBuilder( "cmd", "/c", "\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set" ).start(); process.waitFor(); } catch (IOException|InterruptedException e) { System.out.println(e.getMessage()); } } } 

I compiled it using javac StrangeError.java , copied it to my server running Windows Server 2012 R2, and ran it using java StrangeError .

Here, where things start to get weird. The program freezes, waiting for the completion of the process that it spawned. This is not the expected behavior, since the vcvarsall.bat script should end immediately, as well as set .

So, I started playing and found the following:

  • Removing set terminates vcvarsall.bat
  • Removing vcvarsall.bat terminates set
  • Replacing && with || makes everything finish correctly
  • Copying vcvarsall.bat to a location on the desktop and changing the path leads to the correct shutdown.
  • A nearly equivalent program works fine in Go using the same commands
  • I get this output if I run everything in WinDbg and aborted the process after freezing
  • It does not seem reproducible with vcvarsall.bat from MSVC2013, but it also plays with MSVC2015 on Windows 10

What happened to the original program? If I copy and paste the entire command ( cmd /c "C:\... ) into Start-> Run, it immediately starts cmd and exits as expected.

Is this a bug with Java? Is this a bug with Windows?

+5
source share
2 answers

Is this a bug with Java? Is this a bug with Windows?

This is a mistake in your code. :-)

By default, a child process created using the ProcessBuilder object has an output redirected to a pipe whose parent end can be obtained using Process.getInputStream() and which will not be automatically merged if your code does not use it.

Since your code simply calls .waitFor , without creating any conditions for draining the pipe, it will be blocked as soon as the buffer is full. I believe the default buffer size is 4096 bytes. On my machine, the output of the command you are using is 5,192 bytes, but this will depend on the original contents of the environment block. (Of its sounds, the output length in your environment is limited, only slightly above the limit, so even small changes, such as changing the VS version, matter.)

One of many possible solutions, depending on what you are actually trying to do, is to tell Java not to output the output file:

 import java.io.IOException; public class StrangeError { public static void main(String[] args) { try { ProcessBuilder processb = new ProcessBuilder( "cmd", "/c", "\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set" ); processb.redirectOutput(ProcessBuilder.Redirect.INHERIT); Process process = processb.start(); process.waitFor(); } catch (IOException|InterruptedException e) { System.out.println(e.getMessage()); } } } 
+6
source

It is not possible to read a standard input and output error inside a single ProcessBuilder.

So you need to create two ProcessBuilder

 Process process1 = new ProcessBuilder( "cmd", "/c", "\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\", "amd64"); Process process2 = new ProcessBuilder( "cmd", "/c", "set"); process1.start(); if (process1.waitFor() == 0) { process2.start(); if (process2.waitFor() == 0) { // Successfull execution } } 

And one thing: I do not think that it is good practice to run shell / batch with Java (or another language). Perhaps you should use a script (shell, batch, python, perl ...) to control standard input / output streams.

0
source

Source: https://habr.com/ru/post/1266574/


All Articles