4

I am running a perl script on a Linux host. I'm trying to write a script that forks, where the child starts a program that takes forever and the parent times out after 5 seconds, killing the child. Here is what I have:

my $start = time();
my $timeOut = 5;

my $childPid = fork();
if ($childPid) {
    # I am the parent, and $childPid is my child
    while (kill(0, $childPid)) {
        if (time() > $start + $timeOut) {
            $numKilled = kill(SIGTERM, $childPid);
            printf("numKilled: %s\n", $numKilled);
        }
        sleep 1;
    }
}
else {
    # I am the child - do something that blocks forever
    `adb -s 410bf6c1 logcat`;
    exit;
}

Output:

aschirma@graphics9-lnx:~$ perl perl-fork-test.pl 
numKilled: 1
numKilled: 1
numKilled: 1
numKilled: 1
numKilled: 1
...

The behavior I expect is that I see "numKilled: 1" exactly once, and the child process (and any of its children) is killed after roughly 5 seconds. But I see from experiment that neither the child nor its children are getting killed. The kill(SIGTERM, $childPid) appears to do nothing.

How can I actually kill the child?

Adam S
  • 8,945
  • 17
  • 67
  • 103
  • 1
    Are you importing the `SIGTERM` constant from somewhere? I bet it evaluates to 0. – friedo Feb 21 '13 at 21:11
  • 1
    Are you using `use warnings; use strict;`? Why not? – melpomene Feb 21 '13 at 21:27
  • Hey guys, so I figured out that the problem I'm seeing is actually a bit different - kill is killing the child process but not any of its children. I don't want to just edit the whole question, should I start a new one? – Adam S Feb 21 '13 at 22:40
  • 1
    Slightly off-topic, but consider checking `if ( defined( $childPid ) )` before doing anything with your child process handle. `fork`returns `undef`if it fails to create the process. – antred May 03 '16 at 13:00

6 Answers6

6

Should be something like this. This isn't following best practices, but it should help you with your problem...

#!/usr/bin/perl

use warnings;
use strict;
use POSIX qw(:sys_wait_h);

my $timeOut = 5;
$SIG{ALRM} = \&timeout;
$SIG{CHLD} = 'IGNORE',
alarm($timeOut);

my $childPid = fork();
if ($childPid) {
    while(1) {
        print "[$$]: parent...\n"; 
        sleep(2); 
    }
}else {
    # I am the child - do something that blocks forever
    while(1){
        print "[$$]: child...\n";
        sleep(2);
    }
    exit;
}

sub timeout {
    print "killing $childPid\n";
    print "###\n" . `ps -ef | grep -v grep | grep perl` . "###\n";
    if ( ! (waitpid($childPid, WNOHANG)) ) {
        print "killing $childPid...\n";
        kill 9, $childPid;
        die "[$$]: exiting\n";
    }
}

OUTPUT:

$ forktest.pl
[24118]: parent...
[24119]: child...
[24118]: parent...
[24119]: child...
[24118]: parent...
[24119]: child...
killing 24119
###
cblack   24118 12548  0 14:12 pts/8    00:00:00 /usr/bin/perl ./forktest.pl
cblack   24119 24118  0 14:12 pts/8    00:00:00 /usr/bin/perl ./forktest.pl
###
killing 24119...
[24118]: exiting
chrsblck
  • 3,948
  • 2
  • 17
  • 20
4

From perldoc fork:

If you fork without ever waiting on your children, you will accumulate zombies. On some systems, you can avoid this by setting $SIG{CHLD} to "IGNORE" .

I was able to get the desired behavior when adding $SIG{CHLD} = 'IGNORE'; to the top of your code.

[ben@imac ~]$ perl test.pl
numKilled: 1
[ben@imac ~]$ 

Alternatively, adding waitpid($childPid, 0); after kill did the trick as well.

Ben
  • 1,200
  • 7
  • 13
1

Chances are that you are useing neither strict nor POSIX in your script, so SIGTERM is being interpreted as the bareword "SIGTERM", which isn't behaving in a useful way.

use strict to make this accidental bareword into an error, then use POSIX to pull in the SIGTERM constant.

  • Or just do `kill 'TERM', $pid` and don't bother with constants. – melpomene Feb 21 '13 at 21:29
  • See http://perldoc.perl.org/functions/kill.html: "You may also use a signal name in quotes." – melpomene Feb 21 '13 at 21:39
  • I did everything you suggested, but the behavior is the same. kill(0, $childPid) still constantly shows that the process exists, and I can confirm manually that it does exist. – Adam S Feb 21 '13 at 21:49
1

I am also seeing this behaviour, that kill 0 returns true even after the process was gone; I suspected you might be missing a call to waitpid (often done in a SIGCHLD handler) which would result in this, but even after adding it, the kill 0 continues to return true.

I suggest you use non-blocking waitpid instead of kill (and verify that the process actually does die on a SIGTERM signal - some may not, at least immediately). You may also want to try SIGINT after a while if SIGTERM didn't work, and in the last resort SIGKILL.

ysth
  • 96,171
  • 6
  • 121
  • 214
1

Try killing the process group (use -$pid):

kill TERM => -$pid;

See perlipc.

runrig
  • 6,486
  • 2
  • 27
  • 44
0

I think the problem is with the '0' you are passing as first argument of 'kill'. When I read the docs, they say that '0' will just check to see whether you can send a signal to the process, without sending it. In your case, you want to send the 'KILL' signal to the child process, so do this:

kill( 'KILL', $childPid );
  • 1
    kill(0, $childPid) is basically saying if the process is running then continue.. subsequently he is sending a SIGTERM – PodTech.io Jul 26 '17 at 12:40