2011-01-20 10 views
7

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.

+1

'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

+1

Suena extraño que algunos de sus campos sean estáticos. ¿Puedes publicar más de tu clase? –

+1

'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

Respuesta

5

Creo que debe tener cuidado con el alcance de las variables de E/S. Aquí es un código rápido que funciona perfectamente bien, con flujos de entrada de procesos concurrentes 4 niño ...

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(); 
     } 
} 

La salida del código anterior (% javac MultiExec.java; java MultiExec)

Si nos proporciona el código fuente para su intento, podríamos discutirlo. Buenos deseos, - M.S.

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

Edit: DN: Entiendo su preocupación por las salidas de 1 línea. Deja para tener un pequeño script ...

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

y una versión editada de lo anterior Código de Java ... líneas de comunicación han cambiado, y una Thread.sleep añadido después de cada println()

clase pública 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(); 
}} 

Aquí está la salida ahora (después de compilación/ejecución) ...

[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's 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 

...... 

Los flujos de entrada funcionan bien, no creo que tenga un problema aquí. Lamento que la respuesta sea tan larga. Le deseo lo mejor, y esperando ver su código, - M.S.

+0

No puedo dejar de notar que los ejemplos que usaste son a) no tan complejos como un comando cscript yb) devuelven 1 línea en todos los ejemplos menos uno. Para todos los efectos, técnicamente podría tener un problema similar :) De todos modos, eso es casi todo el código. El resto de las variables de clase son para almacenar los datos extraídos, y están todos definidos (no estáticos) fuera de cualquier constructor o método. Publicaré un poco más de estructura (muchas cosas son irrelevantes) para ayudar a mover la conversación. ¡Tu esfuerzo hasta ahora es muy apreciado! –

+0

Hola de nuevo DN, se publica una respuesta revisada. Me alegro de ser de alguna ayuda, - M.S. –

+0

Al parecer me llevó más de media hora (estaba ocupado, parte de eso, je) escribir el resto. Tómese su tiempo, no podré verlo más esta noche. : P –

3

Asegúrese de estar declarando stdErr y stdIn en el ámbito correcto. En este caso, debe declararlos en Y.

Si se les está declarando en X, cada vez que ejecute el siguiente código: serán reasignados

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

Las variables, y todas las instancias de Y harán referencia a la misma corriente.

+1

Son errores simples como este que explican por qué no soy un programador de profesión. Pero como dije antes, él respondió esto primero y yo simplemente no me di cuenta. ¡Gracias! –

Cuestiones relacionadas