5

If I have a long-running system command like apt-cache search <some query>, is there a way to forward a SIGINT sent via ^C on the command line to the parent Perl process in such a way that all child processes are reaped.

This example does not have the desired behavior. The signal is sent to the child process.

#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use autodie;

# long running command on ubuntu, produces a ton of output.
# replace with your favorite long running command
system("apt-cache search hi");

print("Perl did not catch SIGINT even with autodie\n");

I tried searching around for ways of capturing the pid of the child that would be created by system("apt-cache search hi &"), but couldn't find any, so I tried forking and execing the process and writing a signal handler. This doesn't work because apt-cache itself launches a few processes via the clone system call. Hand-rolling some logic to walk part of the process tree and clean up the

#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use autodie;

my $cpid;

$SIG{INT} = sub {
    kill 'KILL', $cpid;
    exit;
};

# long running command on ubuntu, produces a ton of output.
# replace with your favorite long running command
$cpid = fork;
if ($cpid == 0) {
    exec 'apt-cache', 'search', 'hi';
}

print "Perl did not catch SIGINT even with autodie\n";

I guess essentially what I want is either a way of determining whether a child process launched by system exited due to a signal like SIGINT so I can have the Perl script clean up after itself or a way of walking the child processes and reaping them in such a way that strange edge cases of process management are handled cleanly and portably.

Greg Nisbet
  • 6,710
  • 3
  • 25
  • 65
  • `IPC::Run` may be what you need, rather than system. Or alternatively, just set `SIGCHLD` to "IGNORE" so they'll automatically reap on exit. – Sobrique Dec 24 '15 at 21:34
  • Here is a way to detect if the command was aborted by the user: see [Name of signal number 2](http://stackoverflow.com/questions/29862178/name-of-signal-number-2) – Håkon Hægland Dec 24 '15 at 22:47
  • See also [How to start a process in its own process group?](http://stackoverflow.com/questions/5809034/in-linux-how-to-start-a-process-in-its-own-process-group-and-more) – Håkon Hægland Dec 24 '15 at 23:31

1 Answers1

6

Make the child the head of a process group, then send the signal to the whole process group.

#!/usr/bin/perl

use strict;
use warnings;
use autodie;

use POSIX qw( setpgid );

my $child_pid;

$SIG{INT} = sub {
    kill INT => -$child_pid if $child_pid;
    exit(0x80 | 2);  # 2 = SIGINT
};

my $child_pid = fork();
if (!$child_pid) {
    setpgid($$);
    exec 'apt-cache', 'search', 'hi';
}

WAIT: {
   no autodie;
   waitpid($child_pid, 0);
   redo WAIT if $? == -1 and $!{EINTR};
   die $! if $? == -1;
}

exit( ( $? >> 8 ) | ( 0x80 | ( $? & 0x7F ) ) );
Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
ikegami
  • 367,544
  • 15
  • 269
  • 518