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!"); }
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.