Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Backticks and SIGALRM

by nemo (Sexton)
on Aug 20, 2007 at 11:16 UTC ( [id://633770]=perlquestion: print w/replies, xml ) Need Help??

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

Hi, I have a small piece of code that executes an external application that may hang. I use backticks to execute the external app. and collect output. I have been trying to use an alarm(3) call to timeout if the app. takes too long to execute. The alarm call is not timing out, if the external application hangs the entire perl script hangs. Is this an incorrect use of the alarm call?
Thanks for all responses.
Nemo
my @result; $SIG{ALRM} = sub { die "timeout" }; eval { alarm(3); @result = `extApp $_`; #Not returning if extApp hangs alarm(0); }; if ($@) { if ($@ =~ /timeout/i) { push(@result, "App has hanged itself."); } else { alarm(0); # clear the still-pending alarm die; # propagate unexpected exception } }

Replies are listed 'Best First'.
Re: Backticks and SIGALRM
by BrowserUk (Patriarch) on Aug 20, 2007 at 15:09 UTC

    T'is easy with a thread:

    #! perl -slw use strict; use threads; use threads::shared; $|++; our $N ||= 11; my $TIMEOUT = 10; my $extApp = q[ perl -lwe"$|++; print $_ and sleep 1 for 1 .. $ARGV[0] +" ]; my @results :shared; my $pid :shared; async { $pid = open my $fh, "$extApp $N |" or die "$!, $^E"; @results = <$fh>; }->detach; kill 0, $pid while sleep 1 and $TIMEOUT--; kill 3, $pid and warn 'Command timed out' if $TIMEOUT; print "command return \n'@results'"; __END__ c:\test>junk6 -N=8 command return '1 2 3 4 5 6 7 8 ' c:\test>junk6 -N=10 command return '1 2 3 4 5 6 7 8 9 10 ' c:\test>junk6 -N=12 Command timed out at c:\test\junk6.pl line 21. command return '1 2 3 4 5 6 7 8 9 10 11 '

    Wrapping that into a sub is left as an exercise.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      BrowserUk,
      Thanks for your reply, it's a working solution to the problem. :)
      Many thanks for all replies.

      Nemo

        YW. Here's a slightly cleaner implementation. The only caveat is that it abitrarially throws away any output received prior to the timeout occuring. Only you can decide how you want to signal timeout if you also wish to retrieve any partial output.

        I guess this could form the basis of a whole CPAN module, but it just seems altogether too trivial for that?

        #! perl -slw use strict; $|++; my $extApp = q[ perl -lwe"$|++; print $_ and sleep 1 for 1 .. 10" ]; for my $timeout ( map $_*2, 4,5,6 ) { my @results = timedCommand( $extApp, $timeout ); if( @results ) { print "Command returned\n", join '', @results; } else { print "Command timed out after $timeout seconds"; } } sub timedCommand { use threads; use threads::shared; my( $cmd, $timeout ) = @_; my @results :shared; my $pid :shared; async { $pid = open my $fh, "$cmd |" or die "$!, $^E"; @results = <$fh>; }->detach; kill 0, $pid while sleep 1 and $timeout--; kill 3, $pid and return if $timeout; return @results; } __END__ c:\test>junk6 Command timed out after 8 seconds Command returned 1 2 3 4 5 6 7 8 9 10 Command returned 1 2 3 4 5 6 7 8 9 10

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Backticks and SIGALRM
by moritz (Cardinal) on Aug 20, 2007 at 12:03 UTC
    I would also like to see a good solution to that problem.

    I worked around it the other day by

    1. forking
    2. Setting execution time limit in the child with BSD::Resource
    3. writing result to a temporary file
    4. in the parent, wait for the child and read the temp file

    This is rather messy (and probably not very secure because the handling of temporary files is tricky). And I couldn't find a sandbox executer on cpan :(

    BTW I needed that mess for an evalbot for #perl6.

Re: Backticks and SIGALRM
by Anno (Deacon) on Aug 20, 2007 at 11:39 UTC
    I can't reproduce that result. In particular, using the external command
    `echo start; sleep 5; echo done`
    I see the expected timeout. With
    `echo start; sleep 0; echo done`
    the result is the two echoed lines, again as expected.

    Anno

      Yes
      I have tested the alarm code in isolation using an infinite loop and it does work as expected. The alarm timeout works correctly when the external app is executed using "system" but as I need to collect the output I cannot use it.
      Thank you for your reply.

      Nemo
        Can I pipe the output of the system command to my own program and read it in line by line. That way getting the best of both?
        When did system() enter the equation? I tested your code using backticks and it works as expected.

        Anno

Re: Backticks and SIGALRM
by jbert (Priest) on Aug 20, 2007 at 13:23 UTC
    Your code appears to work here (ubuntu linux, perl 5.8.8).

    This is using backticks (not a system call, as you have in your code. All I did was to replace the extApp $_ with sleep 5.

    Can you give some info on your platform and perl version please, and also let us know what the following script prints on your system?

    #!/usr/bin/perl use strict; use warnings; my @result; $SIG{ALRM} = sub { print "ALARM fired\n"; die "timeout"; }; eval { alarm(3); @result = `sleep 5`; alarm(0); }; print "Running after alarm: $@\n"; if ($@) { if ($@ =~ /timeout/i) { push @result, "App has hanged itself\n"; } else { alarm(0); # clear the still-pending alarm die; # propagate unexpected exception } } print "result is: ", join('', @result), "\n";
    I get:
    ALARM fired Running after alarm: timeout at tt.pl line 8. result is: App has hanged itself
    On a side note, when you propagate the 'die' error, it's best to re-throw the original die object/text with die $@ to give higher-level code a chance of working out what went wrong.
      Hi Jbert,
      I haven't tested your code but it will work correctly, I tested the alarm code before with Inifinite loops and it works as expected. The problem is the hanging app in the shell. I am running on MSWIN32-x86-multi-thread Perl version 5.8.8.

      Thank you for your replies.
      Nemo
        Ah, OK. So you want to be able to kill off the offending sub-process when the alarm fires? Then you either need to walk the process table, looking for a process whose parent is your script, or start the process in a way which allows you to get the pid and the output.

        e.g. run the code under a piped open, something like this (untested):

        my $cmd = "your command"; my $pid = open(my $fh, "$cmd|"); $SIG{ALRM} = sub { print "Alarm fired, killing pid $pid\n"; # Replace with windows-specific code to kill process kill 15, $pid; }; die "Can't run command [$cmd] : $!" unless $pid; # Or do some other processing with $fh push @result, $_ while (<$fh>); close $fh or die "Problem running [$cmd] : $?";
        If your child process starts yet more children (and they don't exit when the parent is killed), you'll have to write more elaborate cleanup code.

        Dig through the windows-related process modules on CPAN and you should find what you need for the cleanup.

Re: Backticks and SIGALRM
by zentara (Archbishop) on Aug 20, 2007 at 11:58 UTC
    You might try qx instead of backticks.
    #!/usr/bin/perl # qx does interpolation too # generalized form for using backticks $output=qx(ls -la); print "$output\n"; $output = `ls`; print "$output\n";

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
      It is useful to remember that qx// is backticks. It's just another syntax to get there. It doesn't exercise any different code path, it won't behave any different, except how the parser finds the end of the string (you could type qx/foo `blah`/ without needing special escape characters to get the backquotes into the shell).

      Also, q// is single-quoted, and qq// is double-quoted. Same thing.

      --
      [ e d @ h a l l e y . c c ]

      Thank you for your replies.
      I attempted the solution using qx but I had the same result. Executing the command using the shell seems to be the problem, the sigalrm is not delivered. It's an interesting problem I haven't come across before.
      I was considering using system to redirect the output of the command to a temporary text file and then reading the tmp file to parse output one line at a time.
      I should also mention I am working on Win32 Boo! ;)

      Nemo
      qx and `` produce the same code.
Re: Backticks and SIGALRM
by dwm042 (Priest) on Aug 20, 2007 at 13:17 UTC
    I've seen similar issues in shell scripting, and the solution I settled on was similar to what moritz is suggesting. We opened a FIFO queue, forked the process into a parent and child. The child launched the unreliable app, and helped funnel the output into the FIFO. Parent would read the FIFO and also set a timer. If the child lived longer than the time set on the timer, the child process was killed.
Re: Backticks and SIGALRM
by Prof Vince (Friar) on Aug 20, 2007 at 15:57 UTC
    You might be interested in using IPC-Run.
Re: Backticks and SIGALRM
by Anonymous Monk on Aug 20, 2007 at 13:06 UTC
    try to use unsafe signals

Log In?
Username:
Password:

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

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

    No recent polls found