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

Hello gurus-

I have been wracking my brain with this one, and can't get it to perform quite how I want it to. My goal is to have a script that runs a system call and if it receives a SIGINT (CTRL+C), kill the system call along with the pl script. Here is an oversimplified example:
$SIG{INT}=\&myhand; print "Parent pid = $$ \n"; $pid = open(CMD, "ping google.com -n 15 |"); print "Ping running... pid = $pid \n\n"; # uncommenting this line breaks ability to SIGINT #print <CMD>; # keep the script running; only the SIGINT will exit while (1){sleep 1}; sub myhand() { print "Parent $$ caught SIGINT.\n"; print "Parent $$ will KILL $pid.\n"; kill 9, $pid; exit(0); }
The script works fine to kill the ping until you uncomment the print. Then it will not accept the SIGINT until the ping has completed.

It seems like this would be a pretty common request. I know that system() calls will ignore SIGINT. Am I making this too complicated, or not complicated enough? I even tried with fork(), but I don’t think I am doing it properly:
#parent if ($forkpid = fork) { local $SIG{INT} = sub { print "i'm quitting"; kill 9, $pid; exit; }; waitpid($forkpid, 0); #child } else { die "cannot fork: $!" unless defined $forkpid; $SIG{INT} = sub { print "i'm quitting child"; close(CMD); kill 9, $p +id; exit; }; $pid = open(CMD, "ping google.com -n 15 |"); # exec ("ping google.com -n 15") or die "Can't exec: $!\n"; } print "Done with fork. Here are my results:"; print <CMD>;
I seem to have the same problem if I use open(). If I use exec(), it looks like the ping command itself accepts the CTRL+C?

I would be extremely appreciative of any assistance you could offer!

Replies are listed 'Best First'.
Re: SIGINT and system calls
by Zaxo (Archbishop) on Jan 20, 2005 at 05:01 UTC

    You have a zombie problem. Call wait before exit in the signal handler.

    You don't say what version of perl you use, but "safe" signals might be involved. That could prevent Ctrl-C from interrupting a blocking read in <CMD>. Your code looks fine.

    You can break up the print <CMD>; by calling sysread, perhaps inside select.

    # replaces print <CMD>; vec( my $rin, fileno(*CMD), 1) = 1; while (select my $rd = $rin, undef, undef, undef;) { my $bytes = sysread *CMD, my $buf, 4096; print $buf if 0 < $bytes; last if 1 > $bytes; } # wait added to lay the zombies sub myhand() { print "Parent $$ caught SIGINT.\n"; print "Parent $$ will KILL $pid.\n"; kill INT, $pid; waitpid $pid; exit 0; }
    It's a potential problem that the signal handler is set before the child is launched. That makes the child inherit a handler that is clearly meant for the parent. If you changed the kill signal to the more proper SIGINT, the child would try to signal a nonexistent child.

    After Compline,
    Zaxo

      You monks are great! I am running ActiveState 5.8.6 on Windows. So here's what it translates into:
      $SIG{INT}=\&myhand; print "Parent pid = $$ \n"; $pid = open(CMD, "ping google.com -n 15 |"); print "Ping running... pid = $pid \n\n"; # replaces print <CMD>; vec( my $rin, fileno(*CMD), 1) = 1; while (select (my $rd = $rin, undef, undef, undef)) { my $bytes = sysread *CMD, my $buf, 4096; print $buf if 0 < $bytes; } print "I am finished with my task. My results are: \n"; # where are my results stored if I want them in a variable? # print $results; # wait added to lay the zombies sub myhand() { print "Parent $$ caught SIGINT.\n"; print "Parent $$ will KILL $pid.\n"; kill 9, $pid; waitpid $pid, 0; exit 0; }
      I am not sure if I completely understand what is happening with the output that you have shown -- it appears to me that it stays in the while loop and I can't get it to "I am finished with my task."

      Also, do one of those variables currently have the complete results (of the ping output in this case) or will I need to build a new variable if I want it STDOUT as well as in a variable to pass to another routine later?

      Thanks!!

        I'm surprised that that works on windows. Both select and signals are pretty unixy for the windows ports to emulate. I didn't know they could do that at all.

        You're right that I omitted a way out of the while select loop. Probably last if 1 > $bytes; at the end of the loop would do it. I'll edit that into the original.

        Update: Yes, concatenation in the loop would work fine, or if you wanted to get fancy I think you could say,

        my $result; while (select my $rd = $rin, undef, undef, 60) { my $bytes = sysread *CMD, substr($result, -1), 4096; last if 1 > $bytes; }
        Or else you could use the offset argument of sysread to position at the end of $result.

        The 60 second timeout is new, too. Adjust to taste.

        After Compline,
        Zaxo