Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cmd.exe is hanging unexpectedly depending on where the file I use is located

This has got to be one of the strangest things I have ever observed. 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 with javac StrangeError.java, copied it to my server running Windows Server 2012 R2, and ran it with java StrangeError.

Here's where things start to get weird. The program hangs, waiting for the process it spawned to finish. This is not the expected behavior, since the vcvarsall.bat script should complete immediately as well as set.

So I started playing around and discovered the following:

  • Removing set causes vcvarsall.bat to terminate
  • Removing vcvarsall.bat causes set to terminate
  • Replacing && with || causes everything to terminate correctly
  • Copying vcvarsall.bat to a location on the desktop and changing the path causes everything to terminate correctly
  • A nearly equivalent program works fine in Go using the same commands
  • I get this output if I run everything in WinDbg and interrupt the process after it hangs
  • This does not appear to be reproducible with vcvarsall.bat from MSVC2013 but is also reproducible with MSVC2015 on Windows 10

What on earth is wrong with the original program? If I copy and paste the entire command (cmd /c "C:\...) into Start->Run, it immediately launches cmd and terminates, as expected.

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

like image 918
Nathan Osman Avatar asked Apr 11 '17 08:04

Nathan Osman


1 Answers

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

It's a bug in your code. :-)

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

Since your code simply calls .waitFor without making any provision to drain the pipe, it will deadlock as soon as the pipe's buffer overflows. I believe the default buffer size is 4,096 bytes. On my machine, the output of the command you're running is 5,192 bytes, but this will vary depending on the original contents of the environment block. (From the sounds of it, the output length in your environment is borderline, only just above the limit, so that even small changes like changing the version of VS make a difference.)

One of the many possible solutions, depending on what you're actually trying to do, is to tell Java not to pipe the child's output:

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());
        }
    }
}
like image 132
Harry Johnston Avatar answered Sep 30 '22 07:09

Harry Johnston