Skip Menu |

This queue is for tickets about the Try-Tiny CPAN distribution.

Report information
The Basics
Id: 54050
Status: rejected
Priority: 0/
Queue: Try-Tiny

People
Owner: Nobody in particular
Requestors: mschwern [...] cpan.org
Cc: ribasushi [...] leporine.io
AdminCc:

Bug Information
Severity: Wishlist
Broken in: 0.04
Fixed in: (no value)



Subject: localize and unset $SIG{__DIE__}
Its all but considered a bug that $SIG{__DIE__} triggers inside an eval. It would be very nice if try fixed this once and for all by localizing $SIG{__DIE__}. That would solve one of the major headaches of eval. If you wanted to be perfect about it, there is the edge case of setting a global $SIG{__DIE__} inside try {}. If try {} localizes $SIG{__DIE__} that global set will be lost. So do this: my $original_die = $SIG{__DIE__}; undef $SIG{__DIE__}; ...do the code... $SIG{__DIE__} = $original_die unless defined $SIG{__DIE__}; It turns off $SIG{__DIE__} but preserves any global changes made by the code inside the try. The edge of this edge case is the global $SIG{__DIE__} will effect the die inside the try, effectively defeating the point of unsetting $SIG{__DIE__} in the first place. If you wanted to handle that you'd tie $SIG{__DIE__} so that when its set it wraps the new code ref in sub { return if $^S; $code->(@_) }; But really, localizing $SIG{__DIE__} inside try is enough.
Subject: Re: [rt.cpan.org #54050] localize and unset $SIG{__DIE__}
Date: Thu, 28 Jan 2010 09:06:16 +0100
To: bug-Try-Tiny [...] rt.cpan.org
From: Yuval Kogman <nuffin [...] cpan.org>
2010/1/28 Michael G Schwern via RT <bug-Try-Tiny@rt.cpan.org>: Show quoted text
> Its all but considered a bug that $SIG{__DIE__} triggers inside an eval. >  It would be very nice if try fixed this once and for all by localizing > $SIG{__DIE__}.  That would solve one of the major headaches of eval.
That's making a fairly big assumption about the usage of $SIG{__DIE__}, sometimes people rely on it for things that *should* be triggered in an eval. For example by using it to automatically convert errors to exception objects is something i've seen in real code before, and that's pretty much only useful *inside* an eval. If existing code can't be converted to using Try::Tiny without a full audit of the code being used inside the eval that's a pretty big caveat, so I'm uncomfortable making this change. One option would be to make this a per consumer import-time decision (use Try::Tiny :disable_sig_die => 1) or something like that, but that would seriously complicate the code, which I think still serves as a useful way of documenting the problems with eval in the comments etc. I was very hesitant to add @DB::args and 'finally' because of that, but eventually caved on 'finally'.
On Thu Jan 28 03:07:36 2010, NUFFIN wrote: Show quoted text
> That's making a fairly big assumption about the usage of > $SIG{__DIE__}, sometimes people rely on it for things that *should* be > triggered in an eval.
try is not eval. There is an opportunity here to fix the sins of eval and make new assumptions. try has already diverged significantly from eval, why draw a line here? Show quoted text
> For example by using it to automatically convert > errors to exception objects is something i've seen in real code > before, and that's pretty much only useful *inside* an eval. > > If existing code can't be converted to using Try::Tiny without a full > audit of the code being used inside the eval that's a pretty big > caveat, so I'm uncomfortable making this change.
For every clever hack you can do with $SIG{__DIE__} + eval there's 100 lines of code setting a global $SIG{__DIE__}, not checking $^S and getting it wrong. Here, let's find some using Google Code Search. Global $SIG{__DIE__} handlers which check $^S: 99 http://www.google.com/codesearch?hl=en&lr=&q=%22%24SIG{__DIE__}%22+lang%3Aperl+%22%24^S%22+-local&sbtn=Search Global $SIG{__DIE__} handlers which do not check $^S: ~2000 http://www.google.com/codesearch?hl=en&lr=&q=%22%24SIG{__DIE__}%22+lang%3Aperl+-%22%24^S%22+-local&sbtn=Search I'm willing to bet not even 20 of those 2000 are intentional. Try::Tiny already diverges from eval, there's a whole list of (very well documented, thank you) caveats, and you can't just cut & paste from an eval block to a try block. That ship has sailed. There's even potential action-at-a-distance effects: try doesn't clobber $@, I'm sure somebody is doing something very clever with that somewhere. Even perlvar acknowledges that $SIG{__DIE__} in an eval is deprecated. Due to an implementation glitch, the $SIG{__DIE__} hook is called even inside an eval(). Do not use this to rewrite a pending exception in $@, or as a bizarre substitute for overriding "CORE::GLOBAL::die()". This strange action at a distance may be fixed in a future release so that $SIG{__DIE__} is only called if your program is about to exit, as was the original intent. Any other use is deprecated. The point of Try::Tiny is to provide... bare bones "try"/"catch"/"finally" statements that are designed to minimize common mistakes with eval blocks, and NOTHING else. Setting a global DIE handler and forgetting to check $^S is a very, very, very common mistake. And worse, its one the author of the eval/try block probably did not make but is still punished for. That sucks.
On Thu Jan 28 03:07:36 2010, NUFFIN wrote: Show quoted text
> If existing code can't be converted to using Try::Tiny without a > full audit of the code being used inside the eval that's a pretty > big caveat, so I'm uncomfortable making this change.
I'm also usually against messing with $SIG{__DIE__} because the meddling is often done wrong, but Schwern has me convinced on this one. As long as the change doesn't prevent $SIG{__DIE__} from being installed via e.g. try { require SomeModule }, then you're not breaking anything important. This *would* be introducing the caveat that "try{} is not eval{}" with regard to those things which perlvar tells you not to do with $SIG{__DIE__}. I think that's worth it. Educating the wizards who want a hook to rewrite $@ is going to be easier than educating those who don't know about $^S. Also, a pretty simple and thorough audit is to run: perl -MDevel::NoGlobalSig=die your_program (The proposed change might cause NoGlobalSig to point at Try::Tiny as the culprit if Foo installs $SIG{__DIE__} in e.g. "try {eval Foo}" but -MCarp=verbose and checking one level up the stacktrace will show you the caller. But details of caller() reporting from destructors on die handlers is not really an important edge case.) That covers all of the concrete issues I can come up with. Did I miss something? Thanks, Eric
Subject: Re: [rt.cpan.org #54050] localize and unset $SIG{__DIE__}
Date: Fri, 5 Feb 2010 07:40:25 +0100
To: bug-Try-Tiny [...] rt.cpan.org
From: Yuval Kogman <nuffin [...] cpan.org>
try might be different from eval in the local semantics, but all of the differences are apparent in the lexical scope of usage. Secondly, $SIG{__DIE__} can be used legitimately inside an eval, and has been many times before, I'm not going to break that because it will make using Try::Tiny a lot more difficult for me and many others. There are other try/catch modules on the CPAN that meddle in this sort of idealism, and I think that's just not workable. Code that used to run in global contexts may be later reused inside an eval, thus causing behavior changes that are not directly evident. You're also assuming that running -MDevel::NoGLobalSig=die would do full path coverage, which is most definitely not guaranteed, and precisely the combinatorial issue when bits of code that are not lexically adjacent interact with one another. I don't want another UNIVERSAL::isa on my hands, especially since Try::Tiny was not a tounge in cheek acme module to begin with.
I think it's been decided that this isn't going to happen.
Sorry, I let this drop on the floor. Allow me to please make a rebuttal before the issue is closed. On Fri Feb 05 01:41:11 2010, NUFFIN wrote: Show quoted text
> try might be different from eval in the local semantics, but all of > the differences are apparent in the lexical scope of usage.
I understand the distinction, but this is only relevant if you're converting from eval to try. If you're writing new code with try then it is perpetuating a known trap forward to support a handful of edge cases. Even if you are converting, the overwhelming majority of uses of eval and try will break if $SIG{__DIE__} is set. Find me one which will work and I'll find you twenty which will not. I am serious. Preserving this behavior just to make the differences between eval and try lexical does not make sense. The global behavior $SIG{__DIE__} with respect to eval is clearly dangerous and broken and generates bugs. Show quoted text
> Secondly, $SIG{__DIE__} can be used legitimately inside an eval, and > has been many times before, I'm not going to break that because it > will make using Try::Tiny a lot more difficult for me and many others.
Coded correctly, $SIG{__DIE__} can still be set inside an eval and remain in effect outside of it. I've dealt with exactly this problem before. You can see the code inside base.pm. To make it more explicit... try { $SIG{__DIE__} = sub { ... }; }; I intend that will continue to work in that $SIG{__DIE__} remains set outside of try. The use case is this: try { require Module::Which::Sets::Sig::Die; }; Show quoted text
> Code that used to run in global contexts may be later reused inside an > eval, thus causing behavior changes that are not directly evident.
As mentioned earlier in the ticket, blindly copying and pasting code into try will already not work. The changes to return and $_ have a far more practical effect, yet they were considered ok. Show quoted text
> You're also assuming that running -MDevel::NoGLobalSig=die would do > full path coverage, which is most definitely not guaranteed, and > precisely the combinatorial issue when bits of code that are not > lexically adjacent interact with one another.
Sorry, I don't know what that means. Show quoted text
> I don't want another UNIVERSAL::isa on my hands, especially since > Try::Tiny was not a tounge in cheek acme module to begin with.
The comparison to UNIVERSAL::isa is inverted. try() can only effect code which uses try(). $SIG{__DIE__} is UNIVERSAL::isa. Used without care, it effects all code and causes problems outside of its scope. Try::Tiny already has the UNIVERSAL::isa problem, it was inherited from eval. This is an opportunity to fix it.
Subject: Re: [rt.cpan.org #54050] localize and unset $SIG{__DIE__}
Date: Fri, 29 Apr 2011 20:22:10 +0300
To: bug-Try-Tiny [...] rt.cpan.org
From: Yuval Kogman <nuffin [...] cpan.org>
On 28 April 2011 17:09, Michael G Schwern via RT <bug-Try-Tiny@rt.cpan.org>wrote: Show quoted text
> > I understand the distinction, but this is only relevant if you're > converting from eval to try. If you're writing new code with try then > it is perpetuating a known trap forward to support a handful of edge cases.
This also applies when learning to use try vs. eval. Again, if the differences are all accounted for in the lexical scope, the barrier of entry for refactoring as well as learning a new tool is lowered. Show quoted text
> As mentioned earlier in the ticket, blindly copying and pasting code > into try will already not work. The changes to return and $_ have a far > more practical effect, yet they were considered ok.
Again, there's no action at a distance. Show quoted text
> > You're also assuming that running -MDevel::NoGLobalSig=die would do
> > full path coverage, which is most definitely not guaranteed, and > > precisely the combinatorial issue when bits of code that are not > > lexically adjacent interact with one another.
> > Sorry, I don't know what that means. >
Suppose a module that warns is implemented. This doesn't help unless there is decent code coverage too, and since this change is dynamically scoped in nature then this means pretty good path coverage, not just unit testing. Show quoted text
> The comparison to UNIVERSAL::isa is inverted. try() can only effect > code which uses try(). $SIG{__DIE__} is UNIVERSAL::isa. Used without > care, it effects all code and causes problems outside of its scope. > > Try::Tiny already has the UNIVERSAL::isa problem, it was inherited from > eval. This is an opportunity to fix it. >
I meant UNIVERSAL::isa the module. I never regretted writing anything more than that. There are just too many opinions and not enough time =(
Subject: Re: [rt.cpan.org #54050] localize and unset $SIG{__DIE__}
Date: Tue, 10 May 2011 12:11:15 +1000
To: bug-Try-Tiny [...] rt.cpan.org
From: Michael G Schwern <schwern [...] pobox.com>
(I am, as well, talking about the module UNIVERSAL::isa) I'm confused because your argument has very strange priorities. I'll assume I'm just misunderstanding and reiterate what I'm hearing back to you. 1) The important thing is to help users transition from eval to try. 2) Someone might have written an eval expecting $SIG{__DIE__} to apply. 3) Someone converting the eval (maybe the person who wrote it, but probably not) might not realize that there is a $SIG{__DIE__} in effect and it is necessary for the correct operation of this eval. 4) The existence of a $SIG{__DIE__} is not obvious just from looking at the eval block requiring a full audit of the code or a very good test suite. 5) Therefore, making try immune to $SIG{__DIE__} makes #1 harder. 6) Also by removing $SIG{__DIE__}'s action at a distance upon try this would somehow constitute action at a distance. Which is all logically consistent (except #6 which is fairly Orwellian)... but assumption #2 is so unlikely as to be an absurd basis upon which to make a decision. Even if it is true, it's actively dangerous. The code one programmer writes might be expecting a $SIG{__DIE__} to be in effect and work with it, but all the rest of the code in the process, including CPAN modules, will not. Then something loads CGI::Carp, silently blows away your $SIG{__DIE__} and whatever eval/try logic that depended on $SIG{__DIE__} fails. That is, the argument is saying that Try::Tiny should support a use case that's highly unlikely, extremely fragile, a documented design bug in Perl and should be removed when its encountered. Worse, it should support it to the determent of the extremely common trap of an eval/try *not* expecting there to be a $SIG{__DIE__}. For every bit of code out there that would break if it didn't honor $SIG{__DIE__} I am not exaggerating when I say there are 20, 100 or even 1000 evals that should not trigger a $SIG{__DIE__} but fail to guard against it. Do I have it right? I hope not. I'm going to complete the argument against it, but I hope I'm not sock puppetting you in doing so. While I agree that the $SIG{__DIE__} problem is non-lexical, it's existence is highly unlikely. Proposing it as a real barrier to adoption is absurd when compared to the existing changes in behavior of return, $! and $@ ($! and $@ also being non-lexical) which are much higher and far more frequent and realistic barriers. Worrying about $SIG{__DIE__} is like shaving an ounce off a backpack while keeping the cast iron skillet. It just doesn't make a difference. Furthermore, if your code as a $SIG{__DIE__} which is expecting to dip into eval, it is far more likely that programmers will not realize it exists and write evals without $SIG{__DIE__} protection generating bugs. Therefore, any system designed assuming $SIG{__DIE__} will be dipping into eval should either already have been redesigned or will continue to generate bugs. In which case any bugs caused by transitioning from eval to try are simply the latest in a long string of bugs caused by the design. $SIG{__DIE__} is dangerous in production. The only sane uses for $SIG{__DIE__} is as a global "catch" block or as a debugging and analysis tool. As to the former, everything in try is already caught (or deliberately ignored). As to the latter, this is not critical production functionality that Try::Tiny needs to prioritize. Finally, the UNIVERSAL::isa analogy. One module loading UNIVERSAL::isa affected code in every other module (I don't believe it does anymore... or at least it tries not to). This is like one module defining a $SIG{__DIE__} and inadvertently affecting every eval and try in every other module. That is action at a distance. I'm not sure how try ignoring $SIG{__DIE__} is analogous to UNIVERSAL::isa. On 2011.4.30 3:22 AM, Yuval Kogman via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=54050 > > > On 28 April 2011 17:09, Michael G Schwern via RT > <bug-Try-Tiny@rt.cpan.org>wrote:
>> >> I understand the distinction, but this is only relevant if you're >> converting from eval to try. If you're writing new code with try then >> it is perpetuating a known trap forward to support a handful of edge cases.
> > > This also applies when learning to use try vs. eval. Again, if the > differences are all accounted for in the lexical scope, the barrier of entry > for refactoring as well as learning a new tool is lowered. > >
>> As mentioned earlier in the ticket, blindly copying and pasting code >> into try will already not work. The changes to return and $_ have a far >> more practical effect, yet they were considered ok.
> > > Again, there's no action at a distance. > >
>> > You're also assuming that running -MDevel::NoGLobalSig=die would do
>>> full path coverage, which is most definitely not guaranteed, and >>> precisely the combinatorial issue when bits of code that are not >>> lexically adjacent interact with one another.
>> >> Sorry, I don't know what that means. >>
> > Suppose a module that warns is implemented. This doesn't help unless there > is decent code coverage too, and since this change is dynamically scoped in > nature then this means pretty good path coverage, not just unit testing. > >
>> The comparison to UNIVERSAL::isa is inverted. try() can only effect >> code which uses try(). $SIG{__DIE__} is UNIVERSAL::isa. Used without >> care, it effects all code and causes problems outside of its scope. >> >> Try::Tiny already has the UNIVERSAL::isa problem, it was inherited from >> eval. This is an opportunity to fix it. >>
> > I meant UNIVERSAL::isa the module. > > I never regretted writing anything more than that. > > There are just too many opinions and not enough time =(
-- 39. Not allowed to ask for the day off due to religious purposes, on the basis that the world is going to end, more than once. -- The 213 Things Skippy Is No Longer Allowed To Do In The U.S. Army http://skippyslist.com/list/
This ticket also is shaping up to be a "wontfix". I agree with Yuval that while purists consider $SIG{__DIE__} invoked under $^S an error, it is too ingrained on cpan and darkpan and is there to stay. As such the requested change will in fact break things, instead of fixing them. Somebody with rights please reject this ticket.
I think it's been decided (again) that this isn't going to happen.