Doc. no.   WG21/N2106=06-0176
Date:        2006-10-23
Project:     Programming Language C++
Reply to:   Beman Dawes <bdawes@acm.org>

Cloning and Throwing Dynamically Typed Exceptions
(Supporting exception propagation from threads)

Introduction
Part One: Dynamic clone and throw functions (compiler support not required)
    Proposed library changes
    Rationale for mixin approach
    Impact on the Standard
    Impact on existing code
Part Two: Dynamic clone and throw functions (compiler support required)
    Proposed library changes
    Rationale for library approach
    Impact on the Standard
    Impact on existing code
Part Three: Exception propagation from threads
    Motivation
    Discussion
    Interaction with other proposals
    Proposed changes
    Q & A
    Open issues
Implementation experience using the proposed mixin
Acknowledgements
Revision history   

Introduction

The underlying problem addressed in this proposal is a need to catch an exception by dynamic type (i.e. derived class), save it and later rethrow it, knowing only the static type (i.e. base class), and then finally to catch it again using the derived type.

Although this behavior might be useful elsewhere, it becomes a pressing need if exceptions are to be propagated from a thread-of-execution to its joining thread.

Here is a non-threaded example of the problem expressed in psuedo-C++:

class B {
  /* ... */
  virtual ~B();
};

class D : public B {
  /*...*/
};

void f() { throw D(); }

int g()
{
  B * p = 0;

  try { 
    f();
  }

  catch ( const B & ex ) {
    p = dynamic_new B(ex); // effectively: new D(ex)
  }

  // ...

  try {
    if ( p ) dynamic_throw p; // type thrown is D
  }

  catch ( const D & ex ) {
    // we want this catch to be executed
  }
  catch ( const B & ex ) {
    // we don't want this catch to be executed
  }
}

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 exception propagation from threads while pursuing both library and core language approaches to cloning and throwing exceptions based on dynamic type, which is needed to support implementation of exception propagation.

Part One: Dynamic clone and throw functions (compiler support not required)

Proposed library changes

To 18.7.1, Class exception, change::

class exception {

to:

class exception : public virtual cloneable {

and add:

exception * dynamic_clone() const;

Returns: new cloneable(*this)

void dynamic_throw() const;

Throws: *this

To a header to be decided, add:

namespace std
{
  class cloneable
  {
  public:
    virtual cloneable *  dynamic_clone() const =0;
    virtual void         dynamic_throw() const =0;
    virtual ~cloneable() {}
  };
}

Class cloneable

Class cloneable provides a mixin base class for objects that need to clone themselves or throw themselves based on dynamic type.

[Examples:

class my_exception : public std::exception
{
public:
  my_exception *  dynamic_clone() const { return new my_exception(*this); }
  void            dynamic_throw() const { throw *this; }

  // ...
};
class non_std_exception : public virtual std::cloneable
{
public:
  non_std_exception *  dynamic_clone() const { return new non_std_exception(*this); }
  void                 dynamic_throw() const { throw *this; }

  // ...
};

-- end examples]

Rationale for mixin approach

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 dynamic_clone and dynamic_throw 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.

Impact on the Standard

This is a pure library proposal. It can be portably implemented with any C++03 compiler. It's impact on the standard is limited to the change to std::exception.

Impact on existing code

No practical impact foreseen. It is remotely possible that some existing code somehow depends on std::exception having no base class, but that seems pretty unlikely.

Part Two: Dynamic clone and throw functions (compiler support required)

Note well: It is not yet known if it is even feasible for compilers to provide the required support, or what the cost would be.

Proposed library changes

To a header to be decided, add:

namespace std
{
  template< class T >
  T * dynamic_clone( const T & x );

  template< class T >
  void dynamic_throw( const T & x );
}

The functions in this section are described in terms of types T and D, and an argument x of type T. T is the type given by the template parameter. If T has any virtual functions, D is the dynamic type (1.3.4, dynamic type) of x. Otherwise, D is the same type as T.

[Note: Full implementation of these functions requires compiler support. If such support is not available, D shall be treated as being the same type as T.  This latitude for implementors will be removed in the future. -- end note]

template< class T >
T * dynamic_clone( const T & x );

Returns: new D(x)

template< class T >
void dynamic_throw( const T & x );

Effects: Throws dynamic_cast<const D &>(x).

Rationale for library approach

The rationale of a library approach is simply to avoid addition of new keywords to the core language. Adding new keywords has a much greater potential impact on existing user code than adding new standard library functions.

Impact on the Standard

This is a pure library proposal. It has no impact other impact on the standard.

Impact on existing code

None.

Part Three: Exception propagation from threads

Motivation

In a thread of execution other than the initial (main) thread, 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.

Discussion

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 (i.e. caller) 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).

Interaction with other proposals

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 program the current thread of execution, the function std::terminate() is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined (15.5.1).

Proposed changes

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 occurred in the thread function,

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

Q & A

What happens if an exception is thrown by the joining thread before the call to a join which has a pending exception reached? The exception caused by the joining thread is propagated as usual. There is no effect on the join's pending exception - it is still pending.

What happens if several threads have pending exceptions? For each join for a thread with a pending exception, the join rethrows when reached (if ever). The fact that there are pending exceptions for other threads has no impact.

What happens on a join_all if multiple exceptions are pending? This could depend on the specification of a join_all, but it is clear that multiple pending exceptions should not cause a termination.  And that at least one exception should propagate out.

Should there be a thread launching function for thread that always terminates on uncaught exceptions, and another for a thread that always catches (and rethrows at join)? Yes. This should flow naturally from a layered approach to the thread library's design.

Open Issues

Implementation experience using the proposed mixin

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 *  dynamic_clone() const { return new my_exception(*this); }
  void            dynamic_throw() 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

Acknowledgements

Alisdair Meredith suggested the mixin approach rather than changing std::exception, and also provided an alternative approach that did not require changes to exception classes (but did require changes to code throwing exceptions.) Peter Dimov and Howard Hinnant provided helpful comments to the original paper.

Revision History

This paper is a major revision of N2061.


© Copyright Beman Dawes 2006