home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


5.6 Using Eval for Time-Outs

When you call eval , Perl makes a note of the next statement to start executing just in case a die is invoked somewhere within. Internally, die happens to invoke a longjmp , so Perl wastes no time at all transferring control back up to the statement following eval , regardless of how deep the stack is. (All temporary and local variables created in the eval 'ed code are automatically garbage collected.)

The use of setjmp and longjmp internally gives us one new technique: aborting blocked system calls and infinite loops. Let's say you want to wait at most 10 seconds for the user to type something.[ 4 ] If you say $buf = <> , the program is blocked until the user deigns to hit a carriage return, but we would like Perl to abort it after waiting 10 seconds. Generating a time-out is not really a problem; the built-in function alarm() can be used to generate an ALRM signal after a given number of seconds, like this:

$SIG{ALRM} = \&timed_out;
alarm(10);      # Tells the OS to issue an ALRM signal after 10 seconds
$buf = <>;      # Go into a blocking read

[4] Thanks to Tom Christiansen for this example.

The procedure timed_out is called (after 10 seconds) regardless of what Perl happens to be executing at that time, be it a blocked read or an infinite loop. The problem is, how does timed_out force Perl to abandon what Perl happened to be doing at the time it was called? That's where eval / die come in. Put an eval around $buf = <> and a die inside timed_out() , and control will be restored to the statement following eval (the if statement), as shown here:

$SIG{ALRM} = \&timed_out;
eval {
    alarm (10);
    $buf = <>;
    alarm(0);           # Cancel the pending alarm if user responds.
};
if ($@ =~ /GOT TIRED OF WAITING/) {
    print "Timed out. Proceeding with default\n";
    ....
}

sub timed_out {
    die "GOT TIRED OF WAITING";
}

If the user doesn't hit a return within 10 seconds, timed_out is called from the signal handler, which calls die , which internally longjmps over to the statement following the innermost eval . If the user does hit a return within the allotted time, alarm(0) is called to reset the alarm.

Note that if the alarm goes off, $@ contains something like "GOT TIRED OF WAITING at foo.pl line 100," so you cannot use eq ; you must use a regular expression match (or the index operator).

Tom Christiansen pointed out a subtle and interesting point. It is essential that you set alarm inside the eval block, because on a heavily loaded machine (and for small time-out periods), it is possible to lose the time-slice after the call to alarm and before it has a chance to enter the protected section (the eval block). Later on, when the program regains the time-slice, it is possible that the time-out interval has expired, and the program will abort.