Sep 13, 1995, 3:36 PM
Post #46 of 48
(4819 views)
Permalink
On Wed, 13 Sep 1995, Charles Bailey wrote:
> Ah. Sorry; I was imprecise in my original statement. As far as I can see,
> $SIG{__DIE__} is the only mechanism extant in Perl from which one could build
> an exception handling mechenism. Perl would have to be modified to allow
> execution to resume if a handler returned an "OK" status, and we'd need to sort
> out actions for "nested" exception chains, but the basic principle is there.
>
> If one is willing to build an exception handling mechanism into Perl de novo,
> then yes, there are several approaches one might take, one of which you've
> described below. To my untrained eye, it looks like mechanisms I've seen in
> some object-ish C implementations and in POSIX threads, but I haven't used
> either enough to really know their ins and outs.
But a closure based exception system doesn't actually require any
_changes_ to the Perl core, merely a module of code. So yes, it has to be
built from scratch, but no, there isn't any particular barrier to doing
so. Since the handlers are executing in the scope of the thrown execption,
returning (resuming) where the exception happened is no problem. And then
normal mechanisms, either die() or return(), can be used to pop stack
frames if a handler needs to end up outside the calling code.
> Sounds basically fine, except that I wouldn't advocate executing the "finally"
> block when handling an exception.
That's one of the things I'm a little fuzzy on. ;-)
> I think that an exception shouldn't of
> itself do anything which would alter the current thread of normal execution, so
> that if some handler dismisses the exception, normal execution can resume from
> the statement after the C<throw>, with the same state as before the execution
> (aside modifications made by the handlers). This isn't to say that the
> handlers don't execute in their own scope, and may not be able to see lexicals
> in the caller; only that the process of throwing the exception shouldn't close
> out the calling scope.
Agreed. I think "finally" actions should be taken when the callers stack
frame is discarded, either via the catch block ending without error or by
an unhandled exception. If you don't have "finally" actions then you end
up doing the same clean-up over in all your exception handlers, and that's
a pain. But having "finally" step on the handler's toes isn't much fun
either.
> (BTW, I'd say that handlers ought to execute in a scope
> internal to the caller for purposes of symbol lookup -- they should see the
> caller's C<local> variables -- but outside for purposes of handling nested
> exceptions -- if a handler throws an exception, it should be passed first to
> any handlers the handler established itself, then to handlers established by
> the handler's caller's caller, and so on out. (Try that for a
> tongue-twister. :-)) The point here is that if the handler throws an
> exception, it shouldn't reinvoke itself.
Thank you for that... I'll get back to you if I ever understand it. :-)
Seriously, I'm not at all sure nested exceptions are needed. Merely being
able to rethrow the original exception, or throw a new exception and have
it continue out of the chain (as if it were originally an unhandled
exception) seems sufficnt. Then again, if this whole thing is done with
closures, fully recursive exception handling might come automatically.
As for the scope of handlers: if they are closures, then they'll see two
scopes, I guess. The scope they were created in, overlayed with any
recent local()'ized changes to that scope. So actually, it looks like not
much of the scope of the catch {} block would be available. So standard
de-allocation handling would probably need to look like this:
$ptr = allocate MassiveObject;
catch sub {
}, finally => sub {
deallocate MassiveObject $ptr;
};
> Ideally, I'd want a handler to be able to cause one of four things to happen:
> - exception is dismissed; execution resumes in the normal thread at the
> statement just after the one which threw the exception
Via a "resume" call. Which probably will trigger a "last", or something.
> - exception is dismissed; call stack is unwound by a specified number
> of frames, and execution is resumed at the statement just after the
> last call that was unwound
This is ideally the behaviour you get when you fall all the end of an
exception handler. You get taken out of the handler and the related catch
{}. I'd also say that a "finally" handler, if one is given, will get
execute somewhere in the middle of this.
> - exception is passed out to the next handler
A rethrow. Probably implemented with a "next", or other magic. A question:
if the next (next innermost, actually) handler "resumes", can it get back
to the code that originally threw the exception? If so, we can't unwind
the call stack (and execute our "finally" blocks) until we know for
certain that that isn't possible. This probably means that a handler might
get some lexical variables it wasn't expecting, if it's called from far
enough "up" the handler chain.
> - a new exception is thrown, which is handled as a new, "nested" event
> If it is eventually dismissed, handling of the current exception
> is resumed, as though it were the normal execution path, and
> so on . . .
Or perhaps this is a rethrow? I've never dealt with a language that
supports resumption of exceptions, so I'm sure I'm missing some
subtleties of implementation here.
But I suppose a handler could include a catch block, complete with it's
own exception handlers, that could, if it contained an unhandled execption
"leak" out to the original exception chain.
> Regards,
> Charles Bailey bailey@genetics.upenn.edu
--
Kenneth Albanowski (kjahds@kjahds.com, CIS: 70705,126)