tags:

views:

177

answers:

3

Consider the following script:

use IO::File;
$| = 1;
my ($handle, $pid) = myPipe();
if ($pid == 0) {
  print "$$";
  sleep 5;
  exit;
}

print "child: ".<$handle>."\n";

sub myPipe {
  my $handle = new IO::File();
  my $pid = open($handle, "-|");
  return ($handle, $pid);
}

In this case, the "child:" message doesn't appear for 5 seconds after the process starts. If I remove the sleep call from the forked child, then it prints immediately. Why does the forked child have to exit for the pipe to flush to the parent?

+2  A: 

Flushing the pipe doesn't happen on any fixed schedule. The only two ways that you can force the pipe to flush is by exiting the child process (which is what you're doing now), or explicitly calling flush. You can cause your handle to flush in perl by doing any of the following:

  • Adding a \n to the end of the child's message, which will (usually) cause the pipe to flush
  • Setting $| to 1, which causes the currently selected filehandle to auto-flush
  • Using IO::Handle and calling $handle->flush.
  • Using IO::Handle and setting $handle->autoflush = 1
JSBangs
Flushing the pipe is not handled by the kernel. Buffering is a userland feature!
Leon Timmermans
+3  A: 
mobrule
That's not quite right. A handle is non-buffered (flush after every print) if autoflush is turned on, line-buffered (flush after every newline) if autoflush is turned off and the handle is opened to a tty, and block-buffered (flush when a buffer, usually of a few kB, is full) otherwise.
hobbs
Re: update -- see Sean's answer. The newline matters not because of buffering, but because the readline/`<>` operation in the parent *needs to read a newline before it will return* -- except at EOF :)
hobbs
@hobbs - Thanks for clarification. In this particular example, the call to `<$handle>` in the parent doesn't return until there is a newline in the input or the input is closed. So the newline is still the issue, but for a slightly different reason than I thought. You could do something funky with `$/`, I suppose.
mobrule
+10  A: 

There are two issues. First, the child process is buffering its output; and second, the parent process is using the <> operator, which blocks until a complete line is available, or until end-of-file.

So, one way to get the result you were expecting is to have the child process close its output stream immediately after writing:

if ($pid == 0) {
    print "$$";
    close STDOUT;
    sleep 5;
    exit;
}

Another way is to add a newline to the child process's output, then flush the stream:

if ($pid == 0) {
    print "$$\n";
    STDOUT->flush;  # "close STDOUT;" will work too, of course
    sleep 5;
    exit;
}

The flush is necessary because pipes are (typically) unbuffered, rather than line-buffered as streams connected to the terminal usually are.

A third alternative is to set the child process's output stream to autoflush:

if ($pid == 0) {
    $| = 1;
    print "$$\n";
    sleep 5;
    exit;
}
Sean
Thanks for the thorough answer. I realized later that using <> would have required a new line character.
balor123