This is the mail archive of the
mailing list for the GCC project.
Re: [3.3] Followup to C++ forced unwinding
On Thu, May 01, 2003 at 09:17:22AM -0500, William E. Kempf wrote:
> Nathan Myers said:
> > 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.
> > This alternative is almost tolerable. I will explain below.
> > [...]
> > 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.
> I only partially agree with this. You often can't program to this
> ideal, if for no other reason than because thread synchronization
> requires significant overhead. So many libraries do, in fact, get
> implemented to compile differently when they might be linked with a
> threaded program. In fact, many C RTLs supplied by vendors have
> threaded and non-threaded variants.
The paragraph quoted was an argument that library maintainers cannot
be expected to manage libraries in which catch(...) clauses are skipped
While it is still common for C libraries to have threaded and non-
threaded variants, those variants tend to differ only in whether locking
and unlocking operations are no-ops, rather than in the semantics of
their basic control-flow constructs. (Even so they are a serious
nuisance.) Imagine if you could not even test the code without running
both a threaded and an unthreaded program, because the control flow
paths would be different.
> > 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 188.8.131.52, [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.
> On what schedule? Cooperative cancellation is a request, not a
> demand. Ignoring the mechanism in which cancellation is carried out,
> with the POSIX cooperative cancellation mechanism it is possible for a
> thread to be "cancelled" but still never terminate. For instance, if
> it never calls a cancellation point, or turns off cancellation
> detection before it does, the thread can continue on indefinately,
> even though a request to be cancelled has occurred. So, the only
> "schedule" is the explicit schedule set by the thread being cancelled
> (not the thread making the request).
There are three parties involved:
- the thread being cancelled,
- the complete program, the source of the cancellation,
- the library which has state in the thread's execution context.
"Schedule" refers to the complete program's expectation that the
thread will die, and soon enough, whatever that turns out to mean.
If it doesn't, the program has failed. The library author cannot be
presumed to know anything about the goals of the complete program's
author. A library can only be coded according to reasonable
best-practice rules which the program author must verify are compatible
with its use in the complete program.
We have to make sure the rules are clear enough and simple enough
that it's practical to meet the needs of both library authors and
> Terminating (and I assume when you say terminate() you aren't talking
> about a thread_terminate() that would abort only the thread) in this
> case seems like a sledge hammer being used to enforce a very strict
> exception handling mechanism. I wouldn't have problems with an
> individual program electing to do this, but the library should not do
> this (at least by default, even if I can turn it off). Forcing a
> re-throw would be a better idea... but even that was rejected during
> the discussion on the Boost list, for the reasons you gave above. IOW,
> it's sometimes valid/necessary to catch without re-throwing, and isn't
> a violation of the cooperative cancellation mechanism, so long as the
> cancelled state remains true.
The use of termination (i.e. the call to std::terminate()) here is
analogous to its use when exception handling has got too bollixed up
to continue, such as when it is called by unexpected() (See 15.5.1).
The purpose is to prevent spurious bug reports from users who have
corrupted their process state all unawares. Calling terminate() means
the error is detected immediately and unambiguously. As a default,
therefore, it's reasonably safe. A sophisticated user must be equipped
to turn it off, though, and deal with the consequences,
I agree that it would be better to make the cancellation event behave
as an ordinary exception, but the thread library maintainers seem
unwilling to implement it. Programs that want that behavior would
have to add (perhaps) "set_cancellation_destructor(0)" to the common
preamble at the beginning of main(), along with the
"std::ios::sync_with_stdio(false);" and "signal(SIGPIPE, SIG_IGN);"
we are already accustomed to doing. :-P
A compiler flag, perhaps "-Wrethrow", might enable warnings about
falling off the end of a "catch(...)" block.