2012-05-20 7 views
9

En Windows, no puedo manipular de manera confiable la E/S de mi hijo cuando mi programa se inició desde la línea de comandos. Es frustrante, ya que es estándar que los servidores utilicen una consola para E/S. Las GUI son agradables, pero realmente prefiero mantenerme en la línea de comandos y mantener las cosas simples. Me di cuenta de que el proceso secundario de E/S está bien cuando estoy ejecutando mi servidor desde el Eclipse IDE, pero es una historia completamente diferente que se ejecuta desde la línea de comandos. No puedo leer ni escribir en el proceso hijo, pero el proceso aún se estaría ejecutando. A continuación, he escrito un código de prueba que demuestra este problema, y ​​espero que el problema pueda reproducirse en otra máquina y, con suerte, obtener una solución. Cuando se ejecuta desde Eclipse, las E/S heredadas funcionan como se esperaba. Sin embargo, cuando se ejecuta desde el símbolo del sistema de Windows, nada se puede leer o escribir en el proceso secundario. En ambos casos, redirigir la salida del proceso secundario a un archivo siempre tiene éxito, pero aún no se puede pasar la entrada al niño. Si ya hay una solución a este problema, por favor enlace la página.El proceso hijo de Windows Java no ingresa ni da salida cuando se establece en IO estándar del padre (Símbolo del sistema)

JRE/JDK Implementación:

>java -version 
java version "1.7.0_01" 
Java(TM) SE Runtime Environment (build 1.7.0_01-b08) 
Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode) 

Considere el siguiente código:

package com.comp8nerd4u2.io.test; 

/* 
* These tests attempt to confirm what I'm experiencing under my build environment 
*/ 

import java.io.File; 
import java.io.IOException; 

