Discussion:
I'm Lost
(too old to reply)
David Abrahams
2005-07-26 13:36:41 UTC
Permalink
I got lost in this discussion a week or two ago, but my sense is that
not much progress is being made because we are getting stuck in
details. I think we have to try to deal with some large principles of
importance to the various participants. For example, it's important
to me that

Otherwise-correct C++ code not written to explicitly deal with
cancellation should have a good chance of remaining correct in a
POSIX environment with cancellation exceptions

However, it may yet be possible to convince me to give that one up. I
think it's important to sort out the big principles and then weed out
any major incompatibilities among them, prioritize if necessary, and
when all that's done, think about how (or if) they can be implemented.

Of course, maybe you guys are all going like gangbusters and I'm just
too stupid to keep up. If so, please forge on ahead without me!
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Wil Evers
2005-07-26 21:19:56 UTC
Permalink
For example, it's important to me that
Otherwise-correct C++ code not written to explicitly deal with
cancellation should have a good chance of remaining correct in a
POSIX environment with cancellation exceptions
However, it may yet be possible to convince me to give that one up.
The problem is not to convince you. The problem is to convince the
otherwise-correct C++ code to behave like it used to.
I think it's important to sort out the big principles and then weed out
any major incompatibilities among them, prioritize if necessary, and
when all that's done, think about how (or if) they can be implemented.
I'd say it's time to face the truth, which is that POSIX cancellation
semantics are incompatible with commonly established C++ coding
practices. As you may have noticed, my preferred solution is to
selectively apply POSIX cancellation to small regions of code
specifically written with cancellation in mind.
Of course, maybe you guys are all going like gangbusters and I'm just
too stupid to keep up.
I doubt that :-).
If so, please forge on ahead without me!
That doesn't sound like a good idea at all.

- Wil
Alexander Terekhov
2005-07-27 11:31:43 UTC
Permalink
Wil Evers wrote:
[...]
Post by Wil Evers
I'd say it's time to face the truth, which is that POSIX cancellation
semantics are incompatible with commonly established C++ coding
practices.
It's incompatible with cancel-unaware C++ code. Note that it's
incompatible with cancel-unaware C code as well, but that didn't
stop POSIX and ongoing creation of cancel-safe code in both C
and C++ languages. Very many tons of cancel-safe code.

regards,
alexander.
Dave Butenhof
2005-07-27 13:07:25 UTC
Permalink
Post by Alexander Terekhov
[...]
Post by Wil Evers
I'd say it's time to face the truth, which is that POSIX cancellation
semantics are incompatible with commonly established C++ coding
practices.
It's incompatible with cancel-unaware C++ code. Note that it's
incompatible with cancel-unaware C code as well, but that didn't
stop POSIX and ongoing creation of cancel-safe code in both C
and C++ languages. Very many tons of cancel-safe code.
Indeed. The only real (and unfortunately the hardest) questions are how
to reconcile the (apparent) contradictions between cancel scope and C++
throw specs (explicit or implicit as in destructors), and cultural
investment in catch(...).

Perhaps integration would be simplified if threads created using native
C++ mechanisms (rather than the C pthread_create) started with
cancellation disabled, allowing C++ programs to enable where desired.
Then again, though, it's important to keep in mind that a thread is not
subject to cancellation unless some thread with access to the thread ID
CHOOSES to cancel it; nobody can force cancellation on a thread without
its ID, and thread IDs are not generally available. And while any
routine can cancel the current thread (pthread_self), doing this without
certain knowledge that the thread's call stack can handle it is a pretty
stupid programming error.

Cancellation is not like 'kill <pid>', forced arbitrarily from outside
forces unknown; it's a COOPERATIVE request from friendly code. Only code
running inside the thread, or code that closely collaborates with the
creation or management of the thread (by prior agreement) can ever
cancel a thread.

And the C++ committee could define C++ threading without cancellation; I
think that would be a mistake in the real world, and I know many others
will, but the committee need not care. However, if C++ has cancellation
it has to be cleanly integrated with C/POSIX cancel and cleanup, and the
only viable language/implementation model is to make cancel an exception.
Alexander Terekhov
2005-07-27 13:59:41 UTC
Permalink
Dave Butenhof wrote:
[...]
Post by Dave Butenhof
Indeed. The only real (and unfortunately the hardest) questions are how
to reconcile the (apparent) contradictions between cancel scope and C++
throw specs (explicit or implicit as in destructors),
Fix ES (get rid of ugly implicit catch(...) semantics and totally
idiotic transfer of terminate()/unexpected() handlers up-stack along
with unexpected exception), mandate 2-phase EH and intelligent cancel
delivery.

http://groups.google.de/group/comp.lang.c++.moderated/msg/c897f898de7a97cd
Post by Dave Butenhof
and cultural
investment in catch(...).
Invest in weak_catch().
Post by Dave Butenhof
Perhaps integration would be simplified if threads created using native
C++ mechanisms (rather than the C pthread_create) started with
cancellation disabled, allowing C++ programs to enable where desired.
That would go against the premise of POSIX's modulary argument

http://www.codesourcery.com/archives/c++-pthreads/msg00419.html

and would not really solve any problems.

http://www.codesourcery.com/archives/c++-pthreads/msg00425.html
http://www.codesourcery.com/archives/c++-pthreads/msg00445.html

regards,
alexander.
David Abrahams
2005-07-27 18:18:57 UTC
Permalink
Post by Dave Butenhof
Post by Alexander Terekhov
[...]
Post by Wil Evers
I'd say it's time to face the truth, which is that POSIX cancellation
semantics are incompatible with commonly established C++ coding
practices.
It's incompatible with cancel-unaware C++ code. Note that it's
incompatible with cancel-unaware C code as well, but that didn't
stop POSIX and ongoing creation of cancel-safe code in both C and
C++ languages. Very many tons of cancel-safe code.
Indeed. The only real (and unfortunately the hardest) questions are how
to reconcile the (apparent) contradictions between cancel scope and C++
throw specs (explicit or implicit as in destructors), and cultural
investment in catch(...).
Well, you guys just dove right back into the detail as far as I can
tell. I guess I'm going to remain lost, but it's probably not a big
loss to the discussion.

Regards,
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Kevlin Henney
2005-07-27 17:37:01 UTC
Permalink
Post by Alexander Terekhov
[...]
Post by Wil Evers
I'd say it's time to face the truth, which is that POSIX cancellation
semantics are incompatible with commonly established C++ coding
practices.
It's incompatible with cancel-unaware C++ code. Note that it's
incompatible with cancel-unaware C code as well, but that didn't
stop POSIX and ongoing creation of cancel-safe code in both C
and C++ languages. Very many tons of cancel-safe code.
To the best of my knowledge there have been zero lines of cancel-safe
C++ code written against the C++ binding of POSIX threads.

Of course, C and C++ have an intimate relationship, but that does not
mean to say that they are the same or that C++ must slavishly follow the
same model that C has decided on. There is a question of
interoperability, but that is not the same as portability. The
discussion so far seems to have taken the latter view, which is subtly
different.

If the C++ binding chooses to throw exceptions for cancellation, no C++
code will be broken by silent changes. Code written in C++ that is
correct against the C binding will continue to be correct against that
binding. But should the program be modified to take advantage of a C++
binding, that guarantee no longer holds. However, this is unsurprising
and is to be expected of any migration between APIs. Of course, it makes
sense to minimise gratuitous differences, but minimal difference is not
the same as no difference.

Kevlin
--
____________________________________________________________

Kevlin Henney phone: +44 117 942 2990
mailto:***@curbralan.com mobile: +44 7801 073 508
http://www.curbralan.com fax: +44 870 052 2289
Curbralan: Consultancy + Training + Development + Review
____________________________________________________________
Alexander Terekhov
2005-07-27 17:38:44 UTC
Permalink
< Foward Quoted >
[Sorry for personal email, I seem to have lost my list subscription - could you forward this onto the main list too please?]
The problem as I see it is that we have 2 distinct communities of programmers, with a small intersection - Posix thread users and C++ users.
The C++ community addresses a wide variety of problem domains and deals with many users who never need Posix cancellation - indeed may never run on any Posix system.
Likewise, many Posix threaders may code in C or other posix-friendly lanuages without worrying about C++. The posix-cancellation rules were developed for this set of customers.
C++ has very clear resource acquisition/release semantics through object construction/destruction that apply across its broad spectrum of users. This is a fundamental, invioble rule of the language.
What I am reading here is that this large group of users now need to be aware of some quite complicated posix-specific semantics to understand when and why their guarantees will not hold, and learn some as-yet unclear new fundamental technique to wrap resources in any 'portable' code they wish to publish.
Quite simply, I believe that is far too great a burden for the casual C++ user. They are already struggling under the burden of a complicated language without us expecting them to understand intricacies of thread safety and lifetimes.
The people actively working in the posix/C++ subset are much more familiar with these complex issues, and I believe the burden is on them to find a compromise that will not impact on the non-expert user who is NOT using this combination. I do not pretend this is an easy task though ;¬)
AlisdairM
[going back to lurk mode]
-----Original Message-----
Sent: Wednesday 27 July 2005 12:32
Subject: [c++-pthreads] Re: I'm Lost
[...]
Post by Wil Evers
I'd say it's time to face the truth, which is that POSIX cancellation
semantics are incompatible with commonly established C++ coding
practices.
It's incompatible with cancel-unaware C++ code. Note that it's
incompatible with cancel-unaware C code as well, but that didn't
stop POSIX and ongoing creation of cancel-safe code in both C
and C++ languages. Very many tons of cancel-safe code.
regards,
alexander.
---------------------------------------------------------------------
For further information on Renault F1 visit our web site at www.renaultf1.com.
WARNING: please ensure that you have adequate virus protection in place before you open or detach any documents attached to this email.
This e-mail may constitute privileged information. If you are not the intended recipient, you have received this confidential email and any attachments transmitted with it in error and you must not disclose, copy, circulate or in any other way use or rely on this information.
E-mails to and from the Renault F1 Team are monitored for operational reasons and in accordance with lawful business practices.
The contents of this email are those of the individual and do not necessarily represent the views of the company.
Please note that this e-mail has been created in the knowledge that Internet e-mail is not a 100% secure communications medium. We advise that you understand and observe this lack of security when e-mailing us.
---------------------------------------------------------------------
_________________________________________________________________________
Mit der Gruppen-SMS von WEB.DE FreeMail können Sie eine SMS an alle
Freunde gleichzeitig schicken: http://freemail.web.de/features/?mc=021179
Ted Baker
2005-07-27 18:34:42 UTC
Permalink
| ... Quite simply, I believe that is far too great a burden for the
| casual C++ user. They are already struggling under the burden of
| a complicated language without us expecting them to understand
| intricacies of thread safety and lifetimes. ...

With this encouragement, I dare repeat what I suggested earlier,
i.e., that thread cancellation should just be omitted from the C++
thread API.

It is very easy to say that anyone who uses thread cancellation
and C++ should take care to write code that is safe for cancellation
(whatever way it is defined to act in the new API).

It is another thing for the rank and file of ordinary C++
programmers to actually do this thing, reliably and consistently.

I see adding a standard C++ API with thread cancellation in the
same category as giving loaded guns to babies. Say whatever you
want, but people will see this is as a blessing that cancellation
is a generally good thing, and that the gods of standards and
implementations have seen to making it safe for mortal use.

I can attest that I have been burned using Ada's equivalent to
thread cancellation (the "select...then...abort...end select;"
construct) to time out certain occasionally long-running
computations. I missed some code that was unsafe to abort, and
then wasted a few days tracking down and intermittent error.
Rather than patch that one hole, and hope that I had not misssed
any others, I decided to do the conservative (and simpler) thing.
I switched over to a polling solution, inserting over-time checks
at a three key points in the computations.

Of course, polling won't help with cases where a thread is stuck
on a blocking system call, but then one does
have (dare I say it?) pthread_kill().

If there is no standard API for thread cancellation, then
implementors are to quietly do that they think is the right thing
for cases where a user goes outside the standards, e.g., by mixing
C++ into a C application that uses thread cancellation (e.g., the
implementation could execute any C++ finalizers that are found
along the way during stack unrolling). To make sure this does not
happen too easily, the implementor can provide a link-time or
run-time hack that must be invoked to allow this inter-mixture.
(For example, I guess that a little bit of header-file magic with
macros and symbol redefinition could cause the linker to reject
calls to pthread_cancel() from code that includes pthread.h when
they are linked with modules compiled using the C++ thread API
header.)

--Ted
Alexander Terekhov
2005-07-27 19:20:51 UTC
Permalink
Ted Baker wrote:
[...]
Post by Ted Baker
Of course, polling won't help with cases where a thread is stuck
on a blocking system call, but then one does
have (dare I say it?) pthread_kill().
You mean EINTR "cancellation" hack? You'd need a pselect()-like
logic and sigmask arguments

If sigmask is not a null pointer, then the pselect() function
shall replace the signal mask of the caller by the set of
signals pointed to by sigmask before examining the descriptors,
and shall restore the signal mask of the calling thread before
returning.

added to all blocking system calls to make that castrated
"cancellation" really work (not being subject to a race with
respect to reaching interruptible state and signal delivery).

regards,
alexander.
Ted Baker
2005-07-27 19:52:28 UTC
Permalink
I'm just saying that an application programmer who has a need to
cancel threads, and is willing to take the trouble to make sure
all the code in the cancellable thread is safe for use with
cancellation, could just as well "roll his own" solution, and then
will know for certain what is the overhead and how it will work.

In a single-threaded POSIX/UNIX application, the only standard way
to break it out of a catonic state is via a signal. Therefore,
potentially blocking POSIX calls can return EINTR when a thread
receives a a signal and the signal is succesfully handled. IFAIK,
every blocking call that POSIX guarantees will be interrupted by
pthread_cancel can also be interrupted by signals.

Although many (most?) programmers seems to ignore this fact, fully
conformant code is suppose to have recovery logic for every one of
these interruptible system calls, that will either retry the call
or take other appropriate action if the call returns prematurely
due to interruption.

I'm just saying that this recovery code provides a natural place
to insert application-specific polling for a request that the
thread cancel itself. Another thread can post such a "die"
request in a volatile global variable before using pthread_kill to
send a signal to a given thread. The thread will notice this
at the next point where it polls for a "die" request.

As you imply, the code must deal with a window between the thread
polling the "die" variable and the thread doing the blocking call,
during which a signal might come in and not wake up the thread.
You can get around the race problems pretty simply if the changes
to the "die" request variable monotonic, which will be the case if
you insist that a once-cancelled thread must terminate. With this
simplification you can also provide a binary "ack" variable that
is set by the target thread once it has recognized that it has
been told to die. The killer thread has to periodically retry the
pthread_kill() until the target thread acknowledges.

By the way, even if this seems ugly, it may not too far from what
your friendly C-language implementation of pthread_cancel is
doing. In fact, I suspect that is why POSIX defined thread
cancelation as being a one-shot thing.

--Ted
Post by Alexander Terekhov
[...]
Post by Ted Baker
Of course, polling won't help with cases where a thread is stuck
on a blocking system call, but then one does
have (dare I say it?) pthread_kill().
You mean EINTR "cancellation" hack? You'd need a pselect()-like
logic and sigmask arguments
If sigmask is not a null pointer, then the pselect() function
shall replace the signal mask of the caller by the set of
signals pointed to by sigmask before examining the descriptors,
and shall restore the signal mask of the calling thread before
returning.
added to all blocking system calls to make that castrated
"cancellation" really work (not being subject to a race with
respect to reaching interruptible state and signal delivery).
Alexander Terekhov
2005-07-27 20:17:10 UTC
Permalink
Ted Baker wrote:
[...]
Post by Ted Baker
receives a a signal and the signal is succesfully handled. IFAIK,
every blocking call that POSIX guarantees will be interrupted by
pthread_cancel can also be interrupted by signals.
"The pthread_join() function shall not return an error code of
[EINTR]."

