Как я могу приостановить разветвленный процесс, который может зависнуть?

Я пишу 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 все еще сохраняется. Как мне убить этот процесс?

4 ответа

Решение

Мне удалось успешно убить мой процесс exec(), убив группу процессов, как показано в ответе на вопрос В Perl, убивая child и его дочерние элементы, когда child был создан с помощью 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 успешно убит.

Вышеприведенный код (строго по 27) не работал "из коробки", потому что -$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
  }
}

Хммм, ваш код работает для меня после некоторых незначительных изменений, которые, как я полагаю, являются изменениями, внесенными вами самим, чтобы превратить код в общий пример.

Так что у меня остаются две идеи:

  1. Вы удалили проблему, когда создали пример кода - попробуйте создать небольшой пример, который действительно работает (мне пришлось изменить 'program_of_interest' и $ num_secs_to_timeout на реальные значения, чтобы протестировать его). Убедитесь, что образец имеет ту же проблему.
  2. Это как-то связано с тем, что вы запускаете program_of_интерес - насколько я знаю, вы не можете замаскировать убийство 9, но, возможно, что-то происходит. Вы пробовали тестировать свой код с помощью действительно простого скрипта. Я создал один для моего тестирования, который идет в то время как (1) { print "hi\n"; сон 1; }
  3. Что-то еще

Удачи...

Единственный способ игнорировать SIGKILL - это если процесс застревает в системном вызове, который не прерывается. Проверьте состояние зависшего процесса (с помощью ps aux) если состояние D, процесс не может быть убит.

Вы также можете проверить, что функция вызывается путем вывода чего-либо из нее.

Другие вопросы по тегам