NAME

Signals::XSIG - install multiple signal handlers through %XSIG

VERSION

Version 0.16

SYNOPSIS

use Signals::XSIG q{:all};

# drop-in replacement for regular signal handling through %SIG
$SIG{TERM} = \&my_sigterm_handler;
$SIG{USR1} = sub { ... };
$SIG{PIPE} = 'DEFAULT';

# %XSIG interface to installing multiple signal handlers
$SIG{TERM} = \&handle_sigterm;  # same as  $XSIG{TERM}[0] = ...
$XSIG{TERM}[3] = \&posthandle_sigterm;
$XSIG{TERM}[-1] = \&prehandle_sigterm;
# SIGTERM calls prehandle_sigterm, handle_sigterm, posthandle_sigterm
# in that order.

# array operations allowed on @{$XSIG{signal}}
push @{$XSIG{__WARN__}}, \&log_warnings;
unshift @{$XSIG{__WARN__}}, \&remotely_log_warnings;
warn "This warning invokes both handlers";
shift @{$XSIG{__WARN__}};
warn "This warning only invokes the 'log_warnings' handler";

DESCRIPTION

Perl provides the magic global hash variable %SIG to make it easy to trap and set a custom signal handler (see "%SIG" in perlvar and perlipc) on most of the available signals. The hash-of-lists variable %XSIG provided by this module has a similar interface for setting an arbitrary number of handlers on any signal.

There are at least a couple of use cases for this module:

  1. You have written a module that raises signals and makes use of signal handlers, but you don't want to preclude the end-user of your module from doing their own handling of that signal. This module solves this issue by allowing you to install a list of handlers for a signal, and installing your own signal handler into a "non-default" index. Now your module's end-user can set and unset $SIG{signal} as much as he or she would like. When the signal is trapped, both your module's signal handler and the end-user's signal handler (if any) will be invoked.

    package My::Module::With::USR1::Handler;
    use Signals::XSIG;
    sub import {
       ...
       # use $XSIG{USR1}, not $SIG{USR1}, in case the user of
       # this module also wants to install a SIGUSR1 handler.
       # Execute our handler BEFORE any user's handler.
       $XSIG{'USR1'}[-1] = \&My_USR1_handler;
       ...
    }
    sub My_USR1_handler { ... }
    sub My_sub_that_raises_SIGUSR1 { ... }
    ...
    1;

    Now users of your module can still install their own SIGUSR1 handler through $SIG{USR1} without interfering with your own SIGUSR1 handler.

  2. You have multiple "layers" of signal handlers that you want to enable and disable at will. For example, you may want to enable some handlers to write logging information about signals received.

    use Signals::XSIG;
    
    # log all warning messages
    $XSIG{__WARN__}[1] = \&log_messages;
    do_some_stuff();
    
    # now enable extra logging -- warn will invoke both functions now
    $XSIG{__WARN__}[2] = \&log_messages_with_authority;
    do_some_more_stuff();
    
    # done with that block. disable extra layer of logging
    $XSIG{__WARN__}[2] = undef;
    # continue, &log_warnings will still be called at next warn statement

%XSIG

Extended signal handling is provided by making assignments to and performing other operations on the hash-of-lists %XSIG, which is imported into the calling namespace by default.

A signal handler is one of the following or any scalar variable that contains one of the following:

    DEFAULT
    IGNORE
    undef
    ''
    unqualified_sub_name  # qualified to main::unqualified_sub_name
    qualified::sub_name
    \&subroutine_ref
    sub { anonymous sub }
    *unqualified_glob     # qualified to *CallingPackage::unqualified_glob
    *qualified::glob

(the last two handler specifications cannot be used with Perl 5.8 due to a limitation with assigning globs to tied hashes. See "BUGS AND LIMITATIONS").

There are several ways to enable additional handlers on a signal.

$XSIG{signal} = handler

Sets a single signal handler for the given signal.

$XSIG{signal}[0] = handler

Behaves identically to the conventional $SIG{signal} = handler expression. Installs the specified signal handler as the "main" signal handler. If you are using this module because you don't want your signal handlers to trample on the signal handlers of your users, then you generally don't want to use this expression.

$XSIG{signal}[n] = handler for n > 0
$XSIG{signal}[-n] = handler for -n < 0

