Java - problem with multiple parallel runtime.exec () InputStreams

I have no choice but to get some external data with a few calls to Runtime.exec() for VBScript. I really hate this implementation because I am losing my cross-platform flexibility, but in the end I can develop similar * nix scripts to at least mitigate the problem. Before anyone asks, I cannot get around the need to call an external script to collect my data. I will live with the problems that cause.

The exec() processes are executed in a custom class that extends Runnable . It uses a BufferedReader to read data from getInputStream() .

Edit : another code has been added as requested, but I don’t see how much additional code is needed :) I hope this helps, because it took some time to format! Oh, and approach my code style if it's ugly, but constructive criticism is encouraged ...

 public class X extends JFrame implements Runnable { ... static final int THREADS_MAX = 4; ExecutorService exec; ... public static void main(String[] args) { ... SwingUtilities.invokeLater(new X("X")); } // End main(String[]) public X (String title) { ... exec = Executors.newFixedThreadPool(THREADS_MAX); ... // Create all needed instances of Y for (int i = 0; i < objects.length; i++) { Y[i] = new Y(i); } // End for(i) // Initialization moved here for easy single-thread testing // Undesired, of course for (int i = 0; i < objects.length; i++) { Y[i].initialize(parent); } // End for(i) } // End X class Y implements Runnable { // Define variables/arrays used to capture data here String computerName = ""; ... public Y(int rowIndex) { row = rowIndex; ... computerName = (String)JTable.getValueAt(row, 0); ... exec.execute(this); } // End Y(int) public void run() { // Initialize variables/arrays used to capture data here ... // Initialization should be done here for proper threading //initialize(parent); } // End run() public void initialize(Z obj) { runTime = Runtime.getRuntime(); ... try { process = runTime.exec("cscript.exe query.vbs " + computerName); stdErr = process.getErrorStream(); stdIn = process.getInputStream(); isrErr = new InputStreamReader(stdErr); isrIn = new InputStreamReader(stdIn); brErr = new BufferedReader(isrErr); brIn = new BufferedReader(isrIn); while ((line = brIn.readLine()) != null) { // Capture, parse, and store data here ... } // End while } catch (IOException e) { System.out.println("Unable to run script"); } catch (Exception e) { e.printStackTrace(); } finally { try { stdErr.close(); stdIn. close(); isrErr.close(); isrIn. close(); brErr. close(); brIn. close(); } catch (IOException e) { System.out.println("Unable to close streams."); } // End try } // End try } // End initialize(Z) ... } // End class Y } // End class X 

If I execute the commands separately, I collect the data as I expect. However, if I execute the commands in the run() block of the class (this means that the calls are parallel, as I hope), it seems that only one input stream is created, which all BufferedReaders consume at the same time.

To debug the problem, I output each line consumed to the console, the prefix of which, when an instance of my class read the input stream. I expect something like the following, realizing that they may be out of order from instance to instance, but the row order of one instance will be intact:

 exec 0: Line1 exec 1: Line1 exec 2: Line1 exec 0: Line2 exec 1: Line2 exec 2: Line2 exec 0: Line3 exec 1: Line3 exec 2: Line3 ... 

How strange I get the expected number of instances of the very first line of output ( Microsoft (R) Windows Script Host Version 5.7 ), but after this line only one process continues to create data in the input stream, and all readers randomly consume this single stream, such as this example :

 exec 2: Microsoft (R) Windows Script Host Version 5.7 exec 0: Microsoft (R) Windows Script Host Version 5.7 exec 1: Microsoft (R) Windows Script Host Version 5.7 exec 0: line2 exec 1: line3 exec 2: line4 ... 

Even worse, readers stop and readLine() never returns null. I read that this type of behavior may have something to do with the size of the buffer, but when I start only two parallel threads, even with a short output, it still shows the same behavior. Nothing is fixed in stdErr to indicate that there is a problem.

To find out if this was a restriction for the host script, I created a batch file that START several instances of the script at the same time. I have to say that it was run outside of Java, in the CMD shell, and runs several of its own shells. However, each parallel instance fully returned the expected results and behaved well.

Edit: As another troubleshooting idea, I decided to enable concurrency again, but stagger my initialization method by inserting the following into my Y.run() block:

 try { Thread.sleep((int)(Math.random() * 1200)); } catch (InterruptedException e) { System.out.println("Can't sleep!"); } // End try initialize(monitor); 

into my code. I start to see several exits for the first few lines, but it quickly returns to several consumers consuming the same producer, and as soon as the first completed thread closes, the rest of the consumers break exceptions. The next consumer IOException: Read error , and the remaining fire IOException: Stream closed !

According to maaartinus, is it possible to run several parallel InputStreams , so now the question becomes what causes the unwanted behavior? How can I capture my input streams myself? I do not want to write to a temporary file for data processing only, if I can avoid it.

+7
source share
2 answers

I think you need to be careful about IO variables. Here is a quick code that works fine, with parallel input streams from 4 child processes ...

 import java.io.*; public class MultiExec { private final static String[] comLines = { "date", "ls /var/spool/postfix", "ls -F /usr/local/bin", "wc -l /etc/apache2/apache2.conf"}; public void execute() { for (int i = 0 ; i < comLines.length ; i++) { ExecutableChild ec = new ExecutableChild (i, comLines[i]); new Thread (ec).start(); }} public class ExecutableChild implements Runnable { private int prIndex; private String executable; public ExecutableChild (int k, String cmd) { prIndex = k; executable = cmd; } public void run () { try { Process child = Runtime.getRuntime().exec(executable); BufferedReader br = new BufferedReader (new InputStreamReader ( child.getInputStream())); for (String s = br.readLine() ; s != null ; s = br.readLine()) System.out.println ("[" + prIndex + "] " + s); br.close(); } catch (IOException ioex) { System.err.println ("IOException for process #"+ prIndex+ ": " + ioex.getMessage()); }}} public static void main (String[] args) { new MultiExec().execute(); } } 

Output from the above code (% javac MultiExec.java; java MultiExec)

 [2] tomcat* [0] Thu Jan 20 18:38:31 CST 2011 [3] 368 /etc/apache2/apache2.conf [1] active [1] bounce [1] corrupt [1] defer [1] deferred [1] etc [1] flush [1] hold [1] incoming [1] lib [1] maildrop [1] pid [1] private [1] public [1] saved [1] trace [1] usr [1] var 

If you get the source code for your attempt, we can discuss it. Good wishes, - M.S.

==================================================== =============================

Edit: D.N .: I understand your concern about 1-line exits. Lets have a small script ...

 #!/usr/bin/perl -w foreach (1..50) { print "$_\n"; } 

and the edited version of the above Java code ... comLines have changed, and Thread.sleep is added after each println ()

open class MultiExec {

  private final static String[] comLines = { "ls /var/spool/postfix", "perl count50.pl", "cat MultiExec.java", "head -40 /etc/apache2/apache2.conf"}; public void execute() { for (int i = 0 ; i < comLines.length ; i++) { ExecutableChild ec = new ExecutableChild (i, comLines[i]); new Thread (ec).start(); }} public class ExecutableChild implements Runnable { private int prIndex; private String executable; public ExecutableChild (int k, String cmd) { prIndex = k; executable = cmd; } public void run () { try { Process child = Runtime.getRuntime().exec(executable); BufferedReader br = new BufferedReader (new InputStreamReader ( child.getInputStream())); for (String s = br.readLine() ; s != null ; s = br.readLine()) { System.out.println ("[" + prIndex + "] " + s); try { Thread.sleep (20); } catch (InterruptedException intex) { }} br.close(); } catch (IOException ioex) { System.err.println ("IOException for process #"+ prIndex+ ": " + ioex.getMessage()); }}} public static void main (String[] args) { new MultiExec().execute(); }} 

Here is the result now (after compiling / running) ...

 [0] active [1] 1 [2] import java.io.*; [3] # [2] [0] bounce [1] 2 [3] # Based upon the NCSA server configuration files originally by Rob McCool. [2] public class MultiExec { [1] 3 [0] corrupt [3] # [1] 4 [2] [0] defer [3] # This is the main Apache server configuration file. It contains the [2] private final static String[] comLines = { [0] deferred [1] 5 [3] # configuration directives that give the server its instructions. [2] "ls /var/spool/postfix", [0] etc [1] 6 [3] # See http://httpd.apache.org/docs/2.2/ for detailed information about [2] "perl count50.pl", [0] flush [1] 7 [3] # the directives. [2] "cat MultiExec.java", [1] 8 [0] hold [3] # [1] 9 [2] "head -40 /etc/apache2/apache2.conf"}; [0] incoming [3] # Do NOT simply read the instructions in here without understanding [2] [0] lib [1] 10 [3] # what they do. They're here only as hints or reminders. If you are unsure [1] 11 [2] public void execute() { [0] maildrop [3] # consult the online docs. You have been warned. [2] for (int i = 0 ; i < comLines.length ; i++) { [0] pid [1] 12 [3] # [1] 13 [2] ExecutableChild ec = new ExecutableChild (i, comLines[i]); [0] private [3] # The configuration directives are grouped into three basic sections: [1] 14 [2] new Thread (ec).start(); [0] public [3] # 1. Directives that control the operation of the Apache server process as a [2] }} [1] 15 [0] saved [3] # whole (the 'global environment'). [1] 16 [0] trace [2] [3] # 2. Directives that define the parameters of the 'main' or 'default' server, [0] usr [2] public class ExecutableChild implements Runnable { [1] 17 [3] # which responds to requests that aren't handled by a virtual host. [0] var [2] [1] 18 [3] # These directives also provide default values for the settings [1] 19 [2] private int prIndex; [3] # of all virtual hosts. [1] 20 [2] private String executable; [3] # 3. Settings for virtual hosts, which allow Web requests to be sent to [2] [1] 21 [3] # different IP addresses or hostnames and have them handled by the [1] 22 [2] public ExecutableChild (int k, String cmd) { [3] # same Apache server process. [1] 23 [2] prIndex = k; [3] # [1] 24 [2] executable = cmd; [3] # Configuration and logfile names: If the filenames you specify for many [2] } [1] 25 [3] # of the server control files begin with "/" (or "drive:/" for Win32), the [2] [1] 26 [3] # server will use that explicit path. If the filenames do *not* begin [1] 27 [2] public void run () { [3] # with "/", the value of ServerRoot is prepended -- so "/var/log/apache2/foo.log" [1] 28 [2] try { [3] # with ServerRoot set to "" will be interpreted by the [1] 29 [2] Process child = Runtime.getRuntime().exec(executable); [3] # server as "//var/log/apache2/foo.log". [1] 30 [2] BufferedReader br = new BufferedReader (new InputStreamReader ( [3] # [1] 31 [2] child.getInputStream())); [3] [1] 32 [2] for (String s = br.readLine() ; s != null ; s = br.readLine()) { [3] ### Section 1: Global Environment [1] 33 [2] System.out.println ("[" + prIndex + "] " + s); [3] # [1] 34 [2] try { [3] # The directives in this section affect the overall operation of Apache, [1] 35 [2] Thread.sleep (20); [3] # such as the number of concurrent requests it can handle or where it ...... 

Input streams are working fine, I don't think I have a problem. Sorry to answer for so long. I wish you the best and expecting to see your code, - MS

+5
source

Make sure you specify stdErr and stdIn in the correct scope. In this case, you need to declare them in Y

If you declare them in X , every time you run the following code:

 stdErr = process.getErrorStream(); stdIn = process.getInputStream(); 

The variables will be reassigned, and all instances of Y will refer to the same stream.

+3
source

All Articles