diff mbox

Define std::promise::set_value_at_thread_exit() etc.

Message ID 20141023160037.GZ3033@redhat.com
State New
Headers show

Commit Message

Jonathan Wakely Oct. 23, 2014, 4 p.m. UTC
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.
diff mbox

Patch

commit 9f0d052017db0e0484cbfaf79677c62e3532904f
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 (__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_result_aside): Define.
    	(__future_base::_State_baseV2::_M_break_promise): Set _M_ready.
    	(__future_base::_State_baseV2::_Make_ready): New helper class.
    	(__future_base::_Task_state_base::__run_not_ready): Declare new pure
    	virtual function.
    	(__future_base::_Task_state::__run_not_ready): 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 4c6d994..5404094 100644
--- a/libstdc++-v3/config/abi/pre/gnu.ver
+++ b/libstdc++-v3/config/abi/pre/gnu.ver
@@ -124,7 +124,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*;
@@ -1476,6 +1477,11 @@  GLIBCXX_3.4.21 {
     # std::ctype_base::blank
     _ZNSt10ctype_base5blankE;
 
+    # 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..9008b78 100644
--- a/libstdc++-v3/include/std/future
+++ b/libstdc++-v3/include/std/future
@@ -294,12 +294,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;
@@ -309,7 +309,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       {
 	_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,11 +318,11 @@  _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
@@ -337,11 +337,11 @@  _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
@@ -360,12 +360,32 @@  _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));
       }
 
       void
+      _M_set_result_aside(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();
+      }
+
+      void
       _M_break_promise(_Ptr_type __res)
       {
 	if (static_cast<bool>(__res))
@@ -375,6 +395,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    {
 	      lock_guard<mutex> __lock(_M_mutex);
 	      _M_result.swap(__res);
+	      _M_ready = true;
 	    }
 	    _M_cond.notify_all();
 	  }
@@ -473,14 +494,19 @@  _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.
       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
@@ -1012,6 +1038,25 @@  _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_result_aside(_State::__setter(this, __r), _M_future);
+      }
+
+      void
+      set_value_at_thread_exit(_Res&& __r)
+      {
+	_M_future->_M_set_result_aside(_State::__setter(this, std::move(__r)),
+				       _M_future);
+      }
+
+      void
+      set_exception_at_thread_exit(exception_ptr __p)
+      {
+	_M_future->_M_set_result_aside(_State::__setter(__p, this), _M_future);
+      }
     };
 
   template<typename _Res>
@@ -1097,6 +1142,18 @@  _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_result_aside(_State::__setter(this, __r), _M_future);
+      }
+
+      void
+      set_exception_at_thread_exit(exception_ptr __p)
+      {
+	_M_future->_M_set_result_aside(_State::__setter(__p, this), _M_future);
+      }
     };
 
   /// Explicit specialization for promise<void>
@@ -1172,6 +1229,15 @@  _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_result_aside(_State::__setter(__p, this), _M_future);
+      }
     };
 
   // set void
@@ -1191,6 +1257,13 @@  _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_result_aside(_State::_Setter<void, void>{this},
+				   _M_future);
+  }
+
   template<typename _Ptr_type, typename _Fn, typename _Res>
     struct __future_base::_Task_setter
     {
@@ -1251,6 +1324,9 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       virtual void
       _M_run(_Args... __args) = 0;
 
+      virtual void
+      _M_run_not_ready(_Args... __args, weak_ptr<_State_base>) = 0;
+
       virtual shared_ptr<_Task_state_base>
       _M_reset() = 0;
 
@@ -1278,6 +1354,16 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	this->_M_set_result(_S_task_setter(this->_M_result, __boundfn));
       }
 
+      virtual void
+      _M_run_not_ready(_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_result_aside(_S_task_setter(this->_M_result, __boundfn),
+				  std::move(__self));
+      }
+
       virtual shared_ptr<_Task_state_base<_Res(_Args...)>>
       _M_reset();
 
@@ -1413,6 +1499,14 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       void
+      make_ready_at_thread_exit(_ArgTypes... __args)
+      {
+	__future_base::_State_base::_S_check(_M_state);
+	_M_state->_M_run_not_ready(std::forward<_ArgTypes>(__args)...,
+				   _M_state);
+      }
+
+      void
       reset()
       {
 	__future_base::_State_base::_S_check(_M_state);
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 );
+}