And I can go on and on (including stuff like "may fail" vs "shall
fail" on semas, etc.), but I'll just let you check it yourself by
clicking on every "shall occur" cancellation point here:

http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html#tag_02_09_05_02

regards,
alexander.
Ted Baker
2005-07-27 22:03:25 UTC
Permalink
Post by Alexander Terekhov
[...]
Post by Ted Baker
receives a a signal and the signal is succesfully handled. IFAIK,
every blocking call that POSIX guarantees will be interrupted by
pthread_cancel can also be interrupted by signals.
"The pthread_join() function shall not return an error code of
[EINTR]."
Interesting... I should have remembered this, having implemented
it once, about 15 years ago. I had forgotten how many places
Pthreads calls broke the traditional POSIX signal model.
Post by Alexander Terekhov
And I can go on and on (including stuff like "may fail" vs "shall
fail" on semas, etc.),
Yes, POSIX allows implementations to not interrupt certain (as I
recall, most) of the blocking calls, via this "may" language. On
the other hand, if the system does not allow them to be
interrupted a Pthread library is going to have difficulty
implementing a useful (unblocking) thread cancellation point at
those calls.
Post by Alexander Terekhov
but I'll just let you check it yourself by
http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html#tag_02_09_05_02
--Ted
Dave Butenhof
2005-07-28 11:21:36 UTC
Permalink
< Foward Quoted >
Quite simply, I believe that is far too great a burden for the casual C++ user. They are already struggling under the burden of a complicated language without us expecting them to understand intricacies of thread safety and lifetimes.
The people actively working in the posix/C++ subset are much more familiar with these complex issues, and I believe the burden is on them to find a compromise that will not impact on the non-expert user who is NOT using this combination. I do not pretend this is an easy task though ;¬)
Cancellation does not affect application code (or coders) that don't
want to use it. Nothing can cancel a thread without having the thread's
handle through deliberate action of the thread's creator, or through
pthread_self when executing within the thread. No modular "facility" can
legitimately or reasonably do this to any thread to which it has not
been granted "ownership" -- either by having created it within the
facility or by having ownership handed to it through an explicit
protocol (which is rare). Certainly some fool can chose, just for the
heck of it, to cancel the thread in which it runs... but so what? It
could just as easily generate a SIGSEGV and take out the whole process.

That one writes an application using threads, regardless of language,
doesn't require you to use, understand, or even be aware of cancellation.

However when one writes a robust general-purpose facility (library) that
will be used in an environment supporting cancellation, that library
ought to be written to support cancellation (whether or not it actually
uses cancellation on its own behalf). Such libraries are not generally
tasks taken on by "casual users"; and even so while hardly ideal it's
perfectly adequate to simply say "this facility isn't cancel-safe; tough
luck".

"Industrial strength" libraries in the environment, for example the
"language runtime" itself, whether libc or STL, ought to be cancel-safe
certainly. Even at that, however, because the task can be monumental,
POSIX provided "cheats" -- the list of "optional" cancellation points
allow a libc developer to omit all but the most critical. A C++ standard
for STL *could* provide similar "cheats" to avoid requiring full
implementation of cancel-safety. And again, if the user of the library
(whether the main application or another library) doesn't choose to use
cancellation the point is moot.

And while Ted Baker has suggested that "loaded guns" shouldn't be given
to "babies", one could respond cynically that it's far too late in UNIX,
POSIX, C, or C++ history to worry about handing out loaded guns.
(Cancellation is nothing compared to the explosive payload of an
asynchronous signal, or even thread synchronization considerations.)
With the existing well-stocked and easily accessible arsenal lying
about, the addition of one more handgun can hardly be considered even
relevant much less critical. And more importantly, there are many
programmers who really DO know how to use those weapons of mass coding
destruction, and need them. That's NOT a death ray over there in the
corner, it's an industrial excavation tool; misuse is solely the
responsibility of the user, not the manufacturer.

Finally, the question is NOT whether we choose to make these weapons
available, nor even whether they'll be used. They already exist, are
widely (almost universally) deployed; and are used and depended upon by
many real applications. None of that is going to go away no matter what
the C++ committee does. The question HERE is only whether there will be
a STANDARD and PORTABLE specification to aid these developers and
applications in moving between the various systems. Ignoring the issue
won't make it go away -- merely leave those applications and developers
with cumbersome non-portable code, and force someone to resurrect this
same argument all over again in another year or two. (As indeed has
already happened, through several cycles.)
David Abrahams
2006-03-06 16:27:03 UTC
Permalink
Post by Dave Butenhof
However when one writes a robust general-purpose facility (library) that
will be used in an environment supporting cancellation, that library
ought to be written to support cancellation (whether or not it actually
uses cancellation on its own behalf). Such libraries are not generally
tasks taken on by "casual users"; and even so while hardly ideal it's
perfectly adequate to simply say "this facility isn't cancel-safe; tough
luck".
"Industrial strength" libraries in the environment, for example the
"language runtime" itself, whether libc or STL, ought to be cancel-safe
certainly. Even at that, however, because the task can be monumental,
POSIX provided "cheats" -- the list of "optional" cancellation points
allow a libc developer to omit all but the most critical. A C++ standard
for STL *could* provide similar "cheats" to avoid requiring full
implementation of cancel-safety. And again, if the user of the library
(whether the main application or another library) doesn't choose to use
cancellation the point is moot.
Picking this thread up from long ago, lete me say that I'm sort-of in
agreement with the above. I say "sort of" because Dave B's statement
fails to address the following point (hereafter known as "the
statement"), and I can't tell what side of it he'd come down on:

Any code that is already exception-safe could be automatically
cancel-safe depending on our definition of "cancel-safe" and the
semantics we assign to cancellation exceptions.

In the definition of "cancel-safe" that allows the statement to be
true, cancellation is a request, and doesn't absolutely force
_anything_ to happen. IIUC, that is the status quo anyway (nobody is
even forced to invoke a cancellation point).

The cancellation exception semantics that allow the statement to be
true are that they act like any other exception, and are not
automatically rethrown at the end of catch blocks. This is the
question primarily in dispute, IIUC.

IMO, it is worth making cancellation exceptions like any other in
order to make the statement true. It's also worthwhile to avoid
complicating the mental model programmers must use to think about
exception handling, which, believe me, is hard enough even for many
developers of general purpose libraries to grasp (how long did it take
Dinkumware to make their STL exception-safe?) Finally, and this may
be counterintuitive at first,

making cancellation stoppable by catch(...) like everything else is
sometimes **required** in order to ensure that cancellation
propagates all the way through the stack and terminates a thread as
expected without crashing the program. So those who want
cancellation to act more like an absolute guarantee of thread
termination should support it.

For example, I write a C++ library for interfacing with Python.
Python is written in portable 'C' with no special treatment of
cancellation exceptions or the kinds of cleanups POSIX uses to do the
same thing in 'C'. If I let any sort of C/C++-level exception
propagate into Python, its expectations are violated and will most
likely crash. It's very common to have a layer of Python sandwiched
between C++ calls on the stack. I can get proper cancellation
semantics by stopping cancellation exceptions at the boundary and
translating them into Python exceptions. They'll propagate through
Python, and reach an exception translator on the other side that turns
them back into C++ exceptions.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Dave Butenhof
2006-03-06 18:49:33 UTC
Permalink
Post by David Abrahams
Post by Dave Butenhof
However when one writes a robust general-purpose facility (library) that
will be used in an environment supporting cancellation, that library
ought to be written to support cancellation (whether or not it actually
uses cancellation on its own behalf). Such libraries are not generally
tasks taken on by "casual users"; and even so while hardly ideal it's
perfectly adequate to simply say "this facility isn't cancel-safe; tough
luck".
"Industrial strength" libraries in the environment, for example the
"language runtime" itself, whether libc or STL, ought to be cancel-safe
certainly. Even at that, however, because the task can be monumental,
POSIX provided "cheats" -- the list of "optional" cancellation points
allow a libc developer to omit all but the most critical. A C++ standard
for STL *could* provide similar "cheats" to avoid requiring full
implementation of cancel-safety. And again, if the user of the library
(whether the main application or another library) doesn't choose to use
cancellation the point is moot.
Picking this thread up from long ago, lete me say that I'm sort-of in
agreement with the above. I say "sort of" because Dave B's statement
fails to address the following point (hereafter known as "the
Any code that is already exception-safe could be automatically
cancel-safe depending on our definition of "cancel-safe" and the
semantics we assign to cancellation exceptions.
In the definition of "cancel-safe" that allows the statement to be
true, cancellation is a request, and doesn't absolutely force
_anything_ to happen. IIUC, that is the status quo anyway (nobody is
even forced to invoke a cancellation point).
The cancellation exception semantics that allow the statement to be
true are that they act like any other exception, and are not
automatically rethrown at the end of catch blocks. This is the
question primarily in dispute, IIUC.
This has been THE most contentious issue in every C++/threads discussion
I've encountered since the beginning of (pthread) time.

My preference has always been that cancellation is an exception. Period.
In our initial CMA architecture, and in our exception mapping of
cancellation/thread-exit onto C language exceptions in Tru64 UNIX and
OpenVMS, it's possible and reasonable to finalize propagation of a
cancel/exit exception. That was critical for DCE, for example, so that
it could trap cancellation of an RPC server thread, bring the thread
back into the server's work pool, and propagate the exception across the
wire to the client.

To finalize a cancel/exit under almost any normal circumstance is simply
an application error. There are many worse application errors, like
infinite loops, that we can't legislate around anyway. Worrying too much
that someone might finalize the exception unintentionally just seemed
like wasted effort. However it's also important to keep in mind that my
preferences were formed with POSIX cancellation and C language (or
cross-language OS) exceptions. C++ adds a lot of exception semantics and
patterns on top of that.

There have been plenty of people who argue that cancel "can't" be
caught; and some of these arguments trace back to the ubiquity of
catch(...), especially in constructors; and they have some legitimate
concerns about common C++ language patterns that might pretty much
prevent a cancel from ever doing what a cancel should do.

There likely is no perfect solution.
Post by David Abrahams
IMO, it is worth making cancellation exceptions like any other in
order to make the statement true. It's also worthwhile to avoid
complicating the mental model programmers must use to think about
exception handling, which, believe me, is hard enough even for many
developers of general purpose libraries to grasp (how long did it take
Dinkumware to make their STL exception-safe?) Finally, and this may
be counterintuitive at first,
making cancellation stoppable by catch(...) like everything else is
sometimes **required** in order to ensure that cancellation
propagates all the way through the stack and terminates a thread as
expected without crashing the program. So those who want
cancellation to act more like an absolute guarantee of thread
termination should support it.
For example, I write a C++ library for interfacing with Python.
Python is written in portable 'C' with no special treatment of
cancellation exceptions or the kinds of cleanups POSIX uses to do the
same thing in 'C'. If I let any sort of C/C++-level exception
propagate into Python, its expectations are violated and will most
likely crash. It's very common to have a layer of Python sandwiched
between C++ calls on the stack. I can get proper cancellation
semantics by stopping cancellation exceptions at the boundary and
translating them into Python exceptions. They'll propagate through
Python, and reach an exception translator on the other side that turns
them back into C++ exceptions.
The catch lies in whether (and how far) you'll trust application
developers to do the re-throw properly. If we don't clean up all frames
and eventually re-throw the cancel/exit to the runtime's base frame to
terminate the thread, then we don't have cancellation. On the other
hand, if we prevent a catch or force a re-throw, we lose a lot of C++
(particularly in constructors).

Part of the reason that you "can't tell what side of it [I'd] come down
on" is that I've long recognized this as an essentially religious rather
than technical argument. You'll come down on the side of the semantics
toward which you feel the strongest emotional attachment. While I'm
happy to express my experience and even preferences, I also recognize
that "the other side" has some equally strong arguments and
expectations, and they (well, most of them!) are not "wrong".

Someone needs to propose and champion "the great exception compromise";
but if that's to be me I don't yet have the faintest germ of a notion
what it might be. So I sure hope it's going to be someone else. ;-)
David Abrahams
2006-03-06 19:36:37 UTC
Permalink
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
However when one writes a robust general-purpose facility (library)
that will be used in an environment supporting cancellation, that
library ought to be written to support cancellation (whether or not
it actually uses cancellation on its own behalf). Such libraries
are not generally tasks taken on by "casual users"; and even so
while hardly ideal it's perfectly adequate to simply say "this
facility isn't cancel-safe; tough luck".
"Industrial strength" libraries in the environment, for example the
"language runtime" itself, whether libc or STL, ought to be
cancel-safe certainly. Even at that, however, because the task can
be monumental, POSIX provided "cheats" -- the list of "optional"
cancellation points allow a libc developer to omit all but the most
critical. A C++ standard for STL *could* provide similar "cheats"
to avoid requiring full implementation of cancel-safety. And again,
if the user of the library (whether the main application or another
library) doesn't choose to use cancellation the point is moot.
Picking this thread up from long ago, lete me say that I'm sort-of in
agreement with the above. I say "sort of" because Dave B's statement
fails to address the following point (hereafter known as "the
Any code that is already exception-safe could be automatically
cancel-safe depending on our definition of "cancel-safe" and the
semantics we assign to cancellation exceptions.
In the definition of "cancel-safe" that allows the statement to be
true, cancellation is a request, and doesn't absolutely force
_anything_ to happen. IIUC, that is the status quo anyway (nobody is
even forced to invoke a cancellation point).
The cancellation exception semantics that allow the statement to be
true are that they act like any other exception, and are not
automatically rethrown at the end of catch blocks. This is the
question primarily in dispute, IIUC.
This has been THE most contentious issue in every C++/threads discussion
I've encountered since the beginning of (pthread) time.
My preference has always been that cancellation is an exception. Period.
In our initial CMA architecture, and in our exception mapping of
cancellation/thread-exit onto C language exceptions in Tru64 UNIX and
OpenVMS, it's possible and reasonable to finalize propagation of a
cancel/exit exception. That was critical for DCE, for example, so that
it could trap cancellation of an RPC server thread, bring the thread
back into the server's work pool, and propagate the exception across the
wire to the client.
That sounds highly analogous to my case with Python.
Post by Dave Butenhof
To finalize a cancel/exit under almost any normal circumstance is
simply an application error.
The key word being "almost." In some situations, like those we've
both cited, it's absolutely necessary, to even get cancellation to
work.
Post by Dave Butenhof
There are many worse application errors, like infinite loops, that
we can't legislate around anyway. Worrying too much that someone
might finalize the exception unintentionally just seemed like wasted
effort. However it's also important to keep in mind that my
preferences were formed with POSIX cancellation and C language (or
cross-language OS) exceptions. C++ adds a lot of exception semantics
and patterns on top of that.
There have been plenty of people who argue that cancel "can't" be
caught; and some of these arguments trace back to the ubiquity of
catch(...), especially in constructors;
A ctor that does catch(...) without rethrow is almost always badly
designed at best. There was unfortunate advice going around for many
years that you shouldn't throw from ctors, but that's exactly wrong:
ctors that throw allow the establishment of strong invariants, and
programming without them is much harder.

On the other hand, stopping exceptions in dtors is absolutely the
right thing to do.
Post by Dave Butenhof
and they have some legitimate concerns about common C++ language
patterns that might pretty much prevent a cancel from ever doing
what a cancel should do.
Really, legitimate concerns? I can't think of any recommended
patterns that would act that way.
Post by Dave Butenhof
The catch lies in whether (and how far) you'll trust application
developers to do the re-throw properly. If we don't clean up all
frames and eventually re-throw the cancel/exit to the runtime's base
frame to terminate the thread, then we don't have cancellation.
I'm of the school that says it's futile and even dangerous to try to
operate correctly in an environment where you have to assume other
code is incorrect. My library will probably be equally broken if the
user decides to throw out my bad_alloc exception without a proper
response.
Post by Dave Butenhof
On the other hand, if we prevent a catch or force a re-throw, we
lose a lot of C++ (particularly in constructors).
I don't think that should be your concern. The correct and
well-written C++ we lose is generally sitting at module and language
boundaries. And then there are dtors.
Post by Dave Butenhof
Part of the reason that you "can't tell what side of it [I'd] come
down on" is that I've long recognized this as an essentially
religious rather than technical argument. You'll come down on the
side of the semantics toward which you feel the strongest emotional
attachment.
You don't find the idea that exception-safe code implies cancel-safe
code technically compelling? I don't think that's an emotional issue.
Post by Dave Butenhof
While I'm happy to express my experience and even preferences, I
also recognize that "the other side" has some equally strong
arguments and expectations, and they (well, most of them!) are not
"wrong".
I don't think either side is "wrong," either.
Post by Dave Butenhof
Someone needs to propose and champion "the great exception
compromise"; but if that's to be me I don't yet have the faintest
germ of a notion what it might be. So I sure hope it's going to be
someone else. ;-)
If "finalized cancellation exceptions result in a new throw at the
next cancellation point" isn't enough of a compromise, it isn't going
to be me either, because I'm out of new ideas.

Okay, how about this one: we count the number of times the
cancellation is discarded. The cancelling thread can specify the
number of discards to tolerate, where the default is infinite. After
that, at the next cancellation point all pthread cancellation handlers
(but not dtors or catch blocks) are run and the thread is terminated.
Heck, at that point I don't care what happens; you're gambling anyway.
Run all the dtors and catch blocks for all I care.

A simpler approach might be to have two kinds of exception: "forced"
and finalizable. At least then we can say that exception-safe code
implies finalizable cancellation safety. Then "forced" synchronous
cancellation can do whatever people desire. I personally think it
will become a useless appendage sort of like C++ exception
specifications, but at least evolution will take care of it. And if
I'm wrong, evolution will wilt my finalizable cancellations.

I guess I'm not totally out of ideas ;-)
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Mark Mitchell
2006-03-06 19:59:37 UTC
Permalink
Post by David Abrahams
If "finalized cancellation exceptions result in a new throw at the
next cancellation point" isn't enough of a compromise, it isn't going
to be me either, because I'm out of new ideas.
That sounds fine to me. The key invariant that I feel must be preserved
is that cancellation is an exception, with all that implies. (Actually
I'd made one exception to that requirement: if people wanted to say that
the type of the exception is unnameable, and that you cannot therefore
catch it by any means other than ..., I would see that as an
unnecessary, but acceptable, restriction.)

Your suggestion is to make functions which already throw exceptions (in
this context) throw them more often, which is OK; callers have to be
prepared for them to throw exceptions anyhow. (You do have to say what
happens if the thread is cancelled, catches the exceptions, sets the
no-cancel bit, and then reaches a cancellation point. The answer must
be that the thread is not cancelled, since, in that context, the
surrounding code can reasonably be written to assume there will no
exceptions.)

As far as I can tell, the last time we discussed this issue, all of the
C++ experts agreed that not allowing cancellation exceptions to be
caught was a serious violation of C++ exception-safety principles.

The key obstacle to progress here, AFAICT, is that Ulrich Drepper has
claimed that once cancellation occurs it is (in some unexplained way)
impossible for the thread to continue, despite the fact that it can run
its cancellation handlers, etc. I've never been able to tell if he
thinks this to be a POSIX requirement, a Linux kernel issue, a GLIBC
implementation issue, or something else.
--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713
Dave Butenhof
2006-03-06 20:54:54 UTC
Permalink
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
However when one writes a robust general-purpose facility (library)
that will be used in an environment supporting cancellation, that
library ought to be written to support cancellation (whether or not
it actually uses cancellation on its own behalf). Such libraries
are not generally tasks taken on by "casual users"; and even so
while hardly ideal it's perfectly adequate to simply say "this
facility isn't cancel-safe; tough luck".
"Industrial strength" libraries in the environment, for example the
"language runtime" itself, whether libc or STL, ought to be
cancel-safe certainly. Even at that, however, because the task can
be monumental, POSIX provided "cheats" -- the list of "optional"
cancellation points allow a libc developer to omit all but the most
critical. A C++ standard for STL *could* provide similar "cheats"
to avoid requiring full implementation of cancel-safety. And again,
if the user of the library (whether the main application or another
library) doesn't choose to use cancellation the point is moot.
Picking this thread up from long ago, lete me say that I'm sort-of in
agreement with the above. I say "sort of" because Dave B's statement
fails to address the following point (hereafter known as "the
Any code that is already exception-safe could be automatically
cancel-safe depending on our definition of "cancel-safe" and the
semantics we assign to cancellation exceptions.
In the definition of "cancel-safe" that allows the statement to be
true, cancellation is a request, and doesn't absolutely force
_anything_ to happen. IIUC, that is the status quo anyway (nobody is
even forced to invoke a cancellation point).
The cancellation exception semantics that allow the statement to be
true are that they act like any other exception, and are not
automatically rethrown at the end of catch blocks. This is the
question primarily in dispute, IIUC.
This has been THE most contentious issue in every C++/threads discussion
I've encountered since the beginning of (pthread) time.
My preference has always been that cancellation is an exception. Period.
In our initial CMA architecture, and in our exception mapping of
cancellation/thread-exit onto C language exceptions in Tru64 UNIX and
OpenVMS, it's possible and reasonable to finalize propagation of a
cancel/exit exception. That was critical for DCE, for example, so that
it could trap cancellation of an RPC server thread, bring the thread
back into the server's work pool, and propagate the exception across the
wire to the client.
That sounds highly analogous to my case with Python.
Sure; and there are other examples. I've just found that the "inverted
call stack" of this sort of server setup seems to make sense to a lot of
people.
Post by David Abrahams
Post by Dave Butenhof
To finalize a cancel/exit under almost any normal circumstance is
simply an application error.
The key word being "almost." In some situations, like those we've
both cited, it's absolutely necessary, to even get cancellation to
work.
Absolutely. There was a strong sub-faction in POSIX that can be loosely
characterized as "academics" who were determined to try to prevent
constructs that might be misused. It's why the realtime people didn't
get pthread_abort() to force termination without cleanup, why you can't
suspend a thread, or "force unlock" a mutex that might have been
abandoned, and so forth. If cancellation can't be finalized, nobody can
accidentally finalize it; and that's great if you don't trust anyone to
know when it SHOULD be finalized. I started out as something of an
"academic" in this sense and evolved into a pramatist... if someone
thinks they need it, and they're right, don't keep them from doing it.
And if they're WRONG, it's their problem, not yours. ;-) Portable Java
GC would have been a lot easier had POSIX included suspend/resume; and
does it really matter that nearly anyone else who used it would be
breaking their application? Well, it all depends on your point of view...
Post by David Abrahams
Post by Dave Butenhof
There are many worse application errors, like infinite loops, that
we can't legislate around anyway. Worrying too much that someone
might finalize the exception unintentionally just seemed like wasted
effort. However it's also important to keep in mind that my
preferences were formed with POSIX cancellation and C language (or
cross-language OS) exceptions. C++ adds a lot of exception semantics
and patterns on top of that.
There have been plenty of people who argue that cancel "can't" be
caught; and some of these arguments trace back to the ubiquity of
catch(...), especially in constructors;
A ctor that does catch(...) without rethrow is almost always badly
designed at best. There was unfortunate advice going around for many
ctors that throw allow the establishment of strong invariants, and
programming without them is much harder.
On the other hand, stopping exceptions in dtors is absolutely the
right thing to do.
Yes, I may have said that backwards. As I've said before, although I use
C++, it's not a "native language" for me, and a lot of this is based on
opinions others have strongly stated rather than my own knowledge or
experience. Another reason, incidentally, for not trying to come down
too hard on one side or the other where language concerns might trump
"threading" concerns.
Post by David Abrahams
Post by Dave Butenhof
and they have some legitimate concerns about common C++ language
patterns that might pretty much prevent a cancel from ever doing
what a cancel should do.
Really, legitimate concerns? I can't think of any recommended patterns that would act that way.
Hmm. OK. That's interesting. Well, you're the C++ expert here. ;-)
Post by David Abrahams
Post by Dave Butenhof
The catch lies in whether (and how far) you'll trust application
developers to do the re-throw properly. If we don't clean up all
frames and eventually re-throw the cancel/exit to the runtime's base
frame to terminate the thread, then we don't have cancellation.
I'm of the school that says it's futile and even dangerous to try to
operate correctly in an environment where you have to assume other
code is incorrect. My library will probably be equally broken if the
user decides to throw out my bad_alloc exception without a proper
response.
True enough. "Over-engineering" is a persistent danger. Sometimes we
just need to accept that we can't solve all problems, and that
"idiot-proofing" is a losing concept, and move on. ;-)
Post by David Abrahams
Post by Dave Butenhof
On the other hand, if we prevent a catch or force a re-throw, we
lose a lot of C++ (particularly in constructors).
I don't think that should be your concern. The correct and
well-written C++ we lose is generally sitting at module and language
boundaries. And then there are dtors.
Post by Dave Butenhof
Part of the reason that you "can't tell what side of it [I'd] come
down on" is that I've long recognized this as an essentially
religious rather than technical argument. You'll come down on the
side of the semantics toward which you feel the strongest emotional
attachment.
You don't find the idea that exception-safe code implies cancel-safe
code technically compelling? I don't think that's an emotional issue.
Well, yes, I do, because cancel was always intended to be "just" an
exception that happened to be thrown from another thread. But then,
nothing is ever that simple; the asynchronous nature required controls
like cancelability type and state. C++ exceptions are synchronous and
non-interrupting. (The latter a consequence of the former, really.) One
of the main advantages of cancellation is that it can break through an
extended blocking operation; but that's unavoidably an extra condition
over "exception-safety". Cancel-safe has to mean something more unless
we drop interruptibility. If we drop it, then cancel-safety is just
exception-safety but loses much of its value in controlling application
responsiveness.

In any case, though, I wasn't suggesting that you need to convince me.
I'm saying there are diverse and strongly held positions that somehow
need to be unified in order to get consensus on any proposal. I think
that I'm the least of your worries. ;-)
Post by David Abrahams
Post by Dave Butenhof
While I'm happy to express my experience and even preferences, I
also recognize that "the other side" has some equally strong
arguments and expectations, and they (well, most of them!) are not
"wrong".
I don't think either side is "wrong," either.
Post by Dave Butenhof
Someone needs to propose and champion "the great exception
compromise"; but if that's to be me I don't yet have the faintest
germ of a notion what it might be. So I sure hope it's going to be
someone else. ;-)
If "finalized cancellation exceptions result in a new throw at the
next cancellation point" isn't enough of a compromise, it isn't going
to be me either, because I'm out of new ideas.
Okay, how about this one: we count the number of times the
cancellation is discarded. The cancelling thread can specify the
number of discards to tolerate, where the default is infinite. After
that, at the next cancellation point all pthread cancellation handlers
(but not dtors or catch blocks) are run and the thread is terminated.
Heck, at that point I don't care what happens; you're gambling anyway.
Run all the dtors and catch blocks for all I care.
I do NOT favor any model where "dtor/catch" and "cancellation handler"
don't mean the same thing.

