This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [3.3] Followup to C++ forced unwinding


On Wed, Apr 30, 2003 at 07:50:39PM -0700, Ulrich Drepper wrote:
> Richard Henderson wrote:
> > On Wed, Apr 30, 2003 at 10:24:24PM -0400, Jason Merrill wrote:
> > 
> >>Actually, I have an idea for how to DTRT here: give terminate() as
> >>the destructor for the exception object in the forced unwind case.
> >>So if the catch(...) block rethrows, all is good, but if we exit the
> >>catch block some other way, we try to clean up the exception, which
> >>calls terminate.
> > 
> > Oh, cool.  This is a *much* nicer idea than I was contemplating.
> > Yes, I can agree to this.  Uli, does this satisfy?
> 
> This means the object which is passed to the catch() code has a
> destructor which is if necessary executed and can be prevented to be
> called by explicitly rethrowing?  This sounds excellent.

This alternative is almost tolerable.  I will explain below.  

First, though, we should run the choices by the ISO C++ committee 
and the Boost.org list.  The latter, particularly, have done a long 
and deep analysis of C++ thread-library semantics, and have been 
distributing for years a much-admired C++ binding to POSIX threads.  
Large systems have been constructed using this library, leaving both 
a wealth of experience and a huge mass of existing good code.

The Boost thread library implements delayed cancellation using a 
distinguished exception, and a "cancelled" flag that is checked at 
(many) cancellation points.  (The Boost implementation is unable to 
instrument many of the Posix cancellation points, such as read() 
and write(), so its threads don't necessarily die as early as they 
should.)  User code routinely catches this exception and re-throws it.  
Sometimes, though, the exception is caught and discarded, without
grave consequences.

If a cancellation exception is caught and discarded, then at the next
cancellation point another is thrown.  This has turned out to be 
entirely practical.  Since the "change of state" that Ulrich speaks 
of amounts to a boolean flag being set, to be checked at cancellation 
points, continuing out of a catch block has no more dire consequence 
than delaying the death of the thread (which executing the destructors 
does anyhow).  Since destructors may execute arbitrary code, to say 
that the thread state is somehow incapable of executing the code after 
the catch block seems to call for more support than a bald assertion, 
particularly given the Boost experience.

Because the Boost.org thread library is almost a shoo-in candidate for
adoption as part of the next Standard C++ Library, experience with it, 
and its designers and implementers, must not be taken lightly.  It 
would be a grave error to implement runtime semantics fundamentally 
incompatible with the Boost C++ threads library cancellation policy.

Now, about the consequences of registering terminate() as the 
destructor for the cancellation object...  First, the essential
principle to observe in analyzing choices is that code compiled for 
use by a thread has to work the same way in a non-threaded program, 
such as a unit-test harness.  Nobody can afford to maintain libraries 
that must behave differently when called by a thread.  Few can afford 
to compile libraries differently when they might be linked with a 
threaded program.  

Many of the requirements observed by exception-safe code are 
fortuitously very similar to those for thread-safe code, so it is 
common practice to write libraries that are both exception-safe and 
thread-safe.  Since exception-handling code is usually the least
well-exercised part of any system, it is critical to keep it simple 
and transparent.  Accommodating two different behaviors eliminates 
transparency.

What makes the proposal almost tolerable is that it is considered 
best practice under almost all circumstances to rethrow unidentified 
exceptions, and the overwhelming majority of existing code does so.
Code that doesn't, though, tends to be that way for critically 
important reasons which we would be foolish to second-guess.  One
such reason is that it is meant to be called from a language that
cannot handle exceptions.

What would probably be actually tolerable would be to copy some similar 
machinery from the existing C++ runtime, such as the unexpected().
(See ISO 14882, p345: section 18.6.2.2, [lib.unexpected.handler].)

Instead of the cancellation object's destructor registering terminate() 
itself as the destructor, it would register another function which, 
by default, calls terminate().  A program may call a function 
"set_cancellation_destructor(void (*)());" and pass it a function 
pointer to be called instead of terminate(), normally an empty 
function.  In that case discarding the cancellation would be like 
discarding any other exception, and it is up to the author of the 
code to ensure that the thread dies on schedule.

For stack-unwinding code, it suffices to register that runtime function, 
perhaps "cancellation_destructor()", as the destructor for the exception 
object.  For naive users, it calls terminate(), minimizing debugging 
surprises.  Sophisticated users with special needs may instrument it 
as needed, and will not be surprised at the results.  Library writers 
need not do anything special.

Nathan Myers
ncm@cantrip.org


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]