This is the mail archive of the gcc-patches@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]

Re: [patch] Define std::promise::set_value_at_thread_exit() etc.


On 23/10/14 17:00 +0100, Jonathan Wakely wrote:
This adds:
std::notify_all_at_thread_exit()
std::promise<>::set_value_at_thread_exit()
std::promise<>::set_exception_at_thread_exit()
std::packaged_task<>::make_ready_at_thread_exit()

There's a linked list of callbacks that run after TLS destructors
(called by a pthread_key_create destructor) to make shared states
ready and notify condition variables.

The core of the change to futures is that the shared state is
considered ready when _M_ready == true, instead of when _M_result !=
nullptr, so that we can store a result in _M_result without making it
ready. The callback that would make it ready at thread exit stores a
weak_ptr so it can safely check whether the shared state has already
been destroyed before thread exit (see thread c++std-parallel-1162 on
the SG1 list for related discussion).

Tested x86_64-linux, I'd like to commit this next week some time.

This is a slightly updated version of the patch, the differences are:

- added some comments to document the code better

- renamed _M_set_result_aside to _M_set_delayed_result and
 _M_run_not_ready to _M_run_delayed

- changed _Task_state::_M_run and _M_run_delayed to take arguments by
 reference, to avoid unnecessary copies

- changed _Deferred_state::_M_has_deferred to always return true (not
 just before the deferred function has been run) so it doesn't
 require the caller to own the mutex, which doesn't change behaviour
 because it will never be called when the state is already ready
 anyway

- changed ~_Async_state_impl to call _M_thread.join() directly instead
 of via std::call_once, because in the destructor there can be no
 other threads trying to join it

Tested x86_64-linux + powerpc64-linux, committed to trunk.

commit 4701f811a8364bbd009571f5ec6bd562f5433efa
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon Oct 20 12:23:24 2014 +0100

    Define *_at_thread_exit() functions.
    
    	* config/abi/pre/gnu.ver: Add new exports.
    	* include/std/condition_variable (notify_all_at_thread_exit): Declare.
    	(__at_thread_exit_elt): New base class.
    	* include/std/future: Add comments documenting the implementation.
    	(__future_base::_State_baseV2::_State_baseV2()): Use brace-or-equal
    	initializers and define constructor as defaulted.
    	(__future_base::_State_baseV2::_M_ready): Replace member function
    	with member variable.
    	(__future_base::_State_baseV2::_M_set_result): Set _M_ready.
    	(__future_base::_State_baseV2::_M_set_delayed_result): Define.
    	(__future_base::_State_baseV2::_M_break_promise): Set _M_ready.
    	(__future_base::_State_baseV2::_Make_ready): New helper class.
    	(__future_base::_Deferred_state::_M_has_deferred): Remove requirement
    	for caller to own mutex.
    	(__future_base::_Async_state_impl::~_Async_state_impl): Call join
    	directly.
    	(__future_base::_Task_state_base::_M_run): Take arguments by
    	reference.
    	(__future_base::_Task_state_base::_M_run_delayed): Declare new pure
    	virtual function.
    	(__future_base::_Task_state::_M_run_delayed): Define override.
    	(promise::set_value_at_thread_exit): Define.
    	(promise::set_exception_at_thread_exit): Define.
    	(packaged_task::make_ready_at_thread_exit): Define.
    	* src/c++11/condition_variable.cc (notify_all_at_thread_exit): Define.
    	* src/c++11/future.cc
    	(__future_base::_State_baseV2::_Make_ready::_M_set): Define.
    	* testsuite/30_threads/condition_variable/members/3.cc: New.
    	* testsuite/30_threads/packaged_task/members/at_thread_exit.cc: New.
    	* testsuite/30_threads/promise/members/at_thread_exit.cc: New.

diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
index 5176b29..c73ebe7 100644
--- a/libstdc++-v3/config/abi/pre/gnu.ver
+++ b/libstdc++-v3/config/abi/pre/gnu.ver
@@ -128,7 +128,8 @@ GLIBCXX_3.4 {
       std::messages*;
       std::money*;
 #     std::n[^u]*;
-      std::n[^aue]*;
+      std::n[^aueo]*;
+      std::nothrow;
       std::nu[^m]*;
       std::num[^e]*;
       std::ostrstream*;
@@ -1500,6 +1501,11 @@ GLIBCXX_3.4.21 {
     # std::_Sp_locker::*
     _ZNSt10_Sp_locker[CD]*;
 
+    # std::notify_all_at_thread_exit
+    _ZSt25notify_all_at_thread_exitRSt18condition_variableSt11unique_lockISt5mutexE;
+    # std::__future_base::_State_baseV2::_Make_ready::_M_set()
+    _ZNSt13__future_base13_State_baseV211_Make_ready6_M_setEv;
+
 } GLIBCXX_3.4.20;
 
 
diff --git a/libstdc++-v3/include/std/condition_variable b/libstdc++-v3/include/std/condition_variable
index 921cb83..a3682c0 100644
--- a/libstdc++-v3/include/std/condition_variable
+++ b/libstdc++-v3/include/std/condition_variable
@@ -170,6 +170,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
   };
 
+  void
+  notify_all_at_thread_exit(condition_variable&, unique_lock<mutex>);
+
+  struct __at_thread_exit_elt
+  {
+    __at_thread_exit_elt* _M_next;
+    void (*_M_cb)(void*);
+  };
+
   inline namespace _V2 {
 
   /// condition_variable_any
diff --git a/libstdc++-v3/include/std/future b/libstdc++-v3/include/std/future
index 8989474..60c2e4e 100644
--- a/libstdc++-v3/include/std/future
+++ b/libstdc++-v3/include/std/future
@@ -202,7 +202,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       virtual ~_Result_base();
     };
 
-    /// Result.
+    /// A unique_ptr for result objects.
+    template<typename _Res>
+      using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>;
+
+    /// A result object that has storage for an object of type _Res.
     template<typename _Res>
       struct _Result : _Result_base
       {
@@ -243,11 +247,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	void _M_destroy() { delete this; }
     };
 
-    /// A unique_ptr based on the instantiating type.
-    template<typename _Res>
-      using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>;
-
-    /// Result_alloc.
+    /// A result object that uses an allocator.
     template<typename _Res, typename _Alloc>
       struct _Result_alloc final : _Result<_Res>, _Alloc
       {
@@ -266,6 +266,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
       };
 
+    // Create a result object that uses an allocator.
     template<typename _Res, typename _Allocator>
       static _Ptr<_Result_alloc<_Res, _Allocator>>
       _S_allocate_result(const _Allocator& __a)
@@ -278,6 +279,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	return _Ptr<__result_type>(__p);
       }
 
+    // Keep it simple for std::allocator.
     template<typename _Res, typename _Tp>
       static _Ptr<_Result<_Res>>
       _S_allocate_result(const std::allocator<_Tp>& __a)
@@ -285,8 +287,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	return _Ptr<_Result<_Res>>(new _Result<_Res>);
       }
 
