localizing %SIG handlers

A

Anno Siegel

It is common practice to localize %SIG handlers in this fashion:

$SIG{ HUP} = \ &global_handler;

{
local $SIG{ HUP} = \ &local_handler;
# provoke and deal with one or more HUP signals
}

The assumption is that $SIG{ HUP} is either the global or the local
handler at all times, so the (HUP) signal will always be caught, but
apparently that assumption isn't true. "local" sets the handler
to undef, and then "=" sets it to \ &local_handler. It is quite
possible for a signal to arrive when the hander is undefined, which
ends the program through an uncaught signal. The following code
shows this:

my ( $count_a, $count_b) = ( 0, 0); # unused, but left in place
$SIG{ HUP} = sub { $count_a ++ };

# create a stream of HUP signals from kid to parent
my $parent = $$;
defined( my $pid = fork ) or die "fork: $!";
unless ( $pid ) {
require Time::HiRes;
require POSIX;
my $tick = 1/POSIX::sysconf( POSIX::_SC_CLK_TCK());
while ( 1 ) {
kill HUP => $parent or exit; # don't survive parent
Time::HiRes::sleep( $tick); # shortest admissible sleep time
}
exit(); # not reached;
}

# main loop
while ( 1 ) {
{
local $SIG{ HUP} = sub { $count_b ++ };
# my $save = $SIG{ HUP};
# $SIG{ HUP} = sub { $count_b ++ };
# $SIG{ HUP} = $save;
}
}

With the "main loop" as shown ("local" active), after a few seconds
the program ends with a "Hangup" message, characteristic for an
uncaught HUP signal. With "local" commented out and the other three
lines active, the program runs happily as long as I let it.

My conclusion: The practice of localizing %SIG handlers isn't safe,
and probably never has been.

Comments?

Anno
 
B

Ben Morrow

Quoth (e-mail address removed)-berlin.de (Anno Siegel):
It is common practice to localize %SIG handlers in this fashion:

$SIG{ HUP} = \ &global_handler;

{
local $SIG{ HUP} = \ &local_handler;
# provoke and deal with one or more HUP signals
}

The assumption is that $SIG{ HUP} is either the global or the local
handler at all times, so the (HUP) signal will always be caught, but
apparently that assumption isn't true. "local" sets the handler
to undef, and then "=" sets it to \ &local_handler. It is quite
possible for a signal to arrive when the hander is undefined, which
ends the program through an uncaught signal.
My conclusion: The practice of localizing %SIG handlers isn't safe,
and probably never has been.

Comments?

Hmmm.... local $SIG{FOO} is too useful an idiom to lose, even if it
never really worked :).

There are two possible ways out, AFAICT. The first is that assigning
undef to a member of %SIG currently has undefined effects. In fact, what
happens is that any assigned value that 1. isn't a glob or a reference
and 2. stringifies to the empty string causes the signal handler to be
set to SIG_DFL. At the potential risk of breaking programs that rely on
the (undocumented) consequence that $SIG{FOO} = undef; is equivalent to
$SIG{FOO} = 'DEFAULT'; it would be relatively trivial to change
mg.c:perl_magic_setsig to cause undef to leave the signal handler
unchanged, instead of setting it to the default.

The alternative, which would be rather harder to implement but would
only break programs which assume local $SIG{FOO}; sets SIGFOO to SIG_DFL
rather than $SIG{FOO} = undef;, is to special-case %SIG in
scope.c:S_save_scalar_at to keep the old value of $SIG{FOO} on
localising rather than assigning undef.

The Perl solution is obvious: create a class which can be used like

my $sh = SigHandler->new(HUP => \&global_handler);
{
my $sh = SigHandler->new(HUP => \&local_handler);
}

and which saves the old value in the object and restores it on DESTROY.

(It's interesting, in passing, that this uses a 'my' object to implement
a 'local' handler-scoping... :)

Ben
 
A

Anno Siegel

William Ahern said:
Are you using Perl >= 5.7.3? I'm a little surprised (not much, though) that
Perl--with it's delayed signal handling--would actually uninstall a
signal handler rather than mimic an uninstalled handler.

I've seen the behavior with 5.8.1 and 5.8.4.

Anno
 
A

Anno Siegel

Ben Morrow said:
Quoth (e-mail address removed)-berlin.de (Anno Siegel):


Hmmm.... local $SIG{FOO} is too useful an idiom to lose, even if it
never really worked :).