I don't think the count is tenable either because although it always
feels tempting to add a control dial, it doesn't solve any actual
problem if there's nobody who can know to what value the dial should be
set. In this case, I can't see how either the canceler OR any modular
call stack could possibly provide any useful data much less a single
numeric value.

If "canceled" state persists when the exception is discarded, then
cancel is something different from just "an exception"; which is too
bad, but perhaps inevitable. You can't just catch it and continue -- you
need to somehow also reset that state to recover your workgroup thread
that's serially running RPC requests (or Python code, whatever). A lot
of people have suggested various ways of making cancel-pending persist
after the exception is launched; that's not necessarily "wrong", but it
isn't "simple" either and somehow it doesn't feel right to me.
Post by David Abrahams
A simpler approach might be to have two kinds of exception: "forced"
and finalizable. At least then we can say that exception-safe code
implies finalizable cancellation safety. Then "forced" synchronous
cancellation can do whatever people desire. I personally think it
will become a useless appendage sort of like C++ exception
specifications, but at least evolution will take care of it. And if
I'm wrong, evolution will wilt my finalizable cancellations.
Is this the "unwind" vs "exception" idea? (Where "unwind" is like a new
sort of 'throw' that triggers dtors but can't be caught/finalized.) Or
something different...?
Post by David Abrahams
I guess I'm not totally out of ideas ;-)
David Abrahams
2006-03-06 22:10:54 UTC
Permalink
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
This has been THE most contentious issue in every C++/threads
discussion I've encountered since the beginning of (pthread) time.
My preference has always been that cancellation is an
exception. Period. In our initial CMA architecture, and in our
exception mapping of cancellation/thread-exit onto C language
exceptions in Tru64 UNIX and OpenVMS, it's possible and reasonable
to finalize propagation of a cancel/exit exception. That was
critical for DCE, for example, so that it could trap cancellation
of an RPC server thread, bring the thread back into the server's
work pool, and propagate the exception across the wire to the
client.
That sounds highly analogous to my case with Python.
Sure; and there are other examples. I've just found that the "inverted
call stack" of this sort of server setup seems to make sense to a lot of
people.
Okay. So there's a moderate-sized class of applications that need to
do this.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
To finalize a cancel/exit under almost any normal circumstance is
simply an application error.
The key word being "almost." In some situations, like those we've
both cited, it's absolutely necessary, to even get cancellation to
work.
Absolutely. There was a strong sub-faction in POSIX that can be
loosely characterized as "academics" who were determined to try to
prevent constructs that might be misused.
You mean, like, computers?
Post by Dave Butenhof
It's why the realtime people didn't get pthread_abort() to force
termination without cleanup, why you can't suspend a thread, or
"force unlock" a mutex that might have been abandoned, and so
forth.
Oh, too bad.
Post by Dave Butenhof
If cancellation can't be finalized, nobody can accidentally
finalize it; and that's great if you don't trust anyone to know when
it SHOULD be finalized. I started out as something of an "academic"
in this sense and evolved into a pramatist... if someone thinks they
need it, and they're right, don't keep them from doing it. And if
they're WRONG, it's their problem, not yours. ;-) Portable Java GC
would have been a lot easier had POSIX included suspend/resume; and
does it really matter that nearly anyone else who used it would be
breaking their application? Well, it all depends on your point of view...
I don't know about that suspend/resume, but I don't think legitimate
cases where finalization is needed are nearly so rare as you describe
cases where suspend/resume is needed to be.
Post by Dave Butenhof
Post by David Abrahams
A ctor that does catch(...) without rethrow is almost always badly
designed at best. There was unfortunate advice going around for many
ctors that throw allow the establishment of strong invariants, and
programming without them is much harder.
On the other hand, stopping exceptions in dtors is absolutely the
right thing to do.
Yes, I may have said that backwards. As I've said before, although I use
C++, it's not a "native language" for me, and a lot of this is based on
opinions others have strongly stated
It wouldn't surprise me a bit if others had strongly stated ctors
shouldn't throw. It used to be the advice in Stroustrup's books.
Post by Dave Butenhof
rather than my own knowledge or experience. Another reason,
incidentally, for not trying to come down too hard on one side or
the other where language concerns might trump "threading" concerns.
Post by David Abrahams
Post by Dave Butenhof
and they have some legitimate concerns about common C++ language
patterns that might pretty much prevent a cancel from ever doing
what a cancel should do.
Really, legitimate concerns? I can't think of any recommended
patterns that would act that way.
Hmm. OK. That's interesting. Well, you're the C++ expert here. ;-)
None other than, "prevent exceptions from leaking across language
boundaries." But if you don't do that, your program is broken anyway.
Post by Dave Butenhof
Post by David Abrahams
You don't find the idea that exception-safe code implies cancel-safe
code technically compelling? I don't think that's an emotional issue.
Well, yes, I do,
find it technically compelling, or think it's an emotional issue?
Post by Dave Butenhof
because cancel was always intended to be "just" an exception that
happened to be thrown from another thread. But then, nothing is ever
that simple; the asynchronous nature required controls like
cancelability type and state.
"Asynchronous nature?"

I haven't even been considering asynchronous cancellation as it's
completely untenable to write anything but the most restricted code
that could work in the face of async cancellation.
Post by Dave Butenhof
C++ exceptions are synchronous and non-interrupting. (The latter a
consequence of the former, really.) One of the main advantages of
cancellation is that it can break through an extended blocking
operation;
If you make all blocking operations cancellation points you can do
that anyway. No?
Post by Dave Butenhof
but that's unavoidably an extra condition over "exception-safety".
Cancel-safe has to mean something more unless we drop
interruptibility. If we drop it, then cancel-safety is just
exception-safety but loses much of its value in controlling
application responsiveness.
You lost me. I think async cancel safety should be thought of as a
separate level of design.
Post by Dave Butenhof
In any case, though, I wasn't suggesting that you need to convince
me. I'm saying there are diverse and strongly held positions that
somehow need to be unified in order to get consensus on any
proposal. I think that I'm the least of your worries. ;-)
Not that you have any obligation to do so, but it might be easier if
you would recognize the weight your opinion carries. That might mean
learning enough about C++ to form a definite opinion. That's, at
least, what I've tried to do with threading.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Someone needs to propose and champion "the great exception
compromise"; but if that's to be me I don't yet have the faintest
germ of a notion what it might be. So I sure hope it's going to be
someone else. ;-)
If "finalized cancellation exceptions result in a new throw at the
next cancellation point" isn't enough of a compromise, it isn't going
to be me either, because I'm out of new ideas.
Okay, how about this one: we count the number of times the
cancellation is discarded. The cancelling thread can specify the
number of discards to tolerate, where the default is infinite. After
that, at the next cancellation point all pthread cancellation handlers
(but not dtors or catch blocks) are run and the thread is terminated.
Heck, at that point I don't care what happens; you're gambling anyway.
Run all the dtors and catch blocks for all I care.
I do NOT favor any model where "dtor/catch" and "cancellation handler"
don't mean the same thing.
Like I said, I don't care at that point. A forced cancellation is a
big gamble. If "you" want to roll the dice, it's your funeral.
Post by Dave Butenhof
I don't think the count is tenable either because although it always
feels tempting to add a control dial, it doesn't solve any actual
problem if there's nobody who can know to what value the dial should be
set.
Which is why I backed off to the simpler model below.
Post by Dave Butenhof
If "canceled" state persists when the exception is discarded, then
cancel is something different from just "an exception";
It already was something different. The state needed to be stored
somewhere until the next cancellation point. This just says that the
state persists until otherwise specified.
Post by Dave Butenhof
which is too bad, but perhaps inevitable. You can't just catch it
and continue -- you need to somehow also reset that state to recover
your workgroup thread that's serially running RPC requests (or
Python code, whatever).
We could do something awful, like have catch-cancellation-by-value
cause the state to be reset, while catch(...) and
catch-cancellation-by-reference don't. That would preserve the
convenience, at least.
Post by Dave Butenhof
A lot of people have suggested various ways of making cancel-pending
persist after the exception is launched; that's not necessarily
"wrong", but it isn't "simple" either
and somehow it doesn't feel
right to me.
It's simpler, by most measures I can think of, than resetting the
state upon throwing.
Post by Dave Butenhof
Post by David Abrahams
A simpler approach might be to have two kinds of exception: "forced"
and finalizable. At least then we can say that exception-safe code
implies finalizable cancellation safety. Then "forced" synchronous
cancellation can do whatever people desire. I personally think it
will become a useless appendage sort of like C++ exception
specifications, but at least evolution will take care of it. And if
I'm wrong, evolution will wilt my finalizable cancellations.
Is this the "unwind" vs "exception" idea? (Where "unwind" is like a new
sort of 'throw' that triggers dtors but can't be caught/finalized.) Or
something different...?
No, I wasn't suggesting anything that couldn't be caught. I was just
suggesting an exception that couldn't be stopped. It could throw
itself in its dtor (not that I'm advocating it, but it might satisfy
the "other side"), for example.

In fact, a general mechanism like:

cancel( thread_id, exception_object );

is possible, where "cancel" really means throw a copy of the given
exception object when the specified thread reaches the next
cancellation point.

We could call it

throw_synchronously( thread_id, exception_object );

instead, if "cancel" really means forced execution to too many people.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Dave Butenhof
2006-03-06 23:45:58 UTC
Permalink
Post by David Abrahams
Post by Dave Butenhof
Absolutely. There was a strong sub-faction in POSIX that can be
loosely characterized as "academics" who were determined to try to
prevent constructs that might be misused.
You mean, like, computers?
OK, that's the laugh for today. Yeah, well, that's certainly what you
get if you take it too far to the extreme.
Post by David Abrahams
Post by Dave Butenhof
It's why the realtime people didn't get pthread_abort() to force
termination without cleanup, why you can't suspend a thread, or
"force unlock" a mutex that might have been abandoned, and so
forth.
Oh, too bad.
Yes and no. There's a line, somewhere, between something that's
reasonably usable by some set of people to solve real problems, even if
it can be easily and destructively misused by others; and something that
makes a fun toy for a few researchers but is virtually impossible to use
correctly or safely in real code. A lot of the argument hinges on where
to draw that line; it's almost never "cut and dried".
Post by David Abrahams
Post by Dave Butenhof
If cancellation can't be finalized, nobody can accidentally
finalize it; and that's great if you don't trust anyone to know when
it SHOULD be finalized. I started out as something of an "academic"
in this sense and evolved into a pramatist... if someone thinks they
need it, and they're right, don't keep them from doing it. And if
they're WRONG, it's their problem, not yours. ;-) Portable Java GC
would have been a lot easier had POSIX included suspend/resume; and
does it really matter that nearly anyone else who used it would be
breaking their application? Well, it all depends on your point of view...
I don't know about that suspend/resume, but I don't think legitimate
cases where finalization is needed are nearly so rare as you describe
cases where suspend/resume is needed to be.
Probably. I probably also got carried away with the analogy, because the
amount of detail was largely irrelevant in this forum.
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
A ctor that does catch(...) without rethrow is almost always badly
designed at best. There was unfortunate advice going around for many
ctors that throw allow the establishment of strong invariants, and
programming without them is much harder.
On the other hand, stopping exceptions in dtors is absolutely the
right thing to do.
Yes, I may have said that backwards. As I've said before, although I use
C++, it's not a "native language" for me, and a lot of this is based on
opinions others have strongly stated
It wouldn't surprise me a bit if others had strongly stated ctors
shouldn't throw. It used to be the advice in Stroustrup's books.
And often I think it makes sense to construct a viable object even in
the face of errors, and restrict the behavior of the object later.
Post by David Abrahams
Post by Dave Butenhof
rather than my own knowledge or experience. Another reason,
incidentally, for not trying to come down too hard on one side or
the other where language concerns might trump "threading" concerns.
Post by David Abrahams
Post by Dave Butenhof
and they have some legitimate concerns about common C++ language
patterns that might pretty much prevent a cancel from ever doing
what a cancel should do.
Really, legitimate concerns? I can't think of any recommended
patterns that would act that way.
Hmm. OK. That's interesting. Well, you're the C++ expert here. ;-)
None other than, "prevent exceptions from leaking across language
boundaries." But if you don't do that, your program is broken anyway.
Well, non-exception-savvy languages, sure. But (OK, perhaps a VMS bias
here) I think all languages on a platform should have a common exception
model that interoperates cleanly when stack frames are interleaved.
Certainly if you're interleaved for Java, or Ada, you should be able to
have an exception propagate through and unwind, run destructors (or Ada
finally clauses), etc., with no problems. And where ISO C is extended
for exception semantics (e.g., VMS, Tru64, Windows...), C can join the club.

