4

I'm firing up an external process from Java and grabbing its stdin, stdout and stderr via process.getInputStream() etc. My issue is: when I want to write data to my output stream (the proc's stdin) it's not getting sent until I actually call close() on the stream. I am explicitly calling flush().

I did some experimenting and noticed that if I increased the number of bytes I was sending, it would eventually go through. The magic number, on my system, is 4058 bytes.

To test I'm sending the data over to a perl script which reads like this:

#!/usr/bin/perl
use strict;
use warnings;

print "Perl starting";

while(<STDIN>) {
    print "Perl here, printing this: $_" 
}

Now, here's the java code:

import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;

public class StreamsExecTest {


    private static String readInputStream(InputStream is) throws IOException {
        int guessSize = is.available();
        byte[] bytes = new byte[guessSize];
        is.read(bytes); // This call has side effect of filling the array
        String output = new String(bytes);
        return output;
    }

    public static void main(String[] args) {

        System.out.println("Starting up streams test!");

        ProcessBuilder pb;
        pb = new ProcessBuilder("./test.pl");


        // Run the proc and grab the streams
        try {
            Process p = pb.start();
            InputStream pStdOut = p.getInputStream();
            InputStream pStdErr = p.getErrorStream();
            OutputStream pStdIn = p.getOutputStream();


            int counter = 0;
            while (true) {  

                String output = readInputStream(pStdOut);
                if (!output.equals("")) {
                    System.out.println("<OUTPUT> " + output);
                }

                String errors = readInputStream(pStdErr);
                if (!errors.equals("")) {
                    System.out.println("<ERRORS> " + errors);
                }

                if (counter == 50) {
                    // Write to the stdin of the execed proc.  The \n should
                    // in turn trigger it to treat it as a line to process
                    System.out.println("About to send text to proc's stdin");
                    String message = "hello\n";

                    byte[] pInBytes = message.getBytes();
                    pStdIn.write(pInBytes);
                    pStdIn.flush();
                    System.out.println("Sent " + pInBytes.length + " bytes.");

                }

                if (counter == 100) {
                    break;
                }

                Thread.sleep(100);
                counter++;
            }
            // Cleanup
            pStdOut.close();
            pStdErr.close();
            pStdIn.close();
            p.destroy();

        } catch (Exception e) {
            // Catch everything
            System.out.println("Exception!");
            e.printStackTrace();
            System.exit(1);
        }

    }
}

So when I run this, I get effectively nothing back. If immediately after calling flush(), I call close() on pStdIn, it works as expected. This isn't what I want though; I want to be able to continually hold the stream open and write to it whenever it so pleases me. As mentioned before, if message is 4058 bytes or larger, this will work without the close().

Is the operating system (running on 64bit Linux, with a 64bit Sun JDK for what it's worth) buffering the data before sending it? I could see Java having no real control over that, once the JVM makes the system call to write to the pipe all it can do is wait. There's another puzzle though:

The Perl script prints line before going into the while loop. Since I check for any input from Perl's stdout on every iteration of my Java loop, I would expect to see it on the first run through the loop, see the attempt at sending data from Java->Perl and then nothing. But I actually only see the initial message from Perl (after that OUTPUT message) when the write to the output stream happens. Is something blocking that I'm not aware of?

Any help greatly appreciated!

4

3 に答える 3

3

バッファリングされていない出力を使用するようにPerlに指示していません。perlvarを調べ$|て、バッファなしモードを設定するさまざまな方法を探します。本質的に、次のいずれかです。

HANDLE->autoflush( EXPR )
$OUTPUT_AUTOFLUSH
$| 
于 2012-07-18T06:44:59.213 に答える
0

Perlは、何かを印刷し始める前にそれをバッファリングしている可能性があります。

is.read(bytes); //この呼び出しには、配列を埋めるという副作用があります

いいえ、そうではありません。bytes.length-1配列に1バイトからバイトを読み込む効果があります。Javadocを参照してください。

于 2012-07-18T03:44:07.230 に答える
0

コードに明らかなバッファリングが見られないので、Perl 側にある可能性があります。\nprint ステートメントの最後に改行を入れるとどうなりますか?

また、一般に、そのようにメインスレッドで stdin と stderr を読み取ることはできないことに注意してください。デッドロックが発生する可能性があります。たとえば、親が stdin を読み取っているときに子プロセスが大量の stderr を出力すると、stderr バッファーがいっぱいになり、子プロセスはブロックされますが、親は stdin を読み取ろうとして永久にブロックされたままになります。

stderr と stding を読み取るには、別々のスレッドを使用する必要があります (ここではプロセスへの入力をポンピングするために使用されるメインスレッドからも分離されています)。

于 2012-07-18T04:58:13.973 に答える