question on boost.python exception mechanisms
Hi, I'm wrapping a C++ library that's actually just a thin wrapper around a C lib. Through a dispatch() method of a Dispatcher class there's an invocation of callbacks which get implemented on the Python side, by subclassing callback classes exposed through Boost.Python. Now, for performing the actual dispatch the C++ library calls into the underlying C library. This hasn't been a problem so far as we've been targetting Solaris using Sun/Oracle studio compilers. However, on Linux compiled with GCC, if an exception gets raised in the Python callback the program segfaults. Obviously the C part of the vendor library has not been compiled with (GCC-) exception support for Linux. Unfortunately this isn't under my control and the library provider seems not to be willing to make the library robust against exceptions in user callback code. So I need to keep the Boost C++ exception that "signals" the Python exception from passing through the C parts. Instead of introducing some custom error/exception notification scheme I *think* I can simply do this: As I need wrapper classes for the virtual method override mechanisms anyway I can just ignore/suppress the exception in the callback wrapper layer and re-throw the exception again in the dispatcher, provided that in between we do not return to Python and no other call into Python is made: I.e. The callback wrapper simply suppresses the C++ exception "signalled" for the Python exception: class CallbackWrap : public Callback, public bp::wrapper<Callback> { // pure virtual so no need for default implementation // virtual void onMsg(MsgListener* listener, Msg& msg) = 0; virtual void onMsg(MsgListener* listener, Msg& msg) { try { this->get_override("onMsg")(bp::ptr(listener), boost::ref (msg)); } catch (const bp::error_already_set& e) { if (PyErr_Occurred()) { // Do nothing: The Dispatcher that invoked the callback // MUST check if a Python error has been set and re-throw } } } }; The Dispatcher is then responsible for re-throwing, to not let the Python exception go undetected when returning to the Python world: // virtual Status dispatch (); virtual Status dispatch() { Status ret; if (bp::override f = this->get_override("dispatch")) { ret = f(); // *note* } else { ret = Queue::dispatch(); } if (PyErr_Occurred()) { // Re-throw the error that stems from the Python exception in the dispatched callback bp::throw_error_already_set(); } return ret; } Does this sound sane? Any hints appreciated, Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
On 26/03/2013 18:51, Holger Joukl wrote:
Hi,
I'm wrapping a C++ library that's actually just a thin wrapper around a C lib.
Through a dispatch() method of a Dispatcher class there's an invocation of callbacks which get implemented on the Python side, by subclassing callback classes exposed through Boost.Python.
Now, for performing the actual dispatch the C++ library calls into the underlying C library.
This hasn't been a problem so far as we've been targetting Solaris using Sun/Oracle studio compilers.
However, on Linux compiled with GCC, if an exception gets raised in the Python callback the program segfaults. Obviously the C part of the vendor library has not been compiled with (GCC-) exception support for Linux.
Unfortunately this isn't under my control and the library provider seems not to be willing to make the library robust against exceptions in user callback code.
So I need to keep the Boost C++ exception that "signals" the Python exception from passing through the C parts.
How does the library handle callbacks? An event loop in a separate thread? Do you have to explicitly call some blocking function to run the event loop? If it's a C lib likely the callbacks are just function pointers. Being called inside a C object I'd say you assumption is correct: no C++ exceptions to be raised inside callbacks (the lib is gcc-compiled, it has no knowledge of exceptions). If you could recompile with g++ the exception would raise from the event loop, unless catched while calling the callback function pointer. Sidenote: how does the C++ exception mechanism work under the hood? What happens if it's called inside a C compiled function? -- Giuseppe Corbelli WASP Software Engineer, Copan Italia S.p.A Phone: +390303666318 Fax: +390302659932 E-mail: giuseppe.corbelli@copanitalia.com
On 26/03/2013 18:51, Holger Joukl wrote:
Hi,
I'm wrapping a C++ library that's actually just a thin wrapper around a
C
lib.
Through a dispatch() method of a Dispatcher class there's an invocation of callbacks which get implemented on the Python side, by subclassing callback classes exposed through Boost.Python.
Now, for performing the actual dispatch the C++ library calls into the underlying C library.
This hasn't been a problem so far as we've been targetting Solaris using Sun/Oracle studio compilers.
However, on Linux compiled with GCC, if an exception gets raised in the Python callback the program segfaults. Obviously the C part of the vendor library has not been compiled with (GCC-) exception support for Linux.
Unfortunately this isn't under my control and the library provider seems not to be willing to make the library robust against exceptions in user callback code.
So I need to keep the Boost C++ exception that "signals" the Python exception from passing through the C parts.
How does the library handle callbacks? An event loop in a separate
Hi Giuseppe, thanks for answering and sorry for the delayed response. Easter holidays :-) Giuseppe Corbelli <giuseppe.corbelli@copanitalia.com> wrote on 28.03.2013 09:37:39: thread? Do
you have to explicitly call some blocking function to run the event loop?
Yes. There's a separate thread that schedules events to a queue being dispatch in the main loop, in the main thread. And yes, there's a blocking call to a dispatch method with the possibility of a timeout or alternatively a non-blocking poll call (we make sure to properly release & reacquire the Python GIL during blocking).
If it's a C lib likely the callbacks are just function pointers. Being called inside a C object I'd say you assumption is correct: no C++ exceptions to be raised inside callbacks (the lib is gcc-compiled, it has no knowledge of exceptions).
Correct, the callbacks are function pointers from the C libs point of view.
If you could recompile with g++ the exception would raise from the event loop, unless catched while calling the callback function pointer.
While I do have the C++ sources (to be able to recompile for different C++ compilers) I don't have the C sources so I unfortunately I can't recompile the C-lib parts with exceptions support. Sidenote: For Oracle/Sun Solaris Studio compilers this isn't a problem as you basically can't and don't need to switch the exception support of the compiler for mixing C and C++ code (http://www.oracle.com/technetwork/articles/servers-storage-dev/mixingcandcpl...)
Sidenote: how does the C++ exception mechanism work under the hood? What happens if it's called inside a C compiled function?
Not sure if I understand your question correctly, but: - Solaris Studio compilers: An exception raised in a C++ compiled function that got called from a C compiled function called from a C++ main will properly propagate to the main caller - GCC: An exception raised in a C++ compiled function that got called from a C compiled function called from a C++ main will - segfault if the C compiled function hasn't been compiled with exception support (-fexceptions) - properly propagate to the main caller if the C compiled function has been compiled with exception support Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
On 03/04/2013 10:08, Holger Joukl wrote:
Hi Giuseppe,
thanks for answering and sorry for the delayed response. Easter holidays :-)
To punish you here's another late reply.
Giuseppe Corbelli<giuseppe.corbelli@copanitalia.com> wrote on 28.03.2013 09:37:39:
On 26/03/2013 18:51, Holger Joukl wrote:
Hi,
I'm wrapping a C++ library that's actually just a thin wrapper around a C lib.
Through a dispatch() method of a Dispatcher class there's an invocation of callbacks which get implemented on the Python side, by subclassing callback classes exposed through Boost.Python.
Now, for performing the actual dispatch the C++ library calls into the underlying C library.
This hasn't been a problem so far as we've been targetting Solaris using Sun/Oracle studio compilers.
However, on Linux compiled with GCC, if an exception gets raised in the Python callback the program segfaults. Obviously the C part of the vendor library has not been compiled with (GCC-) exception support for Linux.
Unfortunately this isn't under my control and the library provider seems not to be willing to make the library robust against exceptions in user callback code.
So I need to keep the Boost C++ exception that "signals" the Python exception from passing through the C parts.
How does the library handle callbacks? An event loop in a separate thread? Do you have to explicitly call some blocking function to run the event loop?
Yes. There's a separate thread that schedules events to a queue being dispatch in the main loop, in the main thread. And yes, there's a blocking call to a dispatch method with the possibility of a timeout or alternatively a non-blocking poll call (we make sure to properly release& reacquire the Python GIL during blocking).
If it's a C lib likely the callbacks are just function pointers. Being called inside a C object I'd say you assumption is correct: no C++ exceptions to be raised inside callbacks (the lib is gcc-compiled, it has no knowledge of exceptions).
Correct, the callbacks are function pointers from the C libs point of view.
If you could recompile with g++ the exception would raise from the event loop, unless catched while calling the callback function pointer.
While I do have the C++ sources (to be able to recompile for different C++ compilers) I don't have the C sources so I unfortunately I can't recompile the C-lib parts with exceptions support. Sidenote: For Oracle/Sun Solaris Studio compilers this isn't a problem as you basically can't and don't need to switch the exception support of the compiler for mixing C and C++ code (http://www.oracle.com/technetwork/articles/servers-storage-dev/mixingcandcpl...)
I have found a couple of references. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html (see static-libgcc) http://gcc.gnu.org/wiki/Visibility The proprietary lib is shared, right? linked to? shared? static?
Sidenote: how does the C++ exception mechanism work under the hood? What happens if it's called inside a C compiled function?
Not sure if I understand your question correctly, but: - Solaris Studio compilers: An exception raised in a C++ compiled function that got called from a C compiled function called from a C++ main will properly propagate to the main caller - GCC: An exception raised in a C++ compiled function that got called from a C compiled function called from a C++ main will - segfault if the C compiled function hasn't been compiled with exception support (-fexceptions) - properly propagate to the main caller if the C compiled function has been compiled with exception support
Well, I was thinking about ASM level :-) -- Giuseppe Corbelli WASP Software Engineer, Copan Italia S.p.A Phone: +390303666318 Fax: +390302659932 E-mail: giuseppe.corbelli@copanitalia.com
Hi, Giuseppe Corbelli <giuseppe.corbelli@copanitalia.com> wrote on 08.04.2013 11:29:10:
On 03/04/2013 10:08, Holger Joukl wrote:
Hi Giuseppe,
thanks for answering and sorry for the delayed response. Easter holidays :-)
To punish you here's another late reply.
However, on Linux compiled with GCC, if an exception gets raised in
Python callback the program segfaults. Obviously the C part of the vendor library has not been compiled with (GCC-) exception support for Linux.
Unfortunately this isn't under my control and the library provider seems not to be willing to make the library robust against exceptions in user callback code.
So I need to keep the Boost C++ exception that "signals" the Python exception from passing through the C parts.
How does the library handle callbacks? An event loop in a separate thread? Do you have to explicitly call some blocking function to run
event loop?
Yes. There's a separate thread that schedules events to a queue being dispatch in the main loop, in the main thread. And yes, there's a blocking call to a dispatch method with the
;-) the the possibility
of a timeout or alternatively a non-blocking poll call (we make sure to properly release& reacquire the Python GIL during blocking).
If it's a C lib likely the callbacks are just function pointers. Being called inside a C object I'd say you assumption is correct: no C++ exceptions to be raised inside callbacks (the lib is gcc-compiled, it has no knowledge of exceptions).
Correct, the callbacks are function pointers from the C libs point of view.
If you could recompile with g++ the exception would raise from the event loop, unless catched while calling the callback function pointer.
While I do have the C++ sources (to be able to recompile for different C++ compilers) I don't have the C sources so I unfortunately I can't recompile the C-lib parts with exceptions support. Sidenote: For Oracle/Sun Solaris Studio compilers this isn't a problem as you basically can't and don't need to switch the exception support of the compiler for mixing C and C++ code (http://www.oracle.com/technetwork/articles/servers-storage-dev/ mixingcandcpluspluscode-305840.html)
I have found a couple of references. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html (see static-libgcc) http://gcc.gnu.org/wiki/Visibility
Thanks, I'll need to look into these.
The proprietary lib is shared, right? linked to? shared? static?
Sidenote: how does the C++ exception mechanism work under the hood? What happens if it's called inside a C compiled function?
Not sure if I understand your question correctly, but: - Solaris Studio compilers: An exception raised in a C++ compiled function that got called from a C compiled function called from a C++ main will properly
Shared C lib, we compile a shared C++ lib linked to the C lib from the vendor C++ sources, which we shared-link the (shared) Boost.Python wrapper to. propagate
to the main caller - GCC: An exception raised in a C++ compiled function that got called from a C compiled function called from a C++ main will - segfault if the C compiled function hasn't been compiled withexception support (-fexceptions) - properly propagate to the main caller if the C compiled function has been compiled with exception support
Well, I was thinking about ASM level :-)
No clue whatsoever. Regards Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
On 08/04/2013 14:11, Holger Joukl wrote:
I have found a couple of references. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html (see static-libgcc) http://gcc.gnu.org/wiki/Visibility
Thanks, I'll need to look into these.
The proprietary lib is shared, right? linked to? shared? static?
Shared C lib, we compile a shared C++ lib linked to the C lib from the vendor C++ sources, which we shared-link the (shared) Boost.Python wrapper to.
Quoting from the manual: There are several situations in which an application should use the shared libgcc instead of the static version. The most common of these is when the application wishes to throw and catch exceptions across different shared libraries. In that case, each of the libraries as well as the application itself should use the shared libgcc. However, if a library or main executable is supposed to throw or catch exceptions, you must link it using the G++ or GCJ driver, as appropriate for the languages used in the program, or using the option -shared-libgcc, such that it is linked with the shared libgcc. Likely the C_lib.so is not linked to libgcc_s.so. Don't know if it's possible to "extract" the objects from the shared .so. Maybe playing dirty with LD_PRELOAD? -- Giuseppe Corbelli WASP Software Engineer, Copan Italia S.p.A Phone: +390303666318 Fax: +390302659932 E-mail: giuseppe.corbelli@copanitalia.com
On 08/04/2013 14:11, Holger Joukl wrote:
I have found a couple of references. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html (see static-libgcc) http://gcc.gnu.org/wiki/Visibility
Thanks, I'll need to look into these.
The proprietary lib is shared, right? linked to? shared? static?
Shared C lib, we compile a shared C++ lib linked to the C lib from the vendor C++ sources, which we shared-link the (shared) Boost.Python wrapper to.
Quoting from the manual: There are several situations in which an application should use the shared libgcc instead of the static version. The most common of these is when
Hi, Giuseppe Corbelli <giuseppe.corbelli@copanitalia.com> wrote on 09.04.2013 09:09:14: the
application wishes to throw and catch exceptions across different shared libraries. In that case, each of the libraries as well as the application
itself should use the shared libgcc.
Both the shared libboost_python.so and the extension module I'm building link against the shared libgcc_s.so.
However, if a library or main executable is supposed to throw or catch exceptions, you must link it using the G++ or GCJ driver, as appropriate for the languages used in the program, or using the option -shared-libgcc, such that it is linked with the shared libgcc.
Likely the C_lib.so is not linked to libgcc_s.so. Don't know if it'spossible to "extract" the objects from the shared .so. Maybe playing dirty with LD_PRELOAD?
While the shared lib is indeed not linked to libgcc_s.so I now don't think this does make any difference for the problem at hand: Experimenting with this simple test lib: 0 $ cat dispatch.h // File: dispatch.h #ifdef __cplusplus extern "C" { #endif typedef char const *cb_arg_t; typedef void (*callback_func_ptr_t)(cb_arg_t); void invoke(callback_func_ptr_t cb, cb_arg_t arg); #ifdef __cplusplus } #endif 0 $ cat dispatch.c // File: dispatch.c #include <stdio.h> #include "dispatch.h" void invoke(callback_func_ptr_t cb, cb_arg_t arg) { printf("--> invoke(%d, %s)\n", &(*cb), arg); (*cb)(arg); printf("<-- invoke(%d, %s)\n", &(*cb), arg); } $ cat dispatch_main.cpp // File: dispatch_main.cpp #include <iostream> #include <stdexcept> #include "dispatch.h" void callback(cb_arg_t arg) { printf("--> CPP callback(%s)\n", arg); printf("<-- CPP callback(%s)\n", arg); } void callback_with_exception(cb_arg_t arg) { printf("--> CPP exception-throwing callback(%s)\n", arg); throw std::runtime_error("throwing up"); printf("<-- CPP exception-throwing callback(%s)\n", arg); } int main(void) { std::cout << "--> CPP main" << std::endl; cb_arg_t s = "CPP main callback argument"; invoke(&callback, s); try { invoke(&callback_with_exception, s); } catch (const std::exception& exc) { std::cout << "caught callback exception: " << exc.what() << std::endl; } std::cout << "<-- CPP main" << std::endl; return 0; } I've found that: (1) linking the C lib dynamically or statically against libgcc does not make any difference wrt exception segfaulting. I.e. lib compiled with - gcc -static-libgcc -o libdispatch.so -shared -fPIC dispatch.c: $ ldd libdispatch.so libc.so.1 => /lib/libc.so.1 libm.so.2 => /lib/libm.so.2 /platform/SUNW,Sun-Fire-V490/lib/libc_psr.so.1 - gcc -o libdispatch.so -shared -fPIC dispatch.c $ ldd libdispatch.so libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 libc.so.1 => /lib/libc.so.1 libm.so.2 => /lib/libm.so.2 /platform/SUNW,Sun-Fire-V490/lib/libc_psr.so.1 will not make any difference when running the main program: 0 $ g++ -I. -L. -R. -ldispatch dispatch_main.cpp 0 $ ./a.out --> CPP main --> invoke(68316, CPP main callback argument) --> CPP callback(CPP main callback argument) <-- CPP callback(CPP main callback argument) <-- invoke(68316, CPP main callback argument) --> invoke(68372, CPP main callback argument) --> CPP exception-throwing callback(CPP main callback argument) terminate called after throwing an instance of 'std::runtime_error' what(): throwing up Abort (core dumped) (2) Compiling the C lib with exception support i.e. -fexceptions will make the segfault disappear: 0 $ gcc -static-libgcc -fexceptions -o libdispatch.so -shared -fPIC dispatch.c 0 $ ./a.out --> CPP main --> invoke(68720, CPP main callback argument) --> CPP callback(CPP main callback argument) <-- CPP callback(CPP main callback argument) <-- invoke(68720, CPP main callback argument) --> invoke(68776, CPP main callback argument) --> CPP exception-throwing callback(CPP main callback argument) caught callback exception: throwing up <-- CPP main 0 $ ldd libdispatch.so libc.so.1 => /lib/libc.so.1 libm.so.2 => /lib/libm.so.2 /platform/SUNW,Sun-Fire-V490/lib/libc_psr.so.1 0 $ gcc -fexceptions -o libdispatch.so -shared -fPIC dispatch.c 0 $ ./a.out --> CPP main --> invoke(68720, CPP main callback argument) --> CPP callback(CPP main callback argument) <-- CPP callback(CPP main callback argument) <-- invoke(68720, CPP main callback argument) --> invoke(68776, CPP main callback argument) --> CPP exception-throwing callback(CPP main callback argument) caught callback exception: throwing up <-- CPP main 0 $ ldd libdispatch.so libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 libc.so.1 => /lib/libc.so.1 libm.so.2 => /lib/libm.so.2 /platform/SUNW,Sun-Fire-V490/lib/libc_psr.so.1 So, wrapping up, it looks like if the C lib isn't compiled with exception support there is no way that an exception can propagate through the C parts without segfaulting, regardless of linking dynamically or statically against libgcc.
From the cited gcc documentation I understand that linking dynamically or statically will however influence your capabilities on throwing in *one* shared lib and catching in *another* shared lib, all participating libraries *compiled with exception support*, though.
Which means I'll need to do something along the lines that Niall sketched out. Thanks, Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
On 8 Apr 2013 at 14:11, Holger Joukl wrote:
I have found a couple of references. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html (see static-libgcc) http://gcc.gnu.org/wiki/Visibility
Thanks, I'll need to look into these.
I wrote the second one. Really not sure how that helps you. Look into capturing the exception before it enters the C code using std::exception_ptr, and then rethrowing it on reentry to C++. If you don't have C++11 in your C++, Boost provides an okay partial implementation of C++11 exception support. Niall -- Any opinions or advice expressed here do NOT reflect those of my employer BlackBerry Inc. Work Portfolio: http://careers.stackoverflow.com/nialldouglas/
Hi, "Cplusplus-sig" <cplusplus-sig-bounces+holger.joukl=lbbw.de@python.org> wrote on 09.04.2013 03:04:33:
From: "Niall Douglas" <s_sourceforge@nedprod.com>
On 8 Apr 2013 at 14:11, Holger Joukl wrote:
I have found a couple of references. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html (see static-libgcc) http://gcc.gnu.org/wiki/Visibility
Thanks, I'll need to look into these.
I wrote the second one. Really not sure how that helps you.
At the very least I'll definitely learn something :-)
Look into capturing the exception before it enters the C code using std::exception_ptr, and then rethrowing it on reentry to C++.
In a similar approach I'm trying out I currently catch bp::error_already_set before entering C Code and on reentry to C++ call bp::throw_error_already_set() iff PyErr_Occurred(). So I basically make use of Python's global-per-thread exception indicator to "suppress" the exception for the C code and detect the need to re-raise before returning from C++ to Python. I guess that will work just fine unless some non-Python related exception creeps in, either raised from within Boost.Python or in my custom code.
If you don't have C++11 in your C++, Boost provides an okay partial implementation of C++11 exception support.
I'll look into this. This would then mean instrumenting some object with a place to store the caught exception to re-raise upon reentry from C to C++. I take it this would then do much the same as my approach sketched above but "safer and sounder". Thanks, Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
On 10 Apr 2013 at 13:48, Holger Joukl wrote:
If you don't have C++11 in your C++, Boost provides an okay partial implementation of C++11 exception support.
I'll look into this. This would then mean instrumenting some object with a place to store the caught exception to re-raise upon reentry from C to C++. I take it this would then do much the same as my approach sketched above but "safer and sounder".
There is always the trick of keeping a hash table keyed on thread id and call function depth. In fact it could even be made lock free and wait free with Boost v1.53's new lock and wait free queue implementations. Niall -- Any opinions or advice expressed here do NOT reflect those of my employer BlackBerry Inc. Work Portfolio: http://careers.stackoverflow.com/nialldouglas/
Hi,
From: "Niall Douglas" <s_sourceforge@nedprod.com> On 10 Apr 2013 at 13:48, Holger Joukl wrote:
If you don't have C++11 in your C++, Boost provides an okay partial implementation of C++11 exception support.
I'm currently using gcc 4.6.1 which supports the necessary features using -std=c++0x but also Solaris Studio 12.2 which doesn't so I'd need the Boost implementation.
I'll look into this. This would then mean instrumenting some object with a place to store the caught exception to re-raise upon reentry from C to C++. I take it this would then do much the same as my approach sketched above but "safer and sounder".
There is always the trick of keeping a hash table keyed on thread id and call function depth. In fact it could even be made lock free and wait free with Boost v1.53's new lock and wait free queue implementations.
A bit of a simplified example version using Boost's partial C++11 exception support would then be s.th. like: 0 $ cat dispatch_main_exception_map.cpp // File: dispatch_main_exception_map.cpp #include <iostream> #include <exception> #include <stdexcept> #include <pthread.h> // header-only: #include <boost/unordered_map.hpp> // header-only: #include <boost/exception_ptr.hpp> #include "dispatch.h" // the global per-thread exception storage boost::unordered_map<pthread_t, boost::exception_ptr> exception_map; void callback(cb_arg_t arg) { std::cout << "--> CPP callback" << arg << std::endl; std::cout << "<-- CPP callback" << arg << std::endl; } // this callback raises an exception void callback_with_exception(cb_arg_t arg) { std::cout << "--> exception-throwing CPP callback" << arg << std::endl; throw std::runtime_error("throwing up"); std::cout << "<-- exception-throwing CPP callback" << arg << std::endl; } // this callback raises an exception, catches it and stores it in the // global (thread, exception)-map void guarded_callback_with_exception(cb_arg_t arg) { std::cout << "--> guarded exception-throwing CPP callback" << arg << std::endl; try { throw std::runtime_error("throwing up"); } catch (...) { std::cout << "storing exception in exception map" << std::endl; pthread_t current_thread = pthread_self(); exception_map[current_thread] = boost::current_exception(); exception_map.erase(current_thread); //global_exception_holder = boost::current_exception(); } std::cout << "<-- guarded exception-throwing CPP callback" << arg << std::endl; } int main(void) { std::cout << "--> CPP main" << std::endl; cb_arg_t s = "CPP main callback argument"; std::cout << std::endl; invoke(&callback, s); std::cout << std::endl; invoke(&guarded_callback_with_exception, s); pthread_t current_thread = pthread_self(); if (exception_map.find(current_thread) != exception_map.end()) { try { std::cout << "rethrowing exception from exception map" << std::endl; boost::rethrow_exception(exception_map[current_thread]); } catch (const std::exception& exc) { std::cout << "caught callback exception: " << exc.what() << std::endl; } } std::cout << 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; } Which doesn't respect call function depth (how would I do that?) and doesn't use a queue; I suppose you mean using the lockfree queue for threadsafe access to the hash table. I think I don't even need that for my use case as I basically - call dispatch on a Boost.Python wrapped object - which invokes the C libs dispatcher function - which invokes the registered callbacks (these will usually be implemented in Python) i.e. the same thread that produced the exception will need to handle it. So what I have to make sure is that - no exception can propagate back to the C lib - I recognize an exception has happened, in the initial dispatch method in the same thread, to re-raise it and let Boost.Python translate it back to Python It looks like getting at the thread ID is also not too portable. This is something that the simple solution of using (abusing?) the Python per-thread exception indicator would avoid. Thanks for the valuable hints! Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
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.
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.
void guarded_callback_with_exception(cb_arg_t arg) { std::cout << "--> guarded exception-throwing CPP callback" << arg << std::endl; try { throw std::runtime_error("throwing up"); } catch (...) { std::cout << "storing exception in exception map" << std::endl; pthread_t current_thread = pthread_self(); exception_map[current_thread] = boost::current_exception(); exception_map.erase(current_thread); //global_exception_holder = boost::current_exception(); } std::cout << "<-- guarded exception-throwing CPP callback" << arg << std::endl; }
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.
pthread_t current_thread = pthread_self(); if (exception_map.find(current_thread) != exception_map.end()) { try { std::cout << "rethrowing exception from exception map" << std::endl; boost::rethrow_exception(exception_map[current_thread]); } catch (const std::exception& exc) { std::cout << "caught callback exception: " << exc.what() << std::endl; } }
Why not using thread local storage?
Which doesn't respect call function depth (how would I do that?)
Keep a count of nesting levels, so if a callback calls a callback which calls a callback etc.
and doesn't use a queue; I suppose you mean using the lockfree queue for threadsafe access to the hash table.
Maybe a hash table of lockfree queues. Depends on your needs.
I think I don't even need that for my use case as I basically - call dispatch on a Boost.Python wrapped object - which invokes the C libs dispatcher function - which invokes the registered callbacks (these will usually be implemented in Python)
i.e. the same thread that produced the exception will need to handle it.
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 :)
Thanks for the valuable hints!
You're welcome. Niall -- Any opinions or advice expressed here do NOT reflect those of my employer BlackBerry Inc. Work Portfolio: http://careers.stackoverflow.com/nialldouglas/
Hi,
From: "Niall Douglas" <s_sourceforge@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
I feel a bit out of my depths here now wrt to all that boost magic but I've definitely learned a bunch :)
Much of the "boost magic" we talked about is now plain C++11. It's very worth while to read "The C++ standard library" by Josuttis from cover to cover (it's about 1000 pages, get the C++11 edition). The hours spent reading it are very quickly repaid in hundreds of hours saved for the rest of your career. A tedious read, but worth completing. Niall -- Any opinions or advice expressed here do NOT reflect those of my employer BlackBerry Inc. Work Portfolio: http://careers.stackoverflow.com/nialldouglas/
Before trying out Niall's wealth of suggestions, just to fix up my own erroneous example:
From: Holger Joukl <Holger.Joukl@LBBW.de> A bit of a simplified example version using Boost's partial C++11 exception support would then be s.th. like:
0 $ cat dispatch_main_exception_map.cpp // File: dispatch_main_exception_map.cpp
#include <iostream> [...] // this callback raises an exception, catches it and stores it in the // global (thread, exception)-map void guarded_callback_with_exception(cb_arg_t arg) { std::cout << "--> guarded exception-throwing CPP callback" << arg << std::endl; try { throw std::runtime_error("throwing up"); } catch (...) { std::cout << "storing exception in exception map" << std::endl; pthread_t current_thread = pthread_self(); exception_map[current_thread] = boost::current_exception(); exception_map.erase(current_thread); [...] ^^^^^^^^^^^^^^^^^^^ | This defeats the purpose, of course, and rather needs to be put where the re-throw takes place. Shouldn't make late additions and then overlook the missing output.
$ cat dispatch_main_exception_map.cpp // File: dispatch_main_exception_map.cpp #include <iostream> #include <exception> #include <stdexcept> #include <pthread.h> // header-only: #include <boost/unordered_map.hpp> // header-only: #include <boost/exception_ptr.hpp> #include "dispatch.h" // the global per-thread exception storage boost::unordered_map<pthread_t, boost::exception_ptr> exception_map; void callback(cb_arg_t arg) { std::cout << "--> CPP callback" << arg << std::endl; std::cout << "<-- CPP callback" << arg << std::endl; } // this callback raises an exception void callback_with_exception(cb_arg_t arg) { std::cout << "--> exception-throwing CPP callback" << arg << std::endl; throw std::runtime_error("throwing up"); std::cout << "<-- exception-throwing CPP callback" << arg << std::endl; } // this callback raises an exception, catches it and stores it in the // global (thread, exception)-map void guarded_callback_with_exception(cb_arg_t arg) { std::cout << "--> guarded exception-throwing CPP callback" << arg << std::endl; try { throw std::runtime_error("throwing up"); } catch (...) { std::cout << "storing exception in exception map" << std::endl; pthread_t current_thread = pthread_self(); exception_map[current_thread] = boost::current_exception(); } std::cout << "<-- guarded exception-throwing CPP callback" << arg << std::endl; } int main(void) { std::cout << "--> CPP main" << std::endl; cb_arg_t s = "CPP main callback argument"; std::cout << std::endl; invoke(&callback, s); std::cout << std::endl; invoke(&guarded_callback_with_exception, s); pthread_t current_thread = pthread_self(); if (exception_map.find(current_thread) != exception_map.end()) { try { std::cout << "rethrowing exception from exception map" << std::endl; boost::exception_ptr current_exception(exception_map [current_thread]); exception_map.erase(current_thread); boost::rethrow_exception(current_exception); } catch (const std::exception& exc) { std::cout << "caught callback exception: " << exc.what() << std::endl; } } std::cout << 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 $ ./a.out --> CPP main --> invoke(136888, CPP main callback argument) --> CPP callbackCPP main callback argument <-- CPP callbackCPP main callback argument <-- invoke(136888, CPP main callback argument) --> invoke(137608, CPP main callback argument) --> guarded exception-throwing CPP callbackCPP main callback argument storing exception in exception map <-- guarded exception-throwing CPP callbackCPP main callback argument <-- invoke(137608, CPP main callback argument) rethrowing exception from exception map caught callback exception: throwing up --> invoke(137112, CPP main callback argument) --> exception-throwing CPP callbackCPP main callback argument caught callback exception: throwing up <-- CPP main Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
participants (3)
-
Giuseppe Corbelli -
Holger Joukl -
Niall Douglas