[C++-sig] question on boost.python exception mechanisms
Holger Joukl
Holger.Joukl at LBBW.de
Mon Apr 29 16:46:26 CEST 2013
Hi,
> From: "Niall Douglas" <s_sourceforge at nedprod.com>
> On 17 Apr 2013 at 17:13, Holger Joukl wrote:
>
> > // the global per-thread exception storage
> > boost::unordered_map<pthread_t, boost::exception_ptr> exception_map;
>
> You can't assume pthread_t is of integral type. You can a thread_t
> (from C11) I believe. You may not care on your supported platforms
> though.
I see. If it isn't an integral type I suppose I'd need to supply a custom
hash implementation to be able to use unordered_map.
> > throw std::runtime_error("throwing up");
>
> If you're going to use Boost's exception_ptr implementation, you
> really ought to throw using Boost's exception throw macro. Otherwise
> stuff become unreliable.
Right. This should then rather be
throw boost::enable_current_exception(std::runtime_error("throwing
up"));
Looks like unfortunately Boost.Python does not use this itself so a
packaged_task/future will throw unknown_exception for the deferred
invocation of a boost::python::error_already_set-throwing call.
I could work around this in my case by wrapping the original call in
try-catch and re-raise like
try {
this->get_override("onMsg")(bp::ptr(listener), boost::ref(msg));
} catch (const bp::error_already_set& e) {
// enable support for cloning the current exception, to avoid
having
// a packaged_task/future throw an unknown_exception
boost::enable_current_exception(bp::error_already_set());
}
> > [...]
>
> If you're just going to do this, I'd suggest you save yourself some
> hassle and look into packaged_task which is a std::function combined
> with a std::future. It takes care of the exception trapping and
> management for you. Do bear in mind there is absolutely no reason you
> can't use a future within a single thread to traverse some state over
> third party binary blob code, indeed I do this in my own code at
> times e.g. if Qt, which doesn't like exceptions, stands between my
> exception throwing code at the end of a Qt signal and my code which
> called Qt.
>
> > [...]
>
> Why not using thread local storage?
Using thread local storage of a queue of deferred futures might then look
s.th. like this:
$ cat dispatch_main_packaged_task_tls_queue.cpp
// File: dispatch_main_packaged_task_tls_queue.cpp
#include <iostream>
#include <exception>
#include <stdexcept>
#include <boost/thread.hpp>
#include <boost/move/move.hpp> // header-only
#include <boost/bind.hpp> // header-only
#include <boost/lockfree/spsc_queue.hpp> // header-only
#include <boost/lexical_cast.hpp>
#include "dispatch.h"
// global thread local store of void-returning task results
// (per-thread queue of futures)
class CURRENT_THREAD_STATE {
public:
typedef boost::unique_future<void> unique_future_t;
typedef boost::shared_ptr<unique_future_t> unique_future_ptr_t;
typedef boost::lockfree::spsc_queue<
unique_future_ptr_t, boost::lockfree::capacity<10> >
future_queue_t;
typedef boost::thread_specific_ptr< future_queue_t > tls_t;
private:
// the internal per-thread futures queue storage
static tls_t _current_thread_tls;
public:
// package a task and defer it
// (to a thread-local queue of of void-returning futures)
template <typename Fn, typename A0>
static void deferred(Fn func, A0 const & arg1) {
// need a nullary function object as an arg to packaged_task so
// use boost::bind
boost::packaged_task<void> pt(boost::bind(func, arg1));
unique_future_t fut = pt.get_future();
future_queue_t * queue_ptr = _current_thread_tls.get();
if (queue_ptr == NULL) {
queue_ptr = new future_queue_t;
_current_thread_tls.reset(queue_ptr);
}
unique_future_ptr_t fut_ptr(new unique_future_t(boost::move(fut)));
queue_ptr->push(fut_ptr);
pt();
}
// retrieve the deferred task results
static void get_deferred(void) {
future_queue_t * queue_ptr = _current_thread_tls.get();
if (queue_ptr != NULL) {
unique_future_ptr_t fut_ptr;
if (queue_ptr->pop(fut_ptr)) {
if (fut_ptr) {
fut_ptr->get();
}
}
}
}
};
// don't forget the definition of the static member variable
CURRENT_THREAD_STATE::tls_t CURRENT_THREAD_STATE::_current_thread_tls;
void callback(cb_arg_t arg) {
std::cout << "--> CPP callback arg=" << arg << std::endl;
std::cout << "<-- CPP callback arg=" << arg << std::endl;
}
// this callback raises an exception
void callback_with_exception(cb_arg_t arg) {
std::cout << "--> exception-throwing CPP callback arg=" << arg <<
std::endl;
std::string errormsg("throwing up ");
errormsg += arg;
std::cout << "errormsg=" << errormsg << std::endl;
throw std::runtime_error(errormsg);
std::cout << "<-- exception-throwing CPP callback arg=" << arg <<
std::endl;
}
// this calls the exception raising callback but has packaged_task manage
// the result and possible exceptions
void guarded_callback_with_exception(cb_arg_t arg) {
std::cout << "--> guarded exception-throwing CPP callback arg=" << arg
<< std::endl;
CURRENT_THREAD_STATE::deferred(&callback_with_exception, arg);
std::cout << "<-- guarded exception-throwing CPP callback arg=" << arg
<< std::endl;
}
int main(void) {
std::cout << "--> CPP main" << std::endl;
cb_arg_t s = "CPP main callback argument";
std::cout << std::endl << "invoking callback" << std::endl;
invoke(&callback, s);
std::cout << std::endl << "invoking guarded callback" << std::endl;
invoke(&guarded_callback_with_exception, s);
try {
std::cout << "rethrowing exception from global tls" << std::endl;
CURRENT_THREAD_STATE::get_deferred();
} catch (const std::exception& exc) {
std::cout << "caught rethrown callback exception: " << exc.what()
<< std::endl;
}
for (int i=0; i<3; ++i) {
std::string cb_string_arg = std::string("CPP main cb arg ") +
boost::lexical_cast<std::string>(i);
std::cout << std::endl << "invoking guarded callback (run " << i <<
")" << std::endl;
invoke(&guarded_callback_with_exception, cb_string_arg.c_str());
}
for (int i=0; i<3; ++i) {
try {
std::cout << "rethrowing exception from global tls (run " << i
<< ")" << std::endl;
CURRENT_THREAD_STATE::get_deferred();
} catch (const std::exception& exc) {
std::cout << "caught rethrown callback exception: " << exc.what
() << std::endl;
}
}
// this is expected to segfault iff the underlying C lib isn't compiled
with
// exception support
std::cout << std::endl << "invoking exception-throwing callback" <<
std::endl;
std::cout << "(will segfault iff c lib is not exception-enabled)" <<
std::endl;
try {
invoke(&callback_with_exception, s);
} catch (const std::exception& exc) {
std::cout << "caught callback exception: " << exc.what() <<
std::endl;
}
std::cout << std::endl;
std::cout << "<-- CPP main" << std::endl;
return 0;
}
0 $ BOOST_VERSION=1.53.0; BOOST_DIR=boost_${BOOST_VERSION//./_}; echo
$BOOST_DIR; /apps/local/gcc/4.6.1/bin/g++ -pthreads -I. -L. -R.
-R /apps/prod/gcc/4.6.1/lib/
-R /var/tmp/BUILD/gcc/$BOOST_DIR/stage/gcc-4.6.1/py2.7/boost/$BOOST_VERSION/lib
-L /var/tmp/BUILD/gcc/$BOOST_DIR/stage/gcc-4.6.1/py2.7/boost/$BOOST_VERSION/lib
-I /var/tmp/BUILD/gcc/$BOOST_DIR/ -ldispatch -lboost_thread -lboost_system
-lboost_atomic dispatch_main_packaged_task_tls_queue.cpp && ./a.out
boost_1_53_0
--> CPP main
invoking callback
--> invoke(298752, CPP main callback argument)
--> CPP callback arg=CPP main callback argument
<-- CPP callback arg=CPP main callback argument
<-- invoke(298752, CPP main callback argument)
invoking guarded callback
--> invoke(299312, CPP main callback argument)
--> guarded exception-throwing CPP callback arg=CPP main callback argument
--> exception-throwing CPP callback arg=CPP main callback argument
errormsg=throwing up CPP main callback argument
<-- guarded exception-throwing CPP callback arg=CPP main callback argument
<-- invoke(299312, CPP main callback argument)
rethrowing exception from global tls
caught rethrown callback exception: throwing up CPP main callback argument
invoking guarded callback (run 0)
--> invoke(299312, CPP main cb arg 0)
--> guarded exception-throwing CPP callback arg=CPP main cb arg 0
--> exception-throwing CPP callback arg=CPP main cb arg 0
errormsg=throwing up CPP main cb arg 0
<-- guarded exception-throwing CPP callback arg=CPP main cb arg 0
<-- invoke(299312, CPP main cb arg 0)
invoking guarded callback (run 1)
--> invoke(299312, CPP main cb arg 1)
--> guarded exception-throwing CPP callback arg=CPP main cb arg 1
--> exception-throwing CPP callback arg=CPP main cb arg 1
errormsg=throwing up CPP main cb arg 1
<-- guarded exception-throwing CPP callback arg=CPP main cb arg 1
<-- invoke(299312, CPP main cb arg 1)
invoking guarded callback (run 2)
--> invoke(299312, CPP main cb arg 2)
--> guarded exception-throwing CPP callback arg=CPP main cb arg 2
--> exception-throwing CPP callback arg=CPP main cb arg 2
errormsg=throwing up CPP main cb arg 2
<-- guarded exception-throwing CPP callback arg=CPP main cb arg 2
<-- invoke(299312, CPP main cb arg 2)
rethrowing exception from global tls (run 0)
caught rethrown callback exception: throwing up CPP main cb arg 0
rethrowing exception from global tls (run 1)
caught rethrown callback exception: throwing up CPP main cb arg 1
rethrowing exception from global tls (run 2)
caught rethrown callback exception: throwing up CPP main cb arg 2
invoking exception-throwing callback
(will segfault iff c lib is not exception-enabled)
--> invoke(298904, CPP main callback argument)
--> exception-throwing CPP callback arg=CPP main callback argument
errormsg=throwing up CPP main callback argument
terminate called after throwing an instance of 'std::runtime_error'
what(): throwing up CPP main callback argument
Abort (core dumped)
(core dump expected for the unguarded case as I did not compile the
C lib with exception support)
Notes:
- not generalized, supports only 1-argument void-returning callables
- Boost doesn't list Boost.Atomic as a non-header-only lib but you need to
compile & link with it (for lockfree)
- need -lboost_system when using -lboost_thread
- could also use a per-thread future instead of a queue of futures if
there's no possibility of or no interest in a subsequent result overwriting
a previous result
> Keep a count of nesting levels, so if a callback calls a callback
> which calls a callback etc.
Ok so no out-of-the-box functionality.
> Another possibly useful idea is to have the C libs dispatcher
> dispatch packaged_task's into a per-thread lock free queue which you
> then dispatch on control return when the C library isn't in the way
> anymore. Call it deferred callbacks :)
Interesting. So I'd basically transfer the actual callback invocation to
exception-capable-land.
I feel a bit out of my depths here now wrt to all that boost magic
but I've definitely learned a bunch :)
Still quite unsure if I shouldn't just keep it really simple
and go with the original idea of catching boost::python::error_already_set
and re-raising if (PyErr_Occurred()) (and not add a dependency to
Boost.Thread, Boost.System and possibly Boost.Atomic)...
Thanks again for this wealth of useful hints and suggestions,
Holger
Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart
More information about the Cplusplus-sig
mailing list