-    /// Base class for state between a promise and one or more
-    /// associated futures.
+    // Base class for various types of shared state created by an
+    // asynchronous provider (such as a std::promise) and shared with one
+    // or more associated futures.
     class _State_baseV2
     {
       typedef _Ptr<_Result_base> _Ptr_type;
@@ -294,12 +297,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Ptr_type			_M_result;
       mutex               	_M_mutex;
       condition_variable  	_M_cond;
-      atomic_flag         	_M_retrieved;
+      atomic_flag		_M_retrieved = ATOMIC_FLAG_INIT;
+      bool			_M_ready = false;
       once_flag			_M_once;
 
     public:
-      _State_baseV2() noexcept : _M_result(), _M_retrieved(ATOMIC_FLAG_INIT)
-	{ }
+      _State_baseV2() noexcept = default;
       _State_baseV2(const _State_baseV2&) = delete;
       _State_baseV2& operator=(const _State_baseV2&) = delete;
       virtual ~_State_baseV2() = default;
@@ -307,9 +310,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Result_base&
       wait()
       {
+	// Run any deferred function or join any asynchronous thread:
 	_M_complete_async();
+
 	unique_lock<mutex> __lock(_M_mutex);
-	_M_cond.wait(__lock, [&] { return _M_ready(); });
+	_M_cond.wait(__lock, [&] { return _M_ready; });
 	return *_M_result;
       }
 
@@ -318,15 +323,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         wait_for(const chrono::duration<_Rep, _Period>& __rel)
         {
 	  unique_lock<mutex> __lock(_M_mutex);
-	  if (_M_ready())
+	  if (_M_ready)
 	    return future_status::ready;
 	  if (_M_has_deferred())
 	    return future_status::deferred;
-	  if (_M_cond.wait_for(__lock, __rel, [&] { return _M_ready(); }))
+	  if (_M_cond.wait_for(__lock, __rel, [&] { return _M_ready; }))
 	    {
 	      // _GLIBCXX_RESOLVE_LIB_DEFECTS
 	      // 2100.  timed waiting functions must also join
+	      // This call is a no-op by default except on an async future,
+	      // in which case the async thread is joined.  It's also not a
+	      // no-op for a deferred future, but such a future will never
+	      // reach this point because it returns future_status::deferred
+	      // instead of waiting for the future to become ready (see
+	      // above).  Async futures synchronize in this call, so we need
+	      // no further synchronization here.
 	      _M_complete_async();
+
 	      return future_status::ready;
 	    }
 	  return future_status::timeout;
@@ -337,20 +350,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         wait_until(const chrono::time_point<_Clock, _Duration>& __abs)
         {
 	  unique_lock<mutex> __lock(_M_mutex);
-	  if (_M_ready())
+	  if (_M_ready)
 	    return future_status::ready;
 	  if (_M_has_deferred())
 	    return future_status::deferred;
-	  if (_M_cond.wait_until(__lock, __abs, [&] { return _M_ready(); }))
+	  if (_M_cond.wait_until(__lock, __abs, [&] { return _M_ready; }))
 	    {
 	      // _GLIBCXX_RESOLVE_LIB_DEFECTS
 	      // 2100.  timed waiting functions must also join
+	      // See wait_for(...) above.
 	      _M_complete_async();
+
 	      return future_status::ready;
 	    }
 	  return future_status::timeout;
 	}
 
+      // Provide a result to the shared state and make it ready.
+      // Atomically performs:
+      //   if (!_M_ready) {
+      //     _M_result = __res();
+      //     _M_ready = true;
+      //   }
       void
       _M_set_result(function<_Ptr_type()> __res, bool __ignore_failure = false)
       {
@@ -360,11 +381,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	call_once(_M_once, &_State_baseV2::_M_do_set, this,
 		  std::__addressof(__res), std::__addressof(__lock));
 	if (__lock.owns_lock())
-	  _M_cond.notify_all();
+	  {
+	    _M_ready = true;
+	    _M_cond.notify_all();
+	  }
 	else if (!__ignore_failure)
           __throw_future_error(int(future_errc::promise_already_satisfied));
       }
 
+      // Provide a result to the shared state but delay making it ready
+      // until the calling thread exits.
+      // Atomically performs:
+      //   if (!_M_ready) {
+      //     _M_result = __res();
+      //   }
+      void
+      _M_set_delayed_result(function<_Ptr_type()> __res,
+			    weak_ptr<_State_baseV2> __self)
+      {
+	unique_ptr<_Make_ready> __mr{new _Make_ready};
+	unique_lock<mutex> __lock(_M_mutex, defer_lock);
+        // all calls to this function are serialized,
+        // side-effects of invoking __res only happen once
+	call_once(_M_once, &_State_baseV2::_M_do_set, this,
+		  std::__addressof(__res), std::__addressof(__lock));
+	if (!__lock.owns_lock())
+          __throw_future_error(int(future_errc::promise_already_satisfied));
+	__mr->_M_shared_state = std::move(__self);
+	__mr->_M_set();
+	__mr.release();
+      }
+
+      // Abandon this shared state.
       void
       _M_break_promise(_Ptr_type __res)
       {
@@ -372,15 +420,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  {
 	    error_code __ec(make_error_code(future_errc::broken_promise));
 	    __res->_M_error = make_exception_ptr(future_error(__ec));
+	    // This function is only called when the last asynchronous result
+	    // provider is abandoning this shared state, so noone can be
+	    // trying to make the shared state ready at the same time, and
+	    // we can access _M_result directly instead of through call_once.
 	    {
 	      lock_guard<mutex> __lock(_M_mutex);
 	      _M_result.swap(__res);
+	      _M_ready = true;
 	    }
 	    _M_cond.notify_all();
 	  }
       }
 
-      // Called when this object is passed to a future.
+      // Called when this object is first passed to a future.
       void
       _M_set_retrieved_flag()
       {
@@ -401,6 +454,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               || is_same<const _Res, _Arg>::value,   // promise<R>
               "Invalid specialisation");
 
+	  // Used by std::promise to copy construct the result.
           typename promise<_Res>::_Ptr_type operator()()
           {
             _State_baseV2::_S_check(_M_promise->_M_future);
@@ -415,6 +469,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Res>
         struct _Setter<_Res, _Res&&>
         {
+	  // Used by std::promise to move construct the result.
           typename promise<_Res>::_Ptr_type operator()()
           {
             _State_baseV2::_S_check(_M_promise->_M_future);
@@ -431,6 +486,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Res>
         struct _Setter<_Res, __exception_ptr_tag>
         {
+	  // Used by std::promise to store an exception as the result.
           typename promise<_Res>::_Ptr_type operator()()
           {
             _State_baseV2::_S_check(_M_promise->_M_future);
@@ -465,6 +521,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         }
 
     private:
+      // The function invoked with std::call_once(_M_once, ...).
       void
       _M_do_set(function<_Ptr_type()>* __f, unique_lock<mutex>* __lock)
       {
@@ -473,14 +530,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         _M_result.swap(__res);
       }
 
-      bool _M_ready() const noexcept { return static_cast<bool>(_M_result); }
-
       // Wait for completion of async function.
       virtual void _M_complete_async() { }
 
-      // Return true if state contains a deferred function.
-      // Caller must own _M_mutex.
+      // Return true if state corresponds to a deferred function.
       virtual bool _M_has_deferred() const { return false; }
+
+      struct _Make_ready final : __at_thread_exit_elt
+      {
+	weak_ptr<_State_baseV2> _M_shared_state;
+	static void _S_run(void*);
+	void _M_set();
+      };
     };
 
 #ifdef _GLIBCXX_ASYNC_ABI_COMPAT
@@ -531,7 +592,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       _Result() noexcept : _M_value_ptr() { }
 
-      void _M_set(_Res& __res) noexcept { _M_value_ptr = &__res; }
+      void
+      _M_set(_Res& __res) noexcept
+      { _M_value_ptr = std::addressof(__res); }
 
       _Res& _M_get() noexcept { return *_M_value_ptr; }
 
@@ -1012,6 +1075,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       void
       set_exception(exception_ptr __p)
       { _M_future->_M_set_result(_State::__setter(__p, this)); }
+
+      void
+      set_value_at_thread_exit(const _Res& __r)
+      {
+	_M_future->_M_set_delayed_result(_State::__setter(this, __r),
+					 _M_future);
+      }
+
+      void
+      set_value_at_thread_exit(_Res&& __r)
+      {
+	_M_future->_M_set_delayed_result(
+	    _State::__setter(this, std::move(__r)), _M_future);
+      }
+
+      void
+      set_exception_at_thread_exit(exception_ptr __p)
+      {
+	_M_future->_M_set_delayed_result(_State::__setter(__p, this),
+					 _M_future);
+      }
     };
 
   template<typename _Res>
@@ -1097,6 +1181,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       void
       set_exception(exception_ptr __p)
       { _M_future->_M_set_result(_State::__setter(__p, this)); }
+
+      void
+      set_value_at_thread_exit(_Res& __r)
+      {
+	_M_future->_M_set_delayed_result(_State::__setter(this, __r),
+					 _M_future);
+      }
+
+      void
+      set_exception_at_thread_exit(exception_ptr __p)
+      {
+	_M_future->_M_set_delayed_result(_State::__setter(__p, this),
+					 _M_future);
+      }
     };
 
   /// Explicit specialization for promise<void>
@@ -1172,6 +1270,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       void
       set_exception(exception_ptr __p)
       { _M_future->_M_set_result(_State::__setter(__p, this)); }
+
+      void
+      set_value_at_thread_exit();
+
+      void
+      set_exception_at_thread_exit(exception_ptr __p)
+      {
+	_M_future->_M_set_delayed_result(_State::__setter(__p, this),
+					 _M_future);
+      }
     };
 
   // set void
@@ -1191,9 +1299,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   promise<void>::set_value()
   { _M_future->_M_set_result(_State::_Setter<void, void>{ this }); }
 
+  inline void
+  promise<void>::set_value_at_thread_exit()
+  {
+    _M_future->_M_set_delayed_result(_State::_Setter<void, void>{this},
+				     _M_future);
+  }
+
   template<typename _Ptr_type, typename _Fn, typename _Res>
     struct __future_base::_Task_setter
     {
+      // Invoke the function and provide the result to the caller.
       _Ptr_type operator()()
       {
 	__try
@@ -1237,6 +1353,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Fn*		_M_fn;
     };
 
+  // Holds storage for a packaged_task's result.
   template<typename _Res, typename... _Args>
     struct __future_base::_Task_state_base<_Res(_Args...)>
     : __future_base::_State_base
@@ -1248,8 +1365,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: _M_result(_S_allocate_result<_Res>(__a))
 	{ }
 
+      // Invoke the stored task and make the state ready.
       virtual void
-      _M_run(_Args... __args) = 0;
+      _M_run(_Args&&... __args) = 0;
+
+      // Invoke the stored task and make the state ready at thread exit.
+      virtual void
+      _M_run_delayed(_Args&&... __args, weak_ptr<_State_base>) = 0;
 
       virtual shared_ptr<_Task_state_base>
       _M_reset() = 0;
@@ -1258,6 +1380,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Ptr_type _M_result;
     };
 
+  // Holds a packaged_task's stored task.
   template<typename _Fn, typename _Alloc, typename _Res, typename... _Args>
     struct __future_base::_Task_state<_Fn, _Alloc, _Res(_Args...)> final
     : __future_base::_Task_state_base<_Res(_Args...)>
@@ -1270,7 +1393,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
     private:
       virtual void
-      _M_run(_Args... __args)
+      _M_run(_Args&&... __args)
       {
 	// bound arguments decay so wrap lvalue references
 	auto __boundfn = std::__bind_simple(std::ref(_M_impl._M_fn),
@@ -1278,6 +1401,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	this->_M_set_result(_S_task_setter(this->_M_result, __boundfn));
       }
 
+      virtual void
+      _M_run_delayed(_Args&&... __args, weak_ptr<_State_base> __self)
+      {
+	// bound arguments decay so wrap lvalue references
+	auto __boundfn = std::__bind_simple(std::ref(_M_impl._M_fn),
+	    _S_maybe_wrap_ref(std::forward<_Args>(__args))...);
+	this->_M_set_delayed_result(_S_task_setter(this->_M_result, __boundfn),
+				    std::move(__self));
+      }
+
       virtual shared_ptr<_Task_state_base<_Res(_Args...)>>
       _M_reset();
 
@@ -1413,6 +1546,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       void
+      make_ready_at_thread_exit(_ArgTypes... __args)
+      {
+	__future_base::_State_base::_S_check(_M_state);
+	_M_state->_M_run_delayed(std::forward<_ArgTypes>(__args)..., _M_state);
+      }
+
+      void
       reset()
       {
 	__future_base::_State_base::_S_check(_M_state);
@@ -1434,6 +1574,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     : public true_type { };
 
 
+  // Shared state created by std::async().
+  // Holds a deferred function and storage for its result.
   template<typename _BoundFn, typename _Res>
     class __future_base::_Deferred_state final
     : public __future_base::_State_base
@@ -1453,14 +1595,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       virtual void
       _M_complete_async()
       {
-        // safe to call multiple times so ignore failure
+	// Multiple threads can call a waiting function on the future and
+	// reach this point at the same time. The call_once in _M_set_result
+	// ensures only the first one run the deferred function, stores the
+	// result in _M_result, swaps that with the base _M_result and makes
+	// the state ready. Tell _M_set_result to ignore failure so all later
+	// calls do nothing.
         _M_set_result(_S_task_setter(_M_result, _M_fn), true);
       }
 
-      virtual bool
-      _M_has_deferred() const { return static_cast<bool>(_M_result); }
+      // Caller should check whether the state is ready first, because this
+      // function will return true even after the deferred function has run.
+      virtual bool _M_has_deferred() const { true; }
     };
 
+  // Common functionality hoisted out of the _Async_state_impl template.
   class __future_base::_Async_state_commonV2
     : public __future_base::_State_base
   {
@@ -1468,6 +1617,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     ~_Async_state_commonV2() = default;
 
     // Make waiting functions block until the thread completes, as if joined.
+    //
+    // This function is used by wait() to satisfy the first requirement below
+    // and by wait_for() / wait_until() to satisfy the second.
+    //
+    // [futures.async]:
+    //
+    // ??? a call to a waiting function on an asynchronous return object that
+    // shares the shared state created by this async call shall block until
+    // the associated thread has completed, as if joined, or else time out.
+    //
+    // ??? the associated thread completion synchronizes with the return from
+    // the first function that successfully detects the ready status of the
+    // shared state or with the return from the last function that releases
+    // the shared state, whichever happens first.
     virtual void _M_complete_async() { _M_join(); }
 
     void _M_join() { std::call_once(_M_once, &thread::join, ref(_M_thread)); }
@@ -1476,6 +1639,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     once_flag _M_once;
   };
 
+  // Shared state created by std::async().
+  // Starts a new thread that runs a function and makes the shared state ready.
   template<typename _BoundFn, typename _Res>
     class __future_base::_Async_state_impl final
     : public __future_base::_Async_state_commonV2
@@ -1500,7 +1665,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         } };
       }
 
