perl.gg / snippets

Alarm + Eval Timeouts

2026-03-09

Your script hangs. A network request never comes back. A database query runs for eternity. A user walks away from a prompt and never types anything.

You need a timeout. Perl gives you one with three tools from the standard library: alarm, eval, and %SIG.

eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(5); my $result = slow_operation(); alarm(0); }; if ($@ =~ /timeout/) { say "Operation took too long. Moving on."; }
Five seconds. If slow_operation() doesn't finish by then, it gets killed. No external modules. No threads. Just Unix signals and Perl's built-in exception handling.

Part 1: THE THREE PIECES

Each component has a job:
PIECE WHAT IT DOES ------------------ ------------------------------------------ $SIG{ALRM} Signal handler - what to do when alarm fires alarm(N) Set a timer for N seconds eval { ... } Catch the die() so your script survives
The flow looks like this:
eval { set handler ──→ "die on SIGALRM" alarm(5) ──→ start countdown do work ──→ might finish, might not alarm(0) ──→ cancel countdown (if we got here) }; │ ├── work finished? ──→ $@ is empty, continue │ └── alarm fired? ──→ $@ contains "timeout\n"
The alarm(0) at the end is important. If the operation finishes in time, you cancel the alarm so it doesn't fire later and confuse something else.

Part 2: THE SIGNAL HANDLER

local $SIG{ALRM} = sub { die "timeout\n" };
$SIG{ALRM} is Perl's way of registering a handler for the SIGALRM Unix signal. When the alarm goes off, this anonymous sub runs.

The local keyword is critical. It makes this handler temporary, scoped to the current block. When the eval exits, the old handler (if any) is restored. You're not permanently changing global signal behavior.

Why die and not just return? Because die is the only way to break out of whatever operation is running. A signal handler interrupts the current code. If that code is deep inside some I/O wait, die unwinds the stack back to the enclosing eval.

The \n at the end of the die string suppresses the "at script.pl line N" message. It's a Perl convention: if your die message ends with a newline, Perl assumes you've said everything you want to say.

Part 3: THE FULL PATTERN

Here's the robust version with proper error handling:
my $result; my $timed_out = 0; eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(10); $result = do_something_slow(); alarm(0); # Cancel if we finished in time }; if ($@) { if ($@ eq "timeout\n") { $timed_out = 1; warn "Operation timed out after 10 seconds\n"; } else { die $@; # Re-throw unexpected errors } }
Notice the re-throw. If the code inside eval dies for a reason other than our timeout, we don't want to swallow that error. Only catch what you expect.

Part 4: TIMING OUT NETWORK REQUESTS

The classic use case. You're hitting an API and the remote server might be dead:
use IO::Socket::INET; my $socket; eval { local $SIG{ALRM} = sub { die "connect timeout\n" }; alarm(3); $socket = IO::Socket::INET->new( PeerAddr => 'api.example.com', PeerPort => 80, Proto => 'tcp', ) or die "connect failed: $!\n"; alarm(0); }; if ($@ =~ /timeout/) { say "Server didn't respond in 3 seconds. It's probably down."; } elsif ($@) { say "Connection error: $@"; } else { say "Connected!"; # Do stuff with $socket }
Three seconds to connect. If the server is unreachable, you find out fast instead of waiting for the OS-level TCP timeout (which can be minutes).

Part 5: TIMING OUT USER INPUT

Waiting for a human to type something. Humans are slow and unpredictable.
my $answer; eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(30); print "Enter your name (30 seconds): "; $answer = <STDIN>; chomp $answer if defined $answer; alarm(0); }; if ($@ =~ /timeout/) { say "\nToo slow. Using default."; $answer = "Anonymous"; } say "Hello, $answer!";
Thirty seconds to answer. Walk away from the terminal and the script keeps going with a default. This is great for automated install scripts or interactive tools that need to work unattended too.

Part 6: TIMING OUT SYSTEM COMMANDS

External commands can hang forever. Wrap them:
my $output; eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(15); $output = `ping -c 5 192.168.1.1 2>&1`; alarm(0); }; if ($@ =~ /timeout/) { say "Ping took too long. Host might be unreachable."; } else { say "Ping results:\n$output"; }
Fifteen seconds for a ping test. If the network is down and ping just sits there, you're not stuck.

For commands that might produce partial output before hanging:

eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(10); open my $fh, '-|', 'tail', '-f', '/var/log/syslog' or die "Can't run tail: $!\n"; while (<$fh>) { print "LOG: $_"; } alarm(0); }; # tail -f never exits on its own, so we always get here via timeout say "Stopped watching after 10 seconds.";
A self-terminating tail. Read the log for 10 seconds, then move on.

Part 7: NESTED TIMEOUTS

You can nest them. Inner timeouts override outer ones:
eval { local $SIG{ALRM} = sub { die "outer timeout\n" }; alarm(30); # 30 seconds for the whole batch for my $host (@hosts) { eval { local $SIG{ALRM} = sub { die "inner timeout\n" }; alarm(5); # 5 seconds per host check_host($host); alarm(0); }; if ($@ =~ /inner timeout/) { warn "$host: timed out\n"; } } alarm(0); }; if ($@ =~ /outer timeout/) { warn "Entire batch took too long!\n"; }
Five seconds per host, thirty for the whole job. The local on $SIG{ALRM} makes nesting safe. Each level gets its own handler, and exiting the block restores the previous one.

Be careful though. alarm() is a single global timer. Setting alarm(5) inside the loop resets the outer alarm(30). When the inner alarm(0) cancels, the outer timer is gone too.

For truly independent timers, you'd need something like Time::HiRes or a different approach entirely. But for sequential operations, the nesting pattern works fine.

Part 8: GOTCHAS

Sleep interacts with alarm. Both use the same underlying Unix mechanism. If you call sleep() inside an alarmed block, they interfere. Use select(undef, undef, undef, $seconds) for sub-second delays instead.

Windows doesn't have real signals. alarm() is emulated on Windows and might not interrupt blocking I/O. On Unix systems, this pattern is rock solid.

$@ can be clobbered. If code between the eval and the $@ check calls something that uses eval internally, $@ might get overwritten. Check it immediately:

eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(5); do_work(); alarm(0); }; my $err = $@; # Capture immediately! # Now safe to call other code log_something(); if ($err =~ /timeout/) { handle_timeout(); }
Die in signal handlers can be tricky. Some operations (like system calls deep in C libraries) might not respond cleanly to die from a signal handler. Most pure-Perl and standard I/O operations are fine.

Part 9: A REUSABLE WRAPPER

Wrap the pattern into a sub so you don't repeat it everywhere:
sub with_timeout { my ($seconds, $code) = @_; my $result; eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm($seconds); $result = $code->(); alarm(0); }; if ($@ =~ /timeout/) { return (undef, 'timeout'); } elsif ($@) { return (undef, $@); } return ($result, undef); }
Usage:
my ($data, $err) = with_timeout(5, sub { fetch_from_api('https://api.example.com/data'); }); if ($err) { say "Failed: $err"; } else { process($data); }
Clean. Reusable. The timeout logic lives in one place.

Part 10: DATABASE QUERY TIMEOUTS

Database queries are another prime candidate. An unoptimized query against a large table can run for minutes:
use DBI; my $dbh = DBI->connect("dbi:Pg:dbname=production", $user, $pass); my $rows; eval { local $SIG{ALRM} = sub { die "query timeout\n" }; alarm(10); my $sth = $dbh->prepare("SELECT * FROM big_table WHERE complex_condition"); $sth->execute(); $rows = $sth->fetchall_arrayref({}); alarm(0); }; if ($@ =~ /timeout/) { warn "Query took more than 10 seconds. Check your indexes.\n"; $rows = []; } say "Got " . scalar(@$rows) . " rows";
Ten seconds for a query. If it's still running after that, something is wrong and you need to investigate, not wait. The empty arrayref fallback lets the rest of your script continue gracefully.

Note that this kills the Perl-side wait, but the database query might keep running on the server. For true query cancellation, use your database's native timeout settings too.

Part 11: WHEN TO USE THIS

The alarm/eval pattern is the right tool when:
* You're on a Unix system * The operation might block indefinitely * You want whole-second granularity * You don't want to pull in external modules * The blocking code is a single call (not a complex state machine)
For sub-second timeouts, look at Time::HiRes and its ualarm() function. For event-driven code with many concurrent timeouts, look at IO::Async or Mojo::IOLoop.

But for the everyday "don't let this script hang" problem? alarm + eval + $SIG{ALRM} is the Perl sysadmin's best friend. Three lines of setup, zero dependencies, and your script never hangs again.

alarm(5) | +-----+-----+ | | finish RING! in time | | die "timeout" alarm(0) | | eval catches continue | | handle it v v [done] [done] .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/

Created By: Wildcard Wizard. Copyright 2026