[libcxx] Fix PR15638 - Only allocate in parent when starting a thread to prevent calling terminate.

Summary:
Hi,

When creating a new thread libc++ performs at least 2 allocations. The first allocates a tuple of args and the functor that will be passed to the new thread. The second allocation is for the thread local storage needed internally by libc++. Currently the second allocation happens in the child thread, meaning that if it throws the program will terminate with an uncaught bad alloc.

The solution to this is to allocate ALL memory in the parent thread and then pass it to the child.

See https://llvm.org/bugs/show_bug.cgi?id=15638

Reviewers: mclow.lists, danalbert, jroelofs, EricWF

Subscribers: cfe-commits

Differential Revision: http://reviews.llvm.org/D13748

git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@266851 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Eric Fiselier
2016-04-20 02:21:33 +00:00
parent 8d5cbd7ce2
commit e94c1aee2b
2 changed files with 84 additions and 32 deletions

View File

@@ -339,21 +339,21 @@ public:
#ifndef _LIBCPP_HAS_NO_VARIADICS #ifndef _LIBCPP_HAS_NO_VARIADICS
template <class _Fp, class ..._Args, size_t ..._Indices> template <class _TSp, class _Fp, class ..._Args, size_t ..._Indices>
inline _LIBCPP_INLINE_VISIBILITY inline _LIBCPP_INLINE_VISIBILITY
void void
__thread_execute(tuple<_Fp, _Args...>& __t, __tuple_indices<_Indices...>) __thread_execute(tuple<_TSp, _Fp, _Args...>& __t, __tuple_indices<_Indices...>)
{ {
__invoke(_VSTD::move(_VSTD::get<0>(__t)), _VSTD::move(_VSTD::get<_Indices>(__t))...); __invoke(_VSTD::move(_VSTD::get<1>(__t)), _VSTD::move(_VSTD::get<_Indices>(__t))...);
} }
template <class _Fp> template <class _Fp>
void* void* __thread_proxy(void* __vp)
__thread_proxy(void* __vp)
{ {
__thread_local_data().reset(new __thread_struct); // _Fp = std::tuple< unique_ptr<__thread_struct>, Functor, Args...>
std::unique_ptr<_Fp> __p(static_cast<_Fp*>(__vp)); std::unique_ptr<_Fp> __p(static_cast<_Fp*>(__vp));
typedef typename __make_tuple_indices<tuple_size<_Fp>::value, 1>::type _Index; __thread_local_data().reset(_VSTD::get<0>(*__p).release());
typedef typename __make_tuple_indices<tuple_size<_Fp>::value, 2>::type _Index;
__thread_execute(*__p, _Index()); __thread_execute(*__p, _Index());
return nullptr; return nullptr;
} }
@@ -363,9 +363,13 @@ template <class _Fp, class ..._Args,
> >
thread::thread(_Fp&& __f, _Args&&... __args) thread::thread(_Fp&& __f, _Args&&... __args)
{ {
typedef tuple<typename decay<_Fp>::type, typename decay<_Args>::type...> _Gp; typedef unique_ptr<__thread_struct> _TSPtr;
_VSTD::unique_ptr<_Gp> __p(new _Gp(__decay_copy(_VSTD::forward<_Fp>(__f)), _TSPtr __tsp(new __thread_struct);
__decay_copy(_VSTD::forward<_Args>(__args))...)); typedef tuple<_TSPtr, typename decay<_Fp>::type, typename decay<_Args>::type...> _Gp;
_VSTD::unique_ptr<_Gp> __p(
new _Gp(std::move(__tsp),
__decay_copy(_VSTD::forward<_Fp>(__f)),
__decay_copy(_VSTD::forward<_Args>(__args))...));
int __ec = pthread_create(&__t_, 0, &__thread_proxy<_Gp>, __p.get()); int __ec = pthread_create(&__t_, 0, &__thread_proxy<_Gp>, __p.get());
if (__ec == 0) if (__ec == 0)
__p.release(); __p.release();
@@ -376,22 +380,34 @@ thread::thread(_Fp&& __f, _Args&&... __args)
#else // _LIBCPP_HAS_NO_VARIADICS #else // _LIBCPP_HAS_NO_VARIADICS
template <class _Fp> template <class _Fp>
void* struct __thread_invoke_pair {
__thread_proxy(void* __vp) // This type is used to pass memory for thread local storage and a functor
// to a newly created thread because std::pair doesn't work with
// std::unique_ptr in C++03.
__thread_invoke_pair(_Fp& __f) : __tsp_(new __thread_struct), __fn_(__f) {}
unique_ptr<__thread_struct> __tsp_;
_Fp __fn_;
};
template <class _Fp>
void* __thread_proxy_cxx03(void* __vp)
{ {
__thread_local_data().reset(new __thread_struct);
std::unique_ptr<_Fp> __p(static_cast<_Fp*>(__vp)); std::unique_ptr<_Fp> __p(static_cast<_Fp*>(__vp));
(*__p)(); __thread_local_data().reset(__p->__tsp_.release());
(__p->__fn_)();
return nullptr; return nullptr;
} }
template <class _Fp> template <class _Fp>
thread::thread(_Fp __f) thread::thread(_Fp __f)
{ {
std::unique_ptr<_Fp> __p(new _Fp(__f));
int __ec = pthread_create(&__t_, 0, &__thread_proxy<_Fp>, __p.get()); typedef __thread_invoke_pair<_Fp> _InvokePair;
typedef std::unique_ptr<_InvokePair> _PairPtr;
_PairPtr __pp(new _InvokePair(__f));
int __ec = pthread_create(&__t_, 0, &__thread_proxy_cxx03<_InvokePair>, __pp.get());
if (__ec == 0) if (__ec == 0)
__p.release(); __pp.release();
else else
__throw_system_error(__ec, "thread constructor failed"); __throw_system_error(__ec, "thread constructor failed");
} }

View File

@@ -20,23 +20,28 @@
#include <thread> #include <thread>
#include <new> #include <new>
#include <atomic>
#include <cstdlib> #include <cstdlib>
#include <cassert> #include <cassert>
#include "test_macros.h" #include "test_macros.h"
unsigned throw_one = 0xFFFF; std::atomic<unsigned> throw_one(0xFFFF);
std::atomic<unsigned> outstanding_new(0);
void* operator new(std::size_t s) throw(std::bad_alloc) void* operator new(std::size_t s) throw(std::bad_alloc)
{ {
if (throw_one == 0) if (throw_one == 0)
throw std::bad_alloc(); throw std::bad_alloc();
--throw_one; --throw_one;
++outstanding_new;
return std::malloc(s); return std::malloc(s);
} }
void operator delete(void* p) throw() void operator delete(void* p) throw()
{ {
--outstanding_new;
std::free(p); std::free(p);
} }
@@ -94,27 +99,58 @@ public:
#endif #endif
// Test throwing std::bad_alloc
//-----------------------------
// Concerns:
// A Each allocation performed during thread construction should be performed
// in the parent thread so that std::terminate is not called if
// std::bad_alloc is thrown by new.
// B std::threads constructor should properly handle exceptions and not leak
// memory.
// Plan:
// 1 Create a thread and count the number of allocations, 'N', it performs.
// 2 For each allocation performed run a test where that allocation throws.
// 2.1 check that the exception can be caught in the parent thread.
// 2.2 Check that the functor has not been called.
// 2.3 Check that no memory allocated by the creation of the thread is leaked.
// 3 Finally check that a thread runs successfully if we throw after 'N+1'
// allocations.
void test_throwing_new_during_thread_creation() {
throw_one = 0xFFF;
{
std::thread t(f);
t.join();
}
const int numAllocs = 0xFFF - throw_one;
// i <= numAllocs means the last iteration is expected not to throw.
for (int i=0; i <= numAllocs; ++i) {
throw_one = i;
f_run = false;
unsigned old_outstanding = outstanding_new;
try {
std::thread t(f);
assert(i == numAllocs); // Only final iteration will not throw.
t.join();
assert(f_run);
} catch (std::bad_alloc const&) {
assert(i < numAllocs);
assert(!f_run); // (2.2)
}
assert(old_outstanding == outstanding_new); // (2.3)
}
f_run = false;
throw_one = 0xFFF;
}
int main() int main()
{ {
test_throwing_new_during_thread_creation();
{ {
std::thread t(f); std::thread t(f);
t.join(); t.join();
assert(f_run == true); assert(f_run == true);
} }
f_run = false;
{
try
{
throw_one = 0;
std::thread t(f);
assert(false);
}
catch (...)
{
throw_one = 0xFFFF;
assert(!f_run);
}
}
{ {
assert(G::n_alive == 0); assert(G::n_alive == 0);
assert(!G::op_run); assert(!G::op_run);