21

I frequently use the following pattern to set an upper bound to the running time of a particular code fragment in Perl:

my $TIMEOUT_IN_SECONDS = 5;
eval {
    local $SIG{ALRM} = sub { die "alarm\n" };
    alarm($TIMEOUT_IN_SECONDS);
    # do stuff that might timeout.
    alarm(0);
};
if ($@) {
    # handle timeout condition.
}

My questions:

  • Is this the right way to do it?
  • Are there any circumstances under which the running time can exceed $TIMEOUT_IN_SECONDS, or is the above method bullet-proof?
knorv
  • 49,059
  • 74
  • 210
  • 294
  • 1
    Your eval is vulnerable to a nasty bug. Read the Try::Tiny docs for a detailed explanation: http://search.cpan.org/perldoc?Try::Tiny – daotoad Mar 12 '10 at 16:07

3 Answers3

10

You probably want to look at Sys::SigAction. I haven't used it myself, but it has some glowing reviews.

One thing to watch out for is if "stuff that might timeout" uses sleep or alarm itself. Also, in the error handling code, I assume you're prepared for errors other than a timeout.

cjm
  • 61,471
  • 9
  • 126
  • 175
5

You could also try Time::Out. I like the syntax and nested timeouts are supported..

Peter V. Mørch
  • 13,830
  • 8
  • 69
  • 103
  • 1
    +1 This module handles all the worries about nested timeouts etc for you so you don't have to worry about all the edge cases. It is to alarm what try::tiny is to eval. – mikew Aug 15 '14 at 14:23
  • Is the Time::Out module threadsafe? In my case I need a timeout running per thread in my application. – reedog117 May 01 '15 at 21:02
  • The only problem is that is uses Time::HiRes::alarm which isn't windows compatible. – mvsjes2 Aug 26 '21 at 15:05
2

Take care with signal handling. Perl receives signals asynchronously and they may be lost or interfere with each other if a signal is received while another signal is being handled by the callback.

Event-handling libraries' Win32 support is pretty so-so in Perl (I have to support non-cygwin Win32), so I generally use a simple polling loop for timeouts:

use Time::HiRes qw(sleep);

sub timeout {
  my $timeout = shift;
  my $poll_interval = shift;
  my $test_condition = shift;
  until ($test_condition->() || $timeout <= 0) {
    $timeout -= $poll_interval;
    sleep $poll_interval;
  }
  return $timeout > 0; # condition was met before timeout
}

my $success = timeout(30, 0.1, \&some_condition_is_met);

The sleep timer can be easily made user- or caller-configurable and unless you are doing an extremely tight loop or have multiple callers waiting on the loop (where you can end up with a race or dead lock), it is a simple, reliable, and cross-platform way to implement a timeout.

Also note that the loop overhead will mean that you cannot guarantee that the timeout is observed absolutely. $test_condition, the decrement, garbage collection, etc. can interfere.

Jeff Ober
  • 4,967
  • 20
  • 15
  • 1
    This does not answer the question. This code won't achieve the same objective as the OP's code. If some_condition_is_met() never returns, the program will hang. – Will Sheppard Apr 18 '17 at 08:39