Actions

icon Post
text/html Subscribe
text/html Unsubscribe

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [cxx-abi-dev] gcc unwind ABI change for forced unwind


  • To: Cary Coutant <cary@xxxxxxxxxx>
  • Subject: Re: [cxx-abi-dev] gcc unwind ABI change for forced unwind
  • From: Dave Butenhof <David.Butenhof@xxxxxx>
  • Date: Fri, 23 May 2003 10:36:35 -0400

Cary Coutant wrote:

Personally, I dislike the attempt to separate "cleanup" from "finalization" (handle/catch). I don't like the idea of an exception that can't be finalized, because it reduces the application's ability to control behavior.

I didn't mean to imply that I liked this either, but it seemed a useful compromise given the unlikelihood that C++ will ever add try/finally.

Having just jumped into this particular discussion, I really didn't mean to imply that I thought anybody was implying anything. ;-)

Whether or not C++ ought to add finally is an interesting question on its own, though probably not relevant here. It'd be nice, in a way, because it more clearly specifies INTENT than the current catch(...) {throw;} syntax, and that's always nice. On the other hand, C++ cleanup is supposed to be done in destructors, whereas try/catch ought to be for finalization. (And I realize I'm trapping myself in terminology here, since the traditional "finally" keyword sounds like it ought to be associated with the generic term "finalization" whereas it's really non-finalizing cleanup. Oh well. Anyone who can handle signal and signal should be able to handle finalization and finally. ;-) )

And, on yet another hand, if C++ really didn't want anyone to be able to do cleanup in try/catch, the [re]throw syntax shouldn't exist. That it exists means C++ does explicitly and specifically support cleanup in try/catch, just with ugly syntax that obscures the intent.

I'm still not entirely comfortable with the idea of thread cancellation (and other forced unwinds) being a normal (catchable) C++ exception, but if consensus has been reached outside this list, and those on this list generally agree, I don't want to drag the discussion out any more.

I always tell people not to finalize a cancel or exit. I figure if they really know what they're doing, they'll ignore me; and if they're not sufficiently confident in their goal and implementation to ignore me, they shouldn't be playing with that stuff anyway. ;-)

So if the ABI wants to specify that there's something special about "forced unwind" exceptions, I won't really be too inclined to wander around mumbling about how it got all screwed up. Still, there ARE applications where the ability to take full control over unwind is important, and I don't like the idea of locking them out. As I said, one compromise would be to let forced unwind exceptions (individually and, ideally, also as a [super]class) be caught only BY NAME. Naive coders won't accidentally be finalizing someone else's cancel or longjmp_unwind, but someone who really needs to do it will be able to.

I don't think there's any real evidence of a broad industry consensus for this. POSIX doesn't have exceptions, and cleanup handlers are limited and inflexible. While I think I got everyone in the Austin IA64 ABI work to pretty much agree in principle that cancel and exit ought to be exceptions, we never got into this level of detail so I can't guess what the reaction would have been.

I'm speaking mostly out of long experience with the OpenVMS condition handling facility and Tru64 UNIX libexc (which being originally based on code from MIPS CO ought to have some analogy somewhere else in the industry... perhaps IRIX?) They're basic calling standard and runtime library packages to support general stack unwind and handler searches. They're fundamentally DETACHED handlers (associating a handler routine with a procedure descriptor or stack frame rather than dispatching to code within the procedure), which are called without unwinding the stack... but of course can also be used to UNWIND to ATTACHED handlers as in our C "SEH" extension, and C++ or Ada catch clauses.

