4

I have a Perl script that starts some programs using the system() function, in sequential order. While the script is running and a program run is ongoing, I type CTRL + C. I expect the program to terminate (which happens), but I would also expect the script to terminate (which doesn't happen).

I've built a minimum working example. Here's the Perl script:

#!/bin/env perl


sub run_the_test {
  local($name, $_) = @_;

  print "Starting $name\n";
  return system("program.bash $name");
}


&run_the_test("test1");
&run_the_test("test2");
&run_the_test("test3");

and here's the external "program":

#!/bin/env bash

echo "Started $1"
sleep 3
echo "Finished $1"

I started the script and issued a CTRL + Z. I then did a ps -o pid,pgrp,cmd to list the process info. I can see that the script, the bash shell it invokes and the sleep command all belong to the same process group. While the sleep is executing I would also expect that to be the foreground process group. I've read up a bit on Linux, signals and the terminal and found that CTRL + C should send an interrupt to the entire foreground process group, thus terminating the script as well.

I've also tried using exec() instead of system(). This way the script does stop when triggering CTRL + C, but it doesn't run past test1.

Can anyone shed some light on what's going on here?

Tudor Timi
  • 7,453
  • 1
  • 24
  • 53
  • "*This way the script does stop when triggering CTRL + C*" - No, this way the script is gone by the time your program starts. `exec` only returns if it fails. – melpomene Nov 06 '17 at 12:33
  • 7
    Why are you writing perl4-style code? It's not 1993 anymore; please `use strict;` and `use warnings;` and don't call subs with `&`. – melpomene Nov 06 '17 at 12:34
  • 2
    See also [Perl forward SIGINT to parent process from `system` command](https://stackoverflow.com/q/34457955/2173773) and [Propagation of signal to parent when using system](https://stackoverflow.com/q/34795478/2173773) – Håkon Hægland Nov 06 '17 at 13:57
  • @melpomene It's a snippet of some legacy Perl script I have that I stripped down. – Tudor Timi Nov 06 '17 at 17:12

2 Answers2

7

It's good that you've investigated and understood the process groups. Explaining those is usually the hard part of a question like this. So you know that the interrupt signal SIGINT is supposed to be sent to all processes in the process group, including your perl process.

But SIGINT isn't always fatal. It can be caught or ignored. The system() function ignores SIGINT while the child process is running. This feature makes it possible for you to interrupt a child process that has gone bad, without killing the parent too.

In perlfunc, the description of system() says:

Since "SIGINT" and "SIGQUIT" are ignored during the execution of "system", if you expect your program to terminate on receipt of these signals you will need to arrange to do so yourself based on the return value.

@args = ("command", "arg1", "arg2");
system(@args) == 0
    or die "system @args failed: $?"

This is what you need to do. Check the return value of system(), and take whatever action you think is appropriate when a child process fails. If you want to distinguish SIGINT from other kinds of failures, the next paragraph in perlfunc will help:

If you'd like to manually inspect "system"'s failure, you can check all possible failure modes by inspecting $? like this:

if ($? == -1) {
    print "failed to execute: $!\n";
}
elsif ($? & 127) {
    printf "child died with signal %d, %s coredump\n",
        ($? & 127),  ($? & 128) ? 'with' : 'without';
}
else {
    printf "child exited with value %d\n", $? >> 8;
}

For example you could do this in run_the_test:

my $r = system("program.bash $name");
die "Test interrupted, giving up" if ($? & 127) == 2;
return $r;
  • 4
    If you don't want to hardcode `2`, you can `use POSIX qw(SIGINT);` to import a `SIGINT` constant. – melpomene Nov 06 '17 at 13:38
  • 2
    Another option is to use `fork` and `exec` to launch the child process instead of `system`. – salva Nov 06 '17 at 13:48
  • @salva I read in the description of `system()` that it's equivalent to a `fork()`, `exec()` and `wait()` call. – Tudor Timi Nov 06 '17 at 15:14
  • 5
    @TudorTimi that's a broad outline of what `system()` does. It's good enough to understand the relationship between the processes. It omits details like signal manipulation. The point of salva's comment is if you implement a `system()`-like function yourself, you can change the details. –  Nov 06 '17 at 15:41
0

Had similar problem, using system and piping. CTRL-C would not work. Following solved it:

use sigtrap qw/die normal-signals/;

Will Budic
  • 21
  • 4