【问题标题】:Kill a hung child process杀死一个挂起的子进程
【发布时间】:2012-03-12 23:11:27
【问题描述】:

我的 Perl 脚本运行一个外部程序(它接受一个命令行参数)并处理它的输出。最初,我是这样做的:

my @result = `prog arg`;

但是,事实证明该程序存在错误并且在极少数情况下会意外挂起。如果程序在一定时间后没有退出,我该如何杀死程序?该脚本必须在 Windows 和 Linux 中都可以工作,据我了解,警报和分叉在 Windows 中(或根本不能)正常工作。

我找到了一个名为IPC::Run 的模块,但我无法从它的文档中弄清楚如何正确使用它。 :-( 我试过这个:

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;

作为测试,我创建了一个仅休眠 60 秒的程序,将字符串打印到 stdout 并退出。当我尝试使用上面的代码运行它时,它会挂起 60 秒(而不是超时中指定的 10 秒)并中止并出现奇怪的错误:

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

然后我找到了另一个模块,Proc::Reliable。从描述来看,它似乎正是我想要的。除了它不起作用!我试过这个:

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

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

它确实会在 10 秒后中止子进程。到目前为止,一切都很好。但后来我修改了外部程序,让它只休眠了 5 秒。这意味着程序应该在上面代码中指定的 10 秒超时之前完成,并且它的stdout 输出应该被捕获到变量$out 中。但事实并非如此!上面的脚本没有输出任何东西。

任何想法如何正确地做到这一点? (修复有缺陷的外部程序不是一种选择。)提前致谢。

【问题讨论】:

  • 您是否尝试过从 Reliable 对象打印 stderr、status 和 msg 的输出?
  • 设置调试时会发生什么? Proc::Reliable::debug($level);
  • 如果我执行Proc::Reliable::debug (1);,脚本只会输出Proc::Reliable&gt; ATTEMPT 0: 'prog arg',没有别的。 $out 没有输出。 (当然,如果我手动运行程序prog arg,我确实会得到输出。)

标签: perl perl-module


【解决方案1】:

试试穷人的闹钟

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);

穷人的警报在一个单独的过程中运行。每秒钟,它都会检查标识符为 $pid 的进程是否还活着。如果进程不存在,则警报进程退出。如果进程在 $time 秒后仍然存在,它会向进程发送一个终止信号(我使用 9 使其无法捕获,使用 -9 取出整个子进程树,您的需求可能会有所不同。kill 9,... 也是可移植的) .

编辑:你如何用穷人的警报捕捉过程的输出? 不使用反引号 - 那么您将无法获得进程 ID,并且如果进程超时并被终止,您可能会丢失中间输出。替代品是

1) 将输出发送到文件,处理完成后读取文件

$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) 使用 Perl 的open 启动进程

$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, $_;
}

while 循环将在进程自然或非自然结束时结束。

【讨论】:

  • 恐怕我真的不明白你的代码在做什么,但是一旦我用适当的值替换$TIMEOUT,它就可以正常工作——从某种意义上说,它确实终止了子进程在指定的秒数之后并让它完成运行,如果它终止的速度比这快。但是,我如何收集子进程的输出?与反引号不同,system() 不允许我读取它运行的程序的标准输出。也许有管道的东西?
  • 我不希望明确使用临时文件,因此解决方案 1) 已过时。解决方案 2)是我在询问是否可以用管道完成时所寻找的。它几乎可以满足我的需要。它唯一的问题是它总是需要指定的超时时间才能运行——即使子进程终止得比这更快。至少在 Windows 上是这样。我现在无法在 Linux 上测试它。
  • 解决方案 2) 在 Linux 上正常工作 - 它在超时后或子进程终止后终止,以先到者为准。但是,我仍然需要一个在 Windows 上也能正常工作的解决方案;目前,您的解决方案总是需要指定的超时时间才能在那里运行,如上所述。在旁注中,Proc::Reliable 的作者确认它不适用于 Windows。
  • 你的代码不能在 Windows 上运行的原因是因为有分叉,而且很明显,管道是作为线程实现的;不作为单独的过程。只要它的父线程没有终止,一个线程就会保持“活动”(即使在退出之后),所以你的代码kill 0,$pid 总是返回 1。我想出了如何使用Win32::Process 启动一个真正的进程(或者在之后终止它一个指定的超时,或者让它在超时到期之前完成)但现在我不知道如何捕获它的stdout。 :headbang:
【解决方案2】:

这是我能做的最好的。任何关于如何避免在 Windows 上使用临时文件的想法将不胜感激。

#!/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】:

    您可能希望像 perldoc -f alarm 一样使用警报系统调用。

    【讨论】:

      猜你喜欢
      • 2014-05-18
      • 1970-01-01
      • 2012-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-12
      • 1970-01-01
      相关资源
      最近更新 更多