【问题标题】:alarm does not seem to fire if I set $SIG{ALRM}如果我设置 $SIG{ALRM},警报似乎不会触发
【发布时间】:2021-04-07 23:43:03
【问题描述】:

我正在尝试在我的 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, 'commandLineId' => $commandLineId, '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} 行,它将在警报设置为超时时终止。如果你把它留在原地,它永远不会终止。

【问题讨论】:

  • Thread::Queue... 所以这是一个线程程序?在线程程序中使用信号并没有真正意义,因为信号被发送到进程(而不是线程)。您是否甚至根据threads.pm docs在主线程中设置$SIG{ALRM}
  • @ikegami 是的。这一切都是在它分叉到不同的线程之前。每个线程都是检查内容更新的工作人员。直到您在上面看到的那个子例程调用之后才创建线程(当然,除了主进程本身)。目标是如果它在计划运行时间之外被卡住,就杀死整个事情。我可能完全基于 Thread::Queue 影响这一点;我只是想弄清楚为什么直接从文档中提取的代码不起作用。
  • 我想如果我能想出一个好方法来监控 /each/ 线程的运行时间并将 Perl ->kill() 发送到所述线程,如果它运行时间很长,那会更干净而不是杀死整个进程,但我只是试图做一些基本的事情来避免进程永远不会终止(并随后在下次 cron 启动它时阻塞)的情况。
  • 也许看门狗设置对您来说更有意义。每隔一段时间更改 pid 文件的修改时间。下次 cron 作业运行时,如果修改时间超过了允许的时间,如果旧任务仍在运行,则终止旧任务。 [类似的事情也可以在一个线程一个线程的基础上完成。]
  • @ikegami 我已经添加了上面的代码。谢谢!看门狗是有道理的——我实际上一直在阅读关于如何最好地将一个看门狗放在一起的想法,然后遇到了一个用 Perl 编写的。我可能应该这样做,但我想,“如果我能让程序自行处理会怎样?”

标签: perl alarm


【解决方案1】:

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


仅在 Perl 操作之间调用信号处理程序。主线程在调用XS subthread->join,一旦join返回,就会调用信号处理函数。

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


在这种特殊情况下,您可以让线程在它们结束时向主线程发出信号,使用允许主线程阻塞直到出现 a 信号或发生超时的系统。 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;

【讨论】:

  • 添加回答。
  • 有趣。谢谢!这会解决其中一个线程冻结的问题吗?或者在那种情况下我是否需要更多地走你上面提到的“看门狗”路线?我正在尝试隔离 为什么 一个线程冻结,但是其中一个线程冻结了,当它冻结时,后端被卡住,直到我注意到,终止进程并重新开始。
  • Re "这会解决一个线程冻结的问题吗?",是的,你会知道的,因为你没有被阻止调用join,你被阻止拨打cond_timedwait
  • 这似乎工作得很好。谢谢!出于调试目的,该超时处理程序是否能够报告在超时发生之前要运行的最后一段代码是什么?记录下来似乎很有用,这样我就可以弄清楚是什么卡住了。
  • 没有什么能阻止您使用当前步骤更新变量。
猜你喜欢
  • 2021-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多