Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Killing all processes launched using open()

by SmilingBuddha (Acolyte)
on Sep 16, 2004 at 05:08 UTC ( #391370=perlquestion: print w/replies, xml ) Need Help??

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

Dear Monks:

I am executing a shell script (on unix) using pipe so to have access to STDOUT and STDERR in filehandle $jobfh:

$jobfh->open("script.sh 2>&1 |");
This script may internally spawn many child processes. If I want to terminate all processes $jobfh->close does not work if the current process (launched by script.sh) is waiting for STDIN.

Thus, I need to kill the above process and all child (how do I create a list of all PIDs of script.sh?) processes of the above when I meet certain conditions in $jobfh. Can you please suggest how I can achieve killing the script (and all its child processes) when launching through open().


Thanks and best Regards.
SmilingBuddha

edit (broquaint): added formatting

Replies are listed 'Best First'.
Re: Killing all processes launched using open()
by tmoertel (Chaplain) on Sep 16, 2004 at 06:38 UTC
    If you want to "whack" the shell and all of its subprocesses, place the shell into its own process group. That way, when it forks off other processes, they will be in the same group, and you can send a signal to all of them in one fell swoop by killing zero minus the process group's ID. (You might want to check out the perlipc man page for more on this stuff.)

    Here's some code that shows how to do it. First, we need a troublemaker program to test out our mighty kung-fu on. Here's a shell script that spawns five subprocesses that all emit annoying output. We'll love telling this guy to "kick the oxygen habit" later.

    #!/bin/bash # File: forkage.sh for f in $(seq 1 5); do ( while : ; do echo $f; sleep 1; done ) & done wait
    And now the Perl code that runs the troublemaker, reads output from it (and its spawn), and after 20 lines kills 'em all off to rescue us from the annoying brood:
    #!/usr/bin/perl use warnings; use strict; $SIG{CHLD} = 'IGNORE'; # to make sure there are no zombies sub run_shell(@) { my $pid = open my $pipe, "-|"; die "can't open forked pipe': $!" unless defined $pid; if ($pid) { # parent return { pid => $pid, pipe => $pipe }; } # child open STDERR, ">&STDOUT" or die "can't dup 2>&1: $!"; setpgrp; # move into its own process group exec @_; # give control of child to command die "exec @_ failed: $!"; # should never get here } my $subshell = run_shell qw(./forkage.sh); my $output_from_subshell = $subshell->{pipe}; my $lines_left = 20; print while defined($_ = <$output_from_subshell>) and $lines_left--; kill "HUP", -$subshell->{pid};
    The run_shell function is where most of the magic happens. It serves as a wrapper around open that gives us more control over the resulting child process. It forks off a child process that has its STDOUT wired to $pipe, which the parent can read from later. The child then starts a new process group with the same ID as its own pid via setpgrp and finally uses exec to run the command we passed in as arguments.

    Meanwhile, the parent returns from the call to run_shell with a hash containing the pid of and pipe from the child. The parent grabs the pipe's filehandle and reads 20 lines from it, printing each one in passing. Then, getting sick of all the chatter, it sends a hang-up signal to every process in the child's process group, thus shutting their annoying behavior down.

    Hope this helps.

      This is truly MAGICAL!! Thank you so much!!!

      I must admit though that I'm still clueless as to why the lines under #child in run_shell() get executed if the routine returns before that in #parent. Or is it that run_shell() returns the hash and keeps executing until the end of the function?

      Best regards,
      SB

        Glad I could be of assistance!

        Regarding your question, the magic occurs in the first line of run_shell. When open is called with "-|" as its file argument, it does something special: It forks the current process into two processes (parent and child) that run in parallel.

        The parent process is the same as the original process. It receives in $pid the child's process ID, and its $pipe variable contains a filehandle that can read from the STDOUT of the child process.

        The child process – and this is where it gets interesting – is running in parallel to the parent. The only difference is that in the child, the pid returned from open and stored in its copy of $pid is zero.

        So now we have the parent and the child running in parallel. The first thing they both do is check the value of $pid to figure out which is parent and which is child. If $pid non-zero, the one process then knows that it is the parent, and it can package up the information it received about the child from open and return the package to the original caller. If $pid is zero, the other process knows it is the child and then ties its STDOUT to STDERR, starts a new process group, and then uses exec to replace its own process image with that of the command we gave to run_shell as arguments.

        Thus, when run_shell returns, we have the following situation:

        • The original process has become a parent, and it continues running on the line after the call to run_shell. It holds in its variable $subshell the pid of its new child and a pipe to read output from the child. The parent also knows that there is a process group of the same ID as the child's pid, and that all of the child's descendants will belong to it.
        • The child process has now been taken over by the command that we wanted to be executed. The new command, however, is running in the environment that we set up earlier, while we had control of the child. Thus its STDERR is tied to its STDOUT, and it is running in a process group that the child set up and that the parent knows about.

        I hope that explanation clears things up.

        Cheers,
        Tom

Re: Killing all processes launched using open()
by Zaxo (Archbishop) on Sep 16, 2004 at 05:27 UTC

    In general, you need to know what signal handlers are in place to know what to send. If the default SIGHUP handler is effective, you can trick script.sh with kill HUP, $cpid; and it will send an honest SIGHUP to all its children.

    After Compline,
    Zaxo

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://391370]
Approved by broquaint
Front-paged by Courage
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (1)
As of 2022-07-03 21:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?