Installs the given signal handler at the specified indicies. When multiple signal handlers are installed and a signal is trapped, the signal handlers are invoked in order from lowest indexed to highest indexed.

For example, this code:

$XSIG{USR1}[-2] = sub { print "J" };
$XSIG{USR1}[-1] = sub { print "A" };
$XSIG{USR1}[1] = sub { print "H" };
$SIG{USR1} = sub { print "P" };   # $SIG{USR1} is alias for $XSIG{USR1}[0]
kill 'USR1', $$;

should output the string JAPH. If a "main" signal handler is installed, then use this expression with a negative index to register a handler to run before the main handler, and with a positive index for a handler to run after the main handler.

A signal handler at a specific slot can be removed by assigining undef or '' (the empty string) to that slot.

$XSIG{USR1}[1] = undef;
$XSIG{signal} = [handler1, handler2, ...]
@{$XSIG{signal}} = (handler1, handler2, ...)

Installs multiple handlers for a signal in a single expression. Equivalent to

$XSIG{signal} = [];   # clear all signal handlers
$XSIG{signal}[0] = handler1;
$XSIG{signal}[1] = handler2;
...

All the handlers for a signal can be uninstalled with a single expression like

$XSIG{signal} = [];
@{XSIG{signal}} = ();
push @{$XSIG{signal}}, handler1, handler2, ...

Installs additional signal handlers to be invoked after all currently installed signal handlers. There is a corresponding pop operation, but it cannot be used to remove the main handler or any prior handlers.

$XSIG{USR1} = [];
$XSIG{USR1}[-1] = \&prehandler;
$XSIG{USR1}[0] = \&main_handler;
$XSIG{USR1}[1] = \&posthandler;
push @{$XSIG{USR1}}, \&another_posthandler;
pop @{$XSIG{USR1}};   # removes \&another_posthandler
pop @{$XSIG{USR1}};   # removes \&posthandler
pop @{$XSIG{USR1}};   # no effect - pop doesn't remove index <= 0
unshift @{$XSIG{signal}}, handler1, handler2, ...

Analagous to push, installs additional signal handlers to be invoked before all currently installed signal handlers. The corresponding shift operation cannot be used to remove the main handler or any later handlers.

$XSIG{USR1} = [ $h1, $h2, $h3, $h4 ];
$XSIG{USR1}[-1] = $j1;
$XSIG{USR1}[-3] = $j3;
unshift @{$XSIG{USR1}}, $j4; # installed at $XSIG{USR1}[-4]
shift @{$XSIG{USR1}};     # removes $j4
shift @{$XSIG{USR1}};     # removes $j3
shift @{$XSIG{USR1}};     # removes $XSIG{USR1}[-2], which is undef
shift @{$XSIG{USR1}};     # removes $j1
shift @{$XSIG{USR1}};     # no effect - shift doesn't remove index >= 0

OVERRIDING DEFAULT SIGNAL BEHAVIOR

Signals::XSIG provides two ways that the 'DEFAULT' signal behavior (that is, the behavior of a trapped signal when one or more of its signal handlers is set to 'DEFAULT', not the behavior when a signal does not have a signal handler set) can be overridden for a specific signal.

  • define a Signals::XSIG::Default::default_<SIG> function

    sub Signals::XSIG::Default::default_QUIT {
        print "Hello world.\n";
    }
    $SIG{QUIT} = 'DEFAULT';
    kill 'QUIT', $$;
  • set a handler in %Signals::XSIG::DEFAULT_BEHAVIOR

    $Signals::XSIG::DEFAULT_BEHAVIOR{USR1} = sub { print "dy!" }
    $XSIG{'USR1'} = [ sub {print "How"}, 'DEFAULT',  sub{print$/} ];
    kill 'USR1', $$;     #  "Howdy!\n"

Note again that the overridden 'DEFAULT' behavior will only be used for signals where a handler has been explicitly set to 'DEFAULT', and not for signals that do not have any signal handler installed. So

$SIG{USR1} = 'DEFAULT'; kill 'USR1', $$;

will use the overridden default behavior, but

$XSIG{USR1} = []; kill 'USR1', $$;

will not.

Also note that in any chain of signal handler calls, the 'DEFAULT' signal handler will be called at most once. So for example this code

