Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Stopping subprocesses

by James Board (Beadle)
on Aug 24, 2010 at 00:07 UTC ( [id://856826]=perlquestion: print w/replies, xml ) Need Help??

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

I'm on a linux machine. I have a script called B, which runs another Perl script called C. The trimmed version is
#!/usr/bin/perl if (!fork() { system("C"); sleep(100000); } sleep(100000);

If I run B and kill it with a CTRL-C, that also kills C. However, if I kill B with the unix "kill PID" command, it doesn't kill C. What I really want to do is call B from another Perl script A, and A kills B (with system("kill PID") or whatever), thereby killing C as well.

My question, how can I kill script B from script A, and also have it kill C?

I know I can keep track of the PIDs for C in this simple example, and then kill those PIDs. However, in my real program, that would get very complex and I'd just like a simple way to kill B, and have all of B's sub-processes killed also (like with CTRL-C). Is this possible?

Replies are listed 'Best First'.
Re: Stopping subprocesses
by MidLifeXis (Monsignor) on Aug 24, 2010 at 02:34 UTC

    See perlipc, search for "negative". Sending a signal to a negative PID sends the signal to a process group. However, heed the instructions to IGNORE it within 'A' so that you don't kill your original 'A' process.

    After a little research following our discussion in the CB, it appears that the shell creates a new process group when you run 'A'. On further thought, this makes a lot of sense, or else signals sent to 'A' could also cause actions in your shell.

    Update: Per CB discussion (thanks tye), perhaps kill SIGINT -getpgrp() would be more appropriate. Verify the variable of the signal.

    --MidLifeXis

      My attempt at the negative PID didn't work. The complaint was "Bad signal number". What did you mean by the negative PID? IS that the negative of the PID of B?

        My attempt at the negative PID didn't work. The complaint was "Bad signal number".

        Your negative pid was taken to be the signal number. Explicitly specify the signal and you should be ok

        $ perl -esleep & [1] 3298 $ kill -3298 bash: kill: 3298: invalid signal specification $ kill -KILL -3298 $ [1]+ Killed perl -esleep
Re: Stopping subprocesses
by cdarke (Prior) on Aug 24, 2010 at 10:05 UTC
    For background information, it might be worth reading (on Linux) man 7 signal.

    When you hit CTRL-C then a SIGINT is sent to all the processes in the foreground group. When you use the shell built-in command kill the shell will send SIGTERM. If you are getting different behavior with different signals then someone must be trapping the SIGTERM. Ignore signals are usually inherited.

    In addition, usually, when a parent dies it sends a SIGHUP to the child, which might kill the child, again depending on signal handling, for example using the nohup program or trapping SIGHUP will prevent this.

    Keeping track of PIDs should not be difficult, it is the return value of fork in the parent. Provided the children of B are not ignoring hangups, then killing B should bring the children down as well.

    Alternatively, from kill:
    "Unlike in the shell, if SIGNAL is negative, it kills process groups instead of processes."
    Provided the children have not switched process groups (which a daemon might do) then that might be a safer bet. It is not worth calling a shell's version of kill using system when there is one built-in to perl.

      I agree, kill is the way to go. And here's a link to Debian's current man 7 signal.

      Disagree. Keeping track of PIDs is difficult. If A starts B and B starts C, and C starts D, etc, how does A figure out all of those PIDs? Does each of B, C, D, etc open a socket to A and send the child PIDs?
      I appreciate the Unix signals lecture. In my simple example, C isn't ignoring hangups, yet when I kill B, it's child C isn't killed. So I'm not sure you read all of my original question.
      These last two replies should be in reference to cdarke's post. But the perlmonks website put them somewhere else: I'm not sure if this is because of a bug in the perlmonks website, or mozilla.
Re: Stopping subprocesses
by dasgar (Priest) on Aug 24, 2010 at 05:54 UTC

    DISCLOSURE: A Windows guy is about to offer a Linux suggestion. The suggestion may be stupid, crazy, weird or wrong or some combination of the above.

    Now that we've moved past the disclaimer, here's my thoughts. If you have the process ID for script B, wouldn't it be possible to use the Linux command pkill with the -P option to kill script B and all of it's child processes? If that does work, I would think that it should be trivial calling that from script A.

    From the man page on pkill's -P option: Only match processes whose parent process ID is listed.

    Being that I primarily work in the Windows environment, I'm not sure if what I'm suggesting even makes sense, but thought that I'd toss it out in case I'm miraculously correct. Of course, I suspect that I might be overlooking something.

      I don't think that will work because if B ends before C, then C will adopt a new parent and that parent isn't A. So, there's no way to know that C is a descendent of A.

        Hmmm....Two thoughts about that.

        First: Assuming that script B on its own won't end without its child processes to end first, I think that calling pkill from script A will kill script B's child processes. Then if script A kills script B directly, then you won't hit that issue. Of course, I may be assuming more than I should or assuming incorrectly.

        Second: If script B is coded such that it can call die without killing its child processes, I would think that there should be a way to modify the code to call pkill first to kill its child processes before actually exiting out. Of course, if something else is killing from the OS, then you might have the orphaned processes as you described.

        I'm not disagreeing with you. Just thinking out loud in case any of this might be helpful for you or others to figure out a solution. That and hoping that I don't sound like a blabbering idiot. :D

Re: Stopping subprocesses
by zentara (Archbishop) on Aug 24, 2010 at 12:13 UTC
      Based on the documentation for Proc::Killfam, this won't work. If B finishes before C, then the process table will "re-parent" C, and there will be no way of telling, from the process table, that C was a child of B. Unless I'm misunderstanding things, this can't work, right?
        If a process has disassociated itself from its parent (which is not as simple as you say), it won't be reached by any methods short killing it specifically with its PID. This is a straw man.
        then the process table will "re-parent" C,

        Are you making potential zombies? Otherwise it should reparent it just up 1 level in the process tree that spawned it.

        Have you seen the "f" switch for the ps command? It shows the "forrest" view of the process tree. Look at it, before and after B finishes, and see if the branch of the forest gets moved. Proc::Killfam will kill everything that branches off of the pid handed to it.

        You should be able to find the pid of the highest related process up the tree branch, and run Killfam on it, to get all the programs and associated shells.

        But maybe you have hit on an odd behavior?

        Here is an old manual routine that may help you.

        #!/usr/bin/perl use warnings; use strict; #robau #This subroutine takes two arguments, the parent process ID #and the numeric signal to pass to the processes #(which would be 9 if you wanted to issue a -TERM). #Using Proc::Process you could find the process ID #of the process login -- zentara with something similar #to the following : #my $proc = Proc::ProcessTable->new; #my @ps = map { $_->pid if ($_->cmndline =~ /login -- zentara/) } @{$p +roc->table}; #&killchd($_, 9) foreach @ps; &killchd(9895, 9); #kill -9 process 9895 sub killchd ($;$) { use Proc::ProcessTable; my $sig = ($_[1] =~ /^\-?\d+$/) ? $_[1] : 0; my $proc = Proc::ProcessTable->new; my %fields = map { $_ => 1 } $proc->fields; return undef unless exists $fields{'ppid'}; foreach (@{$proc->table}) { kill $sig, $_->pid if ($_->ppid == $_[0]); }; kill $sig, $_[0]; };

        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku flash japh
Re: Stopping subprocesses
by muba (Priest) on Aug 24, 2010 at 01:51 UTC

    I don't know if this works, but have you tried sending a SIGINT signal (which is what Ctrl+C does) to process B with kill, instead of the standard signal that kill sends?

      I tried (kill -INT PID_OF_B). It didn't work.
Re: Stopping subprocesses
by zentara (Archbishop) on Aug 24, 2010 at 16:48 UTC
    Instead of forking, threads may be a better alternative for you. You will at least be able to get all the pids and store them in an shared array for later killing.

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku flash japh
Re: Stopping subprocesses
by rowdog (Curate) on Aug 25, 2010 at 23:26 UTC
    What I really want to do is call B from another Perl script A, and A kills B (with system("kill PID") or whatever), thereby killing C as well.

    One way to do that is to localize the HUP signal handler and then send a HUP signal to your process group. You can have A do

    { local $SIG{HUP} = 'IGNORE'; kill HUP => -$$; }

    I've attached a proof of concept demo. Sorry there's no backport, I got lazy once I had it working.

    a.pl
    b.pl
    c.pl
    output

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (5)
As of 2024-04-23 11:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found