Sigh. The best thing about the abandoned effort to build an Itanium ABI
for the Single UNIX Specification was that we'd succeeded in extracting
the specification of a C++ exception runtime as the beginning of a
standard cross platform and cross language common exception runtime.
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
You don't find the idea that exception-safe code implies cancel-safe
code technically compelling? I don't think that's an emotional issue.
Well, yes, I do,
find it technically compelling, or think it's an emotional issue
What I meant was the former. However the fact that I, or you, find it
technically compelling doesn't make it right, and certainly doesn't mean
everyone will agree. And historically the disagreement has indeed been
emotional. So, both are accurate.
Post by David Abrahams
Post by Dave Butenhof
because cancel was always intended to be "just" an exception that
happened to be thrown from another thread. But then, nothing is ever
that simple; the asynchronous nature required controls like
cancelability type and state.
"Asynchronous nature?"
I haven't even been considering asynchronous cancellation as it's
completely untenable to write anything but the most restricted code
that could work in the face of async cancellation.
Ah, but cancellation is basically asynchronous with respect to the
receiving thread. Even though we deliver the exception only at defined
synchronous points, the cancellation request can arrive at any instant.
This is mostly relevant when you talk about blocking behavior -- that a
blocking operation can be interrupted anywhere in the middle IS
asynchronous.

"Deferred" cancelability converts that asynchronous interrupt into a
synchronous exception, though the definition of the blocking operation
as a cancellation point. So the cancelled thread doesn't necessarily see
the exception as "asynchronous" (it called a function, and got back an
exception); but that doesn't change the fact that it really was
asynchronous all the same.

But I don't mean it in anything like the sense of "asynchronous
cancelability mode", where the exception can be raised (cancel
delivered) at any arbitrary point. Asynchronous cancelability was
invented for use in tight compute-bound loops, and intended to be
unusable anywhere else. It's one of the things we thought reasonable at
the time but that I've since become convinced should have been on the
other side of the "too unsafe and rarely useful to be standardized" line.
Post by David Abrahams
Post by Dave Butenhof
C++ exceptions are synchronous and non-interrupting. (The latter a
consequence of the former, really.) One of the main advantages of
cancellation is that it can break through an extended blocking
operation;
If you make all blocking operations cancellation points you can do
that anyway. No?
That's the intent...
Post by David Abrahams
Post by Dave Butenhof
but that's unavoidably an extra condition over "exception-safety".
Cancel-safe has to mean something more unless we drop
interruptibility. If we drop it, then cancel-safety is just
exception-safety but loses much of its value in controlling
application responsiveness.
You lost me. I think async cancel safety should be thought of as a
separate level of design.
Um, OK; I'm not sure where I lost you. I'm not talking about
asynchronous cancelability. I personally don't think C++ should even
briefly entertain the notion of any support for that.

However, when cancellation is enabled, any blocking call (or any
method/operator that makes or might make a blocking call, like "cout<<",
might raise an exception. Not all code will be prepared to handle that,
and much shouldn't be; it's important to be able to disable
cancelability dynamically over critical scopes. It's not like most
exceptions where the conditions for an exception are generally static;
it could happen at any time for reasons the current thread cannot
possibly anticipate. It's asynchronous simply because it's external and
independent. Also, where a normal exception means "something's wrong and
I can't continue on this code path", cancellation means sometime subtly
different -- "I've been asked not to" rather than "I can't"; but if you
must, you may. ;-)

Therefore we have cancelability state to managed scoped local control
over when the thread can respond to cancellation requests. (An obvious
candidate, in C++, for guard objects.)
Post by David Abrahams
Post by Dave Butenhof
In any case, though, I wasn't suggesting that you need to convince
me. I'm saying there are diverse and strongly held positions that
somehow need to be unified in order to get consensus on any
proposal. I think that I'm the least of your worries. ;-)
Not that you have any obligation to do so, but it might be easier if
you would recognize the weight your opinion carries. That might mean
learning enough about C++ to form a definite opinion. That's, at
least, what I've tried to do with threading.
I'm not ignorant of C++, and I'm much less ignorant than I was 2 years
ago when I started working with C++ and STL on a regular basis. Still, I
am not steeped in the history and tradition of C++ as I am in threads,
and probably never will be. More than that, while I have an
authoritative voice on the POSIX working group and in the community, I'm
not involved with the C++ committee and have no time or management
support to get involved; and I won't put myself in the position of being
an outside expert in some other area pretending to tell the C++
committee what it must (or even should) do. I will happily say that as a
thread expert and C++ dabbler, this is what seems to make sense to me;
but I reject any aura of authority in the C++ side of semantics and syntax.

However my statement above wasn't in any way related to my tradition of
C++ deference. I was merely stating that I've seen many opinions (other
than mine) that will need to be resolved or accommodated to make a standard.
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Someone needs to propose and champion "the great exception
compromise"; but if that's to be me I don't yet have the faintest
germ of a notion what it might be. So I sure hope it's going to be
someone else. ;-)
If "finalized cancellation exceptions result in a new throw at the
next cancellation point" isn't enough of a compromise, it isn't going
to be me either, because I'm out of new ideas.
Okay, how about this one: we count the number of times the
cancellation is discarded. The cancelling thread can specify the
number of discards to tolerate, where the default is infinite. After
that, at the next cancellation point all pthread cancellation handlers
(but not dtors or catch blocks) are run and the thread is terminated.
Heck, at that point I don't care what happens; you're gambling anyway.
Run all the dtors and catch blocks for all I care.
I do NOT favor any model where "dtor/catch" and "cancellation handler"
don't mean the same thing.
Like I said, I don't care at that point. A forced cancellation is a
big gamble. If "you" want to roll the dice, it's your funeral.
Post by Dave Butenhof
I don't think the count is tenable either because although it always
feels tempting to add a control dial, it doesn't solve any actual
problem if there's nobody who can know to what value the dial should be
set.
Which is why I backed off to the simpler model below.
Post by Dave Butenhof
If "canceled" state persists when the exception is discarded, then
cancel is something different from just "an exception";
It already was something different. The state needed to be stored
somewhere until the next cancellation point. This just says that the
state persists until otherwise specified.
Post by Dave Butenhof
which is too bad, but perhaps inevitable. You can't just catch it
and continue -- you need to somehow also reset that state to recover
your workgroup thread that's serially running RPC requests (or
Python code, whatever).
We could do something awful, like have catch-cancellation-by-value
cause the state to be reset, while catch(...) and
catch-cancellation-by-reference don't. That would preserve the
convenience, at least.
Um, yeah; I suppose it would. But I agree more strongly about the
"awful". ;-)
Post by David Abrahams
Post by Dave Butenhof
A lot of people have suggested various ways of making cancel-pending
persist after the exception is launched; that's not necessarily
"wrong", but it isn't "simple" either
and somehow it doesn't feel
right to me.
It's simpler, by most measures I can think of, than resetting the
state upon throwing.
Post by Dave Butenhof
Post by David Abrahams
A simpler approach might be to have two kinds of exception: "forced"
and finalizable. At least then we can say that exception-safe code
implies finalizable cancellation safety. Then "forced" synchronous
cancellation can do whatever people desire. I personally think it
will become a useless appendage sort of like C++ exception
specifications, but at least evolution will take care of it. And if
I'm wrong, evolution will wilt my finalizable cancellations.
Is this the "unwind" vs "exception" idea? (Where "unwind" is like a new
sort of 'throw' that triggers dtors but can't be caught/finalized.) Or
something different...?
No, I wasn't suggesting anything that couldn't be caught. I was just
suggesting an exception that couldn't be stopped. It could throw
itself in its dtor (not that I'm advocating it, but it might satisfy
the "other side"), for example.
The POSIX model where cancel propagates inexorably to thread termination
is an inherently flawed compromise; but simply the best we could do
within the context of ISO C and POSIX APIs. OUR implementation always
allowed finalization, via C++ catch(...), our ISO C "CATCH_ALL"
extensions, or whatever other language syntax might fit.

I really wouldn't want to propagate this restriction to C++.
Post by David Abrahams
cancel( thread_id, exception_object );
is possible, where "cancel" really means throw a copy of the given
exception object when the specified thread reaches the next
cancellation point.
We could call it
throw_synchronously( thread_id, exception_object );
instead, if "cancel" really means forced execution to too many people.
That's actually where we started out in CMA. Resolving down to a single
pre-defined exception was partly a matter of simplicity, but also
represented a basic thread model that "a thread is simple; it's an
asynchronous procedure call within the context of an application, not an
independent application". In any case where you would need to
distinguish between two separate interrupt conditions, the functions
stimulated by those separate interrupts should have been assigned to
separate threads and therefore only one exception is needed.

While this made a lot of sense at the time, we were in a very academic
and theoretical phase, and there was not that great a body of threaded
code in 1987 -- and none using anything closely resembling the thread
model we were inventing and that became the principal influence for
POSIX. SRC's Firefly/Modula-3 had "alert", but it was so much simpler as
to be a distinct variety of beast.

One advantage, though, of the single cancel exception, is that it's
universal. When you asynchronously issue a cancel request for a thread,
you can't really know what code is executing: your's, STL, some other
shared library, etc. Cancel means the same to all of them, and either is
supported with commonly agreed semantics or will be ignored (by
disabling cancellation in critical scopes). Once you start firing off
your own arbitrary exceptions, though, anything might happen because
half the time the exceptions won't belong anywhere in the call tree
that's active at the time they arrive.

Which brings us back to the "academic" resolution: if an exception means
distinct things in different call trees, those call trees should be
distinct threads and only one universal exception is necessary. ;-)
David Abrahams
2006-03-07 21:49:39 UTC
Permalink
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Yes, I may have said that backwards. As I've said before, although
I use C++, it's not a "native language" for me, and a lot of this
is based on opinions others have strongly stated
It wouldn't surprise me a bit if others had strongly stated ctors
shouldn't throw. It used to be the advice in Stroustrup's books.
And often I think it makes sense to construct a viable object even in
the face of errors, and restrict the behavior of the object later.
Weak invariants are the result, if you're saying what it sounds like
you're saying. Weak invariants are almost always worth avoiding.
Post by Dave Butenhof
Post by David Abrahams
None other than, "prevent exceptions from leaking across language
boundaries." But if you don't do that, your program is broken anyway.
Well, non-exception-savvy languages, sure. But (OK, perhaps a VMS bias
here) I think all languages on a platform should have a common exception
model that interoperates cleanly when stack frames are interleaved.
That's nice unless someone decides they're going to write in standard
'C.' And most people do, right? In general, take an arbitrary Unix
library or program written in 'C', and you're unlikely to find any EH
support in there.
Post by Dave Butenhof
Certainly if you're interleaved for Java, or Ada, you should be able to
have an exception propagate through and unwind, run destructors (or Ada
finally clauses), etc., with no problems. And where ISO C is extended
for exception semantics (e.g., VMS, Tru64, Windows...), C can join the club.
Can, yes. But speaking broadly, it's not likely to be the case.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
You don't find the idea that exception-safe code implies cancel-safe
code technically compelling? I don't think that's an emotional issue.
Well, yes, I do,
find it technically compelling, or think it's an emotional issue
What I meant was the former. However the fact that I, or you, find
it technically compelling doesn't make it right, and certainly
doesn't mean everyone will agree.
Goes without saying.
Post by Dave Butenhof
And historically the disagreement has indeed been emotional. So,
both are accurate.
Whatever.
Post by Dave Butenhof
Post by David Abrahams
I haven't even been considering asynchronous cancellation as it's
completely untenable to write anything but the most restricted code
that could work in the face of async cancellation.
Ah, but cancellation is basically asynchronous with respect to the
receiving thread.
Not the kind of cancellation that uses cancellation points.
"Asynchronous exception" means an exception that emanates from code
that is not allowed to throw unless undefined behavior is invoked.
Everything else is a synchronous exception. If you call some of that
latter stuff "asynchronous," (a)synchronicity of exceptions becomes a
useless distinction.
Post by Dave Butenhof
Even though we deliver the exception only at defined synchronous
points, the cancellation request can arrive at any instant.
But exactly when it arrives is irrelevant with respect to the
receiving thread.
Post by Dave Butenhof
This is mostly relevant when you talk about blocking behavior --
that a blocking operation can be interrupted anywhere in the middle
IS asynchronous.
Huh? It sounds like you're interested in some detail that I don't yet
have context for.
Post by Dave Butenhof
"Deferred" cancelability converts that asynchronous interrupt into a
synchronous exception, though the definition of the blocking operation
as a cancellation point. So the cancelled thread doesn't necessarily see
the exception as "asynchronous" (it called a function, and got back an
exception); but that doesn't change the fact that it really was
asynchronous all the same.
Cancellation can be as asynchronous as you like from that definition.
The resulting exception is still synchronous.
Post by Dave Butenhof
But I don't mean it in anything like the sense of "asynchronous
cancelability mode", where the exception can be raised (cancel
delivered) at any arbitrary point.
Good. Let's pick different terms that don't lead to this confusion,
please.
Post by Dave Butenhof
Asynchronous cancelability was invented for use in tight
compute-bound loops, and intended to be unusable anywhere else. It's
one of the things we thought reasonable at the time but that I've
since become convinced should have been on the other side of the
"too unsafe and rarely useful to be standardized" line.
Post by David Abrahams
Post by Dave Butenhof
C++ exceptions are synchronous and non-interrupting. (The latter a
consequence of the former, really.) One of the main advantages of
cancellation is that it can break through an extended blocking
operation;
If you make all blocking operations cancellation points you can do
that anyway. No?
That's the intent...
Post by David Abrahams
Post by Dave Butenhof
but that's unavoidably an extra condition over "exception-safety".
Cancel-safe has to mean something more unless we drop
interruptibility. If we drop it, then cancel-safety is just
exception-safety but loses much of its value in controlling
application responsiveness.
You lost me. I think async cancel safety should be thought of as a
separate level of design.
Um, OK; I'm not sure where I lost you. I'm not talking about
asynchronous cancelability. I personally don't think C++ should even
briefly entertain the notion of any support for that.
Great. Sounds like there's no disagreement, then.
Post by Dave Butenhof
However, when cancellation is enabled, any blocking call (or any
method/operator that makes or might make a blocking call, like
"cout<<", might raise an exception. Not all code will be prepared to
handle that, and much shouldn't be; it's important to be able to
disable cancelability dynamically over critical scopes. It's not
like most exceptions where the conditions for an exception are
generally static; it could happen at any time for reasons the
current thread cannot possibly anticipate.
That's exactly like most exceptions (c.f. out-of-memory). Usually
conditions whose reasons can be anticipated can be effectively tested
and become preconditions or simply should be reported by other means.
Post by Dave Butenhof
It's asynchronous simply because it's external and
independent. Also, where a normal exception means "something's wrong
and I can't continue on this code path", cancellation means sometime
subtly different -- "I've been asked not to" rather than "I can't";
but if you must, you may. ;-)
Once the exception is thrown, it amounts to exactly the same thing.
Post by Dave Butenhof
Therefore we have cancelability state to managed scoped local control
over when the thread can respond to cancellation requests. (An obvious
candidate, in C++, for guard objects.)
Okay. Not sure what your point is, but I don't disagree.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
In any case, though, I wasn't suggesting that you need to convince
me. I'm saying there are diverse and strongly held positions that
somehow need to be unified in order to get consensus on any
proposal. I think that I'm the least of your worries. ;-)
Not that you have any obligation to do so, but it might be easier if
you would recognize the weight your opinion carries. That might mean
learning enough about C++ to form a definite opinion. That's, at
least, what I've tried to do with threading.
I'm not ignorant of C++,
I didn't think so.
Post by Dave Butenhof
and I'm much less ignorant than I was 2 years ago when I started
working with C++ and STL on a regular basis. Still, I am not steeped
in the history and tradition of C++ as I am in threads, and probably
never will be. More than that, while I have an authoritative voice
on the POSIX working group and in the community, I'm not involved
with the C++ committee and have no time or management support to get
involved; and I won't put myself in the position of being an outside
expert in some other area pretending to tell the C++ committee what
it must (or even should) do.
I don't think you should. There are other areas where you could make
a big difference, though, like the ISO committee for C++/POSIX binding
Mr. Drepper is now running.
Post by Dave Butenhof
I will happily say that as a thread expert and C++ dabbler, this is
what seems to make sense to me; but I reject any aura of authority
in the C++ side of semantics and syntax.
However my statement above wasn't in any way related to my tradition
of C++ deference. I was merely stating that I've seen many opinions
(other than mine) that will need to be resolved or accommodated to
make a standard.
Understood. That's normal.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
A simpler approach might be to have two kinds of exception: "forced"
and finalizable. At least then we can say that exception-safe code
implies finalizable cancellation safety. Then "forced" synchronous
cancellation can do whatever people desire. I personally think it
will become a useless appendage sort of like C++ exception
specifications, but at least evolution will take care of it. And if
I'm wrong, evolution will wilt my finalizable cancellations.
Is this the "unwind" vs "exception" idea? (Where "unwind" is like a
new sort of 'throw' that triggers dtors but can't be
caught/finalized.) Or something different...?
No, I wasn't suggesting anything that couldn't be caught. I was just
suggesting an exception that couldn't be stopped. It could throw
itself in its dtor (not that I'm advocating it, but it might satisfy
the "other side"), for example.
The POSIX model where cancel propagates inexorably to thread termination
is an inherently flawed compromise; but simply the best we could do
within the context of ISO C and POSIX APIs. OUR implementation always
allowed finalization, via C++ catch(...), our ISO C "CATCH_ALL"
extensions, or whatever other language syntax might fit.
I really wouldn't want to propagate this restriction to C++.
Be clear, I'm not talking about a restriction. If you ask it to throw
something normal, it's finalizable in the normal way. This is a way
for the _cancelling_ thread to say, "I know what I'm doing; the author
of the thread I'm cancelling doesn't. Force it to be killed at the
next cancellation point."
Post by Dave Butenhof
Post by David Abrahams
cancel( thread_id, exception_object );
is possible, where "cancel" really means throw a copy of the given
exception object when the specified thread reaches the next
cancellation point.
We could call it
throw_synchronously( thread_id, exception_object );
instead, if "cancel" really means forced execution to too many people.
That's actually where we started out in CMA. Resolving down to a
single pre-defined exception was partly a matter of simplicity, but
also represented a basic thread model that "a thread is simple; it's
an asynchronous procedure call within the context of an application,
not an independent application". In any case where you would need to
distinguish between two separate interrupt conditions, the functions
stimulated by those separate interrupts should have been assigned to
separate threads and therefore only one exception is needed.
While this made a lot of sense at the time, we were in a very
academic and theoretical phase, and there was not that great a body
of threaded code in 1987 -- and none using anything closely
resembling the thread model we were inventing and that became the
principal influence for POSIX. SRC's Firefly/Modula-3 had "alert",
but it was so much simpler as to be a distinct variety of beast.
One advantage, though, of the single cancel exception, is that it's
universal. When you asynchronously issue a cancel request for a thread,
you can't really know what code is executing: your's, STL, some other
shared library, etc. Cancel means the same to all of them, and either is
supported with commonly agreed semantics or will be ignored (by
disabling cancellation in critical scopes). Once you start firing off
your own arbitrary exceptions, though, anything might happen because
half the time the exceptions won't belong anywhere in the call tree
that's active at the time they arrive.
That's not the way most exception-safe code works. It goes to the
reason that exception-specifications are a failure: the particular
type of exception that propagates out of a throwing function makes
almost no difference to anyone. The type only becomes important where
errors are reported, or where exceptions are translated -- either to
other exception types or, for example, to error return codes that can
propagate through other languages. So the danger of injecting an
arbitrary exception type into existing code (especially libraries,
which are very often exception-neutral) is very very low.
Post by Dave Butenhof
Which brings us back to the "academic" resolution: if an exception
means distinct things in different call trees, those call trees
should be distinct threads and only one universal exception is
necessary. ;-)
I think you might be missing the point. I am proposing the generalized

