7

I have the following problem, that I have reconstructed in those two mini perl scripts. This is the main script:

#!/usr/bin/perl
$SIG{INT} = \&signal_handler_one;
open(my $pipe, "|-", "/home/pa/Desktop/POC2");
close $pipe;
sub signal_handler_one{
    print "This is expected to print\n";
}

On the third line it opens a pipe to this script:

#!/usr/bin/perl
$SIG{INT} = \&signal_handler_two;
sleep(10);
sub signal_handler_two{
    print "This isn't expected to print\n";
}

The problem is that when I start the first script and then send SIGINT to it while it is closing the pipe on line 4, the signal_handler_two is fired instead of signal_handler_one. Why does it behave like this? Is there any way around this (my goal is to get signal_handler_one to execute).

Edit: I originally sent the signal on the terminal using Ctrl+C, which causes "This isn't expected to print" to print. But when I send the signal using kill to the parent process from another terminal, it just ignores it.

Edit 2: I eventually solved it by not using open to get the pipe, but by manually forking, execing and then waiting on the child instead of just calling close. Now everything seems to work fine. It seems that that behaviour was specific to my environment, but if anyone could reproduce that same error, please let me know.

Void
  • 113
  • 8
  • 1
    So the second script is `POC2`? How do you send the signal, exactly? How do you know that it's while the first script is closing the pipe? – Borodin Apr 30 '16 at 20:15
  • I send it by Ctrl + C on terminal. The close call blocks until the second process terminates so there's a 10 second window for me to send Ctrl+C at the right time. Yes, POC2 is the second script. – Void Apr 30 '16 at 20:18
  • 1
    I think you'll find that Ctrl-C sends the signal to the most recently active process. You can be more selective by getting the first process to report its PID with `print "$$\n"` and then `kill -s INT 1234` on the command line, replacing `1234` with the actual PID – Borodin Apr 30 '16 at 20:26
  • Yes, thank you, that makes sense. I edited the question. But still - why does the first process ignore the signal? In reality I have a much bigger script and I want it to respond to Ctrl+C properly. But it just sends SIGINT to the other process, which doesn't handle it properly and then the first process just hangs... – Void Apr 30 '16 at 20:31
  • 1
    The first process isn't ignoring the signal. Ctrl-C sends the signal to the current foreground process, which is the second one. You could do something fancy like write the signal handler of the second process so that it sends a signal to its parent. But you shouldn't really be writing code that uses Ctrl-C as an official means of interaction – Borodin Apr 30 '16 at 20:51
  • This isn't about Ctrl-C. The problem is that when I kill the parent process using SIGINT from another terminal, nothing happens. Shouldn't the parent process wake up and fire the signal handler when it gets SIGINT? – Void Apr 30 '16 at 20:57
  • 1
    See also: [Propagation of signal to parent when using system](http://stackoverflow.com/questions/34795478/propagation-of-signal-to-parent-when-using-system) – Håkon Hægland Apr 30 '16 at 21:36
  • Thank you. Now I understand why _signal_handler_two_ got fired. Any ideas on why _signal_handler_one_ never executes? Even if I send SIGINT directly to the parent process using kill? – Void Apr 30 '16 at 21:47
  • I do get your behavior, on CentOS 7 with v5.16. I have a rough idea of what is causing it but cannot look into it now. Since this is interesting to me I will try to put it together, and then post if it is still of interest. – zdim May 01 '16 at 10:43

1 Answers1

3

I cannot reproduce the behavior that you are observing. When I press CTRL-C in the terminal, both the child and the parent immediately receives SIGINT:

use diagnostics;
use feature qw(say);
use strict;
use warnings;

$SIG{INT} = sub {  say "This is expected to print"; die };
my $pid = open ( my $pipe, "|-", "script.pl" );
say "PID = $pid";
eval {
    say "Closing..";
    my $close_ok = close $pipe; # Note "close" here waits for child to exit
    if ( ! $close_ok ) {
        say "Error closing: $!";
    }
    else {
        say "Close done.";
    }
};
if ( $@ ) {
    say "Parent caught SIGINT.";
}

where script.pl is:

#! /usr/bin/env perl

use feature qw(say);
use strict;
use warnings;

$SIG{INT} = sub { die };
eval {
    say "Sleeping..";
    for (1..5) {
        sleep 1;
        say $_;
    }
};
if ( $@ ) {
    say "Child caught SIGINT.";
    exit;
}

the output of running the first program in the terminal (gnome-terminal on Ubuntu 16.04) is:

PID = 1746
Closing..
Sleeping..
1
2
^CThis is expected to print
Child caught SIGINT.
Parent caught SIGINT.
Uncaught exception from user code:
    refcnt: fd -1 < 0

Note that there is an uncaught exception refcnt: fd -1 < 0. I have no idea what that is. Maybe because close did not succeed?

Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
  • I guess there is something wrong with my environment then. If I execute your code it doesn't print "Parent caught SIGINT.". – Void May 01 '16 at 09:48
  • @Void, no, your environment is correct. You don't see the "Parent" line because you send your SIGINT during the `close` [which ignores SIGINT](http://stackoverflow.com/a/4719492/132382). This answer is doing something different — sending the SIGINT too early or too late, e.g. – pilcrow May 02 '16 at 18:09
  • @pilcrow I was sure I was sending the signal during `close`. You can see on the output that it says `Closing..` and then it waits for the program to exit. This must happen inside `close` since the print statement after the `close` is not shown.. After 5 seconds, the `close` will return if I do not hit `CTRL-C`.. This is how I interpret the output. I am curious why you mean it does not send `SIGINT` during `close`? – Håkon Hægland May 02 '16 at 18:56
  • @HåkonHægland, neither the OP nor I can reproduce the behavior you claim, even if the _sleep_ is made much greater. Your output is consistent with sending the SIGINT right before _close_ is called. Also, the linked answer describes what _strace_ (e.g.) will confirm: perl ignores SIGINT during a close on a piped filehandle. (Finally, your code as posted cannot be the complete, accurate code you're running. The missing shebang line in _script.pl_ means that the piped open will fail when _script_ is fed through _/bin/sh_) – pilcrow May 02 '16 at 19:03
  • @pilcrow See my updated answer. I have now included the shebang.. and some more print statements from the child.. As far as I can see from the output, the SIGINT must be sent during the close. You can see it is printing `1`, `2`, and then I press `CTRL-C` – Håkon Hægland May 02 '16 at 19:16
  • @HåkonHægland, hmm. I think you have a new, standalone question here: why does this code behave one way for me but differently for everyone else? Maybe a devel version of perl? Maybe a perlio bug? Don't know. – pilcrow May 02 '16 at 19:30
  • @pilcrow I sent a bug report: [Sending SIGINT signal to script while inside close()](https://rt.perl.org/Public/Bug/Display.html?id=128056) – Håkon Hægland May 02 '16 at 20:21