Håkon Hægland Håkon Hægland - 3 months ago 9
Perl Question

How to flush pipe handle obtained from open?



I am trying to flush a pipe handle obtained from

using either
autoflush()
and
flush()
methods from the
IO::Handle
module, but I think it is not working. Here is an example:

host.pl:

use feature qw(say);
use strict;
use warnings;

my $client_pid = open ( my $fh, '|-', 'client.pl' )
or die "Could not open client: $!";
#$fh->autoflush(1); # adding this line does not help
sleep 2;
say "Host: sent message";
print $fh "Hello";
#print $fh "Hello\n"; # adding a newline works fine
$fh->flush() or warn "$!"; # this does not work
sleep 2;
say "Host exits.";
close $fh;


client.pl:

use feature qw(say);
use strict;
use warnings;

say "Client running..";
chomp (my $line = <STDIN>);
say "Client got line: '$line'";
sleep 1;
say "Client exits..";


The output of running
host.pl
is:

Client running..
Host: sent message
Host exits.
Client got line: 'Hello'
Client exits..


Expected output would be:

Client running..
Host: sent message
Client got line: 'Hello'
Client exits..
Host exits.


I know I can fix this by adding a newline at the end of string to be printed:

print $fh "Hello\n";


but I am curious why
$fh->flush()
is not working here?

Answer

The data is being sent to the client immediately, but the client waits for a newline to arrive.


readline (for which <> is a shortcut in your program) reads until a newline is encountered before returning (although changing $/ can change that behaviour. If you want a call that returns as soon as data is available, use sysread.

use BLOCK_SIZE => 64*1024;

say "Client running..";
while (1) {
   my $rv = sysread(\*STDIN, my $buf, BLOCK_SIZE);
   die($!) if !defined($rv);
   last if !$rv;
   say "Got: $buf";
}

Note a single print can result in data being received in multiple chunks. In practice, especially with a socket instead of a pipe, you'd need some way of framing your messages in order to reliably identify them. For example, the following client expects sentinel-terminated messages (the sentinel being a newline):

use BLOCK_SIZE => 64*1024;

say "Client running..";
my $buf = '';
while (1) {
   my $rv = sysread(\*STDIN, $buf, BLOCK_SIZE, length($buf));
   die($!) if !defined($rv);
   last if !$rv;
   while ($buf =~ s/^([^\n]*)\n//) {
      my $msg = $1;
      say "Got: $msg";
   }

   say "Got a partial message" if length($buf);
}

die("Premature EOF\n") if length($buf);

Try sending:

$fh->autoflush();
print($fh "abc");
sleep(1);
print($fh "def\n");
sleep(1);
print($fh "ghi\njkl\nmno");
sleep(1);
print($fh "pqr\n");

This can be adapted to handle length-prefixed messages or any other message format.