3

I want to read STDIN, but at most for 5 seconds. After that I want to process the data I have read so far.

select seems to be made for exactly that: Wait until there is input or until timeout.

If there is input I should then just read that non-blocking.

So I assumed this would work:

#!/usr/bin/perl -w

use strict;
use Fcntl;

open(my $in, "<&", "STDIN") or die;
my $buf = "";
my $readsize = 10;

# Make $in non-blocking
fcntl($in, &F_SETFL, fcntl($in, &F_GETFL, 0) | &O_NONBLOCK);
while(not eof($in)) {
    my $starttime = time();
    my $rin = my $win = my $ein = '';
    vec($rin, fileno($in),  1) = 1;
    while(time() < $starttime + 5 and length $buf < $readsize) {
        # Wait up to 5 seconds for input                                                               
        select($rin, $win, $ein, $starttime + 5 - time());
        # Read everything that is available in the inputbuffer                                         
        while(read($in,$buf,$readsize,length $buf)) {}
    }
    print "B:",$buf;
    $buf = "";
}

When run like:

(echo block1;sleep 1; echo block1; sleep 6;echo block2)|
  perl nonblockstdin.pl 

the blocks are merged together. They should have been two blocks because block2 starts 6 seconds later.

What am I doing wrong?

Ole Tange
  • 35,514
  • "the blocks are merged together" no, they're not. The "block2" will NOT be printed in 99.999% of cases, and the script may not print anything at all if the kernel delays the right hand of the pipeline after the right one (which could be simulated with a (sleep .1; echo ..., and will happen if instead of echo builtin-in there's some heavyweight external command). That's because perl's eof will also return true in the case of an error (like the EAGAIN caused by a read in non-blocking mode). –  Dec 29 '19 at 02:38
  • Example: { sleep .1; echo yup; } | perl -MFcntl -e 'fcntl STDIN, F_SETFL, O_NONBLOCK; warn "EOF!\n" if eof STDIN; sleep 1; print "read after EOF: ", <>' or perl -e '$SIG{TTIN}="IGNORE"; warn "really?\n" if eof STDIN' &. –  Dec 29 '19 at 02:39
  • All in all, this is a very lousy question and I don't understand why "reputable users" are not held to the same standard as new users: what is the output that you're getting and the one that you're expecting, have you RTFM that says do not mix select with buffered I/O, etc. –  Dec 29 '19 at 02:46

2 Answers2

1

Some of the problems:

  • setting O_NONBLOCK affects the open file description, not only the file descriptor. For instance, if you run that-script; cat, you'll see cat: -: Resource temporarily unavailable, as cat's stdin became non-blocking
  • if you use non-blocking I/O, the read() system call returns with a EAGAIN error if there's no input at the moment and eof is set. perl's eof() calls read() and implies doing buffered I/O. You can't really use it for non-blocking I/O. In your example, the first block1 is read upon eof(), then select() waits during sleep 1 and the second block1 is read upon read() which returns both block1s.

Here, you'd be better of using blocking I/O and use alarm() for instance for the timeout. If using non-blocking I/O and select(), don't use eof() and use sysread() instead of read() and make sure you clear the O_NONBLOCK flag on exit if it was set beforehand (still a bad idea to set O_NONBLOCK on stdin as that stdin could be shared with other processes).

0

Following Stephane's suggestions I have come up with this, which so far seems to work.

#!/usr/bin/perl -w                                                                                                                                   

use strict;

my $timeout = 2;
my $buf = "";
my $blocksize = 30;
my $readsize;
my $nread;
my $buflen;
my $alarm;
my $eof;

open(my $in, "<&", "STDIN") or die;

do {
    eval {
        local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required                                                                                  
        alarm $timeout;
        # Add upto the next full block                                                                                                               
        $readsize = $blocksize - (length $buf) % $blocksize;
        do {
            $nread = sysread $in, $buf, $readsize, length $buf;
            $readsize -= $nread;
        } while($nread and $readsize);
        alarm 0;
    };
    if ($@) {
        die unless $@ eq "alarm\n";   # propagate unexpected errors                                                                                  
        $alarm = 1;
    } else {
        $alarm = 0;
    }
    print "B:$buf<\n";
    $buf = "";
    $eof = not ($nread or $alarm);
} while(not $eof);
Ole Tange
  • 35,514