Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

try{ } catch(...) { } in C callback - bad idea?

I'm implementing callbacks in C++ which will be called from ordinary C code. My main() function is C++ already, but C code will be responsible for creating threads that will eventually call my callbacks.

Right now my callbacks look like

int handle_foo(void *userdata) {
    try {
        MyCPPClass *obj = static_cast<MyCPPClass *>(userdata);
        obj->doStuff();
        return 0; // no error
    } catch(...) {
        LogError("doStuff failed"); 
        return -1; // error
    }
}

This works OK, but it seems weird to me. Furthermore, I lose some useful features such as the ability to find out what was thrown (without adding huge amounts of extra catch statements to each and every one of my callbacks).

Is try {} catch(...) {} here reasonable, or is there a better way to write my C callbacks?

like image 750
nneonneo Avatar asked Feb 24 '15 08:02

nneonneo


2 Answers

Yes, you have to catch the exceptions and hopefully translate them into something useful. Letting exceptions propagate through C code leads to undefined behavior. At best you cannot expect the C code to maintain consistent program state.

See this answer for an easy example. A harder example is with some complex piece of software such as SQLite - C code will grab some mutex and will not release it because the exception simply "flies through" and your program is now toast.

Also this has any chance of "working" if all the code is built against the same C++ runtime. If you happen to have callback implemented in say Visual C++ 9 and the rest of the code in say Visual C++ 10 or those parts are compiled against static runtime libraries - you now have two distinct runtimes and the unhandled exception in the callback causes terminate() being called.

like image 133
sharptooth Avatar answered Sep 23 '22 02:09

sharptooth


sharptooth answered most of your question, and James McNellis has written a nice blog post about how to elegantly get rid of the boilerplate using modern C++.

The gist of it is having a translate_exceptions function used like this:

int callback(...) {
   return translate_exceptions([&]{
      // ... your code here
   });
}

The translate_exception is a simple function template (which uses a function level try block) taking an invokable:

template<typename Fn> int translate_exception(Fn fn) try {
  fn();
  return 0;
} catch(std::exception const& e) {
  LOG("[EE] exception ", e.what());
  return -2;
} catch( ... ) {
  LOG("[EE] unknown exception");
  return -1;
}
like image 27
Fabio Fracassi Avatar answered Sep 22 '22 02:09

Fabio Fracassi