Проблемы с сигнализацией при объединении Net::OpenSSH и потоков

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

После долгих попыток мне удалось извлечь минимальный код, чтобы надежно воспроизвести проблему на моей машине:

use warnings;
use strict;
use threads;
use threads::shared;
use Data::Dumper;
use POSIX ":sys_wait_h";
use Net::OpenSSH;
use Time::HiRes qw( usleep );

my @LIST=qw(host038b host039a host039b host040a host040b host041a host041b host043a
   host043b host044a host044b host045a host045b host046a host046b host047a host047b host049a
   host049b host050a host050b host054a host054b host055a host055b host056a host056b host057a
   host057b host058a host059a host059b host060a host060b host062a host062b host063a host068a
   host068b host069a host069b host071a host071b host072a host073a host073b host075a host075b
   host078a host078b host082a host082b host087a host087b host089a host089b host090a host090b
   host091a host091b host092a host092b host096a host096b host097a host097b host098a host099a
   host099b host100a);
my ($SSH, $CPID, %PIDS, @DONE);

sub _testthread {
  # Read stdout pipe
  my $SCROUT=shift;
  while (<$SCROUT>) {
    print $_;              # I normally write that to a logfile
  }
  return (0);
}

foreach (@LIST) {
$SSH->{$_}=Net::OpenSSH->new($_,       async => 1,
                                 master_opts => [ -o => "PasswordAuthentication=no"]);
}

$SIG{CHLD} = sub { my $WPID; 
            push (@DONE, { 'PID' => $WPID, 'RC' => $?, 'ERR' => $!}) while (($WPID = waitpid(-1, WNOHANG)) > 0) };

foreach (@LIST) {
  my ($SCRFH, $SCROUT, undef, $CPID) = $SSH->{$_}->open_ex({stdin_pipe => 1,
                                                           stdout_pipe => 1},  '/bin/bash -s');
  $PIDS{$CPID}='ACTIVE';
  threads->new('_testthread', $SCROUT);
  print $SCRFH "sleep 2\n";
  print $SCRFH "echo test `hostname`\n";
  print $SCRFH "exit 0\n";
  close $SCRFH;
  usleep 10000;
}

while (grep(/^ACTIVE/, values(%PIDS)) > 0) {
  print Dumper \%PIDS;
  while (@DONE) {
    my $DONE = shift (@DONE);
    $PIDS{$DONE->{PID}}='DONE';
  }
  sleep 1;
}

$_->join foreach (threads->list);

С предустановленным perl 5.10 в большинстве случаев происходит ошибка сегментации, даже при удалении некоторых более сложных конструкций перенаправления вывода open_ex в файловый дескриптор. С недавно скомпилированным perl 5.18.2 этот скрипт большую часть времени зависает на неопределенный срок, потому что он, кажется, не получает каждый SIG{CHLD}, даже несмотря на то, что я использую безопасную сигнализацию (насколько я понимаю).

Чтобы воспроизвести проблему, кажутся необходимыми следующие вещи:

  • Достаточное количество хостов в @LIST
  • позволяя open_ex (или производным методам Net::OpenSSH) разветвляться
  • предоставление дескриптора файла STDOUT этого форка потоку
  • используя обработчик сигнала для SIG{CHLD}

Поскольку моя большая программа, использующая эту структуру, в основном непригодна для использования, я был бы очень рад, если бы кто-нибудь помог мне найти решение, возможно, альтернативу.

Спасибо и привет,

Маззе


person Mazze    schedule 08.04.2014    source источник
comment
Я думаю, проблема с 5.18 в том, что сигнал SIGCHLD может прийти в любой поток. Таким образом, ваш скрипт отправляет результаты waitpid вызовов, разделенных на @DONE клоны. Что касается 5.10, segfault указывает на ошибку в Perl или в модуле потоков.   -  person salva    schedule 08.04.2014
comment
Итак, вы думаете, если бы я использовал threads::shared для @DONE, это должно смягчить проблему?   -  person Mazze    schedule 08.04.2014
comment
Я попробовал это сейчас, и, кажется, это работает до сих пор :)   -  person Mazze    schedule 08.04.2014
comment
да. Кстати, вы рассматривали возможность использования Net::OpenSSH::Parallel?   -  person salva    schedule 08.04.2014
comment
может показаться, что это работает, но, вероятно, вы просто свели к минимуму вероятность возникновения проблемы. Я думаю, вы должны установить @DONE как общий, чтобы действительно исключить его.   -  person salva    schedule 08.04.2014
comment
Я рассматривал это только в течение короткого промежутка времени: мне нужна замечательная функция open_ex Net:: OpenSSH, чтобы запустить процесс ('bash -s'), а затем отправить управляемый сценарий оболочки на STDIN этой оболочки. Поэтому я остановился только на Net:OpenSSH.   -  person Mazze    schedule 08.04.2014
comment
Поскольку я отредактировал свой (первоначально неправильный) ответ выше, я действительно установил \@DONE как общий, и теперь он работает как шарм :D Еще раз большое спасибо за ваш быстрый ответ и ценную, любезную поддержку! Если вы запишите решение («сделать \@DONE общим»), я могу проголосовать за него, так как это явно ответ :)   -  person Mazze    schedule 08.04.2014
comment
Это можно сделать с помощью Net::OpenSSH::Parallel, используя parsub действия. См. Часто задаваемые вопросы о sudo.   -  person salva    schedule 08.04.2014
comment
Еще одна вещь: вы можете использовать open2, который является ярлыком для open_ex({stdin_pipe => 1, stdout_pipe => 1}, ...). Например: my ($in, $out, $pid) = $ssh->open2("bash -s");   -  person salva    schedule 08.04.2014


Ответы (1)


Вы пытаетесь смешивать сигналы и потоки, и это всегда будет плохой идеей. Решение состоит в том, чтобы просто прекратить использование потоков; все, что вы делаете, можно сделать лучше по-другому.

Возможно, рассмотрите какую-то асинхронную/управляемую событиями систему ввода-вывода для выполнения этих параллельных задач ввода-вывода.

person LeoNerd    schedule 08.04.2014