Остановить безудержное регулярное выражение

Есть ли способ остановить безудержное регулярное выражение?

Меня не интересуют предложения по его изменению. Я знаю, что он может быть изменен, поэтому он не прерывается и т.д., Но я запускаю одно регулярное выражение против тысяч входов, поэтому его изменение означает, что мне нужно повторно протестировать его на всех входах. Не очень практично.

Итак, точный вопрос: есть ли какая-то форма таймера, которую я могу использовать для завершения регулярного выражения, которое занимает больше времени, чем X секунд?

Ответ 1

Встроенный alarm Perl недостаточно для выхода из долгого регулярного выражения, поскольку Perl не предоставляет возможности тайм-аутов тревоги внутри внутренних кодов операций. alarm просто не может проникнуть в него.

В некоторых случаях наиболее очевидным решением является fork подпроцесс и время его завершения после длительного использования с помощью alarm. Это сообщение PerlMonks демонстрирует, как отключить разветвленный процесс: Re: Timeout на script

В CPAN есть модуль Perl, называемый Sys:: SigAction, который имеет функцию под названием timeout_call, которая прерывает длительный регулярное выражение с использованием небезопасных сигналов. Однако двигатель RE не был спроектирован так, чтобы его прерывали, и его можно оставить в неустойчивом состоянии, что может привести к ошибкам seg в 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( [email protected] ) {
  die unless [email protected] 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 не был предназначен для таких прерываний, весь процесс может стать нестабильным, что приведет к segfault. Хотя это не происходит каждый раз, это может произойти. Вероятно, это не то, что вы хотите.

Я не знаю, как выглядит ваше регулярное выражение, но если оно соответствует синтаксису, разрешенному RE2 engine, вы можете использовать модуль Perl, re:: engine:: RE2 для работы с библиотекой RE2 С++. Этот двигатель гарантирует линейный поиск времени, хотя он обеспечивает менее мощную семантику, чем встроенный движок Perl. Подход RE2 избегает всей проблемы в первую очередь, предоставляя гарантию линейного времени.

Однако, если вы не можете использовать RE2 (возможно, потому что ваша семантика регулярного выражения слишком сложна для него), метод fork/alarm, вероятно, самый безопасный способ гарантировать, что вы остаетесь в контроле.

(Кстати, этот вопрос и версия моего ответа были перекрестно настроены на PerlMonks.)