This is the mail archive of the gcc-help@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

C++ exceptions with many threads and no memory.


I'm assessing the issues involved with migrating a largish project from C to C++. One of the things which has come up is exception handling. This application allocates a number of error-context structures on startup, in order to ensure that they are available even under out-of-memory conditions.

When comparing this to GCC's exception handling, I find that libstdc++ calls malloc() whenever an exception is thrown, and falls back to statically allocated memory only when malloc fails. Only 64 statically allocated exceptions may be in flight at once, and if this limit is violated, std::terminate() is called.

This seems like quite a brittle behaviour to me. I guess not often seen as a problem in practice because Linux overcommit usually ensures malloc() won't fail (although if it does, then you have to take your chances with the oom-killer anyway).

There doesn't seem to be any way around this, is there? Do GCC developers consider it a bug?

The code below demonstrates the issue. With 64 threads it runs to completion, with 65 threads it triggers the following backtrace, with GCC 4.8.1.

(gdb) bt
#0  0x00007ffff7319425 in __GI_raise (sig=<optimised out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1  0x00007ffff731cb8b in __GI_abort () at abort.c:91
#2  0x00007ffff7b36b05 in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7b34c76 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7b34ca3 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7b339c5 in __cxa_allocate_exception () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000401467 in f () at libstdc++-exception-malloc.cpp:37
#7  0x00007ffff7b87c00 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#8  0x00007ffff76aae9a in start_thread (arg=0x7fffd77a7700) at pthread_create.c:308
#9  0x00007ffff73d73fd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
#10 0x0000000000000000 in ?? ()

Cheers,

John

#include <chrono>
#include <condition_variable>
#include <exception>
#include <stdlib.h>
#include <mutex>
#include <thread>
#include <vector>

std::mutex m;
std::condition_variable cv;
static bool all_threads_have_spawned = false;

class MyClass
{
public:
  ~MyClass ()
  {
    /* Simulate the stack unwind taking a long time. */
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }
};

static void f ()
{
  try
  {
    MyClass obj;

    {
      /* Wait for the main thread to signal us, in order to avoid throwing until
       * malloc has been told to fail.
       */
      std::unique_lock<std::mutex> lock(m);
      cv.wait(lock, []{ return all_threads_have_spawned;} );
    }

    throw std::bad_alloc();
  }
  catch (std::bad_alloc)
  {

  }
}

/* Declare glibc's internal malloc(), so we can call it from our malloc() wrapper. */
extern "C" void *__libc_malloc(size_t size);

static bool fail_malloc = false;

extern "C" void *malloc (size_t size)
{
  return fail_malloc ? NULL : __libc_malloc(size);
}

int main ()
{
  std::vector<std::thread> threads;

  /* With 64 threads this program runs to completion. With 65 threads it dies
   * with std::terminate() called from __cxa_allocate_exception().
   */
  const int THREAD_COUNT = 65;

  for (int i = 0; i < THREAD_COUNT; i++)
  {
    threads.emplace_back(f);
  }

  /* All the threads have been created. Tell our version of malloc() to fail,
   * from now on, simulating out-of-memory.
   */
  fail_malloc = true;

  {
    /* Wake all our threads. */
    all_threads_have_spawned = true;
    std::unique_lock<std::mutex> lock(m);
    cv.notify_all();
  }

  for (auto &thread : threads)
  {
    thread.join();
  }

  return 0;
}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]