Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Throw from within a DESTROY block

by ribasushi (Monk)
on Sep 06, 2011 at 23:41 UTC ( #924488=perlquestion: print w/ replies, xml ) Need Help??
ribasushi has asked for the wisdom of the Perl Monks concerning the following question:

EDIT
The problem was solved

NOTE
The code below is a simple concise example of a behavior I want to achieve. It is by no means intended to describe my actual use case. Suffice to say that this particular wart of the perl language is annoying enough for me to wanting to devise a generic way of dealing with it. This site has surprised me before, no reason it can't offer something I never thought of here as well :)

The question is very very simple. How do I make this work:

perl -Mwarnings -Mstrict -E ' sub DESTROY { say "invoked destructor"; die "aieeee" } say "creating object"; { bless( {} ) } say "object destroyed (should not reach here): $@" '

In case it isn't clear - I want to make it so that the "should not reach here" line is never, well reached. In other words - have an "armed" scope guard object throw a real exception. Note: I want an exception, i.e. something trappable. Hence the exit() trick does not apply, since it will kill the entire process.

Cheers

Comment on Throw from within a DESTROY block
Download Code
Re: Throw from within a DESTROY block
by ikegami (Pope) on Sep 07, 2011 at 00:03 UTC

    Move the die out of the destructor or invoke the destructor explicitly.

    Note that the corruption of $@ has been fixed in 5.14. It's now blank in the last statement.

      I was going to mention the possibility of using a $SIG{__DIE__} that calls exit, but it doesn't work. The signal is triggered before the object is destroyed, so a second attempt to destroy the object occurs during global destruction.
        This can be trivially fixed by closing over an $already_fired flag of some sort to neutralize the DESTROY for a specific object. However I am not interested in an exit() - I want a real trappable exception that will happen during runtime calls to DESTROY (I am not interested in the global destruction case, and can always work around it)
      If I move the die() out of the DESTROY, this becomes a non-question don't you think? :) The whole point is to figure out why perl thinks we are cleaning up (even though we are in the middle of runtime), and how to convince it otherwise.

        If I move the die() out of the DESTROY, this becomes a non-question don't you think?

        If you don't, then your question is a non-question. ("How do you make Perl not act like Perl?")

        The whole point is to figure out why perl thinks we are cleaning up (even though we are in the middle of runtime),

        It is cleaning up an object, and you're wrong in thinking Perl think it's in the global destruction phase.

        $ perl -wE' DESTROY { die "foo" } { bless({}) } ' (in cleanup) foo at -e line 2. $ perl -wE' DESTROY { die "foo" } our $o = bless({}); ' (in cleanup) foo at -e line 2 during global destruction.
Re: Throw from within a DESTROY block (bug)
by tye (Cardinal) on Sep 07, 2011 at 02:16 UTC

    Handling throwing from a destructor is "hard". It is very often not done well by an implementing language. This is true for Perl.

    What should happen when you die (even indirectly) inside of a DESTROY method is that Perl should catch (and store) the exception and then continue the tear-down of the current scope.

    If the current scope was being torn down due to the throwing of a prior exception, then the original exception should continue its progression up to where it will be caught (or just emitted). The during-DESTROY exception(s) should be made available in some way (likely just logged to STDERR but also making them available in something like @@ would be a nice touch -- though perhaps it may be enough or even better to just ensure $SIG{__DIE__} can handle them and also be able to tell them apart from non-DESTROY exceptions).

    If the current scope was being torn down because it was exited normally, then once that is done, Perl should simply (re)throw the (first) during-DESTROY exception.

    But because properly handling exceptions thrown during scope tear-down is tricky work (not just the details of implementing it well but also the possibility of an unbounded number of exceptions being thrown in the context of a single 'catch' [eval] leading to interesting design conflicts), some people even start thinking that the entire concept of throwing an exception from a destructor is somehow nonsensical and shouldn't be tolerated.

    But the wonderful value of DESTROY is that every c'tor (constructor) that succeeds will always be followed by its paired d'tor, which makes it a very valuable tool for the sane handling of tons of things (all manner of resource allocation but also other things that should always be done in pairs, like transactions, reference counting, etc.). And handling tons of things means that you need to be able to deal with that handling going wrong or failing.

    Throwing from a d'tor is an important feature and the small extra effort should be invested to support it properly.

    Perl used to store during-DESTROY exceptions in $@. But that was a bad idea. Unfortunately, the "fix" chosen for this may be an even worse idea: just completely ignoring during-DESTROY exceptions? I don't have 5.14 handy anywhere so I can't test exactly what it does.

    Please file a bug (I'd do it but my experience is that me arguing for something to p5p makes it less likely to happen).

    - tye        

      just completely ignoring during-DESTROY exceptions? I don't have 5.14 handy anywhere so I can't test exactly what it does.

      They are output to STDERR like before.

      $ perl -Mwarnings -Mstrict -E ' sub DESTROY { say "invoked destructor"; die "aieeee" } say "creating object"; { bless( {} ) } say "object destroyed (should not reach here): $@" ' creating object invoked destructor (in cleanup) aieeee at -e line 2. object destroyed (should not reach here):

      Like before, that only happens if warnings are on, so it can be completely silent!!

      IIRC, more work is planned, but I haven't heard anything in a while.

        Thanks for the links.

        That hints that the "work to come" that ikegami alluded to is possibly the providing of a proper "we are in the middle of throwing an exception" indicator for use by Perl code.

        So it seems well worth filing a bug here.

        Note that the work to fix this quite well can be quite small, I believe. Perl is already putting implicit 'eval' blocks around each call to a d'tor so the nightmares of lots of C++ implementations aren't the types of problems we are fighting here.

        If we simply change the spot that prepends "(in cleanup)" so that it acts "just normal" if we aren't already unrolling the stack due to a prior exception, then we are in quite good shape.

        [Even better would be to also make the "(in cleanup)" exception be logged to STDERR even if warnings aren't enabled (a "mandatory warning" or what perldiag calls "severe warning"). Then you can silence such by using a __DIE__ handler.]

        Accomplishing the lion's share of the fix does involve tracking "are we unrolling?", but you don't have to do all of the hard work of making this available to Perl code (picking a variable name and getting everybody to like the name). You just need a single stinkin' "global" C variable. Actually, I'd just define a new bit for EVAL_*, something like EVAL_UNWINDING.

        The names G_KEEPERR and EVAL_KEEPERR suck. It would be better to name these more like G_DESTROY and EVAL_DESTROY, documenting both what they are supposed to mean and when they supposed to be used, not what behavior that meaning currently leads to.

        Then the code that (in 5.14) sets ERRSV ($@) early would also set EVAL_UNWINDING. The code that prepends "in cleanup" and just warns, would only do that if EVAL_UNWINDING was also already set. If EVAL_UNWINDING was not already set, then it would set ERRSV and also set EVAL_UNWINDING.

        Then a later enhancement can be done to define ${^THROWN} or whatever and change the early setting of ERRSV to instead set that new (read-only) Perl global (and instead of checking for EVAL_UNWINDING you just check for global being set and we can get rid of EVAL_UNWINDING).

        - tye        

      Please see http://www.perlmonks.org/?node_id=945741 for an icky, but quite workable solution.

      Cheers!

Re: Throw from within a DESTROY block
by ikegami (Pope) on Sep 07, 2011 at 05:25 UTC

    It is by no means intended to describe my actual use case.

    So when can we expect the rest of your question? We can't provide a workaround for something we know nothing about.

      There is no rest. The question is:

      How do I massage perl (with a trick, with XS, whatever) to throw a real (as in eval-trappable) exception from within some DESTROY of some object being destroyed.

        Using die. The catch is that Perl's calls DESTROY inside of an eval (effectively) so it doesn't go far.

        If you want to throw something catchable outside of DESTROY, it'll have to be thrown from outside of DESTROY.

Re: Throw from within a DESTROY block
by Anonymous Monk on Sep 07, 2011 at 20:59 UTC
    Change your design. Destructors are not-quite safe places to be, and you are taking wild chances by trying to throw an exception inside of one. Instead, in whatever routine in your program may be responsible for destroying an object, call a "BeforeDestroy" method before actually destroying it. Put your exceptions and other monkey-business there.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://924488]
Approved by davido
Front-paged by tye
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (3)
As of 2014-09-20 21:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (163 votes), past polls