There are two possible ways out, AFAICT. The first is that assigning
undef to a member of %SIG currently has undefined effects. In fact, what
happens is that any assigned value that 1. isn't a glob or a reference
and 2. stringifies to the empty string causes the signal handler to be
set to SIG_DFL. At the potential risk of breaking programs that rely on
the (undocumented) consequence that $SIG{FOO} = undef; is equivalent to
$SIG{FOO} = 'DEFAULT'; it would be relatively trivial to change
mg.c:perl_magic_setsig to cause undef to leave the signal handler
unchanged, instead of setting it to the default.

Ah, but that would mean that a pristine $SIG{FOO} wouldn't be restored
after { local $SIG{ FOO} = \&something }. \&something would stick,
which is certainly unexpected.

I also admit that I had naively assumed the, as you point out,
undocumented behavior that undef'ing a sig handler resets it to
the default.
The alternative, which would be rather harder to implement but would
only break programs which assume local $SIG{FOO}; sets SIGFOO to SIG_DFL
rather than $SIG{FOO} = undef;, is to special-case %SIG in
scope.c:S_save_scalar_at to keep the old value of $SIG{FOO} on
localising rather than assigning undef.

....so basically, local() would have to be made aware that it is
localizing a sig handler, instead of dealing with it in %SIG magic.
The Perl solution is obvious: create a class which can be used like

my $sh = SigHandler->new(HUP => \&global_handler);
{
my $sh = SigHandler->new(HUP => \&local_handler);
}

and which saves the old value in the object and restores it on DESTROY.

POSIX::SigAction objects appear to implement that, though I don't see
a DESTROY method. Maybe not...

In the particular case, the solution was to scratch local(). It
was only there out of orderliness in the first place :)

Anno
 
B

Ben Morrow

Quoth (e-mail address removed)-berlin.de (Anno Siegel):
Ah, but that would mean that a pristine $SIG{FOO} wouldn't be restored
after { local $SIG{ FOO} = \&something }. \&something would stick,
which is certainly unexpected.

OK, so initialise all of %SIG to 'DEFAULT' rather than to undef
(especially as that's what it's implicitly initialised to anyway). Then
when Perl restores the 'DEFAULT' at the end of the block it will restore
SIG_DFL.
I also admit that I had naively assumed the, as you point out,
undocumented behavior that undef'ing a sig handler resets it to
the default.

Yes... it is a natural assumption, especially given that %SIG starts
empty.
...so basically, local() would have to be made aware that it is
localizing a sig handler, instead of dealing with it in %SIG magic.

Yup.

POSIX::SigAction objects appear to implement that, though I don't see
a DESTROY method. Maybe not...

No... I wondered about POSIX::SigAction, but those objects are *just* a
Perl version of struct sigaction. Creating an object doesn't install a
signal handler at all; you have to call POSIX::sigaction to do that.
In the particular case, the solution was to scratch local(). It
was only there out of orderliness in the first place :)

Yes. However, it *ought* to work: the reason local is useful is because
it's so hard to catch all the exits from a dynamic scope.

Ben
 
A

Anno Siegel

Ben Morrow said:
Quoth (e-mail address removed)-berlin.de (Anno Siegel):

OK, so initialise all of %SIG to 'DEFAULT' rather than to undef
(especially as that's what it's implicitly initialised to anyway). Then
when Perl restores the 'DEFAULT' at the end of the block it will restore
SIG_DFL.

But that would break... Oh, never mind. This discussion really
should be on p5p.

[...]
Yes. However, it *ought* to work: the reason local is useful is because
it's so hard to catch all the exits from a dynamic scope.

Yes. "{ ...; next; ... } continue { $SIG{ FOO} = ... }" is ersatz
at best.

Anno
 
C

Charles DeRykus

It is common practice to localize %SIG handlers in this fashion:

$SIG{ HUP} = \ &global_handler;

{
local $SIG{ HUP} = \ &local_handler;
# provoke and deal with one or more HUP signals
}

The assumption is that $SIG{ HUP} is either the global or the local
handler at all times, so the (HUP) signal will always be caught, but
apparently that assumption isn't true. "local" sets the handler
to undef, and then "=" sets it to \ &local_handler. It is quite
possible for a signal to arrive when the hander is undefined, which
ends the program through an uncaught signal. The following code
shows this:

my ( $count_a, $count_b) = ( 0, 0); # unused, but left in place
$SIG{ HUP} = sub { $count_a ++ };

# create a stream of HUP signals from kid to parent
my $parent = $$;
defined( my $pid = fork ) or die "fork: $!";
unless ( $pid ) {
require Time::HiRes;
require POSIX;
my $tick = 1/POSIX::sysconf( POSIX::_SC_CLK_TCK());
while ( 1 ) {
kill HUP => $parent or exit; # don't survive parent
Time::HiRes::sleep( $tick); # shortest admissible sleep time
}
exit(); # not reached;
}

# main loop
while ( 1 ) {
{
local $SIG{ HUP} = sub { $count_b ++ };
# my $save = $SIG{ HUP};
# $SIG{ HUP} = sub { $count_b ++ };
# $SIG{ HUP} = $save;
}
}

With the "main loop" as shown ("local" active), after a few seconds
the program ends with a "Hangup" message, characteristic for an
uncaught HUP signal. With "local" commented out and the other three
lines active, the program runs happily as long as I let it.

My conclusion: The practice of localizing %SIG handlers isn't safe,
and probably never has been.

I see the Hangup on 5.8.4 almost immediately but a HUP blocker
eliminates 'em (at least for a 10 min. test). Are you
thinking perhaps a HUP block shouldn't be necessary..
or am I missing another point..


unless ( defined sigprocmask(SIG_BLOCK, $sigset, $old_sigset) ) {
die "can't block SIGHUP\n";
} else {
local $SIG{ HUP} = sub { $count_b ++ };
unless ( defined sigprocmask(SIG_UNBLOCK, $old_sigset) ) {
die "can't unblock SIGHUP\n"; # restore
}
...
}
 
A

Anno Siegel

Charles DeRykus said:
I see the Hangup on 5.8.4 almost immediately but a HUP blocker
eliminates 'em (at least for a 10 min. test). Are you
thinking perhaps a HUP block shouldn't be necessary..
or am I missing another point..

HUP is just an example, any other (catchable) signal would do.
It isn't be necessary to block a signal that is being caught.
The fact that the job dies shows that local() leaves a gap when
there is no handler set.
unless ( defined sigprocmask(SIG_BLOCK, $sigset, $old_sigset) ) {
die "can't block SIGHUP\n";
} else {
local $SIG{ HUP} = sub { $count_b ++ };
unless ( defined sigprocmask(SIG_UNBLOCK, $old_sigset) ) {
die "can't unblock SIGHUP\n"; # restore
}
...
}

That's a way around, but it shouldn't be needed.

Anno
 
B

Ben Morrow

Quoth (e-mail address removed)-berlin.de (Anno Siegel):
That's a way around, but it shouldn't be needed.

However, it suggests a third, I think rather attractive alternative to
the two I suggested earlier, namely that $SIG{FOO} = undef; should mask
SIGFOO... This suffers from the same potential breakage of scripts that
rely on undef ~~ 'DEFAULT', and will also require %SIG to be initialised
to all 'DEFAULT', but I feel it could be quite useful in the right
circumstances...

Comments? Or should I take this to p5p?

Ben
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
Anno Siegel
$SIG{ HUP} = \&global_handler;
{
local $SIG{ HUP} = \ &local_handler;
# provoke and deal with one or more HUP signals
}

The assumption is that $SIG{ HUP} is either the global or the local
handler at all times, so the (HUP) signal will always be caught, but
apparently that assumption isn't true. "local" sets the handler
to undef, and then "=" sets it to \ &local_handler. It is quite
possible for a signal to arrive when the hander is undefined, which
ends the program through an uncaught signal.

What you describe is a serious flaw in Perl signal-handling
implementation. But many more others may be present; it is very hard
for unexperienced-in-asm C programmers (i.e., for most of us) to write
signal-safe code.

The first thing to check is whether just *assignment* to $SIG{HUP} is
a signal-safe operation (it may contain some more windows of
vulnerability). If it is safe (10^8 signals savely delivered is a
good indication - should take couple of days on current OSes), I
would think that this would be safe too:

#!/usr/bin/perl -w
use strict;
sub DESTROYER::DESTROY {shift->()}
sub ON_DESTROY (&) {bless shift, 'DESTROYER'}
sub LOCAL_SIG (&$) {my $s=pop; my $o = $SIG{$s}; $SIG{$s}=shift;
ON_DESTROY {$SIG{$s}=$o || 'DEFAULT'}}

$SIG{HUP} = \&global_sig;
{ my $on_leave = LOCAL_SIG {warn 'got SIG'} 'HUP';
# Do something...
}

