Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Killing a system/exec/`` call if it times out

by Cagao (Monk)
on Dec 01, 2011 at 18:25 UTC ( [id://941138]=perlquestion: print w/replies, xml ) Need Help??

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

Hi,

I have a script running under Lighttpd/FastCGI which needs to make a call to a system command, this external command sometimes takes forever, so I've wrapped the call in an eval with an ALRM call.

However, when the alarm goes off, and I break out of the eval, the system command is still running.

what's the best way to kill this command?

Thanks,
Rob
  • Comment on Killing a system/exec/`` call if it times out

Replies are listed 'Best First'.
Re: Killing a system/exec/`` call if it times out
by kennethk (Abbot) on Dec 01, 2011 at 18:47 UTC
    What is the form of the command? If it involves redirection, for example, perl does not directly execute the command but rather invokes the shell to do it. Thus, when you break out of the call, you are actually killing a shell invocation, not the offending process. While I hate to invoke bash-like magic, the simplest solution I have found involves actual process management and essentially grepping the results of ps. The following is code I use to handle this problem in one of my scripts: I invite criticism if someone has a better way.
    my $pid = open my $calc, '-|', "$command 2>&1" or die "Pipe failed on open: $!\n"; my $vector = ''; vec($vector,fileno($calc),1) = 1; # Flip bit for pipe unless (select($vector,undef,undef,$timeout)) { # Calculation +is hanging # collect list of spawned processes my @pids = $pid; my $i = -1; push @pids, `ps --ppid $pids[$i] -o pid` =~ /\d+/g while + ++$i < @pids; kill 9, $_ for @pids; die "Calculation call failed to return with $timeout secon +ds\n"; } local $/; # Slurp my $content = <$calc>; close $calc;
      Thanks, it's a simple call to a program which will hopefully create an image for me, just a program and 2 parameters, no redirection.

      Although it is doing the "sh -c ..." approach as you mentioned. I don't care how it's called either, system(), exec(), or backticks.

      I could collect the PIDs based on their parent, but would love a one-liner-ish. :)
        "I don't care how it's called either, system(), exec(), or backticks."
        Actually, yes you do (or should): see the docs for exec, taking special notes of the first paragraph:
        "The "exec" function executes a system command *and never returns*; use "system" instead of "exec" if you want it to return. It fails and returns false only if the command does not exist *and* it is executed directly instead of via your system's command shell (see below)."

        And the difference between system and backticks? sgifford put it clearly and succinctly ( in Re: system() vs `backtick`, q.v. ) back in 2004, when he said "backticks send the executed program's STDOUT to a variable, and system sends it to your main program's STDOUT."

        But, don't quit there; read similarly re system. And re backticks... well you get the idea.

Re: Killing a system/exec/`` call if it times out
by Eliya (Vicar) on Dec 01, 2011 at 20:15 UTC
    However, when the alarm goes off, and I break out of the eval, the system command is still running.

    In most cases, creating a new process group will help here.  For example, something like this should work on POSIX-compliant systems (i.e. kill the entire tree of child processes):

    if (my $pid = fork()) { wait; } elsif (defined $pid) { setpgrp; # create new process group eval { local $SIG{ALRM} = sub { die "alarm\n" }; alarm 5; system 'bash -c "cat; echo"'; # run something (several proce +sses) that hangs alarm 0; }; if ($@ eq "alarm\n") { kill 9, 0; # kill current process group } exit; }

    I said "in most cases", because in theory this approach will fail in case some subprocess should create another process group of its own, but practice shows this is only rarely done.

    (Of course, you could wrap that up in a subroutine run_with_timeout(...), or something.)

Re: Killing a system/exec/`` call if it times out
by juster (Friar) on Dec 01, 2011 at 21:13 UTC

    Here's a simple fork/exec with an alarm. It won't cleanup multiple processes so it must use the >1 arg version of exec. I think you might already know that exec with more than 1 argument avoids using sh.

    #!/usr/bin/env perl use warnings; use strict; my $ksecs = 5; my $csecs = 10; my $sig = 2; my $pid = fork; die "fork: $!" unless(defined $pid); if($pid == 0){ exec 'sleep', $csecs; # >1 arg exec to avoid using sh die "failed to spawn command: $!"; } local $SIG{'ALRM'} = sub { kill $sig, $pid }; alarm $ksecs; waitpid $pid, 0; # waitpid sets $? alarm 0; # turn off alarm in case child finished before parent if($? == $sig){ # we killed it (or someone else sent the same signal) }elsif($?){ # child had another error or signal }
      Cheers, but yeah, I'm looking for a "kill all children of this PID, and their children too"
        You are looking for Proc::Killfam. Many, if not most times, when you fork to run a program, you end up getting the shell pid of actual program you ran. So if you killfam 9 that toplevel shell, you should get them all.

        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku ................... flash japh

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (4)
As of 2024-04-20 01:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found