22

I can run this command from the command line without any problem (the validation script executes):

c:/Python27/python ../feedvalidator/feedvalidator/src/demo.py https://das.dynalias.org:8080/das_core/das/2.16.840.1.113883.4.349/1012581676V377802/otherAdminData/careCoordinators 

and from java if I leave off the URL parameter and just do:

String[] args1 = {"c:/Python27/python", "../feedvalidator/feedvalidator/src/demo.py" };
Runtime r = Runtime.getRuntime();
Process p = r.exec(args1);

it works fine. If I use certain URLs for a parameter such as:

String[] args1 = {"c:/Python27/python", "../feedvalidator/feedvalidator/src/demo.py" , "http://www.intertwingly.net/blog/index.atom"};
// or 
String[] args1 = {"c:/Python27/python", "../feedvalidator/feedvalidator/src/demo.py" , "http://www.cnn.com"};

it also works fine.

But if I use this particular URL https://das.dynalias.org:8080/das_core/das/2.16.840.1.113883.4.349/1012581676V377802/otherAdminData/careCoordinators, then the script just hangs (java waits for the process to finish). I’m not sure why it works from the command line for that URL but not from a java program. I tried adding quotes to surround the URL parameter but that didn’t work either. I don’t see any character in the URL that I think need to be escaped.

Full Code:

String urlToValidate = "https://das.dynalias.org:8080/das_core/das/2.16.840.1.113883.4.349/1012581676V377802/otherAdminData/careCoordinators";

String[] args1 = {"c:/Python27/python", "C:/Documents and Settings/vhaiswcaldej/DAS_Workspace/feedvalidator/feedvalidator/src/demo.py", urlToValidate };
System.out.println(args1[0] + " " + args1[1] + " " + args1[2]);

Runtime r = Runtime.getRuntime();
Process p = r.exec(args1);
BufferedReader br = new BufferedReader(new InputStreamReader(
p.getInputStream()));
int returnCode = p.waitFor();
 System.out.println("Python Script or OS Return Code: " + Integer.toString(returnCode));
if (returnCode >= 2) {
    .out.println("OS Error: Unable to Find File or other OS error.");
    }

String line = "";
while (br.ready()) {
     String str = br.readLine();
     System.out.println(str);
     if (str.startsWith("line")) {
     //TODO: Report this error back to test tool.
     //System.out.println("Error!");
     }
     }
user994165
  • 9,146
  • 30
  • 98
  • 165
  • Maybe the current directory does matter? You could try switching to another directory in command line. – Yogu Dec 21 '11 at 20:19
  • you should examine the actual command line that is being executed from Java, on Linux - `ps f`, on Win - sysinternals ProcessExplorer – Oleg Mikheev Dec 21 '11 at 20:26
  • 1
    this might be some ssl issue. can you try to call a http endpoint to verify it? – phoet Dec 21 '11 at 20:57
  • are you calling p.waitFor() method of Process after you create the process? exec should be non blocking, so without the call to waitFor, you may be locking up elsewhere in your code do to the fact that the process created has not finished executing –  Dec 21 '11 at 21:02
  • 2
    Try put it double quotes like `String[] args1 = {"c:/Python27/python", "../feedvalidator/feedvalidator/src/demo.py" , "\"https://das.dynalias.org:8080/das_core/das/2.16.840.1.113883.4.349/1012581676V377802/otherAdminData/careCoordinators\""};` – Stan Kurilin Dec 21 '11 at 21:05
  • I added all the code. I do have a p.waitFor() immediately after opening the BufferedReader. It's on waitFor() that the program hangs. I tired using sysinternals but even after enabling command line in the view, I couldn't see the command line arguments. This was true even if i bypassed java and ran the python script directly in the command line. I tried the same XML file in a different host without SSL. The (escaped) double quotes in the String[] causes it not to work even with a good URL so I don't think that's the problem. – user994165 Dec 21 '11 at 22:42
  • Possible duplicate of [How to invoke a Linux shell command from Java](https://stackoverflow.com/q/1410741/608639) – jww Feb 01 '19 at 14:55

3 Answers3

19

You need to drain the output and error streams of the process, or else it will block when the executed program produces output.

From the Process documentation:

Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

prunge
  • 22,460
  • 3
  • 73
  • 80
  • I didn't see a drain method for the process. What code would I actually run? I've updated the question with the full code. – user994165 Dec 21 '11 at 22:45
  • 1
    to drain the output and error streams just do: InputStream in = ...; in.skip(in.available()); InputStream err = ...; err.skip(err.available()); –  Dec 21 '11 at 22:50
  • 2
    @user994165 there is sample code for draining streams [here](http://stackoverflow.com/a/7962434/523391) – prunge Dec 22 '11 at 01:12
  • @SE does this code for draining the stream only need to be called once? or would you need to put it in a loop to keep the InputStream empty? – MoralCode Jun 29 '22 at 19:46
  • @MoralCode the process generating the output may generate more output after you have drained it once, and then you would need to drain it again. it would be safer to drain it this way in a loop and check if the process is still open. –  Jun 30 '22 at 04:58
  • @prunge I don't see it. It would've saved me tons of time if you included a code sample in your answer. – Boris Verkhovskiy Feb 03 '23 at 18:03
8

People usually got caught by exec routine hangs in Java. I was cought by that once too. The problem is that the process you are trying to execute may (depending on lot of things) either first write to stdOut or stdErr. If you handle them in wrong order exec will hang. To handle this properly always you must create 2 threads to read stdErr and stdOut simulteneously. Sth like:

Process proc = Runtime.getRuntime().exec( cmd );

// handle process' stdout stream
Thread out = new StreamHandlerThread( stdOut, proc.getInputStream() );
out.start();

// handle process' stderr stream
Thread err = new StreamHandlerThread( stdErr, proc.getErrorStream() );
err.start();

exitVal = proc.waitFor(); // InterruptedException

...

out.join();
err.join();
Artur
  • 7,038
  • 2
  • 25
  • 39
  • 2
    using redirectErrorStream is a much better idea: http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ProcessBuilder.html#redirectErrorStream%28boolean%29 – Oleg Mikheev Apr 03 '13 at 05:23
7

Read (and close) p.getInputStream() and p.getErrorStream().

For example:

// com.google.common.io.CharStreams
CharStreams.toString(new InputStreamReader(p.getInputStream()));
CharStreams.toString(new InputStreamReader(p.getErrorStream()));
Ali Shakiba
  • 20,549
  • 18
  • 61
  • 88
  • What if the output is bigger than Java's heap memory? Though I guess in most cases you know it won't be. A problem might arise if you write a method like this, and then use it on something producing large output after you forget this limitation. – Evgeni Sergeev Dec 11 '14 at 08:41
  • @EvgeniSergeev That's right, read and close them in another way, I just mentioned them as example. – Ali Shakiba Dec 11 '14 at 15:24