signal handler is not called while piped open is active

G

Glenn

Given the following simple script:

#!/usr/bin/perl -w --

my $terminate = 0;
my $failed = 0;

sub print_signal {
my $signame = shift;
$terminate = 1;
die "got a SIG$signame signal\n";
}

$SIG{INT} = \&print_signal;
$SIG{QUIT} = \&print_signal;
$SIG{TERM} = \&print_signal;

open PIPE, '|-', "sleep 30";
print PIPE "nighty night\n";
$failed |= ! close PIPE;
print "OS error: $!\n";
print "Child error: $?\n";
if (!$terminate) {
print "sleeping after PIPE handling ...\n";
sleep 30;
}
print "terminated by signal\n" if $terminate;
print "failed: $failed\n";

Why is the signal hander not called if I interrupt the script with
SIGINT or SIGQUIT while the PIPE is open? I don't see this behavior
documented anywhere. I expect the %SIG settings to be obeyed as
documented, both during and after the PIPE processing.

Here's the complete behavior, in detail, when the signal is sent while
the PIPE i/o is underway. In each case, I run the script in a shell,
in the foreground.

SIGINT from keyboard => kills PIPE i/o but does not call signal
handler
SIGQUIT from keyboard => kills PIPE i/o but does not call signal
handler
SIGINT from another terminal => has no effect whatsoever
SIGQUIT from another terminal => has no effect whatsoever
SIGTERM from another terminal => signal handler is called

Why is there any difference at all depending on where the signal comes
from, and on what signal is sent?

How do we get these blatant failures documented in the Perl manual?

Note that the signal handlers are clearly still in play after the PIPE
i/o is killed, because a signal sent from any source during the
script's own later sleep() call (either SIGINT, SIGQUIT, or SIGTERM)
does kill the script.

Under this circumstance, how do I get a reliable indication that a
signal was sent (and not just that the PIPE handling failed for quite
possibly some other reason)?

I'm using Perl 5.8.8 for my testing.
 
C

C.DeRykus

Given the following simple script:

#!/usr/bin/perl -w --

my $terminate = 0;
my $failed    = 0;

sub print_signal {
    my $signame = shift;
    $terminate = 1;
    die "got a SIG$signame signal\n";

}

$SIG{INT}  = \&print_signal;
$SIG{QUIT} = \&print_signal;
$SIG{TERM} = \&print_signal;

open PIPE, '|-', "sleep 30";
print PIPE "nighty night\n";
$failed |= ! close PIPE;
print "OS error:  $!\n";
print "Child error:  $?\n";
if (!$terminate) {
    print "sleeping after PIPE handling ...\n";
    sleep 30;}

print "terminated by signal\n" if $terminate;
print "failed:  $failed\n";

Why is the signal hander not called if I interrupt the script with
SIGINT or SIGQUIT while the PIPE is open?  I don't see this behavior
documented anywhere.  I expect the %SIG settings to be obeyed as
documented, both during and after the PIPE processing.

Here's the complete behavior, in detail, when the signal is sent while
the PIPE i/o is underway.  In each case, I run the script in a shell,
in the foreground.

SIGINT from keyboard => kills PIPE i/o but does not call signal
handler
SIGQUIT from keyboard => kills PIPE i/o but does not call signal
handler
SIGINT from another terminal => has no effect whatsoever
SIGQUIT from another terminal => has no effect whatsoever
SIGTERM from another terminal => signal handler is called

Why is there any difference at all depending on where the signal comes
from, and on what signal is sent?

How do we get these blatant failures documented in the Perl manual?

Note that the signal handlers are clearly still in play after the PIPE
i/o is killed, because a signal sent from any source during the
script's own later sleep() call (either SIGINT, SIGQUIT, or SIGTERM)
does kill the script.

Yes, I see evidence of the INT handler if,
for instance, I add:

END { sleep 5; } # --> got a SIGINT signal

I'm not sure why only the signal handler output is
seen only in the parent process however.
Under this circumstance, how do I get a reliable indication that a
signal was sent (and not just that the PIPE handling failed for quite
possibly some other reason)?

I'm using Perl 5.8.8 for my testing.

That should reliably be returned by your
check of $! and $? after close().

On 5.10.1 and FreeBSD, your child $? check was:

Child error: 2

which is waitpid output indicating that child
termination was due to a SIGINT.
(${^CHILD_ERROR_NATIVE} is also 2.)
See perldoc -f waitpid and perldoc -f wait.

You can inspect the child status to see if there
was signal termination:

$signal_termination = $? & 127
See perldoc -f system
 
G

Glenn

Yes, I see evidence of the INT handler if,
for instance, I add:

    END { sleep 5; }  # --> got a SIGINT signal

I'm not sure why only the signal handler output is
seen only in the parent process however.

The child process is running /bin/sleep, not Perl.
That should reliably be returned by your
check of $! and $? after close().

On 5.10.1 and FreeBSD, your child $? check was:

   Child error: 2

which is waitpid output indicating that child
termination  was due to a SIGINT.
(${^CHILD_ERROR_NATIVE} is also 2.)
See perldoc -f waitpid and perldoc -f wait.