thread_throw( thread_id, exception_object )

function so that those who wish to hang themselves with homegrown
unstoppable exception types can do so without forcing the standard to
sanction the use of unstoppable exceptions by providing any kind of
"forced cancellation." If "the other side" has A WAY to force
cancellation, maybe they won't insist it has to be THE WAY. I know,
wishful thinking :)
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Dave Butenhof
2006-03-08 02:22:32 UTC
Permalink
I'm cutting off a lot of the top part. It's getting long, the discussion
isn't really going anywhere relevant to the root topic, and we're
probably better off just dropping it. That, at any rate, is my
preference. ;-)
Post by David Abrahams
Post by Dave Butenhof
Ah, but cancellation is basically asynchronous with respect to the
receiving thread.
Not the kind of cancellation that uses cancellation points.
"Asynchronous exception" means an exception that emanates from code
that is not allowed to throw unless undefined behavior is invoked.
Everything else is a synchronous exception. If you call some of that
latter stuff "asynchronous," (a)synchronicity of exceptions becomes a
useless distinction.
The entire point of threads is that their operation is asynchronous.
Everything about the design of a threaded application, a runtime and OS
that support threads, a LANGUAGE that supports threads, has to be to
deal with asynchrony -- with parallel call stacks running
simultaneously. Anything that happens in or because of a thread is
asynchronous with respect to other threads.

Deferred cancellation is a "conversion interface" to allow the
asynchronous external event to manifest synchronously in the target
thread, for ease and consistency of cleanup. But that doesn't make the
event synchronous, and I think that the distinction is important.

Cancellation is asynchronous up to the point where the exception is
thrown, synchronously, by the target thread to unwind and terminate. A
blocking read operation isn't cancelled synchronously at the beginning
or end of the operation, or even the beginning or end of the blocking
part of the I/O -- it can be interrupted asynchronously somewhere in the
middle. In my implementation, I initiated the unwind (the "throw") from
the interrupt handler, if cancellation is enabled, rather than deferring
it until the unblocked operation wakes up... though that would certainly
be another alternative.
Post by David Abrahams
Post by Dave Butenhof
Even though we deliver the exception only at defined synchronous
points, the cancellation request can arrive at any instant.
But exactly when it arrives is irrelevant with respect to the
receiving thread.
I suppose you can reasonably argue that. I think that understanding the
model is important when you making coding decisions.
Post by David Abrahams
Post by Dave Butenhof
This is mostly relevant when you talk about blocking behavior --
that a blocking operation can be interrupted anywhere in the middle
IS asynchronous.
Huh? It sounds like you're interested in some detail that I don't yet
have context for.
Perhaps I'm worried about thought models and implementation while you're
concerned with appearance. Both views are certainly valid within certain
domains.
Post by David Abrahams
Post by Dave Butenhof
"Deferred" cancelability converts that asynchronous interrupt into a
synchronous exception, though the definition of the blocking operation
as a cancellation point. So the cancelled thread doesn't necessarily see
the exception as "asynchronous" (it called a function, and got back an
exception); but that doesn't change the fact that it really was
asynchronous all the same.
Cancellation can be as asynchronous as you like from that definition.
The resulting exception is still synchronous.
Right. Cancellation is asynchronous. The exception that notifies the
thread of cancellation is synchronous. There we go. That's the
conversion interface.
Post by David Abrahams
Post by Dave Butenhof
But I don't mean it in anything like the sense of "asynchronous
cancelability mode", where the exception can be raised (cancel
delivered) at any arbitrary point.
Good. Let's pick different terms that don't lead to this confusion,
please.
But C++ people should be good at overloading. ;-)

"Asynchronous cancelability type" is (at least potentially) an
asynchronous EXCEPTION in response to an asynchronous event, while
"deferred cancelability type" is a synchronous (deferred) exception in
response to the same asynchronous event. Does that help?

Cancelability type affects only the DELIVERY of notification within the
target thread, not either the "launching" or "delivery" mechanisms of
cancellation.
Post by David Abrahams
Post by Dave Butenhof
However, when cancellation is enabled, any blocking call (or any
method/operator that makes or might make a blocking call, like
"cout<<", might raise an exception. Not all code will be prepared to
handle that, and much shouldn't be; it's important to be able to
disable cancelability dynamically over critical scopes. It's not
like most exceptions where the conditions for an exception are
generally static; it could happen at any time for reasons the
current thread cannot possibly anticipate.
That's exactly like most exceptions (c.f. out-of-memory). Usually
conditions whose reasons can be anticipated can be effectively tested
and become preconditions or simply should be reported by other means.
Sometimes. Exceptions are great for "out of band" notifications that may
not be intended for the direct caller; they can be picked up with full
state, and without additional mechanism, by anyone along the call path
who cares.
Post by David Abrahams
Post by Dave Butenhof
and I'm much less ignorant than I was 2 years ago when I started
working with C++ and STL on a regular basis. Still, I am not steeped
in the history and tradition of C++ as I am in threads, and probably
never will be. More than that, while I have an authoritative voice
on the POSIX working group and in the community, I'm not involved
with the C++ committee and have no time or management support to get
involved; and I won't put myself in the position of being an outside
expert in some other area pretending to tell the C++ committee what
it must (or even should) do.
I don't think you should. There are other areas where you could make
a big difference, though, like the ISO committee for C++/POSIX binding
Mr. Drepper is now running.
"ISO committee" is rather a strong description; it's a simple mailing
list that's hoping to gain some preliminary consensus towards
constructing a formal proposal to request permission to develop a
charter and start a working group with the intent of building a proposal
for a binding. But, yeah, OK, fine. ;-)

In any case, I am following it, and contributing to the (sporadic)
discussions. In fact, when I got your re-opening of this mailing list I
initially thought you were writing to that one; though this discussion
is rather more detailed and relevant than what we've seen so far on the
other.

I will get as involved as I can given my time constraints, but we'll see
what that amounts to as the mailing list discussions progress. I assure
you that if I hold back it's not for lack of interest or motivation. ;-)
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
No, I wasn't suggesting anything that couldn't be caught. I was just
suggesting an exception that couldn't be stopped. It could throw
itself in its dtor (not that I'm advocating it, but it might satisfy
the "other side"), for example.
The POSIX model where cancel propagates inexorably to thread termination
is an inherently flawed compromise; but simply the best we could do
within the context of ISO C and POSIX APIs. OUR implementation always
allowed finalization, via C++ catch(...), our ISO C "CATCH_ALL"
extensions, or whatever other language syntax might fit.
I really wouldn't want to propagate this restriction to C++.
Be clear, I'm not talking about a restriction. If you ask it to throw
something normal, it's finalizable in the normal way. This is a way
for the _cancelling_ thread to say, "I know what I'm doing; the author
of the thread I'm cancelling doesn't. Force it to be killed at the
next cancellation point."
The whole concept of cancellation is exactly that the TARGET thread, not
the cancelling thread, knows what it's doing and should control the
cleanup and termination entirely. Without explicit synchronization, the
cancelling thread can't know what it's doing and whether a forced abort
is appropriate or safe. And if it has enough synchronization to know
that, there are far better ways to gain a cooperative termination than
cancellation.
Post by David Abrahams
Post by Dave Butenhof
One advantage, though, of the single cancel exception, is that it's
universal. When you asynchronously issue a cancel request for a thread,
you can't really know what code is executing: your's, STL, some other
shared library, etc. Cancel means the same to all of them, and either is
supported with commonly agreed semantics or will be ignored (by
disabling cancellation in critical scopes). Once you start firing off
your own arbitrary exceptions, though, anything might happen because
half the time the exceptions won't belong anywhere in the call tree
that's active at the time they arrive.
That's not the way most exception-safe code works. It goes to the
reason that exception-specifications are a failure: the particular
type of exception that propagates out of a throwing function makes
almost no difference to anyone. The type only becomes important where
errors are reported, or where exceptions are translated -- either to
other exception types or, for example, to error return codes that can
propagate through other languages. So the danger of injecting an
arbitrary exception type into existing code (especially libraries,
which are very often exception-neutral) is very very low.
That's an intriguing statement. I'll need to think about that some.

I've certainly always thought that exception specifications were little
more than a trap into which people could mire themselves as deeply as
they like. So maybe that means I agree. I'm not sure. ;-)

I guess I'd have to agree that the danger of injecting another exception
type is low. And mostly due to the fact that C++ has no "root exception
type" onto which could be grafted some minimal universal state (an
architected status code space, like VMS condition codes, a descriptive
string, etc.) so that nobody would need an anonymous and semantic-free
catch(...) just to be sure nothing slipped past.
Post by David Abrahams
Post by Dave Butenhof
Which brings us back to the "academic" resolution: if an exception
means distinct things in different call trees, those call trees
should be distinct threads and only one universal exception is
necessary. ;-)
I think you might be missing the point. I am proposing the generalized
thread_throw( thread_id, exception_object )
function so that those who wish to hang themselves with homegrown
unstoppable exception types can do so without forcing the standard to
sanction the use of unstoppable exceptions by providing any kind of
"forced cancellation." If "the other side" has A WAY to force
cancellation, maybe they won't insist it has to be THE WAY. I know,
wishful thinking :)
I'm not sure how sanctioning generalized unstoppable exceptions is going
to mollify anyone opposed to an unstoppable variety of a specific
exception. At best, cancel becomes a subset of cross-thread throw with
specialized additional deferral semantics. And if you're generalizing
the unstoppable exception, I don't quite see how it makes sense not to
generalize the deferral, and now cancel really is just a specific
predefined exception that can be thrown like any other exception. That's
not necessarily bad; I just don't see how it's a compromise. (A
compromise needs to make BOTH sides equally unhappy, not just one side!)
Dave Butenhof
2006-03-08 13:31:51 UTC
Permalink
OK, it's a study group. Slightly more than a mailing list, but hardly
a committee. I guess I'm just alarmist.
I shouldn't belabor the point, but it really is just a mailing list at
this point. That is, membership and participation in the mailing list
does not imply any formal (or even semi-formal) association with a
"study group"; just willingness to sign up and maybe read, possibly even
reply, with no obligation.
Post by Dave Butenhof
I guess I'd have to agree that the danger of injecting another exception
type is low. And mostly due to the fact that C++ has no "root exception
type" onto which could be grafted some minimal universal state
It has a de-facto root type: std::exception.
And if it was used universally that would be almost as good as a de-jure
root type. ;-)
Post by Dave Butenhof
(an architected status code space, like VMS condition codes, a
descriptive string, etc.) so that nobody would need an anonymous and
semantic-free catch(...) just to be sure nothing slipped past.
When you have a root type, catching that is practically anonymous and
semantic-free. It doesn't make much difference in practice.
A root type with properties can be processed and explained, unlike a
truly anonymous 'catch(...)'. For example, I've been doing a lot of
programming lately with the WBEM distributed management system. It has a
C++ layer over the real XML and transport mechanism, making a fairly
decent RPC. (With some archaic annoyances like non-STL standard strings
and arrays because it was standardized before the real STL.) But only
CIM exceptions are carried back "over the wire" from server to client;
anything else allowed to propagate outside the server-side object comes
through just as "unknown error". That's the consequence of 'catch(...)'.

If there was a language requirement for example that all exceptions be
subclasses of std::exception, you could catch(const exception &e) and
always depend on being able to re-throw
CIMOperationFailedException(String(e.what())). In terms of code flow,
the difference is almost irrelevant (though I know of WBEM providers
that include distinctive text in their exception descriptions and
clients that parse the text); in terms of human interaction and even
error logging, the difference is significant.

DCE exceptions carried a standardized error code, which could be
decyphered into facility and cause fields, and looked up in a message
catalog. That has even greater advantages for code as well as humans,
although registering unique facility codes is always a headache.

But, OK, this is entirely off-topic basic C++ stuff, not "C++ pthreads",
so let's move on... ;-)
Additional? I'm lost again. There is no workable cross-thread throw
without deferral. Anything else is an asynchronous exception.
Hmm. So your thread_throw(), like pthread_cancel(), would simply stash
the exception object away somewhere for the target (victim?) thread to
throw synchronously at a later time? At the same defined cancellation
points? At a different set of points? Subject to cancelability state, or
at the next cancellation point no matter what? Do we need to worry about
"cancel disabled but other exceptions enabled", or vice versa... and
will people expect to be able to enable or disable individual exception
types?
Post by Dave Butenhof
And if you're generalizing the unstoppable exception,
Generalizing?
Post by Dave Butenhof
I don't quite see how it makes sense not to generalize the deferral,
and now cancel really is just a specific predefined exception that
can be thrown like any other exception. That's not necessarily bad;
I just don't see how it's a compromise. (A compromise needs to make
BOTH sides equally unhappy, not just one side!)
The compromise I'm proposing makes it possible to generate an
unstoppable cancellation (bad for us) while making it ugly and
unnatural to do so (bad for "the other side").
Ah; I think I misunderstood the semantics of your thread_throw(). From
your introduction I took it as a mechanism for injecting any exception
into another thread as "unstoppable". Apparently what you intended was
that it simply injects ANY exception, and if that happens to be a weird
"unstoppable" exception it works the same as it would from a normal
throw within a single call stack.

OK, I get it.

So fine, perhaps that is a viable compromise. I'm not "morally opposed"
to a generalized cross-thread exception. I'm not thrilled about anyone
injecting unstoppable exceptions into some "innocent" call stack; but
like cancellation it can only be done when you at least know the thread
ID of the call stack (meaning either you created the thread or the
facility that did made the ID available to you in one way or another,
though possibly just by making a call into your facility). And if
someone does it to some arbitrary and unprepared thread it'll may behave
just as badly as they deserve. ;-)
David Abrahams
2006-03-08 15:28:23 UTC
Permalink
Post by Dave Butenhof
Additional? I'm lost again. There is no workable cross-thread throw
without deferral. Anything else is an asynchronous exception.
Hmm. So your thread_throw(), like pthread_cancel(), would simply
stash the exception object away somewhere for the target (victim?)
thread to throw synchronously at a later time? At the same defined
cancellation points? At a different set of points?
Same points.
Post by Dave Butenhof
Subject to cancelability state, or at the next cancellation point no
matter what?
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions). I actually think the parenthesized part could be
removed, for all practical purposes. There's usually little point in
preventing a function from throwing cancellation if it can throw
something else.
Post by Dave Butenhof
Do we need to worry about "cancel disabled but other exceptions
enabled", or vice versa... and will people expect to be able to
enable or disable individual exception types?
Naw, I don't think so. Nobody has mentioned a use case. If we find
it is suddenly in demand because of some unanticipated use case, that
sort of selective cancelability is pretty easy to add.
Post by Dave Butenhof
Post by Dave Butenhof
And if you're generalizing the unstoppable exception,
Generalizing?
Post by Dave Butenhof
I don't quite see how it makes sense not to generalize the deferral,
and now cancel really is just a specific predefined exception that
can be thrown like any other exception. That's not necessarily bad;
I just don't see how it's a compromise. (A compromise needs to make
BOTH sides equally unhappy, not just one side!)
The compromise I'm proposing makes it possible to generate an
unstoppable cancellation (bad for us) while making it ugly and
unnatural to do so (bad for "the other side").
Ah; I think I misunderstood the semantics of your thread_throw(). From
your introduction I took it as a mechanism for injecting any exception
into another thread as "unstoppable". Apparently what you intended was
that it simply injects ANY exception, and if that happens to be a weird
"unstoppable" exception it works the same as it would from a normal
throw within a single call stack.
Correct.
Post by Dave Butenhof
OK, I get it.
So fine, perhaps that is a viable compromise. I'm not "morally
opposed" to a generalized cross-thread exception. I'm not thrilled
about anyone injecting unstoppable exceptions into some "innocent"
call stack; but like cancellation it can only be done when you at
least know the thread ID of the call stack (meaning either you
created the thread or the facility that did made the ID available to
you in one way or another, though possibly just by making a call
into your facility). And if someone does it to some arbitrary and
unprepared thread it'll may behave just as badly as they
deserve. ;-)
Yes.

I do have sympathy, incidentally, for the other side's desire to "try
really hard" to shut a thread down cleanly. I just don't happen to
think unstoppable exceptions are a particularly effective tool for
that job.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Dave Butenhof
2006-03-08 16:29:01 UTC
Permalink
Post by David Abrahams
Post by Dave Butenhof
So fine, perhaps that is a viable compromise. I'm not "morally
opposed" to a generalized cross-thread exception. I'm not thrilled
about anyone injecting unstoppable exceptions into some "innocent"
call stack; but like cancellation it can only be done when you at
least know the thread ID of the call stack (meaning either you
created the thread or the facility that did made the ID available to
you in one way or another, though possibly just by making a call
into your facility). And if someone does it to some arbitrary and
unprepared thread it'll may behave just as badly as they
deserve. ;-)
Yes.
I do have sympathy, incidentally, for the other side's desire to "try
really hard" to shut a thread down cleanly. I just don't happen to
think unstoppable exceptions are a particularly effective tool for
that job.
Indeed. We went through this with pthread_abort() in POSIX. For the
embedded realtime environment, it's both possible and often essential to
forcibly and immediately shut down a nonresponsive thread. In any
modular programming environment, where you don't control all of the code
in the process, it's impossible... and you'll get yourself into worse
trouble by trying.

It all goes back to the fact that threads are just asynchronous
procedure calls within the same application; they have no real
independence. Trash one set of invariants, and you can trash the entire
co-dependent environment with no way to analyze or clean up because of
all this annoying encapsulation and data hiding stuff. ;-)

An embedded application can know precisely what data any thread might
have touched, analyze and repair it all; though even there it's not easy
(or 100% reliable) in any complicated application. When some of the data
a thread might have touched is in STL, libc, or other libraries, you can
just forget it. The only way to forcibly trash a thread is to crash the
entire process and start over.

