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

Hello Monks,
I am seeking knowledge for the following problem. I want to use a perl script to run external applications to perform analysis functions on files. The problem is that I don't have source for the applications, and I notice they can get hungup, without returning. What I would like to do is use SIG{ALRM} to set a timeout and if the application is not done by the the time the timeout comes about, then chop it's head off. I am launching the external application via `...`, but when I do that it seems my perl script loses the ability to perform the SIG{ALRM} until the application has completed. Is there a way to do what I want? Would a double threaded app be the answer, so I can monitor the external application via a 'ps' monitor?

Thank you in advance for your help.
  • Comment on SIG{ALRM} to help kill external program

Replies are listed 'Best First'.
Re: SIG{ALRM} to help kill external program
by cdarke (Prior) on Oct 13, 2010 at 14:12 UTC
    Alarm handlers do not survive a fork (which hides under the covers of backticks), this is not Perl specific, it is a feature of UNIX. So you need to use fork, do the signal handling in the child, then exec your external application. See also Proc::Background which includes timeout handling (it uses sleep for the timeout).

      cdarke:

      Can you point to some documentation? I did a quick search (not very thorough), and haven't found any evidence that the alarm handlers don't survive a fork. The OpenGroup alarm documentation clearly states that pending alarm signals are cleared for the child, and their fork docs state that the child process will have its alarm cleared and any pending alarm reset. So I don't see why the parent using alarm & kill wouldn't work.

      I don't have my hands on a Unix box at the moment, but under windows, I get this:

      301058@LOU-PC0288 /Work/METABASE $ perl forktest.pl nofork: ALARM! nofork: done... 301058@LOU-PC0288 /Work/METABASE $ perl forktest.pl a PID(2560): ALARM! PID(0): QUITTING! PID(2560): done... 301058@LOU-PC0288 /Work/METABASE $ cat forktest.pl use strict; use warnings; my $prefix="nofork"; my $PID; $SIG{INT} = sub { die "$prefix: QUITTING!\n\n"; }; $SIG{ALRM} = sub { print "$prefix: ALARM!\n\n"; kill 2,$PID if $PID; } +; alarm(5); if (@ARGV) { # with any argument we fork, o/w we don't $PID = fork; $prefix = "PID($PID)"; } # Wait long enough for alarm to trigger my $t = time; while ($t + 10 > time) { # doze } print "$prefix: done...\n\n"; alarm(0); 301058@LOU-PC0288 /Work/METABASE $

      ...roboticus

Re: SIG{ALRM} to help kill external program
by roboticus (Chancellor) on Oct 13, 2010 at 13:46 UTC

    locked0wn:

    I'd suggest forking off the job and retaining the process ID. Then you can use your signal for the timeout, and kill the child process if it's still executing:

    my $SIGINT=2; my $PID=fork("my command"); . . . sub SIGHANDLER { kill $SIGINT, $PID; }

    ...roboticus

Re: SIG{ALRM} to help kill external program
by BrowserUk (Patriarch) on Oct 13, 2010 at 14:34 UTC
Re: SIG{ALRM} to help kill external program
by kennethk (Abbot) on Oct 13, 2010 at 14:06 UTC
    The behaviors of backticks are described in `STRING` in perlop. Backticks only return once the external program has finished execution - otherwise, how would perl know it had returned to you the entire output? Therefore, you can't use a $SIG{ALRM} to control it - the child process has control of the thread.

    For the simple case, you can follow roboticus's suggestion to use fork.(Update: See cdarke's comment below) You might also consider threads (core) or POE for more complicated queuing behaviors. If you are feeling adventurous, you can combine a piped open with select or $SIG{ALRM} - see Using open() for IPC some some basics.

      kennethk,
      I want to thank you for your suggestion of using threads. I have implemented a dual thread setup that uses a shared variable for tracking completion of thread 1, which launches the external application. Thread 2 monitors the value of the shared variable, and after several loops of checking state on the variable, it either ends the thread (which means that thread 1 completes), or it kills all threads, because thread 1 has been hung up (which means the external app did not complete. It works very well, and I intend to post after I clean up the code for final. Thank you very much for everyones' input.
Re: SIG{ALRM} to help kill external program
by locked_user sundialsvc4 (Abbot) on Oct 13, 2010 at 17:42 UTC

    If it were me, I’d follow the suggestion of forking-off the analysis program as a child of a command-process that then goes to sleep waiting for it to end, or to receive a signal.   This controller-program can do nice things like set priorities for the child, divert its output this way or that, set up any peculiar things that the child might need, and even monitor it to see if it is still consuming CPU time.     Upon receipt of the proper signal, the controller program deals the coup de grace to the analysis program, waits to bury it and set up a nice tombstone, and so on.   None of this magick is visible to anyone who just wants “to analyze a file,” but it really works out well.

Re: SIG{ALRM} to help kill external program
by jakeease (Friar) on Oct 13, 2010 at 23:55 UTC

    A couple suggestions from the Perl Cookbook -- not sure if they are what you need but here they are.

    16.19 Avoiding Zombie Processes

    If you don’t need to record the children that have terminated, use:

    $SIG{CHLD} = 'IGNORE';

    If you do, use a SIGCHLD handler:

    $SIG{CHLD} = \&REAPER; sub REAPER { my $stiff; while (($stiff = waitpid(-1, WNOHANG)) > 0) { # do something with $stiff if you want } $SIG{CHLD} = \&REAPER; # install *after* calling waitpid }

    some info on waitpid & WNOHANG:

    http://www.mkssoftware.com/docs/man3/waitpid.3.asp