使用alarm设置读取stdin的超时时间

时间:2013-07-23 08:30:04

标签: perl signals alarm race-condition

我有这段代码:

#!/usr/bin/perl
use strict;
use warnings;
my ($timeout, $size, $buffer) = (10, 10, undef);
eval {
    local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm $timeout;
    my $nread = sysread STDIN, $buffer, $size;
    # !!!! race condition !!!!!
    alarm 0;
    print "$nread: $buffer";
};
if ($@) {    
    warn $@;
}

这是对的吗? 可能是8到9行之间有竞争条件吗?

3 个答案:

答案 0 :(得分:2)

让我们看看,发生了什么:

my ($timeout, $size, $buffer) = (10, 10, undef);
eval {
    #establish ALRM signal handler
    local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required

    #send alarm signal to program in 10 second
    alarm $timeout;

    #try yo read 10 bytes of data into $buffer
    my $nread = sysread STDIN, $buffer, $size;

    #cancel the previous timer without starting a new one 
    #if we returned from sysread. Yes, if 10 seconds pass
    #before the next function is executed, the script will
    #die even though the data was read
    alarm 0;

    #print number of bytes read (will be 10) and the string,
    #read from input
    print "$nread: $buffer";
};
如果要评估的字符串未编译,或者在评估期间执行Perl代码,则设置

$ @()d。在这些情况下,$ @的值是编译错误,或者死亡的参数:

if ($@) {    
    warn $@;
}

因此,如果我们在10秒内没有从sysread返回,这将打印消息“alarm \ n”。

在非常不可能的情况下,当输入将在10秒之前收到并且我们将无法运行警报0;我建议使用以下代码:

my ($timeout, $size, $buffer) = (10, 10, undef);

#I define $nread before the signal handler as undef, so if it's defined
#it means, that sysread was executed and the value was assigned
my $nread = undef;
eval {
    local $SIG{ALRM} = sub {

        #if it's not defined - it means, that sysread wasn't executed
        unless(defined($nread))
        {
            die "alarm\n";
        }
    };
    alarm $timeout;
    $nread = sysread STDIN, $buffer, $size;
    alarm 0;
    print "$nread: $buffer";
};

不幸的是,当没有执行赋值运算符时,它不能保存我们的情况。

链接:

http://perldoc.perl.org/functions/alarm.html

http://perldoc.perl.org/perlvar.html

http://perldoc.perl.org/functions/sysread.html

答案 1 :(得分:1)

您对alarm的使用会引发潜在的竞争条件。

正常的解决方案是在alarm 0;阻止后添加eval,因此如果第一个alarm 0未执行,您仍然可以关闭闹钟。

或者你可以在CPAN上使用Time::Out包来包装你的代码,它会更好更安全。

答案 2 :(得分:0)

你在运行什么操作系统?什么版本的perl?

使用perl 5.12.4在Mac OS X 10.8.3上运行正常。

如果您在Windows上使用perl,您会发现信号与POSIX和类似POSIX的操作系统的工作方式不同,您可能需要使用4参数替代版本的select()。