-      ~_Async_state_impl() { _M_join(); }
+      // Must not destroy _M_result and _M_fn until the thread finishes.
+      // Call join() directly rather than through _M_join() because no other
+      // thread can be referring to this state if it is being destroyed.
+      ~_Async_state_impl() { if (_M_thread.joinable()) _M_thread.join(); }
 
     private:
       typedef __future_base::_Ptr<_Result<_Res>> _Ptr_type;
diff --git a/libstdc++-v3/src/c++11/condition_variable.cc b/libstdc++-v3/src/c++11/condition_variable.cc
index 7f78c39..c2768eb 100644
--- a/libstdc++-v3/src/c++11/condition_variable.cc
+++ b/libstdc++-v3/src/c++11/condition_variable.cc
@@ -77,6 +77,80 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       __throw_system_error(__e);
   }
 
+  extern void
+  __at_thread_exit(__at_thread_exit_elt*);
+
+  namespace
+  {
+    __gthread_key_t key;
+
+    void run(void* p)
+    {
+      auto elt = (__at_thread_exit_elt*)p;
+      while (elt)
+	{
+	  auto next = elt->_M_next;
+	  elt->_M_cb(elt);
+	  elt = next;
+	}
+    }
+
+    void run()
+    {
+      auto elt = (__at_thread_exit_elt*)__gthread_getspecific(key);
+      __gthread_setspecific(key, nullptr);
+      run(elt);
+    }
+
+    struct notifier final : __at_thread_exit_elt
+    {
+      notifier(condition_variable& cv, unique_lock<mutex>& l)
+      : cv(&cv), mx(l.release())
+      {
+	_M_cb = &notifier::run;
+	__at_thread_exit(this);
+      }
+
+      ~notifier()
+      {
+	mx->unlock();
+	cv->notify_all();
+      }
+
+      condition_variable* cv;
+      mutex* mx;
+
+      static void run(void* p) { delete static_cast<notifier*>(p); }
+    };
+
+
+    void key_init() {
+      struct key_s {
+	key_s() { __gthread_key_create (&key, run); }
+	~key_s() { __gthread_key_delete (key); }
+      };
+      static key_s ks;
+      // Also make sure the callbacks are run by std::exit.
+      std::atexit (run);
+    }
+  }
+
+  void
+  __at_thread_exit(__at_thread_exit_elt* elt)
+  {
+    static __gthread_once_t once = __GTHREAD_ONCE_INIT;
+    __gthread_once (&once, key_init);
+
+    elt->_M_next = (__at_thread_exit_elt*)__gthread_getspecific(key);
+    __gthread_setspecific(key, elt);
+  }
+
+  void
+  notify_all_at_thread_exit(condition_variable& cv, unique_lock<mutex> l)
+  {
+    (void) new notifier{cv, l};
+  }
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
 
