bot403 has asked for the wisdom of the Perl Monks concerning the following question:

In the following code, what happens to the buffer from sysread() when the system call is restarted due to EINTR? I am reading from a pipe and not a file and cant afford to lose a single bit. However, my program complains like it is losing data. Are the contents of $buffer in an undefined state or is it guaranteed that the read will be consistent? That is if I have the following data in the pipe:

"1 2 3 4"

and call sysread but the call is interruped and restarted after reading "1 2" can it be guaranteed that $buffer will have "1 2 3 4" or will it have "3 4" and lose "1 2"? Something else? How can I guarantee that I will read all the data even if the call is interrupted by SIGCHLD?

Furthermore, I must get the return code from the INPIPE process and the only way that seems to work ok is to install a blank signal handler and let close() wait on the child so letting perl reap the process by itself is unacceptable.

I am using AIX 5.3 perl 5.8.8

Apologies if the code is rough, I pulled the relevant bits out of a larger program. I'm not good at low level systems programming/semantics so feel free to challenge any of my assumptions or suggest alternate methods.

my $sigset = POSIX::SigSet->new(SIGCHLD); my $sigaction = POSIX::SigAction->new( sub { }, $sigset, &POSIX::SA_RE +START ); my $old_sigset = POSIX::SigSet->new; sigprocmask(SIG_SETMASK, $sigset, $old_sigset) or die "Could not set signal mask\n"; $reader_pid = open(INPIPE,"cat /tmp/file |") my $buffer; #Pipe buffer on AIX seems to be 32K while($rbytes = sysread(INPIPE,$buffer,32768)){ $wbytes = syswrite(OUTPIPE,$buffer); #print OUTPIPE $buffer; if($wbytes != $rbytes){ die("Error writing to outpipe."); } } if(!defined($rbytes)){ bomb("INPIPE had error: $!\n",2); } close(INPIPE) or die $! ? "Error closing inpipe: $!" : "Exit status $? from inpipe";

Replies are listed 'Best First'.
Re: EINTR and sysread()
by pc88mxer (Vicar) on Apr 03, 2008 at 15:57 UTC
    How are you determining that your program is losing data?

    You are never going to lose any data when reading from a pipe, even if the system call is restarted. However, perhaps what could be happening is that your syswrite is getting interrupted and not writing all of $buffer. You really need to check what syswrite returns and re-buffer what didn't get written for a future write. Note that $wbytes != $rbytes doesn't necessarily mean that an error has occurred, especially when writing to a pipe. The write can be short if the pipe doesn't have enough space for the entire write buffer. The only return values from syswrite that signal an error are 0 and undef.

    Update 1: I'll have to double check if a return value of 0 from syswrite signals an error. It does with the system call write but it might not be the case with perl.

    Update 2: A common technique for handling an I/O buffer is as follows:

    my $buf; while (1) { my $nr = sysread(IN, $buf, 1024, length($buf)); unless ($nr) { ...handle EOF or error... }; ... if (length($buf)) { my $nw = syswrite(OUT, $buf, length($buf)); unless (defined($nw)) { ...handle error... } substr($buf, 0, $nw, ''); } ... }
      I may not be losing data but I am definitley doing something wrong with the data. The outpipe is connected to a program which expects records in a very defined and ordered format and complains very loudly if it doesn't get it. I can read my test file fine through other means but somehow when I use my perl the other program complains about the data. I am testing your suggestion and doing more checking on the write now...
Re: EINTR and sysread()
by pc88mxer (Vicar) on Apr 03, 2008 at 19:43 UTC
    To answer your other question about getting the child status, you shouldn't have to install a CHLD signal handler to do that. Perl will keep track of the child's exit status when you fork and create a pipe using open and will set $? when you close the pipe.

    In fact, if you install a signal handler for SIGCHLD, perl won't keep track of the exit status for you, so I would use such a signal handler only if there are other compelling reasons to do so. In that case you'll have to do something like this:

    our $pid; # remember the child's pid our $status; sub handle_sigchld { my $wpid = waitpid -1, WNOHANG; # use POSIX for WNOHANG if ($wpid == $pid) { $status = $?; } } ... $pid = open(INPIPE, "...|");

      In the course of my debugging I was getting a 'No child process' error so I assumed perl was reaping it automatically and discarding the return. I was probably just doing something else wrong.

      I did implement all of your suggestions and it works much better now.

      Thanks!