You can inspect the child status to see if there
was signal termination:

   $signal_termination = $? & 127
See perldoc -f system

I was afraid the test code would elicit that kind of response. The
point is, I'm not really interested in the child status -- that's just
a red herring, something I'm reporting in this test program only
because it's something I can probe. What I care about is whether or
not the parent process received a signal. In this testing, the child
happens to receive the same signal if I generate it from the keyboard,
simply because (I'm guessing) it's sent to the entire process tree (by
the shell). But if I send the signal from a different terminal,
targeted specifically at the parent process, then the child process
won't be receiving it, and thus its exit status is irrelevant.
 
C

C.DeRykus

The child process is running /bin/sleep, not Perl.

Oh d'oh... exec happens immediately.
I was afraid the test code would elicit that kind of response.  The
point is, I'm not really interested in the child status -- that's just
a red herring, something I'm reporting in this test program only
because it's something I can probe.  What I care about is whether or
not the parent process received a signal.  In this testing, the child
happens to receive the same signal if I generate it from the keyboard,
simply because (I'm guessing) it's sent to the entire process tree (by
the shell).  But if I send the signal from a different terminal,
targeted specifically at the parent process, then the child process
won't be receiving it, and thus its exit status is irrelevant.

Hm, the program does ignore signals
(except KILL and STOP of course) sent to
just the parent from another terminal.

However, and I'm not sure why, if I signal
the process group - not just the parent,
it works:

kill -2 -11730 # from another terminal

The result in the running program:

parent=11730; # added debug print

got a SIGINT signal
 
S

Skye Shaw!@#$

<snip code>

$SIG{INT}  = \&print_signal;
$SIG{QUIT} = \&print_signal;
$SIG{TERM} = \&print_signal;

open PIPE, '|-', "sleep 30";
print PIPE "nighty night\n";
$failed |= ! close PIPE;

<snip code>

Why is the signal hander not called if I interrupt the script with
SIGINT or SIGQUIT while the PIPE is open?  I don't see this behavior
documented anywhere.  
I expect the %SIG settings to be obeyed as
documented

What docs are you referring to?

Modern perls can defer the delivery of signals to the %SIG handlers.
Here it appears that when close() is blocking on a handle attached to
a child process perl never delivers the INT and QUIT signals.

In similar cases these signals are delivered: a fork()/wait()
combination, blocking for IO, etc...

Maybe this behavior underscores the nice convenience Perl provides via
a piped open()? Not only do you not have to pipe(), dup(), fork(),
exec() and wait(), but you can almost always reap your child without
having to get caught up in the signal world.
Here's the complete behavior, in detail, when the signal is sent while
the PIPE i/o is underway.  In each case, I run the script in a shell,
in the foreground.

SIGINT from keyboard => kills PIPE i/o but does not call signal
handler
SIGQUIT from keyboard => kills PIPE i/o but does not call signal
handler
SIGINT from another terminal => has no effect whatsoever
SIGQUIT from another terminal => has no effect whatsoever
SIGTERM from another terminal => signal handler is called

Given the close() issue above this sounds "right".

The parent ignores all but SIGTERM and, in the first two cases, the
signal was sent to the process group so the child receives it and the
parent's close() returns.
Why is there any difference at all depending on where the signal comes
from, and on what signal is sent?

Signals are odd beasts.
 
C

C.DeRykus

What docs are you referring to?

Modern perls can defer the delivery of signals to the %SIG handlers.

Here it appears that when close() is blocking on a handle attached to
a child process perl never delivers the INT and QUIT signals.

Yes, perl ignores INT and QUIT in the parent so they'll
kill only the child and not the parent. As I think about
my earlier response, that's why an INT to the process
group works and you'll see the close() complete with a
child status set to 2, ie., INT on most Unix systems.

Perl does the same thing with system() calls by design
so the child will terminate and the parent can then
check its status. perldoc -f system.

In similar cases these signals are delivered: a fork()/wait()
combination, blocking for IO, etc...

Yes, and, in the case of I/O, the signal delivery
may be deferred though. From perlipc:

With the "deferred" scheme the handler is not
called immediately, and if Perl is using system's
"stdio" library that library may re-start the "read"
without returning to Perl and giving it a chance to
call the %SIG handler.

I also notice that perl now may fail and set $! to
EINTR in the case of restartable system calls like
read according to perlipc. In the OP's case for
instance, sending a SIGTERM to the parent with
a slight tweak to his handler will demo this:

die "got a SIG$signame signal \$!=$!\n";

--> got a SIGTERM signal $!=Interrupted signal call

Maybe this behavior underscores the nice convenience Perl provides via
a piped open()? Not only do you not have to pipe(), dup(), fork(),
exec() and wait(), but you can almost always reap your child without
having to get caught up in the signal world.


downside is that
Given the close() issue above this sounds "right".

The parent ignores all but SIGTERM and, in the first two cases, the
signal was sent to the process group so the child receives it and the
parent's close() returns.


Signals are odd beasts.

Really odd and needs lot of careful reading
of perlipc, etc.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top