diff --git a/libstdc++-v3/src/c++11/future.cc b/libstdc++-v3/src/c++11/future.cc
index 6ffab18..ca42dc19 100644
--- a/libstdc++-v3/src/c++11/future.cc
+++ b/libstdc++-v3/src/c++11/future.cc
@@ -82,6 +82,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __future_base::_Result_base::_Result_base() = default;
 
   __future_base::_Result_base::~_Result_base() = default;
+
+  void
+  __future_base::_State_baseV2::_Make_ready::_S_run(void* p)
+  {
+    unique_ptr<_Make_ready> mr{static_cast<_Make_ready*>(p)};
+    if (auto state = mr->_M_shared_state.lock())
+      {
+	{
+	  lock_guard<mutex> __lock{state->_M_mutex};
+	  state->_M_ready = true;
+	}
+	state->_M_cond.notify_all();
+      }
+  }
+
+  // defined in src/c++11/condition_variable.cc
+  extern void
+  __at_thread_exit(__at_thread_exit_elt* elt);
+
+  void
+  __future_base::_State_baseV2::_Make_ready::_M_set()
+  {
+    _M_cb = &_Make_ready::_S_run;
+    __at_thread_exit(this);
+  }
 #endif
 
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/testsuite/30_threads/condition_variable/members/3.cc b/libstdc++-v3/testsuite/30_threads/condition_variable/members/3.cc
new file mode 100644
index 0000000..0da545d
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/condition_variable/members/3.cc
@@ -0,0 +1,55 @@
+// { dg-do run { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* *-*-solaris* *-*-cygwin *-*-darwin* powerpc-ibm-aix* } }
+// { dg-options " -std=gnu++11 -pthread" { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* powerpc-ibm-aix* } }
+// { dg-options " -std=gnu++11 -pthreads" { target *-*-solaris* } }
+// { dg-options " -std=gnu++11 " { target *-*-cygwin *-*-darwin* } }
+// { dg-require-cstdint "" }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2014 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <condition_variable>
+#include <thread>
+#include <mutex>
+
+std::mutex mx;
+std::condition_variable cv;
+int counter = 0;
+
+struct Inc
+{
+  Inc() { ++counter; }
+  ~Inc() { ++counter; }
+};
+
+
+void func()
+{
+  std::unique_lock<std::mutex> lock{mx};
+  std::notify_all_at_thread_exit(cv, std::move(lock));
+  static thread_local Inc inc;
+}
+
+int main()
+{
+  bool test __attribute__((unused)) = true;
+
+  std::unique_lock<std::mutex> lock{mx};
+  std::thread t{func};
+  cv.wait(lock, [&]{ return counter == 2; });
+  t.join();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/packaged_task/members/at_thread_exit.cc b/libstdc++-v3/testsuite/30_threads/packaged_task/members/at_thread_exit.cc
new file mode 100644
index 0000000..5bbdd3d
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/packaged_task/members/at_thread_exit.cc
@@ -0,0 +1,61 @@
+// { dg-do run { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* *-*-solaris* *-*-cygwin *-*-darwin* powerpc-ibm-aix* } }
+// { dg-options " -std=gnu++11 -pthread" { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* powerpc-ibm-aix* } }
+// { dg-options " -std=gnu++11 -pthreads" { target *-*-solaris* } }
+// { dg-options " -std=gnu++11 " { target *-*-cygwin *-*-darwin* } }
+// { dg-require-cstdint "" }
+// { dg-require-gthreads "" }
+// { dg-require-atomic-builtins "" }
+
+// Copyright (C) 2014 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+
+#include <future>
+#include <testsuite_hooks.h>
+
+bool executed = false;
+
+int execute(int i) { executed = true; return i + 1; }
+
+std::future<int> f1;
+
+bool ready(std::future<int>& f)
+{
+  return f.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready;
+}
+
+void test01()
+{
+  bool test __attribute__((unused)) = true;
+
+  std::packaged_task<int(int)> p1(execute);
+  f1 = p1.get_future();
+
+  p1.make_ready_at_thread_exit(1);
+
+  VERIFY( executed );
+  VERIFY( p1.valid() );
+  VERIFY( !ready(f1) );
+}
+
+int main()
+{
+  std::thread t{test01};
+  t.join();
+  VERIFY( ready(f1) );
+  VERIFY( f1.get() == 2 );
+}
diff --git a/libstdc++-v3/testsuite/30_threads/promise/members/at_thread_exit.cc b/libstdc++-v3/testsuite/30_threads/promise/members/at_thread_exit.cc
new file mode 100644
index 0000000..3842a13
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/promise/members/at_thread_exit.cc
@@ -0,0 +1,66 @@
+// { dg-do run { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* *-*-solaris* *-*-cygwin *-*-darwin* powerpc-ibm-aix* } }
+// { dg-options " -std=gnu++11 -pthread" { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* powerpc-ibm-aix* } }
+// { dg-options " -std=gnu++11 -pthreads" { target *-*-solaris* } }
+// { dg-options " -std=gnu++11 " { target *-*-cygwin *-*-darwin* } }
+// { dg-require-cstdint "" }
+// { dg-require-gthreads "" }
+// { dg-require-atomic-builtins "" }
+
+// Copyright (C) 2014 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+
+#include <future>
+#include <testsuite_hooks.h>
+
+int copies;
+int copies_cmp;
+
+struct Obj
+{
+  Obj() = default;
+  Obj(const Obj&) { ++copies; }
+};
+
+std::future<Obj> f1;
+
+bool ready(std::future<Obj>& f)
+{
+  return f.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready;
+}
+
+void test01()
+{
+  bool test __attribute__((unused)) = true;
+
+  std::promise<Obj> p1;
+  f1 = p1.get_future();
+
+  p1.set_value_at_thread_exit( {} );
+
+  copies_cmp = copies;
+
+  VERIFY( !ready(f1) );
+}
+
+int main()
+{
+  std::thread t{test01};
+  t.join();
+  VERIFY( ready(f1) );
+  VERIFY( copies == copies_cmp );
+}

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