Wake up! Time to open presents!
Wake up! Time to open presents!
Clark W. Grisman faced a dilemma, it was the night before Christmas; and he found himself up against a programming deadline. Why a company at the cutting edge of milk-proof cereal coatings required him to write a Perl program, Clark can't remember, but he does know that he's trying to read from a socket that keeps timing out!
Traditional alarm
Handling
In the past, whenever Clark wanted to call a procedure inside his Perl code that might take too long to return, he always simply copied the example in perldoc -f alarm
, which at the time of this Advent season looks like the following:
# ...
eval {
local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
alarm $timeout;
my $nread = sysread $socket, $buffer, $size;
alarm 0;
};
if ($@) {
die unless $@ eq "alarm\n"; # propagate unexpected errors
# timed out
}
else {
# didn't
}
If he didn't need it quite so fancy, he'd just nip it down to:
$SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
alarm $timeout;
my $nread = sysread $socket, $buffer, $size;
alarm 0;
Clark never liked using alarm
in this latter way because it didn't feel very perlish. But he did what he had to, then would just bottle up the guilt. That's what the eggnog is for, he'd tell himself.
alarm
Handling Comes to Christmastown
Clark is not only a food scientist by day, but he considers himself a Perl artist by night. Realizing this approach seemed a little dated, he recalled seeing a module fly by the CPAN radar that treats alarm
's ALRM
signal as a catchable exception.
After some searching, he found the module. Try::ALRM. Turns out there was no need to fiddle with the eval
stuff directly. Using this handy module, it can be done in the following way using a multi-level block that looks a lot a try
/catch
construct.
use strict;
use warnings;
use Try::ALRM;
# ...
# thing that can time out
try_once {
my $nread = sysread $socket, $buffer, $size;
}
# code block locally assigned to $SIG{ALRM}
ALRM {
warn qq{Wake up! `sysread` has timed out!};
}
# code run after $SIG{ALRM}
finally {
my ($attempt, $successful) = @_;
if (not $successful) {
# timed out
}
else {
# didn't
}
}
# trailing modifier to set timeout (in seconds)
timeout => $timeout;
Cleaned up, Clark's code came out to be:
try_once {
my $nread = sysread $socket, $buffer, $size;
}
# NB: no ALRM block is a localized no-op $SIG{ALRM}!
finally {
my ($attempt, $successful) = @_;
if (not $successful) {
# timed out
}
else {
# didn't
}
} timeout => $timeout;
In the above code, Clark found he didn't even need to define the ALRM
handler block, so he didn't include it. Effectively the $SIG{ALRM}
handler was a no-op.
retry
: Never Gonna Give It Up ...
.. not immediately, anyway.
Clark was not satisfied, since he wanted to add some retries; and this is when Try::ALRM really proved its worth. And Clark really needed a win. Chicago PD just SWAT'd the Grisman home because in a misguided attempt at some Christmas cheer, cousin Eddie had, among other things, just emptied the entire contents of his RV's septic tank into the neighborhood's drainage system.
In all its full glory, Clark finished his code in time to enjoy a lot of spiked eggnog before getting some sleep.
use strict;
use warnings;
use Try::ALRM;
# ...
retry {
my $attempt = shift;
my $total_tries = tries;
printf qq{Attempt #%d of %d ...\n}, $attempt, $total_tries;
my $nread = sysread $socket, $buffer, $size;
}
ALRM {
my $attempt = shift;
my $total_tries = tries;
my $msg = sprintf qq{FAILED: Attempt #%d of %d ...\n}, $attempt, $total_tries;
warn $msg;
}
finally {
my ($attempt, $successful) = @_;
if (not $successful) {
# timed out
}
else {
# didn't
}
} timeout => $timeout, tries => 5;
It Was Just A Dream
... or was it! Clark frantically searched the house for evidence of the shenanigans of the night before, but realized that it was all a dream. Good thing, because he was really starting to get a hankerin' for the pickle jelly from the Jelly of the Month Club.
Clark did realize that he wished for one part of that dream to be true. Something like this Try::ALRM sure would be a blessing for a lot of things! And what to his wondering eyes did appear, after searching on MetaCPAN, was Try::ALRM and 8 tiny reindeer!
And just at that moment, he's convinced he heard Santa's bells and a jolly old, Ho, Ho, Ho! Merrry Christmas!. He also now sees 8 tiny reindeer everywhere and is really worried that Uncle Eddie slipped more than bottom shelf Kentucky Bourbon in that eggnog.