skgbanga
skgbanga

Reputation: 2667

Reason for unknown exception in promise without lpthread

Note that this question has an 'answer' at here and here, but my question is not on how to get rid of the error, but why this error occurs.

Consider the following code:

include <cstdio>
#include <future>

int main() {
  std::promise<int> promise;
  auto future = promise.get_future();
  promise.set_value(42);

  auto result = future.get();
  printf("%d\n", result);
}

This code throws an exception:

$ g++ -g -std=c++1z main.cpp
$ ./a.out
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error -1
Aborted (core dumped)

The solution is to pass -lpthread on command line:

$ g++ -g -std=c++1z -lpthread main.cpp
$ ./a.out
42

Now, I am used to get a linking error when I don't link with a required library. This is the first time when I got a runtime error.

When you run the version (without lpthread) under gdb, this is the stack trace you'll get (redacted some of it):

#5  0x00007ffff7aa6ef3 in __cxxabiv1::__cxa_throw (obj=obj@entry=0x61ad00,
    tinfo=tinfo@entry=0x7ffff7dce6b0 <typeinfo for std::system_error>,
    dest=dest@entry=0x7ffff7ad02b0 <std::system_error::~system_error()>)
    at /tmp/tmp.kmkSDUDFn8/build/../gcc-9.1.0/libstdc++-v3/libsupc++/eh_throw.cc:95

#6  0x00007ffff7a9d0ec in std::__throw_system_error (__i=-1)
    at /tmp/tmp.kmkSDUDFn8/build/x86_64-pc-linux-gnu/libstdc++-v3/include/ext/new_allocator.h:89

#7  0x000000000040240f in std::call_once<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::once_flag&, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&) (__once=..., __f=
    @0x7fffffffdd80: (void (std::__future_base::_State_baseV2::*)(class std::__future_base::_State_baseV2 * const, class std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>()> *, bool *)) 0x40200c <std::__future_base::_State_baseV2::_M_do_set(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*)>, __args#0=@0x7fffffffdd78: 0x61ac30, __args#1=@0x7fffffffdd70: 0x7fffffffddf0,                                                                                        
    __args#2=@0x7fffffffdd68: 0x7fffffffdd67) at /.../include/c++/9.1.0/mutex:697

#8  0x0000000000401e5d in std::__future_base::_State_baseV2::_M_set_result(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>, bool) (this=0x61ac30, __res=..., __ignore_failure=false)                                                 
    at /.../include/c++/9.1.0/future:401

So it has something to do with call_once. Curious on why that manifests as a runtime error instead of link time.

Upvotes: 2

Views: 235

Answers (1)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136208

GNU libc provides alternative implementations of Pthread functions, so that linking succeeds even without -pthread. See is pthread in glibc.so implemented by weak symbol to provide pthread stub functions? and Why glibc and pthread library both defined same APIs?

The GNU C++ standard std::call_once does

int __e = __gthread_once(&__once._M_once, &__once_proxy);

__gthread_once is:

static inline int
__gthread_once (__gthread_once_t *__once, void (*__func) (void))
{
  if (__gthread_active_p ())
    return __gthrw_(pthread_once) (__once, __func);
  else
    return -1;
}

The GNU C++ standard library detects at run-time whether pthread library implementation is available in __gthread_active_p function.

/* For a program to be multi-threaded the only thing that it certainly must
   be using is pthread_create.  However, there may be other libraries that
   intercept pthread_create with their own definitions to wrap pthreads
   functionality for some purpose.  In those cases, pthread_create being
   defined might not necessarily mean that libpthread is actually linked
   in.
   For the GNU C library, we can use a known internal name.  This is always
   available in the ABI, but no other library would define it.  That is
   ideal, since any public pthread function might be intercepted just as
   pthread_create might be.  __pthread_key_create is an "internal"
   implementation symbol, but it is part of the public exported ABI.  Also,
   it's among the symbols that the static libpthread.a always links in
   whenever pthread_create is used, so there is no danger of a false
   negative result in any statically-linked, multi-threaded program.
   For others, we choose pthread_cancel as a function that seems unlikely
   to be redefined by an interceptor library.  The bionic (Android) C
   library does not provide pthread_cancel, so we do use pthread_create
   there (and interceptor libraries lose).  */

As the referenced answers state, one should be using -pthread option when both compiling and linking multi-threaded programs. -lpthread isn't sufficient because it doesn't define _REENTRANT macro which some code may require:

$ diff <(g++ -E -dD -xc++ /dev/null) <(g++ -E -dD -xc++ -pthread /dev/null)
289a290
> #define _REENTRANT 1

Upvotes: 3

Related Questions