Both are fundamentally 2-phase exception mechanisms; a "virtual unwind" scan is made to find active exception handlers in the call stack, and the detached handlers are called to determine what should be done. The scan may eventually terminate with no handlers expressing interest, in which case the process is generally terminated with an unhandled exception. Or a handler may tell the unwinder to RESUME execution at the point of the exception (it hasn't been unwound at that point). Or some handler may choose to UNWIND to some point. That begins the second phase, as the stack is unwound frame by frame, and each handler (where any exists) is called again with special exception context indicating that the unwind is in progress. A semi-formalized (but unenforced and usually ignored) convention on OpenVMS is that cleanup should occur only on unwind. In principle, this gives the advantage that when a process dies with an unhandled exception the "core file" represents what happened up to the point of the exception -- which can be a big help in diagnosing the problem. Once cleanup (including unwind) has happened, the original problem is obscured.

Of course the C++ runtime need not provide the ability to continue from exceptions (most languages don't), or to run application code on the search phase (normally the runtime simply determines whether to start an unwind to call the application's handlers in the unwind phase). But in the context of an ABI discussion, I'd like to define a model for a "common exception library" (CEL) that's flexible enough to allow all that.

What changes are needed in the C++ ABI document? Do we need to add anything in the base (C) ABI to cover thread cancellation and exit so that cleanups can be properly interleaved?

I'd like to see a full exception library ABI that supports enough flexibility to do all this, and to experiment and build other languages with different semantics.

I'd like to see some form of exception syntax in the C language, whether it's a full try/catch/finally, a C++-style try/catch/[re]throw, or a basic enabling syntax like the MS SEH syntax that our compiler borrowed (try/except, where the except clause just provides an expression, usually a routine call, to be evaluated to determine whether to continue the search or resume execution; or of course that routine can call 'unwind' to begin the unwind phase). The pthread_cleanup_push() and pthread_cleanup_pop() macros can easily be written in terms of any of the alternatives. The important point is simply that C source code has some way to tap into the common exception library's unwind and handler mechanisms.

Like this C "SEH" extension, C++ destructors and try/catch, Ada, Java, and so forth ought also to be built on top of the CEL. The CEL will then simply see a chain of call frames, some of which have handler routines -- it doesn't need to know the language of any frame, so it doesn't matter how they're interleaved.

Cancel and thread exit are just exceptions. "By convention", nobody should do anything to these on the search phase, and should do only cleanup on the unwind phase. But the CEL wouldn't care, and everything will work consistently if someone does. Each language's handler will need to decide how to handle various exceptions though. The C++ handler could simply ignore some or all of these on search phase, and run only object destructors on unwind phase. The C runtime's 'except' clause exception handler might decide not to even evaluate the 'except' expression, and so forth. However, I would strongly prefer that there be some way to finalize at least cancel and exit in both C and C++. It allows for a modularization of cancel, where cancel really means "cancel this function" without terminating the thread, which might be a generic serial server thread fully capable of continuing to service other functions.

I'm not at all sure whether longjmp_unwind is a separate case. I just checked, and on Tru64 longjmp_unwind (exc_longjmp) skips the search phase (and therefore normal frame-based capture/continue/search decisions) entirely. I guess that makes sense, since it starts out with a target frame and knows the intent is to unwind. So it just calls exc_unwind(). However, frame handlers will still be called for UNWIND disposition, with the exception name EXC_LONGJMP_UNWIND, so the handlers can do cleanup. On OpenVMS, I believe that an unwind phase handler could actually raise a new exception or start a nested unwind -- I'm not convinced that the Tru64 package supports that much flexibility; the code is convoluted and I've never had occasion to experiment.

Anyway, this seems like a clear basis for distinction. Forced unwind only does the unwind phase, not search phase, and therefore cannot generally be finalized by normal language facilities. If C++ catch(...) triggers only on search phase, it won't even see these. Destructors would fire on unwind phase, and would run "transparently" on a forced unwind. If desired, the runtime could make a concession for forced unwind exceptions to run catch(...) {throw;} blocks, for example, or anything else determined to be a "finally" type construct, even though we skipped the search phase. (Though I'd be inclined to argue against it, I think.)

Armed with all this terminology and context, one could argue that cancel and exit, like longjmp, should skip the search phase entirely, making themselves immune to finalization. As I've said, I don't like this -- but it does avoid the risk of fools breaking the application with unprotected catch(...) blocks. If the runtime's handler routine made special cases to detect specific exception names during unwind phase, it could make 'catch (cancel)', or 'catch(longjmp)', or even 'catch(all_forced_unwinds)' fire in the unwind phase to handle applications that really want to finalize.

--
/--------------------[ David.Butenhof@xxxxxx ]--------------------\
| Hewlett-Packard Company       Tru64 UNIX & VMS Thread Architect |
|     My book: http://www.awl.com/cseng/titles/0-201-63392-2/     |
\----[ http://homepage.mac.com/dbutenhof/Threads/Threads.html ]---/