问题
I don't know anything about signals, and only a little about pipes.
From the comments on zdim's answer here it seems that signals may interfere with pipe communication between parent and child processes.
I was told that, if you're using
IO::Select
and
sysread,
then the exit of a child process
could somehow mess up the behavior of IO::Select::can_read
,
especially if there are multiple child processes.
Please describe how to account for signals when using pipes? The below code is an example where signals are not accounted for.
use warnings;
use strict;
use feature 'say';
use Time::HiRes qw(sleep);
use IO::Select;
my $sel = IO::Select->new;
pipe my $rd, my $wr;
$sel->add($rd);
my $pid = fork // die "Can't fork: $!"; #/
if ( $pid == 0 ) { # Child code
close $rd;
$wr->autoflush;
for ( 1..4 ) {
sleep 1;
say "\tsending data";
say $wr 'a' x ( 120 * 1024 );
}
say "\tClosing writer and exiting";
close $wr;
exit;
}
# Parent code
close $wr;
say "Forked and will read from $pid";
my @recd;
READ:
while ( 1 ) {
if ( my @ready = $sel->can_read(0) ) { # beware of signals
foreach my $handle (@ready) {
my $buff;
my $rv = sysread $handle, $buff, ( 64 * 1024 );
warn "Error reading: $!" if not defined $rv;
if ( defined $buff and $rv != 0 ) {
say "Got ", length $buff, " characters";
push @recd, length $buff;
}
last READ if $rv == 0;
}
}
else {
say "Doing else ... ";
sleep 0.5;
}
}
close $rd;
my $gone = waitpid $pid, 0;
say "Reaped pid $gone";
say "Have data: @recd"
回答1:
Two things.
Writing to a pipe after the reader was closed (e.g. perhaps because the process on the other end exited) leads to a SIGPIPE. You can ignore this signal (
$SIG{PIPE} = 'IGNORE';
) in order to have the write to return errorEPIPE
instead.In your case, if you wanted to handle that error instead of having your program killed, simply add
$SIG{PIPE} = 'IGNORE';
If you have any signal handler defined (e.g. using
$SIG{...} = sub { ... };
, but not$SIG{...} = 'IGNORE';
or$SIG{...} = 'DEFAULT';
), long-running system calls (e.g. reading from a file handle) can be interrupted by a signal. If this happens, they will return with errorEINTR
to give the signal handler a chance to run. In Perl, you don't have to do anything but restart the system call that failed.In your case, you have no signal handlers defined, so this doesn't affect you.
By the way, you check $rv == 0
even when $rv
is known to be undefined, and you place the length of the data in @recd
instead of the data itself. In fact, it doesn't make much sense to use an array there at all. Replace
my @recd;
...
my $rv = sysread $handle, $buff, ( 64 * 1024 );
warn "Error reading: $!" if not defined $rv;
if ( defined $buff and $rv != 0 ) {
say "Got ", length $buff, " characters";
push @recd, length $buff;
}
last READ if $rv == 0;
...
say "Have data: @recd"
with
my $buf = '';
...
my $received = sysread($handle, $buf, 64 * 1024, length($buf));
warn "Error reading: $!" if !defined($received);
last if !$received;
say "Got $received characters";
...
say "Have data: $buf"
回答2:
Signals may also interrupt I/O functions, causing then to fail with $!
set to EINTR
. So you should check for that error and retry when it happens.
Not doing it is a common source of hard to find bugs.
来源:https://stackoverflow.com/questions/48569304/what-are-the-ways-that-signals-can-interfere-with-pipe-communication