public final class PIOTest { 

/** The command to run as a child process. The command itself isn't the test, but what you use to run this Java program is the test. */ 
private static final String[] COMMAND = {"cmd.exe", "/c", "echo This is a test. Feel free to change this."}; // Change this to just {"cmd.exe"} or some other program that accepts input and you'll see how frustrating this is 
/** Controls how the test process is built */ 
private static final ProcessBuilder PB = new ProcessBuilder(COMMAND); 
/** How long to allow the process to run before forcibly terminating it. */ 
private static final long PROCESS_TIMEOUT = 10000L; 
private static final Runnable R = new TimedInterruptWorker(PROCESS_TIMEOUT); 

private static int n = 0; 

static { 
    PB.redirectErrorStream(true); 
} 

private PIOTest() {} 

public static void main(String[] args) { 

    // ----- Begin Tests ----- 

    /* 
    * Test #1: Let's test putting our command's output onto our standard I/O streams 
    * Goal condition: Child process outputs expected output, and exits before the timeout. If child process expects input, it should accept entered input. 
    * Known success factors: Parent process' standard I/O is piped to Eclipse. Tests would probably succeed with Netbeans as well 
    * Known fail factors: Parent process' standard I/O is piped to Windows Command Prompt 
    * Result under fail condition: Child process hangs if it fills up its output buffer or requests input, but exits on its own otherwise, unless it took longer than the timeout. 
    */ 
    PB.inheritIO(); 
    doTest(); 

    // Test #2: Let's test putting our command's output into a file 
    PB.redirectOutput(new File("piotest.txt")); 
    doTest(); 
} 

/** 
* Performs the I/O test. 
*/ 
private static void doTest() { 
    n++; 
    Process p = null; 
    try { 
     p = PB.start(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
     return; 
    } 
    try { 
     Thread t = new Thread(R); 
     t.setDaemon(true); 
     t.start(); 
     System.out.format("[Test #%d] Child exited with status code %d\n", n, p.waitFor()); 
     t.interrupt(); 
    } catch (InterruptedException e) { 
     p.destroy(); 
     System.out.format("[Test #%d] Child took longer than the timeout.\n", n); 
    } 
} 

/** 
* Useful for sending interrupts after a certain amount of time has passed. 
* 
* @author comp8nerd4u2 
*/ 
private static final class TimedInterruptWorker implements Runnable { 

    private long timeout = 0; 
    private Thread target = null; 

    public TimedInterruptWorker(long timeout) { 
     this(timeout, Thread.currentThread()); 
    } 

    public TimedInterruptWorker(long timeout, Thread target) { 
     this.timeout = timeout; 
     this.target = target; 
    } 

    @Override 
    public void run() { 
     try { 
      Thread.sleep(timeout); 
     } catch (InterruptedException e) { 
      return; 
     } 
     target.interrupt(); 
    } 

} 

} 

ACTUALIZACIÓN: He modificado la prueba para aceptar cualquier comando en tiempo de ejecución, y subido a mi servidor VPS Linux. Lo ejecuté desde una sesión ssh y todas las E/S de los procesos secundarios se pueden leer y escribir con facilidad. Hubo una cosa que noté. Cuando abrí un shell bash interactivo como un proceso secundario, y luego redirigí su salida a un archivo, CentOS detuvo mi programa, creo. Ese o mi programa se estrelló.

[[email protected] piotest]$ java -jar piotest.jar 
Enter command to run : bash 
[[email protected] piotest]$ [Test #1] Child took longer than the timeout. 

[1]+ Stopped     java -jar piotest.jar 
[[email protected] piotest]$ 

Primera línea es mi tipeo en el comando. La segunda línea es el shell bash que se generó pero nunca escribí nada en él, así que mi programa lo mata después del tiempo de espera. Se prepara para la segunda prueba, crea el archivo "piotest.txt" y luego se bloquea o el SO lo detiene. La prueba real en sí no se modificó, excepto que la prueba ahora le permite ingresar qué comando ejecutar en tiempo de ejecución. Esto funciona bien en Linux, pero no en Windows. Espero que alguien que conozca la API de Win32 pueda explicar de alguna manera por qué esta prueba falla en Windows.

+0

No hay ninguna razón intrínseca por la que esto no debería funcionar en Windows, se busca como un error en el tiempo de ejecución de Java 7 para mí. –

+0

¿Sigues trabajando en esto? Puedo tener una sugerencia. –

+0

La solución consiste en transmitir manualmente la E/S.Creo que la implementación de JVM solo tiene errores para la E/S heredada. Supongo que el kilometraje podría variar en diferentes implementaciones. – comp8nerd4u2

Respuesta

2

¿Has visto este artículo? http://www.javaworld.com/jw-12-2000/jw-1229-traps.html?page=1

Me parece que necesita reparar los flujos de entrada/salida en Windows. El artículo trata sobre Runtime.exec, pero apuesto a que el código nativo para ProcessBuilder es muy similar y tiene el mismo tipo de problemas en Windows.

Supongo que por qué esto funciona en Eclipse en Windows es que Eclipse está dando servicio a las transmisiones en su nombre para mostrar cosas en la vista de la consola.

+0

El OP está pidiendo explícitamente que la entrada y salida estándar existente sea heredada por el proceso hijo, lo que significa que no hay flujos de E/S que necesiten servicio. (Esa funcionalidad solo se agregó en Java 7, que no existía cuando se escribió el artículo que se referencia). –

1

Sé que llego tarde para responder, pero me encontré con esta pregunta antes de encontrar la respuesta, y quería salvar a alguien más en el mismo barco buscando.

Esto es realmente un error conocido para Windows: https://bugs.openjdk.java.net/browse/JDK-8023130

Puede conseguir alrededor de él mediante la reorientación de los flujos de sí mismo:

Process p = pb.start(); 
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); 
String line = null; 
while ((line = br.readLine()) != null) { 
    System.out.println(line); 
} 

p.waitFor(); 

br.close(); 
Cuestiones relacionadas