NAME

Retry::Policy - Simple retry wrapper with exponential backoff and jitter

SYNOPSIS

use Retry::Policy;

my $p = Retry::Policy->new(
    max_attempts  => 5,
    base_delay_ms => 100,
    max_delay_ms  => 10_000,
    jitter        => 'full',
);

my $value = $p->run(sub {
    my ($attempt) = @_;
    die "transient\n" if $attempt < 3;
    return "ok";
});

DESCRIPTION

Small, dependency-light retry helper for backend and system code where transient failures are expected.

DESIGN NOTES

  • Exponential backoff with a maximum cap is a common production default for transient failures.

  • Full jitter helps avoid synchronized retries (thundering herd) across multiple workers or hosts.

  • Defaults are intentionally conservative; callers should tune retry behavior per dependency (database, HTTP service, filesystem, etc.).

  • Validation is strict: invalid configurations fail fast rather than producing undefined retry behavior.

PRACTICAL USE CASES

Retry a flaky HTTP call (transient network or service errors)

use Retry::Policy;

my $p = Retry::Policy->new(
    max_attempts  => 6,
    base_delay_ms => 200,
    max_delay_ms  => 5000,
    retry_on      => sub {
        my ($err) = @_;
        return ($err =~ /timeout|temporarily unavailable|connection reset/i) ? 1 : 0;
    },
);

my $body = $p->run(sub {
    # Replace this block with your HTTP client of choice
    die "timeout\n" if rand() < 0.2;
    return "ok";
});

Retry a database connection (DBI)

use DBI;
use Retry::Policy;

my $p = Retry::Policy->new(
    max_attempts  => 5,
    base_delay_ms => 250,
    max_delay_ms  => 8000,
    retry_on      => sub {
        my ($err) = @_;
        return ($err =~ /server has gone away|lost connection|timeout/i) ? 1 : 0;
    },
);

my $dbh = $p->run(sub {
    my $dbh = DBI->connect($dsn, $user, $pass, { RaiseError => 1, PrintError => 0 });
    return $dbh;
});

Retry acquiring a lock or contended resource

use Fcntl qw(:flock);
use Retry::Policy;

my $p = Retry::Policy->new(
    max_attempts  => 20,
    base_delay_ms => 50,
    max_delay_ms  => 1000,
    retry_on      => sub { 1 }, # lock contention is typically transient
);

$p->run(sub {
    open my $fh, ">>", "/tmp/my.lock" or die "open lock: $!\n";
    flock($fh, LOCK_EX | LOCK_NB) or die "lock busy\n";
    # work while lock held
    return 1;
});

LICENSE

Same terms as Perl itself.