Signals

Perl supports many facilities for Interprocess Communication (IPC), including signals and sockets. Regardless of which IPC form you use with Perl, you’ll almost always tangle with signals.

Signals can be generated from any source, including key sequences on your keyboard, an event on your system, or from a failed program.

For quite some time, Perl has used a simple signal handling module, in which %SIG was populated with keys that represented the signal handlers your system supported. You’d invoke one of these handlers when Perl came across $SIG{'whatever_signal_here'}. In fact, even in Perl 5.8, the %SIG interface is the same, but the behavior of signals is entirely different. More on this shortly.

For signals to be useful, they must be trapped and dealt with. However, you shouldn’t try to do too much when you trap a signal. Generally, you should only generate a warning and deal with the condition that caused the error. In a way, Perl’s %SIG keeps you from doing too much. %SIG works like this:

$SIG{I_GOT_THIS_SIGNAL} = \&now_call_this_sub_to_handle_it;
sub now_call_this_sub_to_handle it {
    ...
}

If you need to know which signals Perl is aware of, do a 'kill -l' on a Unix system, or use Config.pm:

#!/usr/local/bin/perl -w

use Config;

print "Perl knows about the following signals:\n";
print $Config{sig_name_init}, "\n";

Here’s a simple snippet that lets you know if someone sent you a HUP signal:

$SIG{HUP} = \&hic_hup;
sub hic_hup {
	my $signal = shift;
	# Don't die for now.  Just warn us.
	warn "Somebody sent me a SIG${signal}\n";
}

Often, the type of signal you encounter in your Perl program will dictate how you should handle it. For example, you should assign 'IGNORE' to the 'CHLD' signal on many Unix platforms to reduce the possibility of creating zombie processes. Other signals, such as KILL and STOP, cannot and should not be ignored so easily. For this purpose, Perl lets you use a local( ) statement, which lets you temporarily ignore a signal:

sub kill_handler {
    local $SIG{KILL} = 'IGNORE'; # Inherited by all functions here
    i_will_settle_for_denial();
}

sub i_will_settle_for_denial {
	# KILL ignored for now.  Go in peace.
}

Unix allows you to kill processes with negative process IDs by sending a signal zero. If you have a program that starts itself and several children, use 'kill -PARENT_PID' to kill the parent and all of the children processes. But obviously, if you send a HUP to the parent, you don’t want the parent to die. You can avoid this with Perl and signals:

sub be_nice_to_your_parents {
	local($SIG{HUP}) = 'IGNORE';
     kill('HUP', -$$);    # This pid and its kids
}

Naturally, you don’t have to do anything fancy with signal handlers. You can simply die if the given $SIG{NAME} won’t cause negative effects on your system if you mishandle it:

$SIG{INT} = sub {
    die "\nYou've interrupted me for one last time!\n"
};

Keep in mind that it’s not all fun and games with Perl and signals. If you don’t know how your system’s C library and its signals implementation behave, or if you haven’t read the instructions before firing your BB gun, you’ll shoot your eye out. We guarantee it!

As of Perl 5.8, you can probably be a little more confident in Perl’s ability to handle even a bad system-specific signals implementation. In the old days, when men were men and eyeless men were everywhere, a bad signals implementation at both a system and Perl level, or a signal cropping up at the wrong time, could corrupt Perl’s internal state. Thankfully, Perl 5.8 and later will postpone signal handling until it’s safe to proceed. This means that while the signals interface doesn’t change, even if your program catches a signal at a specific place, Perl 5.8 and later will finish whatever they are doing when the signal is encountered.

Finally, signals aren’t limited to coping with the behaviors of processes. You can also trap alarm signals for Perl functions and system calls alike if your Perl code lives in an eval( ) block. For example, let’s say you want to ping a host, your-host.your.domain, and you want the ping process to timeout in a reasonable amount of time if your-host.your.domain isn’t available. You can use alarm( ) inside of eval( ), like so:

#!/usr/local/bin/perl -w

my $to_ping = 'your-host.your.domain';
my $a_secs = 5;

eval {
    local $SIG{ALRM} = sub { die "no dice for $to_ping" };
    alarm $a_secs;
    system("/usr/sbin/ping", $to_ping);
    alarm 0;
};

if ($@ and $@ !~ /no dice for $to_ping/) { die }

Note that you may have to wait for the ping command itself to timeout. While alarm occurs after $a_secs, you won’t see the “no dice for . . . " message until system( ) (or qx( )) is complete. This is not the case for Perl functions such as flock( ), open( ), etc.

Get Perl in a Nutshell, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.