This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: [3.3] Followup to C++ forced unwinding
- From: Nathan Myers <ncm at cantrip dot org>
- To: Ulrich Drepper <drepper at redhat dot com>
- Cc: Richard Henderson <rth at redhat dot com>,Jason Merrill <jason at redhat dot com>,Mark Mitchell <mark at codesourcery dot com>, gcc-patches at gcc dot gnu dot org,dave at boost-consulting dot com, wekempf at cox dot net
- Date: Wed, 30 Apr 2003 21:52:19 -0700
- Subject: Re: [3.3] Followup to C++ forced unwinding
- References: <20030430175335.GA18958@twiddle.net> <1051727981.3301.100.camel@minax.codesourcery.com> <20030430210342.GB697@redhat.com> <1051743305.3301.248.camel@minax.codesourcery.com> <wvl4r4finq6.fsf@prospero.boston.redhat.com> <1051748960.3301.295.camel@minax.codesourcery.com> <wvlznm7h09z.fsf@prospero.boston.redhat.com> <20030501024059.GA697@redhat.com> <3EB08B7F.2060206@redhat.com>
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