Perl 的内置 alarm 不足以打破长期运行的正则表达式,因为 Perl 不会在内部操作码内提供警报超时的机会。 alarm 根本无法穿透它。
在某些情况下,最明显的解决方案是 fork 一个子进程,并在它运行时间过长后使用 alarm 将其超时。这篇 PerlMonks 帖子演示了如何使分叉进程超时:Re: Timeout on script
在 CPAN 上有一个名为 Sys::SigAction 的 Perl 模块,它有一个名为 timeout_call 的函数,它将使用不安全的信号中断一个长时间运行的正则表达式。但是,RE 引擎并非设计为中断,并且可能会处于不稳定状态,这可能导致大约 10% 的时间段发生故障。
下面是一些示例代码,展示了 Sys::SigAction 成功突破正则表达式引擎,以及证明 Perl 的 alarm 无法做到这一点:
use Sys::SigAction 'timeout_call';
use Time::HiRes;
sub run_re {
my $string = ('a' x 64 ) . 'b';
if( $string =~ m/(a*a*a*a*a*a*a*a*a*a*a*a*)*[^Bb]$/ ) {
print "Whoops!\n";
}
else {
print "Ok!\n";
}
}
print "Sys::SigAction::timeout_call:\n";
my $t = time();
timeout_call(2,\&run_re);
print time() - $t, " seconds.\n";
print "alarm:\n";
$t = time();
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
alarm 2;
run_re();
alarm 0;
};
if( $@ ) {
die unless $@ eq "alarm\n";
}
else {
print time() - $t, " seconds.\n";
}
输出将类似于以下内容:
$ ./mytest.pl
Sys::SigAction::timeout_call:
Complex regular subexpression recursion limit (32766) exceeded at ./mytest.pl line 11.
2 seconds.
alarm:
Complex regular subexpression recursion limit (32766) exceeded at ./mytest.pl line 11.
^C
您会注意到,在第二次调用中——应该与alarm 超时的调用,我最终不得不将ctrl-C 退出,因为alarm 不足以打破RE 引擎。
Sys::SigAction 的最大警告是,即使它能够打破长期运行的正则表达式,因为 RE 引擎不是为此类中断而设计的,整个过程可能会变得不稳定,从而导致段错误。虽然它不会每次都发生,但它可能会发生。这可能不是你想要的。
我不知道您的正则表达式是什么样的,但如果它符合RE2 engine 允许的语法,您可以使用Perl 模块re::engine::RE2 来使用RE2 C++ 库。这个引擎保证线性时间搜索,尽管它提供的语义不如 Perl 的内置引擎强大。 RE2 方法首先通过提供线性时间保证来避免整个问题。
但是,如果您无法使用 RE2(可能是因为您的正则表达式的语义要求太高),fork/alarm 方法可能是确保您保持控制的最安全方法。
(顺便说一句,这个问题和我的答案的一个版本被交叉发布到PerlMonks。)