pileofrogs has asked for the wisdom of the Perl Monks concerning the following question:

Hail all Monks!

I want to try reading from a handle (a pipe in my case) and timing out if nothing happens after X seconds. I'm using a $SIG{ALRM} right now, but that involves a callback when all I really want to do is stop trying to read and continue execution right after the read. I found myself considering a GOTO inside the $SIG{ALRM} callback, and that's when I decided a perlmonks post was in order.

To clarify, here's the code of my $SIG{ALRM} rendition:

# $read is an open pipe from earlier local $SIG{ALRM} = sub { goto('WIBBLE'); }; alarm(60); $result = <$read>; :WIBBLE if ( !defined($result) ) { print "Oh well, I didn't need that anyway...\n"; }

And to restate my question: What's the best way to handle a timeout like this?

Replies are listed 'Best First'.
Re: Best way to timeout on a read
by moritz (Cardinal) on Aug 20, 2008 at 18:11 UTC
    If you don't like goto, you can throw an exception instead:
    use strict; use warnings; local $SIG{ALRM} = sub { die 'timeout' }; alarm(3); my $result = eval { <STDIN> }; alarm 0; if ( !defined($result) ) { print "Oh well, I didn't need that anyway...\n"; }

    I don't know if that's the "best" way, but it certainly is better. (Maybe it's even better to do the local $SIG... in a smaller scope). And don't forget to reset alarm.

    There are also various modules on cpan that do IO with timeouts and async IO.

      I second the recommendation to use exception instead of goto and to reset the alarm afterwards.

      Personally, I prefer to have the signal handler and alarm calls enclosed inside the eval as well - to me, it's more obvious what's going on that way. alarm and perlipc show the same approach:

      eval { local $SIG{ALRM} = sub { die "timeout" }; alarm 10; $input = <STDIN>; alarm 0; }; if ($@ and $@ =~ /timeout/) { print "Reading timed out"; } elsif ($@) { die "Other error: $@"; }
Re: Best way to timeout on a read
by Perlbotics (Archbishop) on Aug 20, 2008 at 18:16 UTC

    I remember that there was a good description in the Camel book (16: Signals - Timing out Slow Operations). Since Perl tries to recover from interrupted system calls, a eval/die-construct was necessary to handle this situation properly. A similar description can be found in the perlipc-doc after the second occurrence of longjmp.

    You've probably done that, but if not then you should consider resetting the alarm after successful completion (alarm(0);).

    Update: In the Camel book example, the alarm was cleared again after the eval-block to cope with a race condition when the signal occurred after a successful IO operation but before resetting the alarm within the same eval block. I am not sure if this is still an issue with modern kernels.