Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Java child process doesn't input or output when set to parent's standard IO (Command Prompt)

Tags:

java

windows

cmd

Under Windows, I am unable to reliably manipulate my child process' I/O when my program has been started from the command line. It's frustrating as it is standard for servers to use a console for I/O. GUIs are nice, but I'd really prefer to stick to the command line and keep things simple. I've noticed that child process I/O is just fine when I'm executing my server from the Eclipse IDE, but it's a whole different story being ran from the command line. I can't read or write to the child process, but the process would still be running. I've written some test code below that demonstrates this problem, and I'm hoping the problem could be reproduced on another machine, and then hopefully get a solution out of it. When executed from Eclipse, inherited I/O works as expected. However, when executed from the Windows command prompt, nothing can be read or written to the child process. In both cases, redirecting child process output to a file always succeeds, but input still can't be passed to the child. If there is already a solution to this problem then please link the page.

JRE/JDK Implementation:

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

Consider the following code:

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

}

}

UPDATE: I modified the test to accept any command at runtime, and uploaded it to my linux vps server. I ran it from a ssh session and all child processes' I/O can be read and written to with ease. There was one thing that I have noticed. When I opened a interactive bash shell as a child process, and then redirect it's output to a file, CentOS stopped my program, I think. That or my program crashed.

[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]$

First line is my typing in the command. Second line is the bash shell that was spawned but i never typed anything into it so my program kills it after the timeout. It gets ready for the second test, creates the "piotest.txt" file, and then either crashes or is stopped by the OS. The actual test itself was unchanged, except that the test now allows you to enter what command to run at runtime. This works fine in linux, but not in windows. I'm hoping that someone who knows the Win32 API can somehow explain why this test fails in windows.

like image 814
comp8nerd4u2 Avatar asked May 20 '12 05:05

comp8nerd4u2


1 Answers

Have you seen this article? http://www.javaworld.com/jw-12-2000/jw-1229-traps.html?page=1

It sounds to me like you need to service the input/output streams on Windows. The article is about Runtime.exec, but I bet the native code for ProcessBuilder is very similar and has the same type of issues on Windows.

My guess as to why this works on Eclipse on Windows is that Eclipse is servicing the streams on your behalf in order to display things in the Console view.

like image 140
James Branigan Avatar answered Sep 18 '22 19:09

James Branigan