Friday, July 1, 2011

Timing out long computations in Perl

I am gonna clarify the matter of alarms and enforcing timeouts: this is the Unix way of using alarm signals, straight from the perlipc manpage:

my $ALARM_EXCEPTION = 'alarm clock restart';
eval {
    local $SIG{ALRM} = sub { die $ALARM_EXCEPTION };
    alarm 10;

    ... long computation ...

    alarm 0;
};
if ($@ && $@ !~ quotemeta($ALARM_EXCEPTION)) { die }

After 10s the alarm goes off, die inside the callback is executed and returns a string you choose - 'alarm clock restart' is good choice because no other normal die command inside the computation is likely to use it.

Since die $ALARM_EXCEPTION is executed inside an eval block, $@ is set but execution of the main script does not terminate. quotemeta just backslashes non-alphanumeric characters that may have a special meaning in regexs. There are no such characters in the case of 'alarm clock restart', therefore it will just backslash blanks, which is redundant, but there could be any in the full error message that $@ contains.

Outside the block one can check whether a real error or a timeout occurred:

if ($@) {
    # An error occurred.
    if ($@ =~ quotemeta($ALARM_EXCEPTION)) {
        # It was a timeout.
        die 'Timed out: operation is taking too long';
    } else {
        # It was a real error.
        die;
    }
}

Previous code just ignored timeouts, but now they are considered errors.

All this eval "dance" is needed because you do not want to overwrite a global ALRM signal handler you have no control of. Note that die without any argument just propagates the error message generated by the computation to the main program.

No comments: