Signals and local $@

A

Adrien BARREAU

Hello all.


I'm trying to write a little wrapper which properly handles a call with
a timeout.

Since I'm on a 5.10.1, I take care of all eval{} pitfalls I know (more
or less take some parts of Try::Tiny ideas just to do a proper eval).

Here is the thing I don't know how to deal with.

# ---
#!/usr/bin/perl

use strict;
use warnings;

sub sleep10
{
local $@;
sleep 10;
}

{
eval {
local $SIG{'ALRM'} = sub { die "Here comes the death\n"; };
alarm 2;
sleep10();
};

alarm 0;
print "Err: $@\n";
}
# ---

That script does its timeout after 2 seconds, but prints "Err :".
I don't find a way not to loose my handler error message if the code I
try to protect localize $@.
I don't even know if I have a chance to achieve that.

Might somebody help me a bit about that? :)

Thanks.
Adrien.
 
D

Dr.Ruud

Hello all.


I'm trying to write a little wrapper which properly handles a call with
a timeout.

Since I'm on a 5.10.1, I take care of all eval{} pitfalls I know (more
or less take some parts of Try::Tiny ideas just to do a proper eval).

Here is the thing I don't know how to deal with.

# ---
#!/usr/bin/perl

use strict;
use warnings;

sub sleep10
{
local $@;
sleep 10;
}

{
eval {
local $SIG{'ALRM'} = sub { die "Here comes the death\n"; };
alarm 2;
sleep10();
};

alarm 0;
print "Err: $@\n";
}
# ---

That script does its timeout after 2 seconds, but prints "Err :".
I don't find a way not to loose my handler error message if the code I
try to protect localize $@.
I don't even know if I have a chance to achieve that.

Might somebody help me a bit about that? :)

You are doing many things wrong in the above. Try::Tiny is meant for
coders that don't know how to do it properly, so why don't you just use
that?

Be aware that (the value of) $@ has no meaning, unless there has been an
error detected.


my $ok;

eval {
$ok = can_die();
1; # success
}
or do {
my $eval_error = $@ || 'Zombie Error';
warn $eval_error;
};

So only use $@ when eval{} tells you that you can.
 
A

Adrien BARREAU

You are doing many things wrong in the above. Try::Tiny is meant for
coders that don't know how to do it properly, so why don't you just use
that?

Well, at first, if I never learn, I'll never know, will I?
And I can not install Try::Tiny. If it had been an option, I wouldn't
have tried by myself.
Be aware that (the value of) $@ has no meaning, unless there has been an
error detected.


my $ok;

eval {
$ok = can_die();
1; # success
}
or do {
my $eval_error = $@ || 'Zombie Error';
warn $eval_error;
};

So only use $@ when eval{} tells you that you can.

I'm not thinking something else.
It seems that my code works, and follows the same pattern you just wrote.

The only thing that troubles me is the very case I wrote: if "can_die()"
localize $@ and receive a signal that makes it die.


Thanks for responding :).
 
D

Dr.Ruud

On 05/24/2013 03:08 PM, Dr.Ruud wrote:

I'm not thinking something else.

Your code shows differently.

It seems that my code works, and follows the same pattern you just wrote.

It doesn't.

The only thing that troubles me is the very case I wrote: if "can_die()"
localize $@ and receive a signal that makes it die.

If that troubles you, then go and find out what localize does.
After that: Why are you localizing $@?
 
A

Adrien BARREAU

Try::Tiny is one .pm file. If you can run perl at all you can install it
somewhere and add that somewhere to @INC.

Ben

Of course, but this is meant to run on some computer where adding new
CPAN files is not an option.
That's not my choice and I can't help it.

So it became a precondition: I must do without it.


Adrien.
 
A

Adrien BARREAU

Your code shows differently.



It doesn't.



If that troubles you, then go and find out what localize does.
After that: Why are you localizing $@?

My introduction was: "I'm trying to write a little wrapper which
properly handles a call with a timeout.".
Some forgotten detail: "a call" meant "a call I don't write".
It's meant to be a little part of some API, somewhere.

I found that, if there is a localization of $@ in "the call", my wrapper
fails.

I'm trying to figure if I can do better. Nothing more.

Adrien.
 
R

Rainer Weikusat

[...]
Be aware that (the value of) $@ has no meaning, unless there has been
an error detected.

Please be aware of the fact that this it absolute, total, utter
rubbish, cf

If there is a syntax error or runtime error, or a "die"
statement is executed, "eval" returns an undefined value in
scalar context or an empty list in list context, and $@ is set
to the error message. If there was no error, $@ is guaranteed
to be a null string.
[perdoc -f eval]

In particular, eval might return 'an undefined value or an empty list'
for other reasons than 'an error occured', namely, when the last
statement executed by the eval returned undef.
 
R

Rainer Weikusat

[...]
I'm trying to write a little wrapper which properly handles a call
with a timeout.

Since I'm on a 5.10.1, I take care of all eval{} pitfalls I know (more
or less take some parts of Try::Tiny ideas just to do a proper eval).

Here is the thing I don't know how to deal with.

# ---
#!/usr/bin/perl

use strict;
use warnings;

sub sleep10
{
local $@;
sleep 10;
}

{
eval {
local $SIG{'ALRM'} = sub { die "Here comes the death\n"; };
alarm 2;
sleep10();
};

alarm 0;
print "Err: $@\n";
}
# ---

That script does its timeout after 2 seconds, but prints "Err :".

This is inherently racy: With your code above, the SIGALRM could
happend after the handler was deinstalled when exiting the eval but
before the running alarm was cancelled. This would the terminate the
process because the default action for SIGALRM would occur.

The problem with $@ being cleared can be fixed by removing the 'local $@'
from the sleep10 routine:

----------------
#!/usr/bin/perl

use strict;
use warnings;

sub sleep10
{
sleep 10;
}

{
eval {
local $SIG{'ALRM'} = sub { die "Here comes the death\n"; };
alarm 2;
sleep10();
};

alarm 0;
print "Err: $@\n";
};
 
A

Adrien BARREAU

This is inherently racy: With your code above, the SIGALRM could
happend after the handler was deinstalled when exiting the eval but
before the running alarm was cancelled. This would the terminate the
process because the default action for SIGALRM would occur.

The problem with $@ being cleared can be fixed by removing the 'local $@'
from the sleep10 routine:

----------------
#!/usr/bin/perl

use strict;
use warnings;

sub sleep10
{
sleep 10;
}

{
eval {
local $SIG{'ALRM'} = sub { die "Here comes the death\n"; };
alarm 2;
sleep10();
};

alarm 0;
print "Err: $@\n";
};

That's what I thought, but I hoped to find a way to protect it.
As far as I understand, none exists. Is that true?
 
R

Rainer Weikusat

Adrien BARREAU said:
That's what I thought, but I hoped to find a way to protect it.
As far as I understand, none exists. Is that true?

*If* the die is executed while a scope with a localized $@ is active,
it will change the value of the localized $@ and this change will be
undone as part of the stack-unwinding which happens when exiting that
scope.
 
A

Adrien BARREAU

*If* the die is executed while a scope with a localized $@ is active,
it will change the value of the localized $@ and this change will be
undone as part of the stack-unwinding which happens when exiting that
scope.

Thank you for your answers :).

Adrien.
 
A

Adrien BARREAU

So copy the code out of Try/Tiny.pm into your own script.

More generally, this sort of restriction can usually be worked around
with a little thought, and it's usually worth doing so. For instance,
PAR::packer provides the pp utility, and pp -P will pack a script and
any pure-Perl modules it uses into a single Perl file. (It doesn't
always work perfectly--some modules do some rather strange things--but
it works well for simple cases.)

Ben
Once again, I agree about that idea, but I'm not allowed to do that either.
I'll take a look to PAR, you described some interesting stuff (for other
developments).

Thanks :).
 
A

Adrien BARREAU

If you must work around this sort of thing, you can do so by using your
own variable:

sub make_call {
my $alarmed;
$SIG{ALRM} = sub { $alarmed = 1 };
try { ... }
catch {
if ($alarmed) { ... }
};
}

IMHO code which localises $@ without rethrowing any exceptions it
doesn't handle is broken, and you would be better off simply documenting
that this doesn't work.

Ben
I did not think about that variable idea, thanks.

I know the kind of thing I try to protect my code against is very dirty.
I try my best, I'll document everything else, indeed.

Adrien.
 
J

Jürgen Exner

Adrien BARREAU said:
Once again, I agree about that idea, but I'm not allowed to do that either.

Then take 4 days off at full pay, come back, and claim that during those
4 days you developed that piece of code. Problem solved.

jue
 
C

Charles DeRykus

Hello all.


I'm trying to write a little wrapper which properly handles a call with
a timeout.

Since I'm on a 5.10.1, I take care of all eval{} pitfalls I know (more
or less take some parts of Try::Tiny ideas just to do a proper eval).

Here is the thing I don't know how to deal with.

# ---
#!/usr/bin/perl

use strict;
use warnings;

sub sleep10
{
local $@;
sleep 10;
}

{
eval {
local $SIG{'ALRM'} = sub { die "Here comes the death\n"; };
alarm 2;
sleep10(); alarm(0); # <--- to here
};

# alarm 0; #----- move inside eval {};


Just an aside but, if you're not using Try::Tiny, you should move the
alarm inside the eval{} to remove another small race condition.
 
D

Dr.Ruud

On 5/24/2013 4:05 AM, Adrien BARREAU wrote:


Just an aside but, if you're not using Try::Tiny, you should move the
alarm inside the eval{} to remove another small race condition.

It must be in both places, if you assume that sleep10 can die for
multiple reasons.
And the return-value of the eval{} must be checked, after forcing it to
1 for success. Etc.
 
C

Charles DeRykus

It must be in both places, if you assume that sleep10 can die for
multiple reasons.

Yes, mea culpa. Of course sleep10 as shown didn't 'die' and wasn't
re-throwing the exception... which threw me :)
And the return-value of the eval{} must be checked, after forcing it to
1 for success. Etc.

And, even with that, it should be mentioned Try::Tiny is a better solution.
 
R

Rainer Weikusat

Charles DeRykus said:
On 5/25/2013 5:59 AM, Dr.Ruud wrote:
[...]
And the return-value of the eval{} must be checked, after forcing it to
1 for success. Etc.

And, even with that, it should be mentioned Try::Tiny is a better
solution.

In the sense that it has three times as much 'voodoo coding' but
(hark!) nobody needs to look at the mess. Apart from that, these
opinion statements regarding 'proper error handling' would be more
convincing if they were expressed as opinion statements and came with
a reason eg "I'm generally of the opinion that exceptions shouldn't be
used for error handling because ...".

I'm using eval and die extensively, both for aborting some sequence of
processing steps from nested subroutines and for actual exception
handling and this works very nicely. Some remarks about Try::Tiny:

,----
| finally (&;$)
|
| [...]
|
| This allows you to locate cleanup code which cannot be
| done via local() e.g. closing a file handle.
`----

This would be more aptly described as 'This construct is necessary to
work around deficiencies of a sixty year old concept for "automatic
memory management" which is limited to manageing memory while all
other resources which need to be allocated and freed have to be
managed with explicitly written code. That's not a problem for Perl 5
which uses a more modern approach for automatic resource management
with support for stack unwinding, deterministic finalization and
automatic management of file handles *BUT* it will become a problem
with Perl 6, should that ever evolve beyond being an abandoned Haskell
demo."

,----
| There are a number of issues with eval.
`----

,----
| Clobbering $@
|
| When you run an eval block and it succeeds, $@ will be cleared,
| potentially clobbering an error that is currently being caught.
`----

That's an inherent limitation of the idea to use 'a global variable'
as 'the exception location' and part of the documented functionlity of
eval. The solution is that someone who wants to use $@ for exception
handling has to save the value in some other location before starting
any more complicated unrelated computation.

,----
| Localizing $@ silently masks errors
|
| Inside an eval block, die behaves sort of like:
|
| sub die {
| $@ = $_[0];
| return_undef_from_eval();
| }
|
| This means that if you were polite and localized $@ you can't die in
| that scope, or your error will be discarded (printing "Something's
| wrong" instead).
`----

'Localizing $@# does not 'silently mask errors', it localizes $@, that
is, it creates a lexically scoped binding for a global
variable. Because of this, changes to $@ while this binding is in
scope will not affect 'the outside world' anyhow. Since $@ is used for
exception propagation, this means that code which localizes $@ without
dealing with this possibility 'might silently mask an error' aka 'is
broken'.

,----
| $@ might not be a true value
|
| This code is wrong:
|
| if ( $@ ) {
| ...
| }
|
| because due to the previous caveats it
| may have been unset.
`----

This example is incomplete: The code supposed to set $@ is
missing. Also, there were no general 'previous caveats': The first
situation roughly amounts to the following:

eval {
...;
...;
}

eval {
...;
...;
}

if ($@) {
}

plus the expectation that the value of $@ would reflect something
which happened during the first eval which it doesn't: This is a
coding error and needs to be avoided. Localizing $@ without dealing
with its 'special magic' is also a coding error.

,----
| The classic failure mode is:
|
| [...]
|
| In this case since Object::DESTROY is not localizing $@ but still uses
| eval, it will set $@ to "".
`----

That's the sole valid concern so far: Object destructors are executed
automatically during stack unwinding. Because of this, it is prudent
to write them such that they don't modify any kind of 'state
information' visible to unrelated parts of the program. That's the
downside of any 'convenience mechanism' which might lead to the
execution of subroutines in places where this isn't obvious when
looking at the code causing these invocation. 'Operator overloading'
suffers from the same problem. Not related to eval/ $@ in any
particular way.

NB: This text is an opinion statement itself and there might well be
valid counterarguments for anything contained in it.
 
