Proces potomny Java w systemie Windows nie wprowadza ani nie wysyła danych wyjściowych po ustawieniu standardowego We / Wy rodzica (wiersz polecenia)

W systemie Windows nie jestem w stanie niezawodnie manipulować we / wy mojego procesu potomnego, gdy mój program został uruchomiony z wiersza poleceń. Jest to frustrujące, ponieważ standardem jest, że serwery używają konsoli do operacji we / wy. GUI są ładne, ale naprawdę wolałbym trzymać się wiersza poleceń i zachować prostotę. Zauważyłem, że I / O procesu potomnego jest w porządku, gdy wykonuję mój serwer z IDE Eclipse, ale jest to zupełnie inna historia uruchamiana z wiersza poleceń. Nie mogę odczytywać ani zapisywać do procesu potomnego, ale proces nadal będzie działał. Napisałem poniżej kod testowy, który demonstruje ten problem, i mam nadzieję, że problem może zostać odtworzony na innym komputerze, a potem, miejmy nadzieję, rozwiążę problem. Po wykonaniu z Eclipse odziedziczone operacje we / wy działają zgodnie z oczekiwaniami. Jednak po uruchomieniu z wiersza polecenia systemu Windows nic nie może zostać odczytane ani zapisane w procesie potomnym. W obu przypadkach przekierowanie danych wyjściowych procesu potomnego do pliku zawsze się powiedzie, ale dane wejściowe nadal nie mogą zostać przekazane dziecku. Jeśli jest już rozwiązanie tego problemu, proszę połączyć stronę.

Implementacja JRE / JDK:

>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)

Rozważ następujący kod:

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

}

}

AKTUALIZACJA: Zmodyfikowałem test, aby zaakceptować dowolne polecenie w czasie wykonywania i załadowałem go na mój serwer linux vps. Uruchomiłem go z sesji ssh i wszystkie operacje we / wy procesów potomnych można z łatwością odczytywać i zapisywać. Zauważyłem jedną rzecz. Kiedy otworzyłem interaktywną powłokę bash jako proces potomny, a następnie przekierowałem ją do pliku, CentOS zatrzymał mój program. To lub mój program się zawiesił.

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

[1]+  Stopped                 java -jar piotest.jar
[admin@comp8nerd4u2 piotest]$

Pierwsza linia to moje wpisanie polecenia. Druga linia to powłoka bash, która została utworzona, ale nigdy nic w niej nie wpisałem, więc mój program zabija ją po upływie limitu czasu. Przygotowuje się do drugiego testu, tworzy plik „piotest.txt”, a następnie albo zawiesza się, albo jest zatrzymywany przez system operacyjny. Sam test nie zmienił się, z wyjątkiem tego, że test pozwala teraz na wpisanie polecenia, które ma zostać uruchomione w czasie wykonywania. Działa to dobrze w Linuksie, ale nie w Windows. Mam nadzieję, że ktoś, kto zna API Win32, może w jakiś sposób wyjaśnić, dlaczego ten test nie działa w systemie Windows.