Perl 结合Net::OpenSSH和线程时的信令问题

Perl 结合Net::OpenSSH和线程时的信令问题,perl,thread-safety,signals,openssh,Perl,Thread Safety,Signals,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(host

我写了一个相当大的程序,在许多远程主机上执行命令,但我遇到了一个严重的问题,我不知道如何解决它

经过多次尝试,我能够提取最小代码,以便在我的机器上可靠地再现问题:

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);
使用警告;
严格使用;
使用线程;
使用线程::共享;
使用数据::转储程序;
使用POSIX“:sys_wait_h”;
使用Net::OpenSSH;
使用时间:雇佣qw(usleep);
my@LIST=qw(主机038B主机039A主机039B主机040A主机040B主机041A主机041B主机043A
主机043B主机044A主机044B主机045A主机045B主机046A主机046B主机047A主机047B主机049A
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
主机078A主机078B主机082A主机082B主机087A主机087B主机089A主机089B主机090A主机090B
主机091A主机091B主机092A主机092B主机096A主机096B主机097A主机097B主机098A主机099A
host099b host100a);
我的($SSH,$CPID,%PIDS,@DONE);
子测试线程{
#阅读标准管道
我的$SCROUT=shift;
而(){
打印$。#我通常将其写入日志文件
}
返回(0);
}
foreach(@LIST){
$SSH->{$\u}=Net::OpenSSH->new($\ux,async=>1,
master_opts=>[-o=>“PasswordAuthentication=no”];
}
$SIG{CHLD}=sub{my$WPID;
推送(@DONE,{'PID'=>$WPID,'RC'=>$?,'ERR'=>$!}),而($WPID=waitpid(-1,WNOHANG))>0);
foreach(@LIST){
my($SCRFH,$SCROUT,unde,$CPID)=$SSH->{$}->open_-ex({stdin_-pipe=>1,
stdout_pipe=>1},“/bin/bash-s”);
$PIDS{$CPID}='ACTIVE';
线程->新建(“测试线程”,$SCROUT);
打印$SCRFH“睡眠2\n”;
打印$SCRFH“echo test`hostname`\n”;
打印$SCRFH“退出0\n”;
关闭$SCRFH;
美国LEEP 10000;
}
而(grep(/^ACTIVE/,值(%PIDS))>0){
打印转储程序\%PIDS;
while(@DONE){
我的$DONE=班次(@DONE);
$PIDS{$DONE->{PID}}='DONE';
}
睡眠1;
}
$\->加入foreach(线程->列表);
对于预安装的perl 5.10,这在大多数情况下都是错误的,即使在使用一些更复杂的结构将open_ex输出重新定义为文件描述符时也是如此。 在新编译的perl 5.18.2中,这个脚本大部分时间都无限期地挂起,因为它似乎没有接收到每个SIG{CHLD},即使我使用的是安全信号(据我所知)

要重现问题,似乎有必要采取以下措施:

  • @LIST中有足够数量的主机
  • 让open_ex(或Net::OpenSSH的派生方法)分叉
  • 将该fork的STDOUT文件句柄提供给线程
  • 使用SIG{CHLD}的信号处理程序
由于我使用这种结构的更大的程序大部分无法使用,如果有人能帮助我找到解决方案,也许是替代方案,我将非常高兴

谢谢,大家好,


Mazze

您试图混合信号和线程,这总是一个坏主意。解决方法就是停止使用线程;无论你在做什么,都可以用不同的方式做得更好


也许考虑某种异步/事件驱动的IO系统,用.</P>执行这些并发的IO任务。我认为5.18的问题是<代码> SigCHLD信号可以到达任何线程。因此,您的脚本正在将

waitpid
调用的结果拆分为
@DONE
克隆。关于5.10,segfault表示perl或threads模块上存在错误。因此,您认为,如果我将@DONE使用“threads::shared”,这应该可以缓解问题吗?我现在尝试过,目前为止似乎有效:)是的。顺便说一句,你有没有考虑过使用它?它可能看起来很有效,但可能你只是将问题发生的可能性降到了最低。我认为您应该将
@DONE
设置为共享,以真正消除它。