http://qs321.pair.com?node_id=572412

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

I'm sure there's an easy way around this with fork, but rather than guess at the answer, I'll just present the problem (I've been trying to solve this for a bit).

#!/usr/bin/perl use strict; use warnings; eval { local $SIG{ALRM} = sub { die "alarm\n" }; alarm 2; system("perl -e '1 while 1'") == 0 or die "Cannot do infinite loop: $!"; alarm 0; }; die $@ unless $@ eq "alarm\n"; # the following lines let me do a 'ps awux|grep perl' # while this is running print 'Hanging ...'; <STDIN>;

I don't do a huge amount of work with process management, so this is giving me fits. Basically, I sometimes need call code code which may never return but I have no way of knowing in advance if this is the case. I want an alarm to catch when that happens. However, when I do that, the proces is hanging around in the process table. How can I ensure that when the system times out that the process is reaped? I'm making about 10,000 system calls and this is rapidly killing my system.

Update:

I forgot that this isn't a real example of what I need. I also need the result, if any, of the command I execute. My actual code looks like this:

sub timeout { my ( $package, $timeout, $code ) = @_; my $result; eval { local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required alarm $timeout; $result = $code->(); alarm 0; }; if ($@) { die "Could not make $package: $@" unless $@ eq "alarm\n"; # + propagate unexpected errors # timed out } return $result; } timeout( $package, 10, sub {`perl $make_prog 2>&1`} );

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Killing a hanging child process
by liverpole (Monsignor) on Sep 11, 2006 at 23:33 UTC
    Hi Ovid,

    What sgifford said makes a lot of sense:

        You can also do tricks with double-fork'ing to arrange for the process to be a child of init(8)

    because I was using this trick just today, in a situation where I was trying to disassociate a Perl script from its calling CGI process.  The trick he mentions isn't too difficult, though:

    sub spawn_process { my $pid = fork; defined($pid) or die "Failed first fork of child process\n"; # Parent waitpid $pid, 0; # This will happen momentarily return; # Child my $kid = fork; defined($kid) or die "Failed second fork of grandchild process\n"; $kid and exit; # Grandchild perform_my_tasks(); exit; # All your grandchildren are belong to init! }

    The nice thing about this is that it lets the parent wait for the child process, which quickly exits, and then go on its way doing more processing.  Meanwhile the grandchild process is inherited by init, and doesn't need to be waited for.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Killing a hanging child process
by sgifford (Prior) on Sep 11, 2006 at 22:21 UTC
    If the process is hanging around in a "zombie" state, you need to wait for it. The problem is that the parent process has the right to know the exit status of its child, and the exit status is stored in the process table entry. The entry can't be deleted until after the status has been read with wait. Under some types of Unix, you can set $SIG{CHLD}='IGNORE' to tell it you don't care. You can also do tricks with double-fork'ing to arrange for the process to be a child of init(8), but that's probably more trouble than it's worth.

    If the process is still running, you'll need to kill it, then wait for it. You'll probably need the PID in this case, so it would be easiest to do your own fork and exec instead of letting system do it for you.

    Good luck!

Re: Killing a hanging child process
by ozone (Friar) on Sep 11, 2006 at 22:15 UTC
    You could use a non-blocking waitpid and simply time all the children, then signal/kill them if they go over whatever limits you set.

    (from perldoc -f waitpid)

    use POSIX ":sys_wait_h"; ... do { $kid = waitpid(-1, WNOHANG); } until $kid > 0;

    status of the child is in $?

    HTH!

Re: Killing a hanging child process
by andyford (Curate) on Sep 11, 2006 at 22:09 UTC
Re: Killing a hanging child process
by zentara (Archbishop) on Sep 12, 2006 at 12:52 UTC
    I'm not sure I completely understand your problem, but I usually use the "piped-open" form of forking,
    my $pid = open(PH, "ls &|");
    to get a real $pid, which you can then kill later. I've found that sometimes, the pid you get is the shell, so you need to be aware of the ppid....brutally stop a perl program if it runs too long. I've even seen some cases like forking of mplayer, where you get the ppid from the piped open, then mplayer will fork, so you need to kill all the children of the ppid.
    #!/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. Cogito ergo sum a bum
Re: Killing a hanging child process
by bluto (Curate) on Sep 12, 2006 at 19:21 UTC
    To clarify: an alarm() in the parent doesn't necessarily affect a child process since the child doesn't have to honor it. When the alarm occurs, you need to hunt down the children and kill them off. The problem with qx, system, and backtics is that you don't get the pid, so it is almost never a good idea to use alarm with them.

    I can't think of a clean way to write timeout() so that it is ignorant of the code it is calling. It seems to me that it needs to spawn/kill the children itself, or the caller needs to do this when the timeout occurs (e.g. with a callback).

Re: Killing a hanging child process
by sgt (Deacon) on Sep 13, 2006 at 22:43 UTC

    It is not an easy problem in the general case

    I implemented in C a system with time-out and a command ish wich I use in production. It has one limitation and there is a trick: I use setpgid() in the father and the child (you need both to avoid a race condition), this way I can kill(pgid), which actually kills everything the exec()ed process starts ...except if it itself uses setpgid() like daemons (this is the limitation ;)</>

    using the POSIX module, I think a pure perl solution is not too complicated (a line by line translation, but hey if it works...) if you want the code contact me at sgt19DELETE@tidALLCAPS.es

      I need to kill an exec()ed process together with all processes it starts, too. I would like to use the setpgid() trick, but I don't understand why I have to use setpgid() in the father process. Why is it a race condition if I use setpgid() in the child only?