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.