Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Terminate called without an active exception (GCC)

Consider the following program:

#include <iostream>
#include <pthread.h>
#include <stdexcept>
#include <unistd.h>

static void* busy(void*)
{
  int oldstate ;
  auto result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate) ;
  if (result != 0)
#ifdef NOEXCEPT
    { std::cerr << "pthread_setcanceltype" << std::endl ; abort() ; }
#else
    throw std::runtime_error("pthread_setcanceltype") ;
#endif
  while (true) 
    ;
  return nullptr ;
}

static pthread_t start()
{
  pthread_t t ;
  int result = pthread_create(&t,nullptr,busy,nullptr) ;
  if (result != 0)
    throw std::runtime_error("pthread_create") ;
  return t ;
}

static void terminate(pthread_t t)
{
  auto result = pthread_cancel(t) ;
  if (result != 0) 
    throw std::runtime_error("pthread_cancel()") ;
  result = pthread_join(t,nullptr) ;
  if (result != 0) 
    throw std::runtime_error("pthread_join()") ;
}

int main()
{
  auto t = start() ;
  sleep(1) ; // may not cause an abort otherwise
  terminate(t) ;
  return 0 ;
}

This runs fine as long as no optimization (or -O1) is used, e.g. with g++ -std=c++11 -Wall -o test test.cc -pthread

However, with -O2 or -O3 the program aborts with the message above.

Also kind of interesting: it runs thru if compiled with -DNOEXCEPT. So it appears, if a thread is cancelled in a function that potentially [sic!] throws an exception, and if optimization is switched on, the program may abort. -- And I can't see any way to prevent this.

It's for me reproducible on amd64 gcc 4.8.4 (Ubuntu 14.04.3) and armv7l gcc 4.9.2 (Raspbian 4.9.2-10).

Can you reproduce this? Do you have an explanation? This behavior seems to be odd (at least to me). I'd be happy to receive some kind of feedback. Thank you!

like image 463
mikro77 Avatar asked Jun 07 '16 11:06

mikro77


1 Answers

On Linux (as on most OSes) exceptions are a language-agnostic feature, and pthread cancellation is implemented using language-agnostic exceptions (See e.g. Cancellation and C++ Exceptions).

When a pthread cancellation is delivered to a thread (using a signal, but you don't need to know that) the unwind machinery invokes all the installed personalities so that they can perform language-specific cleanup prior to the thread exiting. (This is pretty cool; it means that as in the above article you can insert a catch block for abi::__forced_unwind to detect - though not to prevent - a thread cancellation.)

The problem is that an asynchronous cancellation can occur at any instruction, and the C++ exception tables generated by g++ only handle exceptions occurring at instructions known to be capable of generating exceptions (i.e. but not only calls to exception-throwing functions). If an exception is generated at a point not covered by the C++ tables, the C++ personality panics and terminates the process (hence "terminate called without an active exception").

The reason this is influenced by optimization is that the C++ personality is installed lazily, but with higher optimization levels the compiler might decide to preemptively install the C++ personality. You can guarantee the crash even at lower optimization levels by exercising the C++ exception machinery e.g. with try { throw 0; } catch (int) {}.

The simplest fix is to ensure that the C++ personality is not installed in the thread you want to asynchronous cancel. You can ensure this by compiling the thread function as C and not calling any C++ functions from it.

A more hacky and highly unsupported solution is to ensure that all the asynchronous cancellation points (that is, all the instructions where the cancelled thread could be when the asynchronous cancellation is received) are in fact covered by the C++ unwind tables. Firstly you need to compile with -fnon-call-exceptions; secondly you have to ensure that every instruction that could be an asynchronous cancellation point is between two points known to be synchronous cancellation points, e.g. pthread_testcancel:

static void* busy(void*)
{
  int oldstate ;
  auto result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate) ;
  if (result != 0)
#ifdef NOEXCEPT
    { std::cerr << "pthread_setcanceltype" << std::endl ; abort() ; }
#else
    throw std::runtime_error("pthread_setcanceltype") ;
#endif
  pthread_testcancel();
  for (unsigned i = 1; ; ++i) 
    if (i == 0)
      pthread_testcancel();
  return nullptr ;
}
like image 116
ecatmur Avatar answered Oct 31 '22 18:10

ecatmur