如果我设置 $SIG{ALRM},警报似乎不会触发

问题描述

我正在尝试在我的 Perl 后端进程中实现一个警报,以便它在卡住时间过长时终止。我试图实现 alarm documentation page on Perldoc 上给出的代码(这是文档中的逐字逐句,而不是调用我的程序的关键子例程的行而不是文档中的示例行):

eval {
    local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm $timeout;
    &FaithTree::Backend::commandLine({ 'skipwidgets' => $skipwidgets,'commandLineId' => $commandLineId,'force' => $force });
    alarm 0;
};
if ($@) {
    die unless $@ eq "alarm\n";   # propagate unexpected errors
    # timed out
}
else {
    # didn't
}

给定这个代码,当警报应该超时时什么也不会发生。另一方面,如果我删除 $SIG{ALRM}自定义定义(同样直接来自 Perl 文档),警报确实会触发,只是没有自定义处理程序。

我想知道我使用 Thread::Queue 的事实是否在警报失败中起作用,但这并不能解释为什么只要我跳过重新定义 $SIG{ALRM} 就可以工作。

这是一个带有子程序的最小的、可运行的版本,该子程序有意无限循环以进行测试:

eval {
    $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm 1;
    &FaithTree::Test::Backend::commandLine({ 'skipwidgets' => $skipwidgets,'force' => $force });
    alarm 0;
};


if ($@) {
    die unless $@ eq "alarm\n";   # propagate unexpected errors
    # timed out
}
else {
   exit;
}


package FaithTree::Test::Backend;

use File::Tail;
use threads;
use threads::shared;
use Thread::Queue;

sub commandLine {

    our $N //= 4;
    my $Q = new Thread::Queue;
    my @kids = map threads->create( \&FaithTree::Test::Backend::fetchChild,$Q ),1 .. $N;  

    my @Feeds = ( "1","2","3","4" );

    foreach my $Feed (@Feeds) {
        $Q->enqueue( $Feed );
    }

    $Q->enqueue( ( undef ) x $N );
    $_->join for @kids; 

}

sub fetchChild {

    print "Test";
    # Access queue.
    my $Q = shift;

    #What is my thread id?
    my $tid = threads->tid();

    my ($num,$num2);

    for ( ; ; ){ 
        if ($num2 == 10000) {
            say STDERR $tid . ': ' . $num;
            $num2 = 0;
        }
    
        $num++; 
        $num2++;
    }

    return 1;

}

如果您注释掉 $SIG{ALRM} 行,它将在闹钟设置为超时时终止。如果你把它留在原地,它永远不会终止。

解决方法

信号和线程不能很好地混合。您可能需要重新考虑对信号的使用。例如,您可以将所有线程内容移至子进程。


信号处理程序仅在 Perl 操作之间调用。主线程正在调用 XS sub thread->join,一旦 join 返回,将调用信号处理程序。

大多数阻塞系统调用都可以被中断(返回错误 EINTR),因此可以编写 join 的信号感知版本。除了我似乎记得 pthread 函数是不可中断的,所以也许不是。


在这种特殊情况下,您可以让线程在线程结束时向主线程发送信号,使用允许主线程阻塞直到出现信号或超时的系统。 cond_signal/cond_timedwait 就是这样一个系统。

use Sub::ScopeFinalizer qw( scope_finalizer );
use Time::HiRes         qw( time );

my $lock :shared;
my $threads_remaining = $N;
my $Q = new Thread::Queue;

my @threads;
{
   lock $lock;
   for (1..$N) {
      ++$threads_remaining;

      push @threads,async {
         my $guard = scope_finalizer {
            lock $lock;
            --$threads_remaining;
            cond_signal($lock);
         };

         worker($Q);
      }
   }
}

my $max_end_time = time + 1;

# ...

{
   lock $lock;
   while ($threads_remaining) {
      if (!cond_timedwait($lock,$max_end_time)) {
         # ... Handle timeout ...
      }
   }
}

$_->join for @threads;