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

I have a series of test scripts that launches server subprocesses of different kinds - httpd, HTTP::Server::Simple, etc. A test script might launch multiple instances of each type of server. I'd like to behave well and kill all these children before the test exits. But it's a pain to keep track of all the child pids, especially when the code that creates the subprocess is many levels down from the test script itself.

Is there any way to automatically kill all the child processes (and their descendents) created by a script? For example, can I catch each subprocess creation somehow and record the pid in a global array? Then I could kill each pid in an END block or a Guard.

At first I tried using Proc::ProcessTable to find all the processes where ppid = $$, then kill all those processes and their children. But that doesn't work, because some servers, like httpd, launch in such a way that the parent httpd process ends up with ppid = 1.

Thanks!

  • Comment on automatically killing all child processes created in a script

Replies are listed 'Best First'.
Re: automatically killing all child processes created in a script
by doom (Deacon) on Aug 22, 2009 at 03:05 UTC

    This is probably not The Right Way, but I had the thought the other day that one way to deal with this sort of problem is to run the child processes under fictitious names that include a label to identify the parent. So, for example, you might run httpd as "httpd_by_$$", then you could grep through the process list for "by_$$" to find pids you want to zap.

    Note: if you're using the list form of exec or system, the first entry in the list is the name that appears in the process listing, as opposed to the actual name of the program you're launching. Here's a somewhat hacky proof-of-concept (single child case):

    my $progname = 'emacs'; my $label = "_by_" . $$; my $proglabel = $progname . $label; my @args = ( $0 ); if ( my $pid = fork ) { # this is parent code # pause, then zap the child sleep 10; my @lines = grep{ m/$label/ } qx{ ps axw }; my @pids = map{ ( split " ", $_ )[0] } @lines; foreach my $pid ( @pids ) { my $status = kill 1, $pid; ($DEBUG) && print STDERR "Tried to kill (1) pid $pid, status: $sta +tus \n"; } } else { # this is child code die "cannot fork: $!" unless defined $pid; exec { $progname } ( $proglabel, @args ); }
Re: automatically killing all child processes created in a script
by Marshall (Canon) on Aug 21, 2009 at 22:41 UTC
    I may be mis-understanding something here, but normal way would be to install handler for $SIG{CHLD} in the parent, like:
    #kill zombie children $SIG{CHLD} = 'reaper'; while (1) { server code to accept new clients and fork()... } sub reaper { my ($kidpid); while (($kidpid = waitpid(-1,WNOHANG))>0 ) { print "Reaped $kidpid\n"; } close $client; }
    Update: I think: use POSIX qw(sys_wait_h); is needed too.
      That waits for all the children to finish - clearly quite the opposite of what the OP wants.
        This does not wait for all children to finish. It processes multiple children from the same SIG CHLD signal if they are "ready". If the parent "dies", then you as a child eventually get this CHLD signal and you get removed from the process table. This is a non-blocking situation, that's what WNOHANG does - it doesn't "hang up" waiting for anybody. If say 3 of 20 children have CHLD signal at the same time, then you have to process those 3 children at once. After that, then the other 17 is a different story. So no, this will not "hang" and wait for all children to finish. This just says, "hey at least one and maybe more than one children" are in CHLD signal state". "At least one" does not mean "all", and it doesn't mean wait for all children to be in CHLD signal state!

        Ok, then if you are launching servers that spawn other child processes, then you should keep track of the PID's or names of those things. If you have permission level that allows you to kill that server, then its children will also be killed if you have right signal handling installed.

        Correct - the traditional reaper is the opposite of what I want. The processes are persistent servers and will never die on their own, so I'd be waiting forever.
Re: automatically killing all child processes created in a script
by JavaFan (Canon) on Aug 21, 2009 at 23:13 UTC
    You could let the parent process become process leader of a new session, and then kill the process group by using a negative signal to kill itself. See POSIX::setpgid/POSIX::setsid, perlipc and perldoc -f kill.
      But if httpd, for example, properly daemonizes and becomes the leader of its own new session, this method won't catch httpds I create, right?
        True.

        And if you cannot record the pid of a grandchild process, there's always a way for the grandchild to distance itself from the family tree. Double forking will leave its parent PID to be 1, so there's no way to find it by tracing the process tree. And it can escape from a process group.

Re: automatically killing all child processes created in a script
by Illuminatus (Curate) on Aug 22, 2009 at 01:35 UTC
    kill 9, 1;
    as root ought to get them all. Of course, you will have to deal with a few side-effects...

    NO WAIT -- don't actually do it

    fnord

Re: automatically killing all child processes created in a script
by ig (Vicar) on Aug 23, 2009 at 06:08 UTC

    The easiest way might be to run your tests on a virtual server and shut down the virtual server when you are done.