What are the real issues?
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

What are the real issues?



I'm just trying to trace back why people see C++ thread cancellation as a problem.

First: the usual assumption has been that thread cancellation is most naturally represented in C++ as an exception. That's because the POSIX model for thread cancellation is that if you cancel a particular thread, then, the next time the thread reaches one of a set of well-defined cancellation points, it'll perform some cleanup and then stop. In C the cleanup is via handlers that you register. We could have that model in C++ as well, but most C++ programmers would think it's very strange that you would stop execution at some point without cleaning up the stack. And once you're talking about stack cleanup, i.e. invoking destructors, you're talking about something that's close enough to an exception that it makes no difference.

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

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.

It's actually pretty hard for me to imagine any model for thread 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.

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.

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

			--Matt