Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Timing out shell commands (paranoia)

by bluto (Curate)
on May 02, 2001 at 04:02 UTC ( [id://77188]=perlquestion: print w/replies, xml ) Need Help??

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

I'm trying to figure out an effective way of timing out shell commands in Perl. At several places I've looked the canonical solution appears to be something like...
eval { local $SIG{ALRM} = sub { die "TIMEDOUT" }; alarm(10); <command> alarm(0); }; if ($@) { die $@ unless $@ =~ /TIMEDOUT/; # command timed out ... } ...
If "command" is an internal perl command this works fine. If it is an external comand (e.g. system(), backticks, etc.) the code above is executed just fine, but the child continues to execute as well rather than dying on the spot.

I guess I should expect this since I'm assuming that the exception has longjmp'd past the code that would reap the child. I suppose another way of handling this would be to use a pipe open like this...

$pid = open(PIPE, "$command |") or die $!; eval { local $SIG{ALRM} = sub { die "TIMEDOUT" }; alarm(10); while (<PIPE>) { <do whatever> }; close(PIPE); alarm(0); }; if ($@) { die $@ unless $@ =~ /TIMEDOUT/; kill 9, $pid; close(PIPE); ## needed?? $? ||= 9; }
I'm probably being overly paranoind, but this all seems rather cheesy. If the close is outside the eval, it seems like it won't catch cases where the child closes STDOUT/STDERR but decides not to quit (not sure if that happens much). If it is inside the eval, as above, will things get cleaned up properly within Perl if the code breaks out while in the close? Is there something better?

Thanks

Replies are listed 'Best First'.
Re: Timing out shell commands (paranoia)
by Dominus (Parson) on May 02, 2001 at 18:31 UTC
    Says bluto:
    I'm trying to figure out an effective way of timing out shell commands in Perl.
    The technique I usually use for this is to make a very small tool called stopafter:
    #!/usr/bin/perl # stopafter - run a command with a timeout my $time = shift; alarm($time); exec @ARGV; die "Couldn't exec @ARGV: $!; aborting";
    To use stopafter, you say something like this:
    stopafter 300 command arg arg...
    The command runs, but it dies automatically after 300 seconds.

    Now in your Perl program, use:

    open FH, "stopafter 10 command |" or die ...;
    Now read from the pipe as usual. After 10 seconds, the timer expires and you get an end-of-file condition on the pipe.

    Hope this helps.

    --
    Mark Dominus
    Perl Paraphernalia

Re: Timing out shell commands (paranoia)
by jbert (Priest) on May 02, 2001 at 15:15 UTC

    (As you aleady know...but to give context...)
    The 'alarm(X)' function arranges for a signal (SIGALRM) to be delivered to your process (and hence interrupt it) in X seconds (give or take a bit of rounding).

    If your process happens to be executing your perl function 'command' then it is interupted and your signal handler called. This then does a 'die' and your eval catching mechanism picks up and carries on.

    If you have started another process (this is your case when you have an external command started with system or backticks) then it neither knows nor cares that your process has received an alarm...which is why it continues to run.

    I think what you want to do is pretty much in your second example. When you receive the SIGALRM you'll want to kill the child...which means you need the child pid.

    Zigster mentioned a good point. 'kill -9' is a bit rude (it depends on what the subprocess does if you care about being rude to it). I differ about the best signal to kill with initially though. The unix default for the 'kill' command is SIGTERM (15), which means 'please die'.

    This is all assuming Unix because you mentioned the 'alarm' function. If you want to achieve a similar effect on Win32, the last time I looked at this the best (only?) way was to use the Win32::Process::Create, which creates a handle to the new process upon which you can then wait for a specified period.

    Lastly, a quick bit of CPANning shows up:
    Proc::Background - Generic interface to Unix and Win32 background process management

    "This is a generic interface for placing processes in background on both Unix and Win32 platforms. This module lets you start, kill, wait on, retrieve exit values, and see if background processes still exist."

    Which (if it does what it says on the tin) sounds interesting...

Re: (Zigster) Timing out shell commands (paranoia)
by zigster (Hermit) on May 02, 2001 at 12:34 UTC
    Not sure if I follow what you mean, in the second case the reason it is more effective is not because of the pipe but because you store the process id of the child and send a sigkill to that child when you want it to die. I do agree that having a pipe is an unrequired overhead and seems a tad ugly for what you want to achieve. I would recomend that you drop back to basics and fork/exec your child. Fork will return the pid of the child to allow you to kill it later. Just keep in mind that if you start using fork/exec you need to keep track of zombie processes (all described in fork).

    As an asside I would Strongly recomend that you do not use kill 9, this sends a sigkill to your child process. SigKill cannot be trapped by the process to allow clean sutdown. The chance that you will leave your child in a mess is high. Send sigquit (3) this is a nice gentle tap on the head requesting the child die. If you feel that it is important to ensure the child dies and you are concerned that the child may ignore sigquit then use waitpid to ensure that the process dies, send sigkill iFF the child refuses to die. --

    Zigster

      Your "gentle tap" suggestion is a good idea (esp if timeouts are short). I get sloppy with kill 9, since I tend to give long timeouts usually just as sanity checks. If they ever trigger, the child is usually at the point where anything other than 9 won't do much. Thanks.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (6)
As of 2024-04-19 14:38 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found