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

I'm trying to figure out how to kill a child process after a timeout has occurred, where the child runs a Java app.

I have a program script.pl, that forks a child which calls 'exec' on a shellscript.sh which ends with 'exec $JAVA_BIN $OPT class "${1+$@}"'. The 'exec' call is supposed to replace the shell of the child so that it can be killed from the parent process after a timeout. However, the java process seems to disconnect itself from the execution chain and is not killed.

When I run the script.pl, the process tree looks like:

script.pl(27177)---sh(27180)---java(27181)-+-{java}(27186)
                                       |-{java}(27187)
                                       |-{java}(27188)
                                       `-{java}(27271)

When I run shellscript.sh directly, the process tree looks like:

java(26855)-+-{java}(26860)
            |-{java}(26861)
            |-{java}(26864)
            `-{java}(26871)

The child executes the process this way...

my @cmd = ($SCRIPT,'arg1');
exec(@cmd) or croak "Unable to execute @cmd: $!";

How can I call 'exec' on the script so if killed, the java app is killed?

Is the only way to make this work, calling the java app directly from the perl exec call?

Thanks Monks!

Replies are listed 'Best First'.
Re: Calling exec on exec
by jettero (Monsignor) on Sep 19, 2008 at 19:31 UTC
    Skip the shell script and pull the java args into your script.pl ...
    $ENV{JAVA_STUFF} = "things"; exec(@cmds, @args, @etc);

    Although, I must be missing something, because it really aught to work like you expect. The only thing I can think is that you're showing us exec(@cmds), but you're really calling exec("@cmds"); (I could easily be missing something else.)

    -Paul

      Yep, that was it. :)

          my @cmd = (qq(
              $SCRIPT -log="$logfile" -level="$LOG_LEVEL" -file="$job"
          ));
      

      I had some problems with the quoted variables, so I enclosed them in a qq(). I may potentially have strange filenames (spaces, etc.), so I should be doing some error checking on the filenames instead of quoting.

      Thanks for the help Paul.

        If you use the multi-argument form of exec

        exec($SCRIPT, qq{-log="$logfile"}, qq{-level="$LOG_LEVEL"}, qq{-file=" +$job"});

        the strings aren't subject to shell processing before being passed to the child (the script), so whatever you put here will be received. For example, the following three statements are equivalent.

        exec('ls "foo bar"') -> shell passes [foo bar] to [ls] exec('ls foo\ bar) -> shell passes [foo bar] to [ls] exec('ls', 'foo bar') -> perl passes [foo bar] to [ls]

        The problem is that you're using a bad syntax for your parameters. What if you have a file name that contains '"'?

Re: Calling exec on exec
by sflitman (Hermit) on Sep 22, 2008 at 00:33 UTC
    I think you might like this sub I wrote based on stuff I saw in perlipc. You can pass your timeout as a switch --timeout=N if you don't like the timeout of 15 seconds. I use it for calling an external script which does OCR of PDF files. If your $cmd has space-separated switches, they will be turned into more @args. Using multi-parameter open is much faster than backticks since you don't invoke a shell, and you don't have to deal with shell-mangling your filenames (so they don't need quotes if they contain spaces, eg).
    sub execute { # exec with timeout my ($cmd,@args)=@_; my $timeout=15; # seconds my ($result,$pid,$i,$time); if ($args[$#args]=~/^--?timeout=(\d+)$/i) { # or pass as last arg $timeout=$1; pop @args; } $i=index($cmd,' '); # args appended to command? if ($i>-1) { unshift @args,split(/\s+/,substr($cmd,$i+1)); # could be more +than one $cmd=substr($cmd,0,$i); } eval { local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required local($/)=undef; alarm $timeout; $time=time if debug_has(EXEC); $pid=open(CMD,'-|',$cmd,@args); # run without shell overhead if ($pid) { $result=<CMD>; close CMD; } alarm 0; }; if ($@) { $result='TIMEOUT' if $@ eq "alarm\n"; } $result; }
    Hope that helps. SSF