Hope this helps,
Ilya
 
A

Anno Siegel

Ilya Zakharevich said:
[A complimentary Cc of this posting was sent to
Anno Siegel
$SIG{ HUP} = \&global_handler;
{
local $SIG{ HUP} = \ &local_handler;
# provoke and deal with one or more HUP signals
}

The assumption is that $SIG{ HUP} is either the global or the local
handler at all times, so the (HUP) signal will always be caught, but
apparently that assumption isn't true. "local" sets the handler
to undef, and then "=" sets it to \ &local_handler. It is quite
possible for a signal to arrive when the hander is undefined, which
ends the program through an uncaught signal.

What you describe is a serious flaw in Perl signal-handling
implementation. But many more others may be present; it is very hard
for unexperienced-in-asm C programmers (i.e., for most of us) to write
signal-safe code.

The first thing to check is whether just *assignment* to $SIG{HUP} is
a signal-safe operation (it may contain some more windows of
vulnerability). If it is safe (10^8 signals savely delivered is a
good indication - should take couple of days on current OSes),

Eleven, signalling at a rate of 100 ticks per second. One can signal
faster, but (as far as I can see) only with a busy timer. Is there
anything particular to 10^8 or is it just an arbitrary big number?

I don't know if plain assignment is safe, but it (and your derived
method below) are by orders of magnitude safer than local(). At a
rate of 100 signals per sec, local()izing the hander in a tight loop
crashes in less than a second. With your method (or explicit
assignment), it ran through a lengthy phone call. (How's that for
precision timing? :)
I would think that this would be safe too:

#!/usr/bin/perl -w
use strict;
sub DESTROYER::DESTROY {shift->()}
sub ON_DESTROY (&) {bless shift, 'DESTROYER'}
sub LOCAL_SIG (&$) {my $s=pop; my $o = $SIG{$s}; $SIG{$s}=shift;
ON_DESTROY {$SIG{$s}=$o || 'DEFAULT'}}

$SIG{HUP} = \&global_sig;
{ my $on_leave = LOCAL_SIG {warn 'got SIG'} 'HUP';
# Do something...
}

Ah... so you oblige the user to accommodate an additional variable
in the scope of the local handler.

I had tried to employ DESTROY for this purpose, but made the handler
itself a code object. I had given up on that approach since anyone
can keep a copy of the handler (and may have reasons to), preventing
DESTROY to be called, I had given up on that approach. Next time I'll
remember that trick of yours.

Anno
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
Anno Siegel
Eleven, signalling at a rate of 100 ticks per second. One can signal
faster, but (as far as I can see) only with a busy timer. Is there
anything particular to 10^8 or is it just an arbitrary big number?

I can signal at 1000/sec (recent OS/2). Given reasonable delay on
Usenet of a day or two, this gives 10^8. ;-) [If one believes
back-of-envelopo calculations, then the active code of a small test
program should be about 10^4 instructions; so n*10^4 signals have a
good probability to hit at all instruction boundaries. However, I
have seen Perl signal handling problems which happen much more rare
than this...]
Ah... so you oblige the user to accommodate an additional variable
in the scope of the local handler.

Actually, I sent a patch to p5p a couple of years ago which "would do
the same" without an extra variable. It would work as

{ ON_LEAVE { do something };
# Do something here
}

Do not know whether it made it into 5.8.*... (It was a part of
Scalar::Utils, IIRC.)

Yours,
Ilya
 
I

Ilya Zakharevich

I wrote in article said:
I can signal at 1000/sec (recent OS/2). Given reasonable delay on
Usenet of a day or two, this gives 10^8. ;-) [If one believes
back-of-envelopo calculations, then the active code of a small test
program should be about 10^4 instructions; so n*10^4 signals have a
good probability to hit at all instruction boundaries.

I missed one log(10^4) in this estimage: with n*10^4 signals, the
probability that *one* particular instruction boundary is hit is about
exp(-n). So if one wants that *all* instruction boundaries are hit
with high probability, one needs about n*10^4*log(10^4), so about
n*10^5 signals. (n about 5 should be a reasonable safeguard...)
However, I have seen Perl signal handling problems which happen much
more rare than this...]

Thinking about this more: signals happen not on instruction
boundaries, but on clock cycle boundaries. And with long instruction
pipelines, tentative execution, and branch prediction, one can get
many different states of processor per instruction boundary. Maybe
this can explain such low-probability events?

Ilya
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top