We didn't accept pthread_abort() because we were too concerned about
others trying to use it and getting themselves deep in trouble. And this
is probably well over the line where that was a reasonable decision.
It's fine for any embedded realtime environment to add its own
pthread_abort(); it's just not portable. But the application wouldn't
port anyway to any environment with a different set of state that needed
to be cleaned (much less any hidden state that can't be cleaned).

If you're just trying to get enough control to exit cleanly, there are
usually better ways. If you really intend to recover and continue --
you're probably already in way over your head. ;-)

A "forced unwind" is both better and worse... it is a cleaner mechanism,
but if the call stack context is really trashed somehow, you're less
likely to get the thread to actually terminate that way.
Mark Mitchell
2006-03-08 16:51:56 UTC
Permalink
Post by David Abrahams
I do have sympathy, incidentally, for the other side's desire to "try
really hard" to shut a thread down cleanly. I just don't happen to
think unstoppable exceptions are a particularly effective tool for
that job.
Exactly.

We have SIGKILL for processes so that we can be sure to get rid of them.
(Well, modulo the situations where that doesn't work...) But,
pthread_cancel is not analogous: for example a thread can run with
cancellation disabled, while a process cannot block SIGKILL. The reason
that people object to a pthread_kill is that there is no obvious way to
clean up memory resources, etc., allocated by the thread, in the same
way that the OS can reclaim resources used by a process. So, a
cooperative model makes sense.

I don't think we should support unstoppable exceptions at all. Silently
rethrowing at the end of a catch handler breaks too many important
invariants.
--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713
David Abrahams
2006-03-08 17:09:18 UTC
Permalink
Post by Mark Mitchell
I don't think we should support unstoppable exceptions at all.
Silently rethrowing at the end of a catch handler breaks too many
important invariants.
Usually, yes. Unfortunately that position is not much of a
compromise: it doesn't give an inch to the other side. If we allow a
generalized thread_throw then there's nothing we can do to stop people
from building unstoppable exceptions and throwing them across the
thread boundary, but to do so they'll have to write some tricky and
non-obvious code. I'm hoping that's enough for people like us to say
"don't do that: you get what you deserve when you write code like
that" and for other people to say "I'm satisfied that I _can_ get the
thread to stop once we hit a cancellation point with cancel enabled."
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Wil Evers
2006-03-08 21:41:54 UTC
Permalink
Post by David Abrahams
Post by Mark Mitchell
I don't think we should support unstoppable exceptions at all.
Silently rethrowing at the end of a catch handler breaks too many
important invariants.
Usually, yes. Unfortunately that position is not much of a
compromise: it doesn't give an inch to the other side. If we allow a
generalized thread_throw then there's nothing we can do to stop people
from building unstoppable exceptions and throwing them across the
thread boundary, but to do so they'll have to write some tricky and
non-obvious code. I'm hoping that's enough for people like us to say
"don't do that: you get what you deserve when you write code like
that" and for other people to say "I'm satisfied that I _can_ get the
thread to stop once we hit a cancellation point with cancel enabled."
So who/where is this alledged "other side"? IMHO, one of the inspiring
things in the discussion we've been witnessing here in the last couple
of days is that no one, not even David B., is actually arguing in favor
of unstoppable cancellation exceptions. (Mark mentioned Ulrich
Drepper's glibc implementation; we all know what he did, but so far, I
haven't found any explanation *why* he did that).

It seems to me that, before phantasising about any sort of compromise
with the "other side", we would need to know what the other side is
after, and why.

- Wil
Mark Mitchell
2006-03-08 22:02:52 UTC
Permalink
Post by Wil Evers
So who/where is this alledged "other side"?
IIRC, Jason Merill has also historically been sympathetic to the forced
exception mechanism, because (if I paraphrase correctly!) he feels that
it might help some code move into a threaded environment without change.
In particular, by ignoring "catch" handlers, some subset of real
programs might behave well enough, in that destructors would run to
clean things up, and the code wouldn't have to be modified to know about
thread-cancellation exceptions. However, again, if I recall correctly,
Jason was also OK with the idea that a thread could catch a cancellation
exception, but that future calls to cancellation points would cause it
to be re-canceled.

I might have just stuck an entire giant paragraph of words into Jason's
mouth, though, in which case I very humbly apologize.

As far as I remember, Ulrich is the only person who has really argued
that it is unambiguously wrong to consider the situation in which a
thread catches a cancellation exception.
--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713
Jason Merrill
2006-03-08 23:08:48 UTC
Permalink
Post by Mark Mitchell
Post by Wil Evers
So who/where is this alledged "other side"?
IIRC, Jason Merill has also historically been sympathetic to the forced
exception mechanism, because (if I paraphrase correctly!) he feels that
it might help some code move into a threaded environment without change.
In particular, by ignoring "catch" handlers, some subset of real
programs might behave well enough, in that destructors would run to
clean things up, and the code wouldn't have to be modified to know about
thread-cancellation exceptions.
Yes. This is the choice that Ada made for handling task cancellation;
it functions like an exception except that it cannot be caught, at least
not by user code.
Post by Mark Mitchell
However, again, if I recall correctly,
Jason was also OK with the idea that a thread could catch a cancellation
exception, but that future calls to cancellation points would cause it
to be re-canceled.
Yes. Cancellation is not just an indication of a problem like most
exceptions, it's specifically an attempt to unwind everything. There is
a lot of code out there that does catch (...), tries to do generic
recovery, and continue. iostreams does catch (...) and sets a flag
instead of propagating an exception. Neither of these situations should
cause the cancel to be discarded.

Both the Ada approach and the re-cancellation approach avoid this
problem of accidentally losing the cancellation request.

If you can interrupt cancellation, re-cancellation is implemented
trivially simply by just having the cancellation exception destructor
call 'pthread_cancel (pthread_self ())'. The sticking point is being
able to abort the cancellation in the first place, which is what Uli has
been opposed to.
Post by Mark Mitchell
As far as I remember, Ulrich is the only person who has really argued
that it is unambiguously wrong to consider the situation in which a
thread catches a cancellation exception.
I don't think he's opposed to catching it, just to doing anything that
would involve backing out of the cancellation once it's started.

Jason
Mark Mitchell
2006-03-08 23:15:37 UTC
Permalink
Post by Jason Merrill
If you can interrupt cancellation, re-cancellation is implemented
trivially simply by just having the cancellation exception destructor
call 'pthread_cancel (pthread_self ())'. The sticking point is being
able to abort the cancellation in the first place, which is what Uli has
been opposed to.
It sounds like we really are close to a solution then; I think everyone
here would be happy with the re-cancellation thing, and the destructor
trick means that there's really no way the thread can permanently
discard the cancellation request, short of things like longjmp -- and,
of course, if the thread is really determined not to go away it can just
hang around anyhow. So, it seems like this ought to satisfy everyone
from the user-level perspective.
Post by Jason Merrill
I don't think he's opposed to catching it, just to doing anything that
would involve backing out of the cancellation once it's started.
Would the above satisfy him?
--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713
Ted Baker
2006-03-09 00:30:29 UTC
Permalink
Post by Jason Merrill
If you can interrupt cancellation, re-cancellation is implemented
trivially simply by just having the cancellation exception destructor
call 'pthread_cancel (pthread_self ())'. The sticking point is being
able to abort the cancellation in the first place, which is what Uli has
been opposed to.
This would be illegal according to the C API, since once a thread
begins executing cancellation handlers it becomes (irrevocably)
not cancellable. That means a subsequent call to tthread_cancel
is required to have no effect on the thread.

So, if you want to do this you need a new function that is like
pthread_cancel but has special semantics. That is, the developer
of the C++ binding would need to coordinate with the implementor
of pthread_cancel to provide this function not in the POSIX C API.
... So, it seems like this ought to satisfy everyone
from the user-level perspective.
I've been talking about the implementor-perspective. A standard will not
be much good unless you can also persuade the implementors to buy in .

--Ted
Dave Butenhof
2006-03-09 01:26:29 UTC
Permalink
Post by Ted Baker
Post by Jason Merrill
If you can interrupt cancellation, re-cancellation is implemented
trivially simply by just having the cancellation exception destructor
call 'pthread_cancel (pthread_self ())'. The sticking point is being
able to abort the cancellation in the first place, which is what Uli has
been opposed to.
This would be illegal according to the C API, since once a thread
begins executing cancellation handlers it becomes (irrevocably)
not cancellable. That means a subsequent call to pthread_cancel
is required to have no effect on the thread.
The C binding is necessarily constrained; but we were careful (as much
as possible within the restrictions of POSIX scope) to be clear that
those constraints should not restrict more advanced language
implementation. In particular, that cancel was always intended to be an
exception and that in a language with the capability it SHOULD be. That
of course, can't be stated as a requirement, but it was our intent.
Post by Ted Baker
So, if you want to do this you need a new function that is like
pthread_cancel but has special semantics. That is, the developer
of the C++ binding would need to coordinate with the implementor
of pthread_cancel to provide this function not in the POSIX C API.
Yes; unfortunately most UNIX systems still lack a cross-language
exception infrastructure, so C++ and pthread cancel/exit, Ada, Java, and
so forth have each "rolled their own" largely incompatible interfaces.
Makes me miss OpenVMS and Tru64 UNIX, which did have common exceptions.
The original implementation of the POSIX C API, going back way before
there even WAS a POSIX C API, was based on VMS condition handling (OS
exceptions) and Tru64 UNIX libexc. Interoperable exception support for
cancel across C, C++, Ada, etc. was almost free since pretty much
everyone ran destructors, finally clauses and such even on foreign
exception unwinds. (Even if they didn't have named exception support to
catch them.)

Several other mainstream UNIX vendors nearly had a common exception API
as well as a result of the Itanium UNIX ABI effort, but apparently
everyone backed off when the group disbanded. (That was a major
disappointment.)
Post by Ted Baker
... So, it seems like this ought to satisfy everyone from the user-level perspective.
I've been talking about the implementor-perspective. A standard will not
be much good unless you can also persuade the implementors to buy in.
Nor will getting implementors on board if the final form isn't something
compellingly useful to developers. I have trouble believing that a C++
POSIX binding that doesn't use exceptions will be accepted easily,
widely, or quickly.
Ted Baker
2006-03-09 12:43:55 UTC
Permalink
Post by Dave Butenhof
Post by Ted Baker
I've been talking about the implementor-perspective. A standard will not
be much good unless you can also persuade the implementors to buy in.
Nor will getting implementors on board if the final form isn't something
compellingly useful to developers. I have trouble believing that a C++
POSIX binding that doesn't use exceptions will be accepted easily,
widely, or quickly.
Right. So, the POSIX C++ API must include exceptions, but the way
in which exceptions and thread cancellation interact needs to put
most (maybe all?) of the special burden of supporting C++ onto the
implementors of the C++ compiler and the C++ binding, not
maintainers of the C binding and the C compiler.

Of course, if the C and C++ bindings happen to be done by the same
person/group, this matters less. That may be the case with Gnu/gcc,
but it seems unwise to assume it will be the case on all platforms.

--Ted
Alexander Terekhov
2006-03-09 08:21:24 UTC
Permalink
Mark Mitchell <***@codesourcery.com> wrote:
[...]
Post by Mark Mitchell
It sounds like we really are close to a solution then; I think everyone
here would be happy with the re-cancellation thing, and the destructor
trick
It would not satisfy me. For one. Cancel unaware code should be made
cancel aware with "pthread_cancel (pthread_self ())" added only if/when
it is really needed. I mean stuff like

void oper() throw(int) {
/**/
fclose(/*..*/); // doesn't throw; cancel is unexpected -- language
// change with mandatory 2-phase EH and intelligent
// cancel delivery is required
}

transformed to

void oper() throw(int) {
/**/
try {
fclose(/*..*/); // can throw; cancel IS expected -- catch below
}
catch (std::thread_cancel_request const &) {
/**/
std::enable_thread_cancel(); // re-enable cancel state
std::thread_self().cancel(); // re-inject cancel request
/**/
}
}

Well, I guess I could live with

catch (std::thread_cancel_request & tcr) {
tcr.stop_stupid_stickyness();
}

but that's somewhat unsatisfying, so to speak.

regards,
alexander.


Mark Mitchell <***@codesourcery.com> on 09.03.2006 00:15:37

To: Jason Merrill <***@redhat.com>
cc: Wil Evers <***@bogo.xs4all.nl>, David Abrahams
<***@boost-consulting.com>, c++-***@codesourcery.com
Subject: Re: [c++-pthreads] Re: FW: RE: Re: I'm Lost
Post by Mark Mitchell
If you can interrupt cancellation, re-cancellation is implemented
trivially simply by just having the cancellation exception destructor
call 'pthread_cancel (pthread_self ())'. The sticking point is being
able to abort the cancellation in the first place, which is what Uli has
been opposed to.
It sounds like we really are close to a solution then; I think everyone
here would be happy with the re-cancellation thing, and the destructor
trick means that there's really no way the thread can permanently
discard the cancellation request, short of things like longjmp -- and,
of course, if the thread is really determined not to go away it can just
hang around anyhow. So, it seems like this ought to satisfy everyone
from the user-level perspective.
Post by Mark Mitchell
I don't think he's opposed to catching it, just to doing anything that
would involve backing out of the cancellation once it's started.
Would the above satisfy him?

--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713
Ted Baker
2006-03-09 00:26:30 UTC
Permalink
Post by Jason Merrill
Yes. This is the choice that Ada made for handling task cancellation;
it functions like an exception except that it cannot be caught, at least
not by user code.
Yes, and the POSIX Ada binding chose to ignore the C thread
cancellation API. It was both freed and forced to do so because
the Ada language standard provides equivalent (but not
interoperable) functionality, based on a unified view of a task
abort as a special kind of exception.
Post by Jason Merrill
Both the Ada approach and the re-cancellation approach avoid this
problem of accidentally losing the cancellation request.
Right.

--Ted
David Abrahams
2006-03-09 16:24:40 UTC
Permalink
Post by Jason Merrill
Post by Mark Mitchell
As far as I remember, Ulrich is the only person who has really argued
that it is unambiguously wrong to consider the situation in which a
thread catches a cancellation exception.
I don't think he's opposed to catching it, just to doing anything that
would involve backing out of the cancellation once it's started.
The inability to do that would prevent several systems that Dave
B. and I have cited from working properly. These systems, if allowed
to "back out" from a language/library point-of-view, will unwind
completely and terminate the thread as though there was no logical
"backing out," but otherwise will exhibit undefined behavior. So I
don't think preventing "backing out" at any level that can be enforced
by the language or library is tenable if you actually want
cancellation to be able to cancel threads.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Ted Baker
2006-03-13 20:35:09 UTC
Permalink
Maybe two different meanings of "back out"?

a) back out = work your way out of the nest of active subprogram
calls, doing appropriate cleanups

b) back out = stop the cancellation, i.e.,
stop processing the cancellation, and let the thread resume
normal execution

Doing (b) would mean not completing (a).
I think Ulrich and some others expect (a) will always complete,
and so would not like to allow (b).

--Ted
Post by David Abrahams
Post by Jason Merrill
Post by Mark Mitchell
As far as I remember, Ulrich is the only person who has really argued
that it is unambiguously wrong to consider the situation in which a
thread catches a cancellation exception.
I don't think he's opposed to catching it, just to doing anything that
would involve backing out of the cancellation once it's started.
The inability to do that would prevent several systems that Dave
B. and I have cited from working properly. These systems, if allowed
to "back out" from a language/library point-of-view, will unwind
completely and terminate the thread as though there was no logical
"backing out," but otherwise will exhibit undefined behavior. So I
don't think preventing "backing out" at any level that can be enforced
by the language or library is tenable if you actually want
cancellation to be able to cancel threads.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
David Abrahams
2006-03-14 06:37:18 UTC
Permalink
Post by Ted Baker
Maybe two different meanings of "back out"?
a) back out = work your way out of the nest of active subprogram
calls, doing appropriate cleanups
b) back out = stop the cancellation, i.e.,
stop processing the cancellation, and let the thread resume
normal execution
Doing (b) would mean not completing (a).
Exactly wrong. Sometimes doing (b) is /required/ in order to do (a)
successfully. Please read the foregoing thread where this is
explained.
Post by Ted Baker
I think Ulrich and some others expect (a) will always complete,
and so would not like to allow (b).
Yes, but they are not aware that (b) is sometimes necessary in order
to achieve (a)
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Ted Baker
2006-03-09 00:22:15 UTC
Permalink
Post by Mark Mitchell
As far as I remember, Ulrich is the only person who has really argued
that it is unambiguously wrong to consider the situation in which a
thread catches a cancellation exception. -- Mark Mitchell
This is one of the points where (*IF* this "study group" recommends to PASC that
a project be started, and once the actual standard working group gets down to
business) the POSIX C++ API group will need to make a choice between
compatibility with the POSIX C API and "doing the right thing" from their own
point of view.

A core issue will be the canonical implementation model. I see two
approaches:

(A) Limit required semantics to what can be implemented using the POSIX C API,
in C++-specific libraries, possibly with some help from the C++ compiler.

(B) Create new semantics, which go beyond what can be done using the
POSIX C API.

When it comes to thread cancellation, there is an existing C
library mechanism. You either build on it and limit your choices
to what that permits (A), or you build something new (B). If (B)
it will not be suitable for mixed C/C++ programs unless you can
think of a unified mutually compatible underlying implementation
mechanism and persuade the maintainer of the C library to do the
work needed to make them interoperable.

As this pertains to the question of whether thread cancellation
can be handled, option (A) must be implementable using a library
that supports the POSIX C API (and does no know about C++).

The POSIX C API presumes that once cancellation of a thread
begins it will progress through to termination of the thread. For
example, the description of "acting on" thread cancellation says
that "The cancelability state shall remain set to
PTHREAD_CANCEL_DISABLE until the thread has terminated." In
particular, it seems to me that the code of cancellation handlers
is permitted to assume that it will run to completion, all the way
out to the point of termination.

It seems to me that if you try to layer a C++ API on top of the C
API, this limitation will catch you. That is, if the C++ layer
allows thread cancellation to be handled (and resume execution of
the thread) there is no way that you could use the C API directly
to later cancel the thread (again), since from that point on the C
API sees that cancellation is disabled.

This seems to be a special case of a more general problem, which
will come up with every C library function that is a potential
cancellation point. If a C++ application or library calls such a
C library function, the C function will not know about C++
exceptions, and so will invoke some C-specific mechanism to
implement the thread cancellation. In particular, it will
probably call a C runtime library routine that starts walking the
stack, finding cancelation handlers, and executing them.

