No tengo más remedio que recuperar algunos datos externos mediante varias llamadas Runtime.exec()
a un VBScript. Realmente odio esta implementación, ya que pierdo mi flexibilidad multiplataforma, pero eventualmente puedo desarrollar scripts * nix similares para al menos mitigar el problema. Antes de que nadie pregunte, I no puede solucionar la necesidad de llamar a un script externo para recopilar mis datos. Viviré con los problemas que causa.Java - Problema con múltiples runtime.exec() concurrentes InputStreams
Los procesos exec()
se ejecutan en una clase personalizada que se extiende a Runnable
. Utiliza un BufferedReader
para leer los datos del getInputStream()
.
Editar: más código añadido a lo solicitado, pero no veo cómo el código adicional es relevante :) Espero que ayude, porque se tomó un tiempo para dar formato! Ah, y tenga cuidado con mi estilo de código si es feo, pero se fomenta la crítica constructiva ...
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
Si ejecuto los comandos de forma individual, deduzco los datos como espero. Sin embargo, si ejecuto los comandos en el bloque run()
de la clase (lo que significa que las llamadas son concurrentes, como estoy esperando), parece que solo se genera una secuencia de entrada, que todos los BufferedReaders
consumen simultáneamente.
Para solucionar el problema, imprimo cada línea consumida en la consola con el prefijo de qué instancia de mi clase estaba leyendo la secuencia de entrada. Espero que algo como lo siguiente, entendiendo que pueden estar fuera de servicio de la instancia de instancia, pero el orden de línea de una sola instancia estaría intacta:
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
...
Lo extraño es consigo el número esperado de instancias de la primera línea de la salida (Microsoft (R) Windows Script Host Version 5.7
), pero después de esta línea, sólo un proceso continúa para producir datos en la corriente de entrada, y todos los lectores azar-consumen este una corriente, tal como este ejemplo:
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
...
Para empeorar las cosas, los lectores se paran y readLine()
nunca devuelve nulo. Leí que este tipo de comportamiento podría tener algo que ver con el tamaño del búfer, pero cuando solo ejecuto dos subprocesos concurrentes, incluso con un resultado corto, todavía muestra el mismo comportamiento. No se captura nada en stdErr
para indicar que hay un problema.
Para ver si esto era una limitación del servidor de secuencias de comandos, creé un archivo de proceso por lotes que START
s varias instancias de la secuencia de comandos al mismo tiempo. Debo afirmar que se ejecutó fuera de de Java, en un shell cmd, y lanza varios de sus propios shells. Sin embargo, cada instancia simultánea devolvió completamente los resultados esperados y se comportó bien.
Editar: Como otra idea de solución de problemas, he decidido volver a habilitar la concurrencia, pero escalonar mi método de inicialización insertando el siguiente en mi bloque Y.run()
:
try {
Thread.sleep((int)(Math.random() * 1200));
} catch (InterruptedException e) {
System.out.println("Can't sleep!");
} // End try
initialize(monitor);
en mi código. Empiezo a ver múltiples salidas para las primeras líneas, pero rápidamente revierte a múltiples consumidores que consumen el mismo productor, y tan pronto como se cierra la primera transmisión completa, el resto de los consumidores lanza excepciones. El próximo consumidor dispara un IOException: Read error
, y el resto dispara IOException: Stream closed
!
Según maaartinus, es posible ejecutar múltiples, simultáneas InputStreams
, por lo que ahora la pregunta es qué está causando el comportamiento no deseado? ¿Cómo puedo tomar independientemente sus flujos de entrada? No quiero tener que escribir en un archivo temporal solo para procesar los datos, si puedo evitarlo.
'Así que estoy confundido: ¿Java es capaz de procesar múltiples flujos de entrada al mismo tiempo o no?' Claro que sí. Puede haber un problema en el Windows Script Host, sea lo que sea. Probaría un guión trivial (u otro programa) produciendo una salida trivial. – maaartinus
Suena extraño que algunos de sus campos sean estáticos. ¿Puedes publicar más de tu clase? –
'Para empeorar las cosas, los lectores se atascan y readLine() nunca devuelve null. Intente usar un hilo propio para cada flujo, es decir, dos hilos por proceso. Hay un pequeño búfer en cada secuencia, y el proceso se bloquea cuando se llena (y no obtienes nada más de la otra transmisión). – maaartinus