my $x = 0;
$Signals::XSIG::DEFAULT_BEHAVIOR{USR2} = sub { $x++ };
$XSIG{USR2} = [ 'DEFAULT', sub {$x=11}, 'DEFAULT', 'DEFAULT' ];
kill 'USR2', $$;
print $x;

will output 11, not 13. This is DWIM.

When is this feature useful? Perhaps when Signals::XSIG makes the wrong assumptions about what a default signal behavior is. Or when you have an unusual system with different default signal behavior than your typical system, and out of portability concerns you want your unusual system to behave the way you are used to.

EXPORT

The %XSIG extended signal handler hash is exported into the calling namespace by default.

FUNCTIONS

None

OTHER NOTES

DEFAULT signal handler

If the main handler for a signal ($XSIG{signal}[0]) is set to DEFAULT, that handler will be ignored if there are any other handlers installed for that signal. This is DWIM.

For example, this will invoke the default behavior for SIGUSR1 (typically terminating the program):

$SIG{USR1} = 'DEFAULT';
kill 'USR1', $$;

but this will not

$SIG{USR1} = 'DEFAULT';
$XSIG{USR1}[1] = \&do_something_else;
kill 'USR1', $$;

This will also invoke the default behavior for SIGTERM (probably terminating the program) since it is not the main handler that is the DEFAULT handler:

$SIG{TERM} = \&trap_sigterm;
$XSIG{TERM}[-1] = 'DEFAULT';
kill 'TERM', $$;

If the DEFAULT handler is installed more than once, the default behavior for that signal will still only be invoked once when that signal is trapped.

AUTHOR

Marty O'Brien, <mob at cpan.org>

BUGS AND LIMITATIONS

Using this module may make it more difficult to use Perl in some other ways.

Avoid local %SIG

This module converts %SIG into a tied hash. As documented in the perltie "BUGS" section, localizing a tied hash will cause the old data not to be restored when the local version of the hash goes out of scope. Avoid doing this:

{
    local %SIG;
    ...
}

or using modules and functions which localize %SIG (fortunately, there are not that many examples of code that use this construction [https://code.google.com/archive/search?q=local%20%25SIG]).

If a code block that localizes %SIG can't be avoided, the workaround for Perl <v5.36 is to save %SIG and restore at the end of the localizing scope:

use Signals::XSIG;
...
my %temp = %SIG;
function_call_or_block_that_localizes_SIG();
%SIG = %temp;

Since Perl v5.36, you must also unforunately untie and retie %SIG around localization.

use Signals::XSIG;
...
my %temp = %SIG;
untie %SIG if $] >= 5.035;
function_call_or_block_that_localizes_SIG();
tie %SIG, 'Signals::XSIG::TieSIG' if $] >= 5.035;
%SIG = %temp;

In addition, the behavior of the tied %SIG while it is local'ized is different in different versions of Perl, and all of the features of Signals::XSIG might or might not work while a local copy of %SIG is in use.

Just avoid local %SIG whenever you can.

Note that it is perfectly fine to localize an element of %SIG:

{
    local $SIG{TERM} = ...; # this is ok.
    something_that_might_raise_SIGTERM();
} # end of local scope, $SIG{TERM} restored.

$SIG{signal} = *foo on Perl 5.8

"%SIG" in perlvar specifies that you can assign a signal handler with the construction

$SIG{signal} = *foo;    # same as ... = \&__PACKAGE__::foo

It turns out that in Perl 5.8, this causes a fatal error when you use this type of assignment to a tied hash. This is a limitation of tied hashes in the implementation of Perl 5.8, not a problem with the magic of %SIG.

Overhead of processing signals

Signals::XSIG adds some overhead to signal processing and that could ultimately make your signal processing less stable as each signal takes longer to process. This module may not be suitable for applications where many signals need to be processed in a short time.

Using Perl debugger is more painful

This module hangs its hat on many of the same hooks that the Perl debugger needs to use. As you step through code in the debugger, you may often find yourself stepping through the code in this module (say, where some core module is installing a $SIG{__WARN__} handler. You may find this annoying.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Signals::XSIG

You can also look for information at:

Please report any bugs or feature requests to bug-signal-handler-super at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Signals-XSIG. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

LICENSE AND COPYRIGHT

Copyright 2010-2022 Marty O'Brien.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.