2

Here is my sample code, I want to handle the command from standard input while running a new sub process. However, the exec method never returns if I read the system.in. The command in the exec() is very simple and has nothing to do with the stdin.

I'm wondering about is there any way to solve this? How can I start a new sub process while start another thread reading stdin?

public static void main(String[] args){
    new Thread(new Runnable(){
        public void run(){
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            String command = null;
            try{
                while((command = reader.readLine()) != null){
                    System.out.println("Command Received:" + command);
                }
            }catch(Exception ex){
                ex.printStackTrace();
                //failed to listening command
            }

        }
    }).start();
    Process process = null;
    try {
        process = Runtime.getRuntime().exec("java -cp C:/agenttest Test");
        System.out.println("never returns");
        process.waitFor();
    } catch (IOException e) {
        throw new RuntimeException( e );
    } catch (InterruptedException e) {
        throw new RuntimeException( e );
    }
}

The Test class is very simple, here is the Test.java

public static void main(String[] args){
    System.out.println("Standard out");
    System.out.println("Standard out");
    System.err.println("Standard err");
    System.out.println("Standard out");
    try{
        Thread.sleep(10000);
    }catch(InterruptedException ex){}
}
StarPinkER
  • 14,081
  • 7
  • 55
  • 81
  • What eaactly is `java -cp C:/agenttest Test` supposed to do, what does that have anything to do with you reading from System.in? – Jon Lin Jul 31 '12 at 02:57
  • 2
    1) Use a `ProcessBuilder` to contruct the `Process`, which then makes it a little easier to.. 2) Implement all the recommendations of the article linked from the [`runtime.exec` info. page](http://stackoverflow.com/tags/runtime.exec/info). 3) The code shown is making the classic error of not consuming the error stream, as well as various other errors. – Andrew Thompson Jul 31 '12 at 02:58
  • Actually it has nothing to do with system.in. The program is just print some output to standard out. I think change it to any other program will not make any changes to the result. @JonLin – StarPinkER Jul 31 '12 at 03:00
  • Yeah, I've already wrapped the runtime.exec, and thanks for the ProcessBuilder advice. I just want illustrate this weird things. @AndrewThompson – StarPinkER Jul 31 '12 at 03:04
  • Your code works for me, and I suspect the problem is in your calling of the Test class. – Hovercraft Full Of Eels Jul 31 '12 at 11:17
  • I Add the code of the Test class, do you think it is related with the standard out? @HovercraftFullOfEels – StarPinkER Jul 31 '12 at 12:02
  • I think that it's possibly because you're not handling the Process's InputStream or ErrorStream as per the link that @Andrew has given you. – Hovercraft Full Of Eels Jul 31 '12 at 16:28

2 Answers2

2

The problem could be that you're not handling the error stream and input stream and are overrunning the platform's buffers. Try handling that output as per the famous article, When Runtime.exec() won't.

For example:

import java.io.*;

public class TestMain {
   private static final String JAVA_CMD = "java";
   private static final String CP = "-cp";

   // *** your CLASS_PATH and PROG Strings will of course be different ***
   private static final String CLASS_PATH = "C:/Users/hovercraft/Documents/workspace/Yr 2012A/bin";
   private static final String PROG = "yr12.m07.b.Test2";

   private static final String[] CMD_ARRAY = { JAVA_CMD, CP, CLASS_PATH, PROG };

   public static void main(String[] args) {
      new Thread(new Runnable() {
         public void run() {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                  System.in));
            String command = null;
            try {
               while ((command = reader.readLine()) != null) {
                  System.out.println("Command Received:" + command);
               }
            } catch (Exception ex) {
               ex.printStackTrace();
               // failed to listening command
            }

         }
      }).start();
      Process process = null;
      try {
         ProcessBuilder processBuilder = new ProcessBuilder(CMD_ARRAY);
         process = processBuilder.start();
         InputStream inputStream = process.getInputStream();
         setUpStreamGobbler(inputStream, System.out);

         InputStream errorStream = process.getErrorStream();
         setUpStreamGobbler(errorStream, System.err);

         System.out.println("never returns");
         process.waitFor();
      } catch (IOException e) {
         throw new RuntimeException(e);
      } catch (InterruptedException e) {
         throw new RuntimeException(e);
      }
   }

   public static void setUpStreamGobbler(final InputStream is, final PrintStream ps) {
      final InputStreamReader streamReader = new InputStreamReader(is);
      new Thread(new Runnable() {
         public void run() {
            BufferedReader br = new BufferedReader(streamReader);
            String line = null;
            try {
               while ((line = br.readLine()) != null) {
                  ps.println("process stream: " + line);
               }
            } catch (IOException e) {
               e.printStackTrace();
            } finally {
               try {
                  br.close();
               } catch (IOException e) {
                  e.printStackTrace();
               }
            }
         }
      }).start();
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thanks for your answer, but your code still hangs on my computer. I run it in eclipse. Windows 32bit platform. – StarPinkER Aug 01 '12 at 02:04
  • In particular, the sub process will be created and run after I send some command to the standard input. But this is not what I want. – StarPinkER Aug 01 '12 at 02:12
  • @JermaineXu: since the reading in from the console is run in a different thread, it will run concurrently with the sub-process. – Hovercraft Full Of Eels Aug 01 '12 at 02:15
  • But why the exec never returns if nothing is sent to system.in in eclipse. However, I tried it on command line. It works fine. – StarPinkER Aug 01 '12 at 02:31
  • @JErmaine: I can't say is it works fine for me. Are you sure that your class-path and file name are absolutely correct? Are you using full pathname, starting with the drive letter, not relative path names for your class path? – Hovercraft Full Of Eels Aug 01 '12 at 02:33
  • Yes, I use absolute path. And if I send a command to the system.in, the exec will return and the subprocess will work fine. It's weird having this difference in command line and eclipse. – StarPinkER Aug 01 '12 at 02:37
  • @JermaineXu: I don't see "bin" anywhere in your class-path String, so it makes me wonder how it would work with Eclipse, unless your Test class is not in the workspace. – Hovercraft Full Of Eels Aug 01 '12 at 02:53
  • Yeah, the Test class is not in the workspace. And the subprocess will work normally when enter a command into system.in. So I think the it is not caused by the classpath. – StarPinkER Aug 01 '12 at 03:58
  • @Andrew: all I can say is "meh". – Hovercraft Full Of Eels Aug 01 '12 at 13:04
  • 1
    "Things that make you go 'meh'" - should be a t-shirt. ;) – Andrew Thompson Aug 01 '12 at 13:06
1

You should keep reading the input stream, otherwise it will get blocked. It has nothing to do with JVM but the underyling operating system.