views:

244

answers:

2

Hello all,

Let me say first that my experience with threading is pretty low.

I have an app that starts up several other Java jars via the Runtime.exec method. The problem is that the jars that are started need to be run concurrently, but in order to get at the error stream for the started jars you have to basically have a loop 'sitting and listening' until the process completes.

This is what I have now:

_processes.add( Runtime.getRuntime().exec( commandList.toArray( new String[ commandList.size() ] ) ) );
Thread thread = new Thread( new Runnable() {
    private final int _processNumber = _processes.size() - 1;
    public void run() {
        String streamData = _processNumber + " : ";
        streamData += "StdError [\r";
        BufferedReader bufferedReader =
                new BufferedReader( new InputStreamReader( _processes.get( _processNumber ).getErrorStream() ) );
        String line = null;
        try {
            while ( ( line = bufferedReader.readLine() ) != null ) {
                streamData += line + "\r";
            }
            bufferedReader.close();
            streamData += "]\r";
            LOG.error( streamData );
        }
        catch ( Exception exception ) {
            LOG.fatal( exception.getMessage() );
            exception.printStackTrace();
        }
    }
} );
thread.start();

Can anyone explain how to get the 'error stream listener threads' to work properly?

TIA

A: 

There is a very good tutorial here on how to use Runtime.exec(). Read through all of it but in particular take a look at page 4 where it explains how to use a 'StreamGobbler' running in a separate thread to consume both the std out and std err stream from the process being executed.

Basically, what you should be looking to implement in pseduo code is:

Runtime rt1 = Runtime.getRuntime().exec("my command")
new StreamGobbler(rt1.getOutputStream()).start()
new StreamGobbler(rt1.getErrorStream()).start()
//repeat for each external process (rt2, rt3 etc)
...
rt1.waitFor()
rt2.waitFor()
rt3.waitFor()

I.e. you kick off each process, and immediately start consuming each process's output in a separate thread. With the consuming threads started, just wait for each process to finish and return.

Alex Spurling
Yeah, I've read this before. The problem is that rt1 is opening a listening socket and rt2 is opening the write side of the socket. If I start 2 threads to feed off of rt1's streams then the parent thread seems to block and rt2 never starts. I've tried running all exec commands and then starting all listeners and when I do this it forces the exec'ed commands to block.
javamonkey79
+1  A: 

Instead of using Runtime.getRuntime().exec(), use Process to start external processes.. It'll make your life a whole lot easier..

Example code from a project of mine:

 //Build command 
 List<String> commands = new ArrayList<String>();
 commands.add("my_application");
 commands.add("arg1");
 commands.add("arg2");
 log.debug("{}", commands);

 //Run command with arguments
 ProcessBuilder pb = new ProcessBuilder(commands);
 pb.directory(directory);
 pb.redirectErrorStream(true);
 Process process = pb.start();

 //Read output
 StringBuilder out = new StringBuilder();
 BufferedReader br = new BufferedReader(new InputStreamReader
  (process.getInputStream()));

 //Only log unique lines (you might not need this)
 String line = null, previous = null;
 while ((line = br.readLine()) != null)
  if (!line.equals(previous)) {
   previous = line;
   out.append(line).append('\n');
   log.debug(line);
  }

 //Check result
 if (process.waitFor() == 0)
  return 0;

 //Abnormal termination: Log command parameters and output and throw ExecutionException
 log.error("{}", commands);
 log.error("\n{}", out.toString());
 throw new ExecutionException(new IllegalStateException("MyApplication exit code 1"));
Tim
I think the concurrency of the spawned threads and their corresponding streams is the problem here. I don't see much in ProcessBuilder (or for that matter in all of Java) which handles processes much better. jconfig is mentioned in the article listed above but apparently the project is pretty much dead (at least for now).
javamonkey79
Would PipedOut/InputStream be of any help here than?
Tim