How does this get transferred into a C++ exception. I see a few options
e.g.,

(1) You put a wrapper around every cancellation-point function, which
installs its own cleanup handler, and the cleanup handler raises the
C++ exception instead of returning in the normal way. A problem:
this poentially violates invariants of the C cleanup handler model,
since in C handlers must always return and then the thread must terminate.
To make sure nothing breaks you need to get the C library implementor on board
with the new feature. He/she will want to know how much rewriting of C library
code will this take? Will there be a perforamance hit for C applications?

(2) You roll your own C++ version of the entire library function, which calls
a C++ runtime routine to implement thread cancellation. The C thread
cancellation and the C++ thread cancellation are unrelated and do not
interoperate. You don't need any cooperation from the C library
implementor, but if you have a program that tries to use the two API's
at once (maybe in different libraries) you sometimes get the C handlers
and sometimes the C++, and maybe everthing crashes as one kind of exception
tries to propagate into the scope of the other.

Maybe other readers will suggest more stragies.

In any case, I dare say you need to first decide the degree to
which interoperability of C and C++ code is important enough to
sacrifice other functional and aesthetic considerations. You then
need to come up with an implementation model that provides that
supports that level of interoperability without excessive costs
in modification to working C and C++ standard language libraries
and the standard POSIX API libraries.

--Ted
Mark Mitchell
2006-03-09 00:36:01 UTC
Permalink
Post by Ted Baker
A core issue will be the canonical implementation model. I see two
(A) Limit required semantics to what can be implemented using the POSIX C API,
in C++-specific libraries, possibly with some help from the C++ compiler.
(B) Create new semantics, which go beyond what can be done using the
POSIX C API.
Yes, this is an excellent point to clarify.

I believe the assumption here is that we must be in choice (B). As you
say, even the "forced unwinding" exception approach presently
implemented on GNU/Linux requires help from the C library; it's not
something that be done atop a "generic" POSIX C library, at least
without dynamic linker magic. Any mechanism for running C++ destructors
with reasonable performance is going to require help from the C library.

In the case of GNU/Linux, the C library is already providing
functionality for C++ beyond what's required for POSIX: just not the
functionality that is emerging as the consensus here. :-)

But, your point is well-taken; it's important that we're on the same page.
--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713
Alexander Terekhov
2006-03-09 08:27:29 UTC
Permalink
Ted Baker <***@cs.fsu.edu> wrote:
[...]
Post by Ted Baker
It seems to me that if you try to layer a C++ API on top of the C
API, this limitation will catch you.
That's just wrong way to layer. C API should be layered on top of
the C++ stuff.

http://www.codesourcery.com/archives/c++-pthreads/msg00465.html

regards,
alexander.
Ted Baker
2006-03-09 12:14:28 UTC
Permalink
You may feel that way, Alexander, but I assume you are just
enjoying being playfully provocative.

You know well that insistence on a POSIX C++ API (or, say, an API
for any other language) that implicitly imposes implementation
requirements on implementations of the POSIX C API is a sure way
to kill this project politically.

--Ted
Post by Alexander Terekhov
[...]
Post by Ted Baker
It seems to me that if you try to layer a C++ API on top of the C
API, this limitation will catch you.
That's just wrong way to layer. C API should be layered on top of
the C++ stuff.
http://www.codesourcery.com/archives/c++-pthreads/msg00465.html
regards,
alexander.
David Abrahams
2006-03-08 12:40:59 UTC
Permalink
Post by Dave Butenhof
Perhaps I'm worried about thought models and implementation while you're
concerned with appearance. Both views are certainly valid within certain
domains.
I don't know what you meant by "appearance," but I'm concerned exactly
with thought models. From the point of view of all the things one has
to think about to decide how cancellation exceptions will act, whether
it's possible to handle them correctly, etc., the fact that they are
ultimately initiated by asynchronous events is totally irrelevant.
They could just as easily be triggered by reading from stdin at each
cancellation point to decide whether to throw. So I'd prefer to keep
the conversation simple. If you'd rather be precise and talk about
asynchronous cancellation while always mentioning synchronous
exceptions as a response in the same breath, that's fine with me. in
this context, though, it's more confusing than necessary, especially
If you don't mention synchronous exceptions in the same breath (note
that you confused me).
Post by Dave Butenhof
"Asynchronous cancelability type" is (at least potentially) an
asynchronous EXCEPTION in response to an asynchronous event, while
"deferred cancelability type" is a synchronous (deferred) exception in
response to the same asynchronous event.
If you insist.
Post by Dave Butenhof
Does that help?
I don't think it helps in this conversation, but suit yourself; we now
understand each other.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
However, when cancellation is enabled, any blocking call (or any
method/operator that makes or might make a blocking call, like
"cout<<", might raise an exception. Not all code will be prepared to
handle that, and much shouldn't be; it's important to be able to
disable cancelability dynamically over critical scopes. It's not
like most exceptions where the conditions for an exception are
generally static; it could happen at any time for reasons the
current thread cannot possibly anticipate.
That's exactly like most exceptions (c.f. out-of-memory). Usually
conditions whose reasons can be anticipated can be effectively tested
and become preconditions or simply should be reported by other means.
Sometimes. Exceptions are great for "out of band" notifications that may
not be intended for the direct caller; they can be picked up with full
state, and without additional mechanism, by anyone along the call path
who cares.
I know that. I did say "most," didn't I?

To get things back on track, your assertion was that the reasons for
most exceptions can be anticipated. I'm not _precisely_ sure what you
meant by "reasons ... anticipated," but I think I have a pretty good
idea, and my assertion was that most exceptions are in fact just like
that, making this particular aspect of asynchrony uninteresting as a
distinction.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
and I'm much less ignorant than I was 2 years ago when I started
working with C++ and STL on a regular basis. Still, I am not steeped
in the history and tradition of C++ as I am in threads, and probably
never will be. More than that, while I have an authoritative voice
on the POSIX working group and in the community, I'm not involved
with the C++ committee and have no time or management support to get
involved; and I won't put myself in the position of being an outside
expert in some other area pretending to tell the C++ committee what
it must (or even should) do.
I don't think you should. There are other areas where you could make
a big difference, though, like the ISO committee for C++/POSIX binding
Mr. Drepper is now running.
"ISO committee" is rather a strong description; it's a simple mailing
list that's hoping to gain some preliminary consensus towards
constructing a formal proposal to request permission to develop a
charter and start a working group with the intent of building a proposal
for a binding.
That wasn't my impression, but maybe I misinterpreted the announcement
I saw.

The IEEE, Portable Applications Standard Committee, has just
approved the formation of a "C++ Bindings Study Group" to
investigate providing C++ bindings to the current POSIX Standard,
IEEE Std 1003.1. If the group concludes that C++ bindings should be
done, they will generate a request to IEEE PASC for project approval
to develop a POSIX C++ bindings specification. The group is to
report its findings to to PASC either by March 31, 2007, or within
12 months of it's first meeting, whichever comes first. The group
will be chaired by Ulrich Drepper of Red Hat (***@redhat.com).

OK, it's a study group. Slightly more than a mailing list, but hardly
a committee. I guess I'm just alarmist.
Post by Dave Butenhof
But, yeah, OK, fine. ;-)
OK, thanks.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
No, I wasn't suggesting anything that couldn't be caught. I was just
suggesting an exception that couldn't be stopped. It could throw
itself in its dtor (not that I'm advocating it, but it might satisfy
the "other side"), for example.
The POSIX model where cancel propagates inexorably to thread
termination is an inherently flawed compromise; but simply the best
we could do within the context of ISO C and POSIX APIs. OUR
implementation always allowed finalization, via C++ catch(...), our
ISO C "CATCH_ALL" extensions, or whatever other language syntax
might fit.
I really wouldn't want to propagate this restriction to C++.
Be clear, I'm not talking about a restriction. If you ask it to throw
something normal, it's finalizable in the normal way. This is a way
for the _cancelling_ thread to say, "I know what I'm doing; the author
of the thread I'm cancelling doesn't. Force it to be killed at the
next cancellation point."
The whole concept of cancellation is exactly that the TARGET thread, not
the cancelling thread, knows what it's doing and should control the
cleanup and termination entirely.
Believe me, I agree. You said we had to look for compromises, so
that's what I was doing.
Post by Dave Butenhof
Without explicit synchronization, the cancelling thread can't know
what it's doing and whether a forced abort is appropriate or
safe. And if it has enough synchronization to know that, there are
far better ways to gain a cooperative termination than cancellation.
Sure.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
One advantage, though, of the single cancel exception, is that it's
universal. When you asynchronously issue a cancel request for a
thread, you can't really know what code is executing: your's, STL,
some other shared library, etc. Cancel means the same to all of
them, and either is supported with commonly agreed semantics or
will be ignored (by disabling cancellation in critical
scopes). Once you start firing off your own arbitrary exceptions,
though, anything might happen because half the time the exceptions
won't belong anywhere in the call tree that's active at the time
they arrive.
That's not the way most exception-safe code works. It goes to the
reason that exception-specifications are a failure: the particular
type of exception that propagates out of a throwing function makes
almost no difference to anyone. The type only becomes important where
errors are reported, or where exceptions are translated -- either to
other exception types or, for example, to error return codes that can
propagate through other languages. So the danger of injecting an
arbitrary exception type into existing code (especially libraries,
which are very often exception-neutral) is very very low.
That's an intriguing statement. I'll need to think about that some.
I've certainly always thought that exception specifications were little
more than a trap into which people could mire themselves as deeply as
they like. So maybe that means I agree. I'm not sure. ;-)
I guess I'd have to agree that the danger of injecting another exception
type is low. And mostly due to the fact that C++ has no "root exception
type" onto which could be grafted some minimal universal state
It has a de-facto root type: std::exception.
Post by Dave Butenhof
(an architected status code space, like VMS condition codes, a
descriptive string, etc.) so that nobody would need an anonymous and
semantic-free catch(...) just to be sure nothing slipped past.
When you have a root type, catching that is practically anonymous and
semantic-free. It doesn't make much difference in practice.
Post by Dave Butenhof
Post by David Abrahams
Post by Dave Butenhof
Which brings us back to the "academic" resolution: if an exception
means distinct things in different call trees, those call trees
should be distinct threads and only one universal exception is
necessary. ;-)
I think you might be missing the point. I am proposing the
generalized
thread_throw( thread_id, exception_object )
function so that those who wish to hang themselves with homegrown
unstoppable exception types can do so without forcing the standard to
sanction the use of unstoppable exceptions by providing any kind of
"forced cancellation." If "the other side" has A WAY to force
cancellation, maybe they won't insist it has to be THE WAY. I know,
wishful thinking :)
I'm not sure how sanctioning generalized unstoppable exceptions
I said I'm not sanctioning such a thing. You can build one today in
standard C++ (it throws a copy of itself from its destructor), so
there's no need. And of course we can't stop people from building
them.
Post by Dave Butenhof
is going to mollify anyone opposed to an unstoppable variety of a
specific exception.
Huh? Did you really mean "UNstoppable?" IIUC, you and I are both
opposed to unstoppable thread cancellation. Are you saying you would
need to be mollified in order to accept it, even if it's only
available by hand-writing a very strange exception type?
Post by Dave Butenhof
At best, cancel becomes a subset of cross-thread throw with
specialized additional deferral semantics.
Additional? I'm lost again. There is no workable cross-thread throw
without deferral. Anything else is an asynchronous exception.
Post by Dave Butenhof
And if you're generalizing the unstoppable exception,
Generalizing?
Post by Dave Butenhof
I don't quite see how it makes sense not to generalize the deferral,
and now cancel really is just a specific predefined exception that
can be thrown like any other exception. That's not necessarily bad;
I just don't see how it's a compromise. (A compromise needs to make
BOTH sides equally unhappy, not just one side!)
The compromise I'm proposing makes it possible to generate an
unstoppable cancellation (bad for us) while making it ugly and
unnatural to do so (bad for "the other side").
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Meredith, Alisdair
2006-03-08 16:34:58 UTC
Permalink
-----Original Message-----
Sent: 08 March 2006 15:28
To: Dave Butenhof
Subject: Re: [c++-pthreads] Re: FW: RE: Re: I'm Lost
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions).
Sorry to jump into the middle of a good dialog with what might seem an
irrelevance, but how will these asynchronous exceptions (now being
re-thrown 'as sychronous') interact with exception specifications?

Badly is my only guess.

Not that this should interfere with finding a good model for
asynchronous exceptions or cancellations - far more useful to your
general user! But it might mean we put some more thought into cleaning
up exception specifications in C++0x, so that no problems arise.

Unless, of course, I am imagining a problem that does not exist - would
not be the first time.

AlisdairM
---------------------------------------------------------------------

For further information on Renault F1 visit our web site at www.renaultf1.com.

WARNING: please ensure that you have adequate virus protection in place before you open or detach any documents attached to this email.

This e-mail may constitute privileged information. If you are not the intended recipient, you have received this confidential email and any attachments transmitted with it in error and you must not disclose, copy, circulate or in any other way use or rely on this information.

E-mails to and from the Renault F1 Team are monitored for operational reasons and in accordance with lawful business practices.

The contents of this email are those of the individual and do not necessarily represent the views of the company.

Please note that this e-mail has been created in the knowledge that Internet e-mail is not a 100% secure communications medium. We advise that you understand and observe this lack of security when e-mailing us.

If you have received this email in error please forward to: ***@uk.renaultf1.com quoting the sender, then delete the message and any attached documents
---------------------------------------------------------------------
David Abrahams
2006-03-08 16:50:17 UTC
Permalink
Post by Meredith, Alisdair
Post by David Abrahams
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions).
Sorry to jump into the middle of a good dialog with what might seem an
irrelevance, but how will these asynchronous exceptions (now being
re-thrown 'as sychronous')
Dave B: This is why I didn't want the term asynchronous in the
discussion. It's just a confusing distraction.

Alisdair M: there are absolutely no asynchronous exceptions here,
period.
Post by Meredith, Alisdair
interact with exception specifications?
Badly is my only guess.
Answer 1: who cares?

Answer 2: just the same as they interact with any other exception
(i.e. no worse).
Post by Meredith, Alisdair
Not that this should interfere with finding a good model for
asynchronous exceptions or cancellations - far more useful to your
general user! But it might mean we put some more thought into
cleaning up exception specifications in C++0x, so that no problems
arise.
Unless, of course, I am imagining a problem that does not exist - would
not be the first time.
I think this is one of those imaginary problems. It doesn't introduce
any new problems; we're just talking about more ordinary exceptions,
and inasmuch as you know where other kinds of exceptions can be
thrown, the same would be true of these.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Dave Butenhof
2006-03-08 20:59:48 UTC
Permalink
Post by David Abrahams
Post by Meredith, Alisdair
Post by David Abrahams
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions).
Sorry to jump into the middle of a good dialog with what might seem an
irrelevance, but how will these asynchronous exceptions (now being
re-thrown 'as sychronous')
Dave B: This is why I didn't want the term asynchronous in the
discussion. It's just a confusing distraction.
Well, I guess it's a C++ viewpoint vs a threads viewpoint. When I see
"synchronous" cross-thread exceptions, cancel or otherwise, my first
thought has to be, so who is responsible for the synchronization, and
how does it work?

The point is, though, that there IS no synchronization. That is, by
definition, the communication protocol is asynchronous. (Actually, in
pure POSIX cancellation is IS possible to "post" a cancel request
without synchronization, unless you need to blast through a blocking
operation. With a general cross-thread exception that's unlikely to be
possible, but the synchronization is far below the application level,
and provides no useful synchronous behavior for the application
developer; which amounts to the same thing as "asynchronous".)

I can't possibly say "C++ does cross-thread exceptions synchronously",
because it isn't a practical or useful definition, and wouldn't be a
good operational model. The communication has to be asynchronous.

Only the final step, entirely within the target thread, is fully
synchronous with that thread at a level that's of any use to the
application model. The target detects at a cancellation/exception point
that the request currently exists (registered asynchronously at some
previous time ;-) ), and SYNCHRONOUSLY throws the exception into its own
call stack.

So, yeah; the exception is synchronous. Fine. But that's only the final,
and least interesting, step in the protocol. Or perhaps that's just
"least interesting" to ME, because "I'm a thread guy". ;-)
Peter Dimov
2006-03-08 21:44:45 UTC
Permalink
Post by Dave Butenhof
So, yeah; the exception is synchronous. Fine. But that's only the
final, and least interesting, step in the protocol. Or perhaps that's
just "least interesting" to ME, because "I'm a thread guy". ;-)
It is a very interesting step because that's what the thread sees, an
ordinary exception, emanating from a function in the usual way; meaning that
if the thread is already equipped to handle exceptions, as is the case with
C++ code nowadays, it "just works". Which is nice. :-)
Alexander Terekhov
2006-03-09 08:56:13 UTC
Permalink
The C++ language can be extended to cope with the concept of async-
cancel safety. I can even imagine that async cancel can be made the
default with compiler automatically inserting sync_cancel { }
wrappers (resulting in extra map tables entries with no runtime
penalty until attempting async cancel delivery and finding out that
it wasn't expected) for any async-cancel unsafe expressions. I see
no reason why tons of std lib stuff like strlen() can't be declared
to be

size_t strlen(const char *s) async_cancel_safe;

regards,
alexander.


"Peter Dimov" <***@mmltd.net> on 08.03.2006 22:44:45

To: "Dave Butenhof" <***@hp.com>, "David Abrahams"
<***@boost-consulting.com>
cc: "Meredith, Alisdair" <***@uk.renaultf1.com>,
<c++-***@codesourcery.com>
Subject: Re: [c++-pthreads] Re: FW: RE: Re: I'm Lost
Post by Dave Butenhof
So, yeah; the exception is synchronous. Fine. But that's only the
final, and least interesting, step in the protocol. Or perhaps that's
just "least interesting" to ME, because "I'm a thread guy". ;-)
It is a very interesting step because that's what the thread sees, an
ordinary exception, emanating from a function in the usual way; meaning
that
if the thread is already equipped to handle exceptions, as is the case with

