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 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
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. |
|