Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CTRL+C and Perl's system() function - script not getting interrupted

Tags:

linux

perl

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?

like image 906
Tudor Timi Avatar asked Sep 07 '25 20:09

Tudor Timi


1 Answers

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;