'IPC::Run - Detection of premature child exit and closed pipes
I would like to use IPC::Run to communicate with child via child's STDIN, STDOUT and STDERR (start, pump, finish). It seems to work.
I would like to know how to detect
- premature child exit (e.g. caused by errors)
- pipes closed by the child
Solution 1:[1]
The pump
throws a die
on errors, or writes its message to STDERR
if "called after all harnessed activities have completed." See right before ROUTINES section and pump
itself. The second case can come about if the child exited. So wrap the pump
call in eval
, and also convert warnings to die
to catch both cases
if ($talk_to_child)
{
eval {
local $SIG{__WARN__} = sub { die "pump WARNING: @_" };
pump $harness;
};
if ($@) {
print $@;
$talk_to_child = 0;
};
}
# ... and eval {} for finish()
But this alone won't cut it: when a parent tries to write to a child that exited it gets a SIGPIPE
, which outright terminates the process. The same goes when a child closes streams and the parent attempts to write. So also install a signal handler for SIGPIPE
$SIG{PIPE} = sub {
say "$_[0]: $!";
$talk_to_child = 0; # global
};
so that the parent survives the SIGPIPE
. Consider local-izing the change to the global %SIG by doing local $SIG{PIPE} = ...
instead, a good practice even just on general principle. On the other hand, there's good sense in globally handling a signal that can terminate you out of blue (even in cases where the handler may decide to exit).
The eval
is still needed even as $SIG{PIPE}
is handled since pump
throws, too.
These together take care of all tests I came up with, practically as they stand. Still, some processing in the handler and in eval
is needed to distinguish cases of interest if that is wanted.
If this adds up to too much another way is to check before each call. See this post for one-line checks (wrapped in subs) of: (1) whether a child is running, using result
, and (2) whether "there are open I/O channels or active processes", using pumpable
.
I think that you want both, and also throw in the SIGPIPE
handler. That should cover it.
I cannot be more specific here since the question doesn't provide specifics.
Solution 2:[2]
Update: Thanks to @zdim for reminding me to check the SIGPIPE
signal. Here is an update of my answer that also checks SIGPIPE
:
I did a simple test using start
, pump
, and finish
. Here is the main script p.pl
that I used:
use feature qw(say);
use strict;
use warnings;
use IPC::Run;
my $child_in;
my $child_out;
my $child_err;
my $child_name = shift;
my $harness = eval {
IPC::Run::start [ $child_name ], \$child_in, \$child_out, \$child_err;
};
if ( $@ ) {
chomp $@;
die "Caught exception: '$@'";
}
for (1..2) {
$child_in = "Joe$_\n";
say "Parent sleeping for 1 second..";
sleep 1;
eval {
local $SIG{PIPE} = sub {
die "Parent received SIGPIPE. "
. "Child is either dead or has closed its input pipe\n";
};
say "Sending data to child..";
my $result = $harness->pump;
say "IPC::Run::pump() returned: ", $result ? "TRUE" : "FALSE";
};
if ( $@ ) {
chomp $@;
say "IPC::Run::pump() failed: '$@'";
last;
}
say "\$child_in = '$child_in'";
say "\$child_out = '$child_out'";
}
say "Finishing harness..";
my $res = eval {
local $SIG{PIPE} = sub {
die "Parent received SIGPIPE. "
. "Child is either dead or has closed its input pipe\n";
};
$harness->finish;
};
if ( $@ ) {
chomp $@;
die "IPC::Run::finish() failed: '$@'\n";
}
printf "IPC::Run::finish() returned: '%s'\n", $res ? "TRUE" : "FALSE";
chomp $child_out;
say "STDOUT from child: '$child_out'";
chomp $child_err;
say "STDERR from child: '$child_err'";
say "Child returned exit code: ", $harness->result;
say "Parent exited normally.."
I used three different child scripts:
child.pl:
#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;
my $reply = <STDIN>;
chomp $reply;
say "Hello $reply";
my $reply2 = <STDIN>;
chomp $reply2;
say "Got second reply: $reply2";
exit 0;
and output:
$ p.pl child.pl
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() returned: TRUE
$child_in = ''
$child_out = ''
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() returned: TRUE
$child_in = ''
$child_out = ''
Finishing harness..
IPC::Run::finish() returned: 'TRUE'
STDOUT from child: 'Hello Joe1
Got second reply: Joe2'
STDERR from child: ''
Child returned exit code:
Parent exited normally..
child2.pl:
#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;
my $reply = <STDIN>;
chomp $reply;
say "Hello $reply";
die "Child exception\n";
and output:
$ p.pl child2.pl
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() returned: TRUE
$child_in = ''
$child_out = ''
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() failed: 'Parent received SIGPIPE. Child is either dead or has closed its input pipe'
Finishing harness..
IPC::Run::finish() failed: 'Parent received SIGPIPE. Child is either dead or has closed its input pipe'
child3.pl:
#! /usr/bin/env perl
use strict;
use warnings;
close \*STDIN;
close \*STDOUT;
close \*STDERR;
sleep 5;
exit 2;
and output:
$ p.pl child3.pl
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() failed: 'ack Parent received SIGPIPE. Child is either dead or has closed its input pipe'
Finishing harness..
IPC::Run::finish() failed: 'Parent received SIGPIPE. Child is either dead or has closed its input pipe'
So for these tests, it seems that the SIGPIPE
signal can be used to check if a child is a alive or has closed its input pipe. Note that if you try to call pump()
after a child has exited, the previous output from the child is lost, see the child2.pl
example.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |