Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Timeout and kill

by nelson64 (Initiate)
on Mar 20, 2012 at 17:27 UTC ( [id://960605]=perlquestion: print w/replies, xml ) Need Help??

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

Hello, I am trying to use backticks to execute and external method call. However, I would like the function to timeout. I have achieved the timeout with the use of alarm. But, I want to be able to kill the process as it is memory intensive. Also, one key is the processes must be run in a sequential ordering so threading messes up the order. I have this:
my $pid; local $SIG{ALRM} = sub { kill 9, $pid; die "Timeout\n"; }; $pid = fork; eval { alarm $timeout; $result = `$solver $file`; waitpid($pid, 0); }; alarm 0;
But by monitoring through htop I am seeing multiple runs of the solver at one time, when there should only be one. Also, looking at the output the ordering is incorrect. Thanks! Nicole

Replies are listed 'Best First'.
Re: Timeout and kill
by Eliya (Vicar) on Mar 20, 2012 at 18:55 UTC

    There are various "issues" with your code — primarily the following two:

    • after the fork, you don't differentiate between the child and the parent process. This means you have two processes both doing the same thing (running the solver, etc.) — the child process with a $pid of zero, and the parent with a $pid unequal zero.

    • the backticks do fork/wait themselves under the hood, so the pid you're killing is not the solver process, as intended...

    There are several ways to approach this. Personally, I'd probably use a piped open instead of the backticks. Something like this:

    sub run_with_timeout { my ($cmd, $timeout) = @_; local $SIG{ALRM} = sub { die "Timeout\n"; }; my $out; if (my $pid = open my $pipe, "-|") { eval { alarm $timeout; @$out = <$pipe>; }; my $ok = $@ ne "Timeout\n"; alarm 0; if ($ok) { close $pipe; } else { kill 9, $pid; waitpid $pid, 0; } return $out; } else { exec $cmd; exit; } } my $out = run_with_timeout("$solver $file", $timeout); print defined($out) ? "output of solver:\n@$out" : "timed out";

    P.S.: in case your solver command contains shell meta characters (such as 2>&1), it would be run via a shell. This means you'd have to modify the exec call to read:

    exec "exec $cmd";

    The second exec is executed by the shell, so it replaces itself with the solver command. It is required so that you kill the solver, not the shell.

Re: Timeout and kill
by onelesd (Pilgrim) on Mar 20, 2012 at 18:12 UTC

    You didn't include all of your code so I'm not entirely sure what you are doing.

    But, don't use fork() (or threads) if you want things to run sequentially. fork() is why you are seeing multiple processes. You can still use alarm() without it.

      Sorry it's a long script so I was trying to give the portion of code that is the problem. This is the subprocedure:
      sub processCnfDirectory { use Time::HiRes(gettimeofday); use File::Basename; my($path) = @_; my $pid; local $SIG{ALRM} = sub { $didTimeout=1; print OUTPUT "timeout"; print "process id", $pid, "\n"; kill 9, $pid; die "Timeout\n"; }; print( "working in: $path\n" ); # append a trailing / if it's not there $path .= '/' if($path !~ /\/$/); # loop through the files contained in the directory for my $file (glob($path . '*')) { # check if the file is a directory if( -d $file) # pass the directory to the routine ( recursion ) processCnfDirectory($file); } elsif ($file =~ /.*\.cnf/) { print OUTPUT basename($file), ","; # run using the first solver $pid = fork; eval { $didTimeout=0; $start1=gettimeofday(); alarm $timeout; #$pid = `$solver1 $file`; $result = `$solver1 $file`; waitpid($pid, 0); }; alarm 0; $end1=gettimeofday(); if($didTimeout == 0) { print OUTPUT $end1 - $start1; } print OUTPUT ","; # run using the second solver $pid = fork; eval { $didTimeout=0; $start2=gettimeofday(); alarm $timeout; #$pid = `$solver2 $file`; $result = `$solver2 $file`; waitpid($pid, 0); }; }; alarm 0; $end2=gettimeofday(); if($didTimeout == 0) { print OUTPUT $end2 - $start2; } print OUTPUT "\n"; } } }
      So what it does is it reads in a directory on the command line. It loops through the directory recursively to include subdirectories. Inside a directory it runs a solver on each file. Then it records the amount of time it took to run. The solvers are also command line input.

        Okay, you still don't need to use fork(). When you use backticks like that, perl runs that command in another process which you can kill - no fork() needed.

        You may want to look at File::Find instead of glob. It makes this kind of thing easier.

Re: Timeout and kill
by Anonymous Monk on Mar 20, 2012 at 18:49 UTC
    $pid = fork;

    Congratulations, now you have two processes. And they both will execute the next block:

    eval { alarm $timeout; $result = `$solver $file`; waitpid($pid, 0); };

    So now you have two solver programs.

    The standard idiom for forking goes something like this:

    my $pid = fork(); die "failed to fork" unless defined $pid; if ($pid) { # we are parent. do things } else { # we are child. do other things }

    You probably want to put your eval block into the child portion. However -- this still won't work, as the child cannot return the $result variable to the parent -- they don't share memory. Returning a value will require some ipc trickery.

    But the trickery is not worth it. Just listen to the other poster and don't fork. It'll work fine.

Log In?
Username:
Password:

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

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

    No recent polls found