This is the mail archive of the 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

Nathan Myers said:
> 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:
>> 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
> during unwinding.
> 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.

Total agreement.

>> > 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,
>> [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.

Totally agreement.  My point was, the only party that has control over the
schedule is the thread being cancelled.  This is true regardless of the
cancellation implementation, assuming a cooperative form of cancellation
(and asynchronous cancellation is a totally different topic with very
different consequences and requirements).  Given this fact, you're doing
no one any favors by preventing the thread from "eating the exception". 
This prevention only restricts how the thread can fullfill the conceptual
contract, while not giving the other parties any more gaurantees that the
"schedule" will be adhered to.  The "schedule" is conceptual, and requires
strict and _explicit_ cooperation among the parties involved.

> 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
> program authors.

This is best done by imposing no new requirements on the "cancellation
exception".  This does not make it any more difficult for the thread being
cancelled to fullfill the request in a timely manner, and more
importantly, when compared with altering the exception requirements,
imposes no restrictions that can actually make it more difficult to
fullfill the request in a timely and *correct* manner.  (The emphasis on
correct is due to the cases in which it's necessary, or at least less
problematic, to eat the exception because of usage requirements, such as
interaction with languages that can't deal with exceptions.)

>> 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,

Maybe I'm misunderstanding, but I thought the call to terminate under
discussion here occurs when the exception is caught but not re-thrown?  If
that's the case, what process state would be corrupted in this case?  All
invariants should still hold.

> 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

Why are the "thread library maintainers... unwilling to implement it"? 
What concerns do they have with the implementation?  Why is terminate()
considered better?  It seems to me that it only makes things more complex,
not safer.

> A compiler flag, perhaps "-Wrethrow", might enable warnings about
> falling off the end of a "catch(...)" block.

A diagnostic sounds reasonable.  More so than a runtime mechanism that may
not be caught in testing.  And such a diagnostic might make sense for all
exception types, not just cancellation.

William E. Kempf

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