This is the mail archive of the gcc@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: catch(...) and forced unwind


On Tue, 16 Dec 2003 07:23:05 -0500, David Abrahams <dave@boost-consulting.com> wrote:

> Jason Merrill <jason@redhat.com> writes:

>> The text for output streams is similar.  I note, though I concede that this
>> is something of a perverse reading of the standard, that although this says
>> that the functions don't rethrow, nothing precludes them from throwing
>> implementation-defined exceptions of their own.
>
> I think that's beyond perverse.  The language is clear: "otherwise it
> does not throw anything".  That precludes any throwing whatsoever,
> unless of course I've misread that text and paragraph 4 is only
> describing one corner of the behavior while exceptions are allowed to
> escape under other conditions.

That is my interpretation; I think that it only describes what happens when
one of the called functions throws.

>> Also, this restriction only applies to the {un,}formatted i/o functions,
>> not to any other operations.

Any response to this point?

>> I would like to loosen the passage quoted above to allow rethrowing some
>> exceptions as defined by the implementation.  I don't think this change
>> would break anyone's code, since the existing guarantee is so weak.
>
> I don't think that's a safe conclusion.  There's plenty of legacy code
> which acts as though there is no such thing as an exception.  You can
> pretty much do all the iostream-ing you want under that assumption,
> since they are handed to you out of the box in that state.  One
> obvious place output streaming might be done is in an object's
> destructor, for logging purposes.  It seems entirely plausible that
> people might scatter local objects throughout throughout functions in
> order to do tracing.  If thread cancellation is picked up by an output
> streaming call in a destructor during unwinding, we'll go directly to
> terminate (boom).

Yes, that's a significant issue; this would mean people need to be even
more careful about what they do in a destructor, perhaps to the point of
disabling cancellation while the destructor is running.

>>>> Actually, this is backwards -- the standard doesn't allow the C I/O
>>>> functions to throw any exceptions; see the passage above.  This obviously
>>>> needs to change if we're going to do anything useful with POSIX thread
>>>> cancellation.
>>
>>> Not sure what "anything useful" means.
>>
>> If all of the cancellation points are throw(), 
>
> well, then they're not cancellation points anymore, are they?

Yes, the set of cancellation points is defined by POSIX.  Cancellation will
happen regardless of whether or not C++ cleanups are run.  And I believe
that sometimes silently ignoring cleanups is worse than sometimes calling
terminate.

>> the cancellation exception can't propagate, so we can't run any
>> cleanups.  Which isn't very useful.
>
> It's obvious that unwinding due to cancellation has to be possible.
> [I presume there's a function people can call to explicitly induce
> unwinding when the thread is cancelled?]

Yes, pthread_testcancel.  A C++ program that wants (or needs) explicit
control over where cancellation will happen can

  pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL);

in the thread startup function and

  pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL);
  pthread_testcancel ();
  pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL);

to allow cancellation.  But as you say, this requires explicit handling and
most exception-safe code should also be thread-safe, so we want to find the
default behavior which will be most useful.

> The question is whether it's appropriate to force unwinding in this one
> particular case where users have been given license to assume there will
> be none.

I believe that is the least bad solution.

>> Has David Butenhof been involved at all?  He seems to have been
>> thinking about the interaction of threads and exceptions longer than
>> anyone else.
>
> Yes, though not directly.  Bill Kempf (the Boost.Threads guy)
> communicated with him and extensively read his writings on the subject
> before he vaporized.  My understanding, from everything I've read, is
> that David Butenhof believes that *any* asynchronous (forced, not
> under user control) cancellation model is wrong.  IIRC there's
> something like that in pthreads and he regrets it.

I think everyone agrees that asynchronous cancellation is bad and wrong.
We're only talking about synchronous (PTHREAD_CANCEL_DEFERRED) cancellation
here, whereby cancellation occurs only at certain defined cancellation
points.

> But rather than speaking for him, I should let him speak for himself.
>
>   http://tinyurl.com/zg5d
>
> Plus, we can ask him questions.  He's quite approachable, I've found.
>
> The one place I have to pre-emptively disagree with him is (from the
> first link):
>
>    "NOBODY has any business "finalizing" (catching and not 
>     releasing) an exception they don't know by name. If you can't identify the 
>     exception, you can't know why it was raised or what it means. And if you 
>     don't understand what it means, you cannot possibly know that it doesn't 
>     need to be handled further down the stack."
>
> Not all code is exception-safe, and not all languages can handle C++
> exceptions.  At the boundaries between exception-safe code and
> everything else, catch(...) is appropriate.

Perhaps, but it's not clear to me that this is a justification for the use
in iostreams.

I think it's important to understand that, contrary to Bill's post

  http://gcc.gnu.org/ml/gcc-patches/2003-05/msg00211.html

the POSIX standard does not allow cancellation to be deferred again once
the request is acted upon; it states that each of the handlers are run, and
then the thread exits.  Allowing a catch block to finalize a cancellation
exception would be an extension to the POSIX semantics.

That said, Butenhof's postings certainly suggest that he thinks that this
is an appropriate extension:

  http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=3B03B13A.D4796297%40compaq.com

  You cannot "catch" a cancel and continue. That would require a full
  exception model, which was beyond the scope of POSIX. However,
  POSIX cancellation was loosely based on an implementation that did use
  exceptions, and an ideal/complete/"philosophically correct"
  implementation of POSIX cancellation will be based on exceptions. On
  such a system, you can use non-standard mechanisms to "finalize" a
  cancellation exception and continue. For example, you should be able to
  enclose your do_something_with_pthread_testcancel() in a C++
  try/catch(...) block, and "return 1" from the catch.

  That's almost always a bad idea, though, since cancellation is designed
  to cleanly terminate a thread; you shouldn't be using it this way.
  That's especially true where your function may be called in a thread
  someone else created. Only the CREATOR should ever cancel a thread; and
  then your code will be foiling the legitimate intent of the thread's
  creator to terminate it. You should cleanup on a cancel and then allow
  it to propagate.

I agree completely with this, both that catch(...) should finalize a
cancellation and that it should almost never be used that way--in
particular, that it should not be used that way in iostreams.

The other cases of forced unwind are less clear.  It's pretty obvious that
finalizing a cancellation exception would simply cause the cancellation to
be deferred until the next cancellation point.  But what would it mean to
finalize a pthread_exit exception?  And though we could argue that a
cancellation request isn't urgent, since it is already deferred until the
next cancellation point, that's hard to justify for pthread_exit.  Harder
yet for longjmp_unwind.

Does anyone have a Tru64 system with the Compaq C++ compiler installed?

Benjamin: Pending a resolution of these issues, I suggest that we work
around the problem by disabling cancellation in the iostream try blocks.
I'm playing with this now.

Jason


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