Signal handling — %SIG#
%SIG is the hash where Perl exposes signal handlers. Setting $SIG{NAME} installs a handler for signal NAME; deleting it restores the default action. Two pseudo-signals — __WARN__ and __DIE__ — are not real OS signals at all but interpreter hooks into warn and die.
The signal table#
Real OS signals come from the POSIX and signal.h lists. The common ones — keys you will actually use:
Key | Signal | Default action | When you handle it |
|---|---|---|---|
| SIGINT | terminate | Ctrl-C: graceful shutdown |
| SIGTERM | terminate |
|
| SIGHUP | terminate | terminal hangup; daemons reload config |
| SIGQUIT | terminate + core | Ctrl-: like INT but with a core dump |
| SIGPIPE | terminate | wrote to a closed pipe; usually ignored |
| SIGCHLD | ignore | a child process died ( |
| SIGUSR1 | terminate | application-defined event |
| SIGUSR2 | terminate | application-defined event |
| SIGALRM | terminate |
|
| SIGWINCH | ignore | terminal window resized |
| SIGIO | terminate | async I/O ready |
The full list — including signals you cannot catch (KILL, STOP) and platform-specific signals — comes from the POSIX module, which exports the numeric constants (SIGINT, SIGTERM, …) and the WIFEXITED-family wait macros.
Installing a handler#
Three handler-value forms are accepted:
$SIG{INT} = \&handler; # code reference — preferred
$SIG{INT} = sub { ... }; # anonymous sub — also fine
$SIG{INT} = 'IGNORE'; # ignore the signal
$SIG{INT} = 'DEFAULT'; # restore default action
$SIG{INT} = ''; # same as DEFAULT
$SIG{INT} = undef; # same as DEFAULT
Don’t use a bareword ($SIG{INT} = 'handler') — that references a name in the main:: package, which Perl looks up each time the signal arrives. If you rename handler, the bareword form silently keeps targeting the old name.
The handler receives one argument — the signal name as a string:
sub handler {
my ($signame) = @_;
print "received SIG$signame\n";
}
$SIG{INT} = \&handler;
$SIG{TERM} = \&handler;
Safe signals — the deferred-delivery rule#
Since Perl 5.8, signal delivery is deferred. When a signal arrives, the OS sets a flag in the interpreter; the actual Perl handler runs only at the next safe point — typically between op-code executions. This means:
A handler cannot interrupt a
read,open,connect, or any other syscall mid-call. Slow syscalls return early withEINTRin$!and the handler runs after.A handler cannot interrupt a regex match in progress. A long-running regex on a malicious input will not be cut short by SIGINT.
The handler runs in regular Perl context. It can call any Perl code, allocate memory, throw exceptions — none of the re-entrancy hazards of C signal handlers.
This is the right trade-off for almost every program. If you genuinely need pre-5.8 unsafe signal handling — handlers that run synchronously in the signal context, with all the re-entrancy hazards — set the PERL_SIGNALS=unsafe environment variable before starting Perl. Do not do this casually.
PetaPerl implements deferred-delivery signal handling.
The graceful-shutdown pattern#
The standard shape: a flag set by the handler, checked by the main loop:
my $shutting_down = 0;
$SIG{INT} = sub { $shutting_down = 1 };
$SIG{TERM} = sub { $shutting_down = 1 };
while (my $job = $queue->next) {
last if $shutting_down; # check at a safe point
process($job);
}
if ($shutting_down) {
log_and_close();
exit 0;
}
The main loop polls the flag at a place where it is safe to unwind cleanly. The handler does only ”set the flag“ — no logging, no I/O — minimising the time it spends running.
For applications that block on a long syscall (accept, read, select), the loop has to be structured to retry on EINTR:
my $shutting_down = 0;
$SIG{INT} = sub { $shutting_down = 1 };
while (!$shutting_down) {
my $client = accept($srv, $sock);
unless (defined $client) {
next if $!{EINTR}; # signal woke us up — re-check flag
die "accept: $!";
}
handle($client);
}
$!{EINTR} (see error variables) tells you the syscall exited because a signal arrived. The flag was set by the handler; the main loop sees the wake-up, re-checks $shutting_down, and exits cleanly.
Reaping child processes — SIGCHLD#
When a child process exits, the parent gets SIGCHLD. The handler must call waitpid to reap the zombie:
$SIG{CHLD} = sub {
while ((my $kid = waitpid(-1, WNOHANG)) > 0) {
my $status = $?;
log_child_death($kid, $status);
}
};
The while loop with WNOHANG (from POSIX :sys_wait_h) handles the case where two children exit close together — only one SIGCHLD is delivered (it is not queued), so the handler must drain all pending exits each time it runs.
$SIG{CHLD} = 'IGNORE' is a special case: it does not just ignore the signal, it tells the kernel to auto-reap children. Use this when your script forks children whose exit status you do not care about.
SIGPIPE — the closed-pipe case#
When you print to a pipe whose reader has gone away, the kernel sends SIGPIPE. The default action is to terminate the process — which is rarely what you want. Most filter programs should ignore SIGPIPE and check the print return value instead:
local $SIG{PIPE} = 'IGNORE';
print {$child_pipe} $data
or die "write to pipe: $!"; # $! will be EPIPE if the reader closed
With the handler ignored, the syscall returns failure with EPIPE rather than killing the program.
Timeouts via SIGALRM#
Combined with alarm and an eval block, SIGALRM provides a portable timeout for any blocking operation:
sub with_timeout {
my ($seconds, $code) = @_;
my $result;
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm $seconds;
$result = $code->();
alarm 0; # cancel the timer
1;
} or do {
die $@ unless $@ eq "TIMEOUT\n";
return undef;
};
return $result;
}
my $line = with_timeout(5, sub { <$slow_socket> });
The pattern:
local $SIG{ALRM}— handler is in scope only for this call.alarm $seconds— schedule the signal.Perform the blocking work.
alarm 0— cancel the timer if work succeeded.Inside the handler,
die "TIMEOUT\n"— the trailing\nsuppresses the ”at line N“ suffix and makes the error value easy to test against.
__WARN__ and __DIE__ — interpreter hooks#
These two keys in %SIG are not OS signals. They are called when the interpreter is about to emit a warning (warn) or throw a fatal exception (die).
$SIG{__WARN__} — warning interception#
my @warnings;
{
local $SIG{__WARN__} = sub {
my ($msg) = @_;
push @warnings, $msg;
};
do_thing_that_might_warn();
}
# Now @warnings holds every warning that fired inside the block,
# without anything having printed to STDERR.
A __WARN__ handler that does nothing silences the warning entirely. A handler that calls die upgrades warnings into exceptions:
local $SIG{__WARN__} = sub { die $_[0] };
eval { something_warny() }; # warns become catchable
The lexical pragma use warnings FATAL => 'all' is a more targeted way to do this — it makes warnings fatal only in the lexical scope where the pragma is active. Use it instead of __WARN__ for ”treat warnings as errors“ requirements.
$SIG{__DIE__} — die interception#
A __DIE__ handler runs when die is about to throw — both for uncaught exceptions and inside eval. Inside an eval, the handler runs before the exception is caught, which is the classic source of confusion: a global __DIE__ handler that logs every ”death“ will fire on every eval { ... } that calls die for ordinary control flow.
$SIG{__DIE__} = sub { print STDERR "dying: $_[0]" };
eval { die "expected error" }; # the handler still runs!
If you want a global death logger, gate it on $^S:
$SIG{__DIE__} = sub {
return if $^S; # inside eval — let it be caught
print STDERR "uncaught: $_[0]";
};
Realistically, avoid $SIG{__DIE__} for new code. The pragma use warnings and the error variables cover what you need without the surprise. The modern try/catch feature handles exceptions explicitly without the action-at-a-distance of $SIG{__DIE__}.
Listing available signals#
use Config;
my @signals = split / /, $Config{sig_name};
print "available signals: @signals\n";
# HUP INT QUIT ILL TRAP ABRT BUS FPE KILL ...
Or, for the numeric→name mapping:
use Config;
my @names = split / /, $Config{sig_name};
my @nums = split / /, $Config{sig_num};
my %sig_num = map { $names[$_] => $nums[$_] } 0..$#names;
my %sig_name = map { $nums[$_] => $names[$_] } 0..$#names;
This is what most signal-listing utilities do internally.
See also#
POSIX— the canonical source ofSIG*numeric constants and theWIFEXITED/WEXITSTATUSmacros for decoding child status in a SIGCHLD handler.kill— the partner of%SIG: send a signal to a process (typically to test your own handler).alarm— schedule a SIGALRM; the basis of timeout patterns.waitpid— reap a child after SIGCHLD.fork— produces the children whose deaths SIGCHLD reports.Error variables —
$!afterEINTR,$?afterwaitpid,$@afterdiefrom inside a handler.Interpreter introspection —
$^Sfor the__DIE__”are we insideeval“ check.try/catch— the modern alternative to$SIG{__DIE__}-based exception handling.