NAME
Try::ALRM - Structured retry and timeout handling using CORE::alarm
DESCRIPTION
Try::ALRM provides try/catch-like semantics around alarm.
Internally, this module uses Perl prototypes to coerce lexical blocks into CODE references, in the same spirit as Try::Tiny. The public syntax remains compact:
retry { ... }
ALRM { ... }
finally { ... }
timeout => 5,
tries => 10;
Timeouts are handled by a localized $SIG{ALRM} handler. When the alarm fires, the optional ALRM block is executed and the current attempt is immediately aborted. If retry attempts remain, retry continues with the next attempt.
The active alarm is always cleared before control leaves the attempt, whether the block succeeds, times out, or dies for another reason.
retry and try_once may be nested. Active timeout scopes are tracked internally so an inner timeout does not permanently cancel an outer one.
retry and try_once may be nested. Timeout scopes are tracked so that inner timeouts do not cancel outer ones. When multiple timeouts are active, the earliest deadline wins.
EXPORTS
This module exports six keywords.
try_once BLOCK
Runs BLOCK once with an alarm set to the current timeout value.
try_once is equivalent to:
retry { ... } tries => 1;
If an alarm fires, the optional ALRM block is executed, followed by finally if provided.
retry BLOCK
Runs BLOCK up to tries times. Each attempt receives the current attempt number via @_.
Retries stop when either:
The block completes without an alarm
The retry limit is reached
The block dies for a non-timeout reason
If BLOCK dies for a non-timeout reason, finally is still executed before the original exception is rethrown.
Nested retry and try_once blocks operate independently. An inner timeout only affects its own scope, while outer timeouts remain active. If an outer timeout expires during an inner block, it is propagated through the inner scope.
ALRM BLOCK
Optional handler executed when an alarm fires.
Receives the current attempt number:
ALRM {
my ($attempt) = @_;
warn "Attempt $attempt timed out\n";
}
After ALRM runs, the current attempt is aborted and retry moves to the next attempt if one remains.
finally BLOCK
Optional block executed unconditionally after all attempts are complete, or after a non-timeout exception interrupts retry processing.
Receives:
my ($attempts, $successful) = @_;
$attempts is the number of attempts actually made.
$successful is true if one attempt completed without timing out or throwing an exception.
If both the main block and finally die, the main block's exception is preserved and rethrown.
timeout => INT
Getter/setter for the default timeout in seconds.
May also be supplied as a trailing modifier:
try_once { ... } timeout => 2;
The value must be an integer greater than or equal to 1.
tries => INT
Getter/setter for the default retry limit.
May also be supplied as a trailing modifier:
retry { ... } tries => 5;
The value must be an integer greater than or equal to 1.
PACKAGE ENVIRONMENT
The following package variables are exposed:
$Try::ALRM::TIMEOUT$Try::ALRM::TRIES
They may be set globally through the timeout and tries setters.
During a retry or try_once block, trailing timeout and tries modifiers are localized so calls to timeout and tries inside user blocks reflect the active values.
TRAILING MODIFIERS
Trailing modifiers are written as key/value pairs after the final block:
retry {
...
}
ALRM {
...
}
finally {
...
}
timeout => 5,
tries => 10;
Valid trailing keys are:
ALRMfinallytimeouttries
Unknown keys, duplicate keys, invalid timeout values, and invalid retry counts are rejected.
COMPOSABILITY
Try::ALRM supports nested usage of retry and try_once. Each block maintains its own timeout context, allowing helper functions and inner operations to define their own time limits without interfering with outer control flow.
When multiple timeouts are active, the soonest deadline is enforced.
USING WITH Try::Tiny, try keyword, and eval{...}
Try::ALRM provides timeout and retry behavior, but does not catch ordinary exceptions (die). To handle exceptions, wrap your code with Try::Tiny or Perl's built-in try feature.
Because Try::Alarm is orthogonal to try, however it is provided, below show examples of using Try::ALRM's functionality with general exception handling capablities.
Using Try::Tiny
use Try::Tiny;
use Try::ALRM qw/tries timeout/;
Try::ALRM::retry {
my ($attempt) = @_;
try {
print "Attempt $attempt...\n";
die "boom" if $attempt == 1;
sleep 5; # may trigger timeout
print "Work completed\n";
}
catch {
warn "\tException caught: $_";
die $_; # rethrow (this will NOT trigger retry; it escapes)
};
}
Try::ALRM::ALRM {
my ($attempt) = @_;
print "\tTimed out on attempt $attempt\n";
}
Try::ALRM::finally {
my ($attempts, $success) = @_;
print $success ? "Success\n" : "Failure\n";
}
timeout => 3,
tries => 2;
Using Perl try (5.34+)
use feature 'try';
no warnings 'experimental::try';
use Try::ALRM qw/tries timeout/;
Try::ALRM::retry {
my ($attempt) = @_;
try {
print "Attempt $attempt...\n";
die "boom" if $attempt == 1;
sleep 5;
print "Work completed\n";
}
catch ($e) {
warn "\tException caught: $e";
die $e; # rethrow (this will NOT trigger retry; it escapes)
}
}
Try::ALRM::ALRM {
my ($attempt) = @_;
print "\tTimed out on attempt $attempt\n";
}
Try::ALRM::finally {
my ($attempts, $success) = @_;
print $success ? "Success\n" : "Failure\n";
}
timeout => 3,
tries => 2;
Notes
Try::ALRMhandles timeouts and retry logic only.Try::Tinyortryshould be used to catch exceptions.If you catch an exception and rethrow it:
die $_;the exception will escape the retry block.
Try::ALRMdoes not currently retry on ordinary exceptions, only on timeouts.If you swallow the exception, the attempt is treated as successful.
Using eval{ ... }
use Try::ALRM qw/tries timeout/;
Try::ALRM::retry {
my ($attempt) = @_;
eval {
print "Attempt $attempt...\n";
die "boom" if $attempt == 1;
sleep 5; # may trigger timeout
print "Work completed\n";
};
if ($@) {
warn "\tException caught: $@";
# swallow it -> Try::ALRM sees success
}
}
Try::ALRM::finally {
my ($attempts, $success) = @_;
print $success ? "Success\n" : "Failure\n";
}
timeout => 3,
tries => 2;
Notes on eval
If you do not check
$@, the exception is silently ignored and the attempt is treated as successful.If you rethrow the exception:
die $@;it will escape the retry block.
Try::ALRMdoes not retry on ordinary exceptions.
BUGS
Almost certainly.
This module was motivated both by curiosity about Perl prototypes and by the practical question of whether ALRM could be treated as a localized exception.
Mileage may vary. Please report issues.
PERL ADVENT 2022
| \__ `\O/ `-- {} \} {/ {} \} {/ {} \}
\ \_(~)/_..___/=____/=____/=____/=____/=____/=____/=____/=*
\=======/ //\\ >\/> || \> //\\ >\/> || \> //\\ >\/>
----`---`--- `` `` ```` `` `` `` `` ```` `` `` ```` ````
ACKNOWLEDGEMENTS
"This module is dedicated to the least of you amongst us, the defenseless unborn, and to all of those who have died suddenly."
AUTHOR
Brett Estrade (OODLER) <oodler@cpan.org>
COPYRIGHT AND LICENSE
Copyright (C) 2022-Present by Brett Estrade
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.