Doc. no. WG21/N2061=06-0131
Date:
2006-09-08
Project: Programming Language C++
Reply to: Beman Dawes <bdawes@acm.org>
This paper is in two parts:
The proposals in this paper are preliminary, and intended to put the issues on the table for further discussion.
In a thread of execution other than main(), what is useful yet safe behavior if no matching exception handler is found after an exception is thrown?
Users of the Boost threading library, which does not currently do anything
special with exceptions, have asked time-and-time again that the exception to be
caught by the thread library mechanism and saved, and then
re-thrown by the thread object's join()
function or equivalent.
Within a thread of execution, an exception will presumably cause a search for
an exception handler as in any C++ program. Absent any thread library support
for exception propagation, if no
matching exception handler is found within the thread of execution, then std::terminate()
will be
called (15.3/9).
But just as in single-thread programs, it is highly desirable that exceptions propagate from a function to its caller. Because a thread's processing function is in a different thread of execution from its joining function doesn't change that. If exceptions do not propagate out of a thread of execution to its joining function, programmers are forced to construct ad hoc error recovery techniques of the worst kind, such as catching exceptions themselves and converting them to error codes (which are too often ignored by the joiner, with disastrous effects).
std::terminate()
is called.Proposals dealing with a C++ memory model that supports multi-threading will presumable change 15.3, Handling an exception, paragraph 9 as indicated:
If no matching handler is found in
a programthe current thread of execution, the functionstd::terminate()
is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined (15.5.1).
Add a class thread_exception_error
derived from std::_runtime_error
,
details to be supplied.
To the launcher function, add:
Effects: If an exception is thrown by the thread function, and the thread has not been detached, the exception is caught and saved (see join and destructor). Otherwise,
std::terminate()
is called.
To the join function, add:
Throws: If an exception from the thread function has occurred:
- If the exception was of a type derived from
tr2::cloneable
, a copy of the originally thrown object.- Otherwise, an object of type
std::thread_exception_error
.
To the thread destructor, add:
Effects: If the thread object's thread-of-execution ended with an uncaught exception and the join function has not been called, call
std::terminate()
.
nothrow
version of join()
needed?bool exception_pending() const
needed?Implementation of exception propagation requires two actions not directly supported by the C++03 exception handling mechanism:
There are three known approaches:
The question of a core language approach versus a library approach was considered at the Redmond ad hoc threads meeting in August, 2006. There was consensus for going forward with both core language support and library support. If core language support is accepted, the library approach may still be of some value for C++03 compilers, and any cases not covered by core language support.
This proposal relies on a library solution to achieve the desired "join re-throws" behavior.
To a header to be decided, add:
namespace std { namespace tr2 { class cloneable { public: virtual cloneable * clone_self() const; virtual void throw_self() const; virtual ~cloneable(); }; } }Class
cloneable
Class
cloneable
provides a mixin base class for objects that need to clone themselves or throw themselves.
virtual cloneable * clone_self() const;Returns:
new cloneable(*this)
virtual void throw_self() const;Throws:
*this
Users will need to write catch clauses for specific exception classes, both
Standard Library and user-defined. To implement this behavior, it must be
possible save and re-throw exceptions without knowing anything more than their
base class. The clone_self
and throw_self
functions
make this possible. Using a mixin base class allows degraded functionality even
when the version of the Standard Library used does not itself use the mixin. It
also works for user-defined exception types no derived from std::exception
.
Pete Becker pointed out that the C++ runtime support knows the types, so it would be possible to add a clone operator to the language, and change the throw operation rules so the correct type is thrown on the rethrow. This current proposal is intended to work within the current language.
This is a pure library proposal, suitable for TR2. It can be portably implemented with any C++03 compiler.
Boost threads was hacked to provide the proposed functionality. The following program illustrates the results:
#include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <boost/cloneable.hpp> #include <stdexcept> #include <iostream> boost::mutex cout_mutex; // cout race prevention; class my_exception : public std::runtime_error, public virtual boost::cloneable { public: my_exception( const std::string & s ) : runtime_error( s ) {} ~my_exception() {} my_exception * clone_self() const { return new my_exception(*this); } void throw_self() const { throw *this; } }; class foo { int m_argc; public: foo( int argc ) : m_argc( argc ) {} void operator()() { boost::mutex::scoped_lock lock(cout_mutex); std::cout << "starting thread function" << std::endl; if ( m_argc > 1 ) throw my_exception("\"what-string\""); std::cout << "normal return from thread function" << std::endl; } }; int main( int argc, char * argv[] ) { foo child(argc); std::cout << "starting thread" << std::endl; boost::thread t(child); { boost::mutex::scoped_lock lock(cout_mutex); std::cout << "calling join" << std::endl; } try { t.join(); } catch ( const my_exception & ex ) // catch #1 { std::cout << "caught my_exception: " << ex.what() << std::endl; } catch ( const std::exception & ex ) // catch #2 { std::cout << "caught std::exception: " << ex.what() << std::endl; } std::cout << "exiting program" << std::endl; return 0; }
When invoked with no arguments, the output is:
starting thread starting thread function normal return from thread function calling join exiting program
Invoked with any argument, the output is:
starting thread starting thread function calling join caught my_exception: "what-string" exiting program
An early version of this paper was discussed at the Redmond ad hoc threads
meeting in August, 2006. Many participants contributed suggestions, and there
was a strong consensus to go forward with a library based approach to the
threading issues, and at the same time pursue both library and core language
approaches to exception cloning and rethrowing. Alisdair Meredith suggested the
mixin approach rather than changing std::exception
, and provided an
alternative approach that did not require changes to exception classes (but did
require changes to code throwing exceptions.)
© Copyright Beman Dawes 2006