Kill a hung child process

孤街浪徒 提交于 2019-12-01 02:58:21

问题


My Perl script runs an external program (which takes a single command-line parameter) and processes its output. Originally, I was doing this:

my @result = `prog arg`;

However, turns out that the program is buggy and hangs unpredictably in rare cases. How can I kill the program if it hasn't exited after a certain amount of time? The script has to work both in Windows and in Linux, and it is my understanding that alarms and forks don't work well (or at all) in Windows.

I found a module called IPC::Run but I can't figure out how to use it properly from its documentation. :-( I tried this:

use strict;
use warnings;
use IPC::Run qw(run timeout);
my $in;
my $out;
my $err;
my @result;
my @cmd = qw(prog arg);
run \@cmd, \$in, \$out, \$err, timeout (10) or die "@cmd: $?";
push @result, $_ while (<$out>);
close $out;
print @result;

As a test, I created a program that just sleeps 60 seconds, prints a string to stdout and exits. When I try to run it with the above code, it hangs for 60 seconds (instead of for 10 seconds, as specified in the timeout) and aborts with a bizarre error:

IPC::Run: timeout on timer #1 at C:/Bin/Unix/Perl/site/lib/IPC/Run.pm line 2956

Then I found another module, Proc::Reliable. From the description, it seems to do precisely what I want. Except that it doesn't work! I tried this:

use strict;
use warnings;
use Proc::Reliable;

my $proc = Proc::Reliable->new ();
$proc->maxtime (10);
my $out = $proc->run ("prog arg");
print "$out\n";

It indeed aborts the child process after 10 seconds. So far, so good. But then I modified the external program and made it sleep for only 5 seconds. This means that the program should finish before the 10-second timeout specified in the above code and its stdout output should be captured into the variable $out. But it isn't! The above script doesn't output anything.

Any ideas how to do it properly? (Fixing the buggy external program is not an option.) Thanks in advance.


回答1:


Try the poor man's alarm

my $pid;
if ($^O eq 'MSWin32') {
    $pid = system 1, "prog arg";    # Win32 only, run proc in background
} else {
    $pid = fork();
    if (defined($pid) && $pid == 0) {
        exec("proc arg");
    }
}

my $poor_mans_alarm = "sleep 1,kill(0,$pid)||exit for 1..$TIMEOUT;kill -9,$pid";
system($^X, "-e", $poor_mans_alarm);

The poor man's alarm runs in a separate process. Every second, it checks whether the process with identifier $pid is still alive. If the process isn't alive, the alarm process exits. If the process is still alive after $time seconds, it sends a kill signal to the process (I used 9 to make it untrappable and -9 to take out the whole subprocess tree, your needs may vary. kill 9,... is also portable).

Edit: How do you capture the output of the process with the poor man's alarm? Not with backticks -- then you can't get the process id and you may lose the intermediate output if the process times out and gets killed. The alternatives are

1) send output to a file, read the file when the process is done

$pid = system 1, "proc arg > some_file";
... start poor man's alarm, wait for program to finish ...
open my $fh, '<', 'some_file';
my @process_output = <$fh>;
...

2) use Perl's open to start the process

$pid = open my $proc, '-|', 'proc arg';
if (fork() == 0) {
    # run poor man's alarm in a background process
    exec($^X, '-e', "sleep 1,kill 0,$pid||exit ...");
}
my @process_output = ();
while (<$proc>) {
   push @process_output, $_;
}

The while loop will end when the process ends, either naturally or unnaturally.




回答2:


This is the best I could do. Any ideas on how to avoid the use of a temporary file on Windows would be appreciated.

#!/usr/bin/perl

use strict;
use warnings;
use File::Temp;
use Win32::Process qw(STILL_ACTIVE NORMAL_PRIORITY_CLASS);

my $pid;
my $timeout = 10;
my $prog = "prog arg";
my @output;

if ($^O eq "MSWin32")
{
    my $exitcode;
    my $fh = File::Temp->new ();
    my $output_file = $fh->filename;
    close ($fh);
    open (OLDOUT, ">&STDOUT");
    open (STDOUT, ">$output_file" ) || die ("Unable to redirect STDOUT to $output_file.\n");
    Win32::Process::Create ($pid, $^X, $prog, 1, NORMAL_PRIORITY_CLASS, '.') or die Win32::FormatMessage (Win32::GetLastError ());
    for (1 .. $timeout)
    {
        $pid->GetExitCode ($exitcode);
        last if ($exitcode != STILL_ACTIVE);
        sleep 1;
    }
    $pid->GetExitCode ($exitcode);
    $pid->Kill (0) or die "Cannot kill '$pid'" if ($exitcode == STILL_ACTIVE);
    close (STDOUT);
    open (STDOUT, ">&OLDOUT");
    close (OLDOUT);
    open (FILE, "<$output_file");
    push @output, $_ while (<FILE>);
    close (FILE);
}
else
{
    $pid = open my $proc, "-|", $prog;
    exec ($^X, "-e", "sleep 1, kill (0, $pid) || exit for 1..$timeout; kill -9, $pid") unless (fork ());
    push @output, $_ while (<$proc>);
    close ($proc);
}
print "Output:\n";
print @output;



回答3:


You may want to use alarm system call as in perldoc -f alarm.



来源:https://stackoverflow.com/questions/9397937/kill-a-hung-child-process

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!