【问题标题】:How can I timeout a forked process that might hang?如何使可能挂起的分叉进程超时?
【发布时间】:2009-12-26 09:17:43
【问题描述】:

我正在编写一个 Perl 脚本,它将编写一些输入并将这些输入发送到外部程序。该程序挂起的可能性很小但非零,我想将其超时:

my $pid = fork;
if ($pid > 0){
    eval{
        local $SIG{ALRM} = sub { die "TIMEOUT!"};
        alarm $num_secs_to_timeout;
        waitpid($pid, 0);
        alarm 0;
    };
}
elsif ($pid == 0){
    exec('echo blahblah | program_of_interest');
    exit(0);
}

就目前而言,在 $num_secs_to_timeout 之后,program_of_interest 仍然存在。我试图在$SIG{ALRM} 的匿名子例程中杀死它,如下所示:

local $SIG{ALRM} = sub{kill 9, $pid; die "TIMEOUT!"}

但这并没有做任何事情。 program_of_interest 仍然存在。我该如何杀死这个进程?

【问题讨论】:

    标签: perl fork alarm


    【解决方案1】:

    我能够通过杀死进程组成功地杀死我的 exec()ed 进程,如问题 In perl, killing child and its children when child was created using open 的答案所示。我修改了我的代码如下:

    my $pid = fork;
    if ($pid > 0){
        eval{
            local $SIG{ALRM} = sub {kill 9, -$PID; die "TIMEOUT!"};
            alarm $num_secs_to_timeout;
            waitpid($pid, 0);
            alarm 0;
        };
    }
    elsif ($pid == 0){
        setpgrp(0,0);
        exec('echo blahblah | program_of_interest');
        exit(0);
    }
    

    超时后,program_of_interest 被成功杀死。

    【讨论】:

    • 我认为“kill 9, -$PID”应该是“kill -9, $PID”。我用 system("kill -9 $PID") 代替。
    • 我认为“kill -9 $PID”应该进一步是“kill -9 $pid”,但是是的,负的pid表示要杀死​​一个进程组而不仅仅是一个进程。来自 kill 文档:“与 shell 不同,如果 SIGNAL 为负数,它会杀死进程组而不是进程。(在 System V 上,负数 PROCESS 也会杀死进程组,但这不是可移植的。)”
    【解决方案2】:

    上面的代码(由 strictrude27 编写)不能开箱即用,因为 -$PID 用大写字母拼写。 (顺便说一句:还有:http://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html

    这是一个测试示例:

    #!/usr/bin/perl
    use strict;
    use warnings;
    use File::Basename;
    
    my $prg = basename $0;
    my $num_secs_sleep = 2;
    my $num_secs_to_timeout = 1;
    my $orig_program = "sleep $num_secs_sleep; echo \"Look ma, survived!\"";
    my $program = $orig_program;
    my $expect = "";
    
    if (@ARGV){
      if($ARGV[0] eq "test"){
        test();
        exit 0;
      } elsif (@ARGV == 1) {
        $num_secs_to_timeout = $ARGV[0];
      } elsif (@ARGV == 2) {
        $program = $ARGV[0];
        $num_secs_to_timeout = $ARGV[1];
      } else {
        die "Usage: $prg [ \"test\" | [program] seconds ] "
      }
    }
    
    if($orig_program eq $program) {
      if(@ARGV < 2) {
        $expect = $num_secs_to_timeout > $num_secs_sleep ?
          "(we expected to survive.)" : "(we expected to TIME OUT!)";
      }
      print STDERR "sleeping: $num_secs_sleep seconds$/";
    }
    
    print STDERR <<END;
      timeout after: $num_secs_to_timeout seconds,
      running program: '$program'
    END
    
    if($orig_program eq $program) {
      print STDERR "$expect$/";
    }
    
    exit Timed::timed($program, $num_secs_to_timeout);
    
    sub test {
      eval "use Test::More qw(no_plan);";
      my $stdout;
      close STDOUT;
      open STDOUT, '>', \$stdout or die "Can't open STDOUT: $!";
      Timed::timed("sleep 1", 3);
      is($stdout, undef);
      Timed::timed("sleep 2", 1);
      is($stdout, "TIME OUT!$/");
    }
    
    ################################################################################
    package Timed;
    use strict;
    use warnings;
    
    sub timed {
      my $retval;
      my ($program, $num_secs_to_timeout) = @_;
      my $pid = fork;
      if ($pid > 0){ # parent process
        eval{
          local $SIG{ALRM} = 
            sub {kill 9, -$pid; print STDOUT "TIME OUT!$/"; $retval = 124;};
          alarm $num_secs_to_timeout;
          waitpid($pid, 0);
          alarm 0;
        };
        return defined($retval) ? $retval : $?>>8;
      }
      elsif ($pid == 0){ # child process
        setpgrp(0,0);
        exec($program);
      } else { # forking not successful
      }
    }
    

    【讨论】:

      【解决方案3】:

      您的代码适用于我,经过一些小的修改 - 我认为这是您自己进行的更改以使代码成为通用示例。

      所以这让我有两个想法:

      1. 您在创建示例代码时解决了问题 - 尝试创建一个实际运行的小示例(我必须将 'program_of_interest' 和 $num_secs_to_timeout 更改为实际值来测试它)。确保样本有同样的问题。
      2. 这与您正在运行的 program_of_interest 有关 - 据我所知,您无法掩盖杀戮 9,但也许发生了一些事情。您是否尝试过使用非常简单的脚本来测试您的代码。我为我的测试创建了一个,而 (1) { print "hi\n";睡觉1; }
      3. 别的东西

      祝你好运……

      【讨论】:

      • 看起来你是对的:我实际上将一些东西通过管道传输到 program_of_interest:system("echo blahblah | program_of_interest")。 sh 被执行并被正确杀死,但不是 program_of_interest..
      【解决方案4】:

      可以忽略 SIGKILL 的唯一方法是进程卡在不可中断的系统调用中。检查挂起进程的状态(用ps aux),如果状态为D,则进程不能被杀死。

      您可能还想通过输出一些内容来检查该函数是否被调用。

      【讨论】:

        猜你喜欢
        • 2017-12-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-13
        • 2020-06-18
        • 1970-01-01
        • 1970-01-01
        • 2014-04-12
        相关资源
        最近更新 更多