C

Charles DeRykus

Charles DeRykus said:
On 5/25/2013 5:59 AM, Dr.Ruud wrote:
[...]
And the return-value of the eval{} must be checked, after forcing it to
1 for success. Etc.

And, even with that, it should be mentioned Try::Tiny is a better
solution.

In the sense that it has three times as much 'voodoo coding' but
(hark!) nobody needs to look at the mess. Apart from that, these
opinion statements regarding 'proper error handling' would be more
convincing if they were expressed as opinion statements and came with
a reason eg "I'm generally of the opinion that exceptions shouldn't be
used for error handling because ...".

I'm using eval and die extensively, both for aborting some sequence of
processing steps from nested subroutines and for actual exception
handling and this works very nicely.

Good objection. Admittedly, I was mostly parroting the consensus about
Try::Tiny. Still IMO most are better off with T::T than rolling an eval
eval block. It's easy to forget the subtleties of Perl's exception
handling... or remain blissfully unaware of them in the first place. For
the latter reason, I can't comment in any depth.

But, if 'sleep10' had been using T::T -- instead of mangling $@ as it
apparently did and which is easy to do -- the code would have arguably
been cleaner and chances of mayhem reduced. Even visually, I'd prefer:
try{} catch {} rather than: local $@; eval{...;1};

That's not a big edge but with eval, you have also to remember to
localize $@ and to ensure the block returns true. Plus there can be
concerns as you cite of a DESTROY block which zaps $@. IIUC, T:T handles
these problems for you

Both approaches involve discipline of course. Even with T::T, a global
$_ is in play if errors occur. But, at least there's not the potential
of collateral damage to the exception model itself because of $@
vulnerability.
 
R

Rainer Weikusat

Charles DeRykus said:
Charles DeRykus said:
On 5/25/2013 5:59 AM, Dr.Ruud wrote:
[...]

And the return-value of the eval{} must be checked, after forcing it to
1 for success. Etc.

And, even with that, it should be mentioned Try::Tiny is a better
solution.

In the sense that it has three times as much 'voodoo coding' but
(hark!) nobody needs to look at the mess. Apart from that, these
opinion statements regarding 'proper error handling' would be more
convincing if they were expressed as opinion statements and came with
a reason eg "I'm generally of the opinion that exceptions shouldn't be
used for error handling because ...".

I'm using eval and die extensively, both for aborting some sequence of
processing steps from nested subroutines and for actual exception
handling and this works very nicely.

Good objection. Admittedly, I was mostly parroting the consensus about
Try::Tiny.

Consensus of whom? Obviously, all people who use Try::Tiny do so
because they're convinced that it is useful for them. But this doesn't
necessarily mean that it actually is. As someone wrote in a complete
different context: Science is not a democracy because 'truth' is not
'what most people believe in'.
Still IMO most are better off with T::T than rolling an
eval eval block. It's easy to forget the subtleties of Perl's
exception handling... or remain blissfully unaware of them in the
first place.

The Try::Tiny documentation list three 'concerns' about 'Perl exception
handling' (I consider two of them invalid for reasons stated
elsewhere) but it contains no less than seven 'caveats' supposed to
apply to using it (and some of the open bugs -- apparently, bugs in
Try::Tiny are usually neither fixed nor closed -- are about people who
still didn't understand how to use it). Memoizing more than two times
as much 'subtleties' about using Try::Tiny in order to avoid learning
about 'the subtleties of Perl exception handling' doesn't seem like a
sensible tradeoff to me.

Not to mention that most details of the Try::Tiny code are quoted (in
simplified form) in the documentation and the author himself
consistently refers to them as 'ugly hacks' (I agree with this
assessment :->).
But, if 'sleep10' had been using T::T -- instead of mangling $@ as it
apparently did and which is easy to do -- the code would have arguably
been cleaner and chances of mayhem reduced. Even visually, I'd prefer:
try{} catch {} rather than: local $@; eval{...;1};

And I prefer to use a facility for dealing with 'exceptional events'
(which change the 'normally linear' control flow of something) for
doing actual 'exception throwing and handling'. Otherwise, I'd use
some 'special return value' error signalling convention without the
overhead of the former. Mixing both in this way in order to work
around hypothetical bugs in other code (instead of fixing these) is IMHO
just totally bizarre: The point of having 'exceptions' in the first
place is that return values can be used for returning 'useful
information' and exceptions for 'exceptional events'.

I also don't quite understand why one would localize $@ and want to
propagate errors out of the scope of the local at the same time. The
easy way to accomplish that would be not localizing $@ to begin with.
 

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

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,905
Latest member
Kristy_Poole

Latest Threads

Top