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

Eric Fiselier via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 19 19:21:33 PDT 2016


Author: ericwf
Date: Tue Apr 19 21:21:33 2016
New Revision: 266851

URL: http://llvm.org/viewvc/llvm-project?rev=266851&view=rev
Log:
[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

Modified:
    libcxx/trunk/include/thread
    libcxx/trunk/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp

Modified: libcxx/trunk/include/thread
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/thread?rev=266851&r1=266850&r2=266851&view=diff
==============================================================================
--- libcxx/trunk/include/thread (original)
+++ libcxx/trunk/include/thread Tue Apr 19 21:21:33 2016
@@ -339,21 +339,21 @@ public:
 
 #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
 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>
-void*
-__thread_proxy(void* __vp)
+void* __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));
-    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());
     return nullptr;
 }
@@ -363,9 +363,13 @@ template <class _Fp, class ..._Args,
          >
 thread::thread(_Fp&& __f, _Args&&... __args)
 {
-    typedef tuple<typename decay<_Fp>::type, typename decay<_Args>::type...> _Gp;
-    _VSTD::unique_ptr<_Gp> __p(new _Gp(__decay_copy(_VSTD::forward<_Fp>(__f)),
-                                __decay_copy(_VSTD::forward<_Args>(__args))...));
+    typedef unique_ptr<__thread_struct> _TSPtr;
+    _TSPtr __tsp(new __thread_struct);
+    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());
     if (__ec == 0)
         __p.release();
@@ -376,22 +380,34 @@ thread::thread(_Fp&& __f, _Args&&... __a
 #else  // _LIBCPP_HAS_NO_VARIADICS
 
 template <class _Fp>
-void*
-__thread_proxy(void* __vp)
+struct __thread_invoke_pair {
+    // 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));
-    (*__p)();
+    __thread_local_data().reset(__p->__tsp_.release());
+    (__p->__fn_)();
     return nullptr;
 }
 
 template <class _Fp>
 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)
-        __p.release();
+        __pp.release();
     else
         __throw_system_error(__ec, "thread constructor failed");
 }

Modified: libcxx/trunk/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp?rev=266851&r1=266850&r2=266851&view=diff
==============================================================================
--- libcxx/trunk/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp (original)
+++ libcxx/trunk/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp Tue Apr 19 21:21:33 2016
@@ -20,23 +20,28 @@
 
 #include <thread>
 #include <new>
+#include <atomic>
 #include <cstdlib>
 #include <cassert>
 
 #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)
 {
     if (throw_one == 0)
         throw std::bad_alloc();
     --throw_one;
+    ++outstanding_new;
     return std::malloc(s);
 }
 
 void  operator delete(void* p) throw()
 {
+    --outstanding_new;
     std::free(p);
 }
 
@@ -94,27 +99,58 @@ public:
 
 #endif
 
-int main()
-{
+// 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();
-        assert(f_run == true);
     }
-    f_run = false;
-    {
-        try
-        {
-            throw_one = 0;
+    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(false);
-        }
-        catch (...)
-        {
-            throw_one = 0xFFFF;
-            assert(!f_run);
+            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()
+{
+    test_throwing_new_during_thread_creation();
+    {
+        std::thread t(f);
+        t.join();
+        assert(f_run == true);
     }
+
     {
         assert(G::n_alive == 0);
         assert(!G::op_run);




More information about the cfe-commits mailing list