C++ code nowadays, it "just works". Which is nice. :-)
David Abrahams
2006-03-09 16:34:13 UTC
Permalink
Post by Alexander Terekhov
The C++ language can be extended to cope with the concept of async-
cancel safety. I can even imagine that async cancel can be made the
default with compiler automatically inserting sync_cancel { }
wrappers (resulting in extra map tables entries with no runtime
penalty until attempting async cancel delivery and finding out that
it wasn't expected) for any async-cancel unsafe expressions. I see
no reason why tons of std lib stuff like strlen() can't be declared
to be
size_t strlen(const char *s) async_cancel_safe;
In principle, yes. In practice, it's a much bigger problem, still
requires lots of attention from the programmer to use correctly, would
place new restrictions on implementation technique that may be
extremely difficult politically to pass, and could be added atop the
answer for "synchronous" cancellation. Let's solve the "easy" problem
first. Nobody will forget about async-cancel safety and 2-phase EH as
long as you're around, Alexander.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
David Abrahams
2006-03-08 22:23:21 UTC
Permalink
Post by Dave Butenhof
Post by David Abrahams
Post by Meredith, Alisdair
Post by David Abrahams
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions).
Sorry to jump into the middle of a good dialog with what might seem an
irrelevance, but how will these asynchronous exceptions (now being
re-thrown 'as sychronous')
Dave B: This is why I didn't want the term asynchronous in the
discussion. It's just a confusing distraction.
Well, I guess it's a C++ viewpoint vs a threads viewpoint. When I see
"synchronous" cross-thread exceptions, cancel or otherwise, my first
thought has to be, so who is responsible for the synchronization, and
how does it work?
The point is, though, that there IS no synchronization.
Aww, c'mon. You're seriously telling me that one thread can set the
"there's now an active cancellation flag" for another thread, and the
other thread can read it with _NO_ synchronization? That isn't how I
learned things work by reading your book!
Post by Dave Butenhof
That is, by definition, the communication protocol is
asynchronous. (Actually, in pure POSIX cancellation is IS possible
to "post" a cancel request without synchronization, unless you need
to blast through a blocking operation. With a general cross-thread
exception that's unlikely to be possible, but the synchronization is
far below the application level, and provides no useful synchronous
behavior for the application developer; which amounts to the same
thing as "asynchronous".)
So now you're telling me that my mental model for what's required to
write this state in one thread and read it in another is all wrong,
and I should be thinking of it in some way that makes it
"asynchronous" despite the fact that it really is synchronous under
the covers?

This sound like you're just making it worse for "thought models," not
better.
Post by Dave Butenhof
Only the final step, entirely within the target thread, is fully
synchronous with that thread at a level that's of any use to the
application model. The target detects at a cancellation/exception point
that the request currently exists (registered asynchronously at some
previous time ;-) ), and SYNCHRONOUSLY throws the exception into its own
call stack.
So, yeah; the exception is synchronous. Fine. But that's only the final,
and least interesting, step in the protocol. Or perhaps that's just
"least interesting" to ME, because "I'm a thread guy". ;-)
Yes, to you. For the purposes of the big argument over how these
exceptions work, it's a completely irrelevant fact (if you can even
call it that, because the real facts of the matter have only become
blurrier as I read more of what you write).
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Dave Butenhof
2006-03-09 00:35:10 UTC
Permalink
Post by David Abrahams
Post by Dave Butenhof
Post by David Abrahams
Post by Meredith, Alisdair
Post by David Abrahams
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions).
Sorry to jump into the middle of a good dialog with what might seem an
irrelevance, but how will these asynchronous exceptions (now being
re-thrown 'as sychronous')
Dave B: This is why I didn't want the term asynchronous in the
discussion. It's just a confusing distraction.
Well, I guess it's a C++ viewpoint vs a threads viewpoint. When I see
"synchronous" cross-thread exceptions, cancel or otherwise, my first
thought has to be, so who is responsible for the synchronization, and
how does it work?
The point is, though, that there IS no synchronization.
Aww, c'mon. You're seriously telling me that one thread can set the
"there's now an active cancellation flag" for another thread, and the
other thread can read it with _NO_ synchronization? That isn't how I
learned things work by reading your book!
Cancellation state is a "stash and go" transaction; all that's necessary
is visibility guarantees and a confidence that the storage can't go away
in the midst. There are lots of lock free techniques. As far as my book
advice goes, the point here is that there's no standard or portable API
allowing application code to do this. It's "cake" for an implementation.
Post by David Abrahams
Post by Dave Butenhof
That is, by definition, the communication protocol is
asynchronous. (Actually, in pure POSIX cancellation is IS possible
to "post" a cancel request without synchronization, unless you need
to blast through a blocking operation. With a general cross-thread
exception that's unlikely to be possible, but the synchronization is
far below the application level, and provides no useful synchronous
behavior for the application developer; which amounts to the same
thing as "asynchronous".)
So now you're telling me that my mental model for what's required to
write this state in one thread and read it in another is all wrong,
and I should be thinking of it in some way that makes it
"asynchronous" despite the fact that it really is synchronous under
the covers?
This sound like you're just making it worse for "thought models," not
better.
No, never mind. I'll surrender. I'm clearly thinking at a level of
abstraction and/or detail that doesn't matter to most people. Maybe it
is just pointless confusion.

I've had a really busy couple of weeks and maybe I'm just blowing off a
little steam in a nice technical argument. But if I'm arguing about
something that doesn't matter to anyone but me, that's stupid.
Post by David Abrahams
Post by Dave Butenhof
Only the final step, entirely within the target thread, is fully
synchronous with that thread at a level that's of any use to the
application model. The target detects at a cancellation/exception point
that the request currently exists (registered asynchronously at some
previous time ;-) ), and SYNCHRONOUSLY throws the exception into its own
call stack.
So, yeah; the exception is synchronous. Fine. But that's only the final,
and least interesting, step in the protocol. Or perhaps that's just
"least interesting" to ME, because "I'm a thread guy". ;-)
Yes, to you. For the purposes of the big argument over how these
exceptions work, it's a completely irrelevant fact (if you can even
call it that, because the real facts of the matter have only become
blurrier as I read more of what you write)
OK, "least interesting" was mostly a joke, and the part that wasn't a
joke was unnecessarily provocative. Sorry. I got carried away.
Ted Baker
2006-03-09 12:10:14 UTC
Permalink
Post by Dave Butenhof
Post by David Abrahams
Aww, c'mon. You're seriously telling me that one thread can set the
"there's now an active cancellation flag" for another thread, and the
other thread can read it with _NO_ synchronization? That isn't how I
learned things work by reading your book!
Cancellation state is a "stash and go" transaction; all that's necessary
is visibility guarantees and a confidence that the storage can't go away
in the midst. There are lots of lock free techniques.
By the way, this is one (the main?) reason why the transition from normal
to cancelled is one-way, and the effect of cancellation is not necessarily
immediate. That way you can maintain consistency without a lock on the bit that
says whether a thread is cancelled. Another thread can set the cancellation
bit and the target thread some time later notices that the bit is set. Since
the bit is never reset, there is no race condition.

--Ted
Jason Merrill
2006-03-14 21:53:45 UTC
Permalink
Post by Ted Baker
By the way, this is one (the main?) reason why the transition from normal
to cancelled is one-way, and the effect of cancellation is not necessarily
immediate. That way you can maintain consistency without a lock on the bit that
says whether a thread is cancelled. Another thread can set the cancellation
bit and the target thread some time later notices that the bit is set. Since
the bit is never reset, there is no race condition.
This is the first justification I've seen for Ulrich's position. Can
you elaborate on how allowing us to reset the cancelled bit could lead
to a race condition?

Jason
Jason Merrill
2006-03-14 22:51:43 UTC
Permalink
Post by Jason Merrill
Since the bit is never reset, there is no race condition.
This is the first justification I've seen for Ulrich's position. Can
you elaborate on how allowing us to reset the cancelled bit could lead
to a race condition?
Actually, I suppose there's no need to reset the cancelled bit and call
pthread_cancel again to get the sticky cancel behavior. At least with
glibc we just want to clear EXITING_BIT so that the next test will cause
us to throw again.

Jason
Ted Baker
2006-03-17 16:12:00 UTC
Permalink
Post by Jason Merrill
This is the first justification I've seen for Ulrich's position. Can
you elaborate on how allowing us to reset the cancelled bit could lead
to a race condition? -- Jason
The potential race is between resetting the bit and a new
cancellation request, potentially missing a new cancellation
request.

From an implementors point of view, depending on how your thread
implementation does other things internally, if protecting this
bit requires adding a second per-thread lock, you have a problem
with potential deadlock. I seem to recall running into this
problem, years ago, when I was working on such things.

From an applications point of view, allowing threads
once-cancelled to again become "virgin" is also semantically bad,
because of indeterminacy:

1. Some therad B tries to cancel a thread A.
It locks the cancellation state of A, and sets a bit
to indicate that A should cancel.

I'm assuming this is because of some condition detected by another
thread (or process), maybe a timeout because the thread A has not
responded for a while.

2. Some concurrent thread C, who also noticed that the thread is
not responding, also tries to cancel A. It locks the cancellation
state of A, and sets the cancellation pending bit.

We now have three possible scenarios, in a race:

3a. (2) takes effect before A starts recovery,
and so (1) and (2) are merged into a single exception
from A's point of view.

3b. (2) takes effect while A is executing exception
handler code. Depending on implementation details,
the second cancellation may be merged with the first,
or it may appear as a new pending cancellation event,
to be detected as soon as A becomes cancellable again.

3c. (2) takes effect after A finished executing the
handler code, and has resumed normal execution.
A new cancellation process begins for A at the
next point it is cancellable.

The standard C thread cancellation semantics, by not allowing
a thread to return to normal after cancellation, eliminate
possibilities (3b) and (3c), so the outcome of
multiiple cancellation attempts is deterministic.

--Ted

Peter Dimov
2006-03-08 17:16:47 UTC
Permalink
Post by Meredith, Alisdair
Post by David Abrahams
Subject to cancelability. In C++ "cancelability" would indicate
whether cancellation points can throw (these asynchronously-initiated
exceptions).
Sorry to jump into the middle of a good dialog with what might seem an
irrelevance, but how will these asynchronous exceptions (now being
re-thrown 'as sychronous') interact with exception specifications?
Badly is my only guess.
The exceptions are not asynchronous.

You have a function

void f();

that can throw (i.e. it doesn't give the nothrow guarantee). This function
can throw a cancellation exception, but this is no different from throwing
some other exception. From the point of view of the caller, nothing changes.
The function just fails.

If, on the other hand, you have

void f() throw();

or

void f() throw( X );

where X is not a base of the cancellation exception, this function won't
throw a cancellation exception (although it might invoke unexpected() or
terminate() if it invokes other throwing functions, which now includes
cancellation points.)

Alexander Terekhov has been proposing a model where cancellation points
automatically detect that a throw-spec somewhere up the call stack would not
let a cancellation exception through, and if so, refrain from throwing. But
this requires a language change.
George Shimanovich
2006-03-08 17:08:33 UTC
Permalink
Post by Mark Mitchell
I don't think we should support unstoppable exceptions at all.
Silently
Post by Mark Mitchell
rethrowing at the end of a catch handler breaks too many important
invariants.
For example, it is not compatible with thread pool implementations that
try to reuse its threads regardless of how thread callback completes,
normally or via thread cancellation. Note that since cancellation
exception can only be caught via catch(...) there is no way to
distinguish it from any system, non-C++ exception and thus decide to
rethrow.

George Shimanovich
Meredith, Alisdair
2006-03-08 23:26:18 UTC
Permalink
Sorry to be dense, or if I am covering old ground - just confirming I understand correctly:
Also sorry for HTML format - it is all I can get remotely from our Exchange server :¬(

i/ cancellation will propogate as an (uncatchable?) exception, implying stack unwinding.
ii/ if cancellation passes through an exception specification, we call unexpected and abort which pretty much achieves the same thing
iii/ if cancellation interupts a dtor during regular stack unwinding, we call terminate which pretty much has the same effect, so everyone is still happy.

I am still not sure about:
iv/ if an exception is thrown but not caught in main, it is implementation defined whether stack unwinding occurs, so it will really be implementation defined whether the stack is unwound for thread cancellation in this scheme.

I am also not clear whether unexpected / terminate will kill just the thread, or the whole process. I am currently expecting the latter as the status quo, although not with a strong opinion to fight for <g>

AlisdairM

________________________________

From: Mark Mitchell [mailto:***@codesourcery.com]
Sent: Wed 08/03/2006 23:15
To: Jason Merrill
Cc: Wil Evers; David Abrahams; c++-***@codesourcery.com
Subject: Re: [c++-pthreads] Re: FW: RE: Re: I'm Lost
Post by Jason Merrill
If you can interrupt cancellation, re-cancellation is implemented
trivially simply by just having the cancellation exception destructor
call 'pthread_cancel (pthread_self ())'. The sticking point is being
able to abort the cancellation in the first place, which is what Uli has
been opposed to.
It sounds like we really are close to a solution then; I think everyone
here would be happy with the re-cancellation thing, and the destructor
trick means that there's really no way the thread can permanently
discard the cancellation request, short of things like longjmp -- and,
of course, if the thread is really determined not to go away it can just
hang around anyhow. So, it seems like this ought to satisfy everyone
from the user-level perspective.
Post by Jason Merrill
I don't think he's opposed to catching it, just to doing anything that
would involve backing out of the cancellation once it's started.
Would the above satisfy him?

--
Mark Mitchell
CodeSourcery
***@codesourcery.com
(650) 331-3385 x713


---------------------------------------------------------------------

For further information on Renault F1 visit our web site at www.renaultf1.com.

WARNING: please ensure that you have adequate virus protection in place before you open or detach any documents attached to this email.

This e-mail may constitute privileged information. If you are not the intended recipient, you have received this confidential email and any attachments transmitted with it in error and you must not disclose, copy, circulate or in any other way use or rely on this information.

E-mails to and from the Renault F1 Team are monitored for operational reasons and in accordance with lawful business practices.

The contents of this email are those of the individual and do not necessarily represent the views of the company.

Please note that this e-mail has been created in the knowledge that Internet e-mail is not a 100% secure communications medium. We advise that you understand and observe this lack of security when e-mailing us.

If you have received this email in error please forward to: ***@uk.renaultf1.com quoting the sender, then delete the message and any attached documents
---------------------------------------------------------------------
David Abrahams
2006-03-09 16:20:08 UTC
Permalink
Post by Meredith, Alisdair
Also sorry for HTML format - it is all I can get remotely from our Exchange server :¬(
i/ cancellation will propogate as an (uncatchable?) exception, implying stack unwinding.
In the model I'm proposing, cancellation will be expressed by telling
the cancelled thread to throw a specific exception object at the next
(or current) cancellation point reached with cancellation enabled.
All exceptions are catchable. Exceptions can be unstoppable, if you
write them in a particular way.
Post by Meredith, Alisdair
ii/ if cancellation passes through an exception specification, we
call unexpected and abort which pretty much achieves the same thing
No, it doesn't complete stack unwinding, and it might kill the whole
process (I'm not sure about that).
Post by Meredith, Alisdair
iii/ if cancellation interupts a dtor during regular stack
unwinding, we call terminate which pretty much has the same effect,
so everyone is still happy.
Likewise.
Post by Meredith, Alisdair
iv/ if an exception is thrown but not caught in main, it is
implementation defined whether stack unwinding occurs, so it will
really be implementation defined whether the stack is unwound for
thread cancellation in this scheme.
1. This thread may not contain a main(). We are not going to
propagate exceptions across threads.

2. It's "just an exception." If you want to force unwinding in the
main() thread, in principle you have to do a catch+rethrow pair.

3. The standard says nothing today about how other threads may behave
when an exception propagates out uncaught. Fortunately, the
threading library binding can force unwinding if the thread is
initiated from C++ at least.
Post by Meredith, Alisdair
I am also not clear whether unexpected / terminate will kill just
the thread, or the whole process.
I don't think anyone has been trying to decide that here.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Jason Merrill
2006-03-14 21:37:45 UTC
Permalink
Post by David Abrahams
In the model I'm proposing, cancellation will be expressed by telling
the cancelled thread to throw a specific exception object at the next
(or current) cancellation point reached with cancellation enabled.
All exceptions are catchable. Exceptions can be unstoppable, if you
write them in a particular way.
Hmm....

extern "C" int printf (const char *, ...);

struct E
{
bool undead;
E(): undead (true) { }
~E() { if (undead) throw E(); }
};

int main()
{
try
{
try
{
throw E();
}
catch (...)
{
printf ("caught once\n");
}
}
catch (E& e)
{
printf ("caught twice\n");
e.undead = false;
}
}

This works in g++, but EDG calls terminate() when the destructor throws.
I think g++ is correct; 15.5.1 doesn't mention throwing during
destruction of the exception object due to flowing off the end of the
handler as one of the situations that causes terminate() to be called,
and I don't see any reason why it would need to be.

We would still call terminate() if the catch(...) block exits by
throwing another exception, since then the exception object is destroyed
during stack unwinding.
Post by David Abrahams
Post by Meredith, Alisdair
ii/ if cancellation passes through an exception specification, we
call unexpected and abort which pretty much achieves the same thing
No, it doesn't complete stack unwinding, and it might kill the whole
process (I'm not sure about that).
Post by Meredith, Alisdair
iii/ if cancellation interupts a dtor during regular stack
unwinding, we call terminate which pretty much has the same effect,
so everyone is still happy.
Likewise.
In previous discussions everyone has agreed that cancellation should be
disabled during stack unwinding, to avoid this situation. This could be
implemented either by the EH runtime calling pthread_setcancelstate or
by the cancellation runtime checking to see if it's safe to throw a la
Alexander.

Jason
David Abrahams
2006-03-14 21:45:27 UTC
Permalink
Post by Jason Merrill
Post by David Abrahams
In the model I'm proposing, cancellation will be expressed by
telling
the cancelled thread to throw a specific exception object at the next
(or current) cancellation point reached with cancellation enabled.
All exceptions are catchable. Exceptions can be unstoppable, if you
write them in a particular way.
Hmm....
extern "C" int printf (const char *, ...);
struct E
{
bool undead;
E(): undead (true) { }
~E() { if (undead) throw E(); }
You need to check uncaught_exception before throwing here. Remember,
it's unspecified how many copies of the exception are made and if a
destructor of an original is executed due to the throwing of a copy,
you'll terminate. I think that may account for EDG's behavior.
Post by Jason Merrill
Post by David Abrahams
Post by Meredith, Alisdair
ii/ if cancellation passes through an exception specification, we
call unexpected and abort which pretty much achieves the same thing
No, it doesn't complete stack unwinding, and it might kill the whole
process (I'm not sure about that).
Post by Meredith, Alisdair
iii/ if cancellation interupts a dtor during regular stack
unwinding, we call terminate which pretty much has the same effect,
so everyone is still happy.
Likewise.
In previous discussions everyone has agreed that cancellation should be
disabled during stack unwinding, to avoid this situation. This could be
implemented either by the EH runtime calling pthread_setcancelstate or
by the cancellation runtime checking to see if it's safe to throw a la
Alexander.
I'm still happy with that.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
Loading...