[c++-pthreads] What are the real issues?

Ted Baker baker at cs.fsu.edu
Wed Jan 7 19:32:23 UTC 2004


> The Itanium C++ ABI, which gcc adopted, made cancellation a special 
> kind of exception, "forced unwinding", so that a thread can't just 
> catch the cancellation exception and swallow it.  The idea was that a 

This "forced unwinding" sounds different from normal exception
processing, since you cannot catch the exception.  How deeply is
this embedded in the gcc C++ implementation?  e.g., is the
unwinding done by different code (maybe even by a proxy thread?)
for cancellation than for normal exception propagation?

For example, with GNAT, the task abort exception is processed
exactly like other exceptions.  The difference is pretty
superficial in that tha parser does not allow handlers for abort
in user code.  This is easily overridden (a GNAT-specific language
extension) when one is writing system support code, so it is not a
problem for critical libraries.

> thread may disable cancellation tempoarily, but that once a 
> cancellation has begun it can't be thrown away.  This was a slightly 
> controversial decision at the time, and maybe it's at the heart of the 
> real issue.

> Second: the immediate issue was that some people saw a contradiction 
> between this model and the C++ library specification.  For example, 
> POSIX says that read() is a cancellation point.  But it's reasonable to 
> assume that std::istream:;read() invokes POSIX's read() system call, 
> and the C++ standard says that (under ordinary circumstances) 
> std::istream doesn't throw.  If the cancellation exception were an 
> ordinary exception instead of special forced unwinding, what would 
> happen would be that istream would invoke its streambuf, the streambuf 
> would call read(), read would throw a cancellation exception, then 
> istream would catch the exception, swallow it, and set and error flag.  
> Forced unwinding means that the cancellation exception will propagate 
> even though istream tries to swallow it.  This means that an exception 
> will propagate from istream when the C++ standard says it's not 
> supposed to.

See question above.  Is this normal propagation?  If so, then
shouldn't it be possible to catch it?

> Third, some people simply object to having cancellation be an exception 
> (or anything that looks like an exception): it means there are some 
> functions that can throw exceptions in the presence of threads that 
> wouldn't throw in a single-threaded program.

There seems to be no way around this, short of requiring that
cancellation be disabled over all suspect library calls.  As Dave
explained so well, if a system supports thread cancellation it can
happen and it behaves virtually like an exception, regardless of
what you call it.  To ask the pthread library implementors to
change this would be a fundamental change to the pthread
semantics.

> cancellation that's very different from the POSIX.  Anything that 
> doesn't involve thread execution stopping sounds more like a 
> communication mechanism than a cancellation mechanism.  Seems to me 
> that the only real issues for debate are which functions are 
> cancellation points, how a thread can enable and disable cancellation, 
> whether a thread should be allowed to disregard a cancellation request 
> once it has been received, and what kind of cleanup a thread performs 
> before it stops.

POSIX and the Single Unix Specification cover these.  In the SUS,
much of the relevant text is in the Base Definitions part, which
are intended to be language-independent.

---> whether a thread should be allowed to disregard a cancellation
request once it has been received

SUS'98, which subsumes the POSIX standards, says:

"Whenever a thread has cancelability enabled and a cancellation
request has been made with that thread as the target, and the
thread then calls any function that is a cancellation point (such
as pthread_testcancel() or read()), the cancellation request shall
be acted upon before the function returns."

[The "shall" means cancellation cannot be ignored at this point.]

"... If cancelability is disabled ... all cancellation
requests are held pending...".

[So, cancellation must be ignored at this point.]

This seems to be language-independent semantics.

---> how a thread can enable and disable cancellation

The C API defines pthread_setcanceltype() and
pthread_setcancelstate().  A C++ API could define additional
functions or points at which the above functions are implicitly
(virtually) called.

---> what kind of cleanup a thread performs before it stops:

SUS'98 says:
                                 
"When a cancellation request is acted upon, the routines in the
list are invoked one by one in LIFO sequence; that is, the last
routine pushed onto the list (Last In) is the first to be invoked
(First Out). The thread invokes the cancellation cleanup handler
with cancellation disabled until the last cancellation cleanup
handler returns. When the cancellation cleanup handler for a scope
is invoked, the storage for that scope remains valid. If the last
cancellation cleanup handler returns, thread execution is
terminated and a status of PTHREAD_CANCELED is made available to
any threads joining with the target. The symbolic constant
PTHREAD_CANCELED expands to a constant expression of type ( void
*) whose value matches no pointer to an object in memory nor the
value NULL.

"The cancellation cleanup handlers are also invoked when the thread
calls pthread_exit()."

"...When the cancellation is acted on, the cancellation cleanup
handlers for thread shall be called. When the last cancellation
cleanup handler returns, the thread-specific data destructor
functions shall be called for thread. When the last destructor
function returns, thread shall be terminated. ..."

---> which functions are cancellation points:

SUS'98 (and POSIX) define a raft of functions for
which cancellation points "shall" occur, and some more for
which cancellation points "may" occur.  A C++ binding
could define other names for functions with similar
effects that are shall *not* be cancellation points.

> My feeling: it's just plain inevitable that a multithreaded program has 
> more functions that might throw than a single-thread program.  Dealing 
> with this is part of what it means to make a program thread-safe.  We 
> might argue that POSIX defines too many cancellation points, and that 
> cutting it down to a smaller number (and, especially, getting rid of 
> the functions that POSIX says may or may not be cancellation points) 
> would make it easier to write correct code.

Yes.

> But as I said, I think the really fundamental issue is whether a thread 
> should be allowed to receive a cancellation request, start to do some 
> work as a result of the request, and then decide that it doesn't want 
> to be cancelled.  If we think that's reasonable then I think what we 

POSIX and the SUS do not define this.  I believe it would be an
upward-compatible extension to define it.  That is, no existing
UNIX/POSIX conformant application could be broken by an extension
that extends the API to allow a thread to regain control and
"revoke" a cancellation, since no conformant application could
presently include such an API call

However, the debate will be over one *wants* to export such
functionality.

> should probably do is abandon the notion of forced unwinding and make 
> cancellation into an ordinary exception.  If we do that then the 
> std::istream contradiction goes away.  The cost, of course, is that it 
> might become surprisingly hard to cancel some threads.

From what I've read here, and my own experience, it seems this is
the right thing to do, provided:

1) The only way to handle this new cancellation exception is via a
new syntax.

2) You can agree on new C++ API function names for whichever
POSIX/Unix cancellation-point system calls you don't think should
ever propagate a cancellation exception.

To do the latter, you have several choices:

(a) Have the wrapper (e.g., for read()) just disable cancellation
(and not reenable it).

(b) Work with your friendly pthread implementor (the one who
manages the code of the library functions that are cancellation
points) to develop alternate entry points (e.g., read_nocancel())
or a settable flag (e.g., that a C++ thread or process
can call to make read() *not* serve as a cancellation point)
for all the cancellation-point system calls that you don't think
should really be cancellation points.

--Ted










More information about the c++-pthreads mailing list