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.
commit 9f0d052017db0e0484cbfaf79677c62e3532904f Author: Jonathan Wakely <jwak...@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 = ¬ifier::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 ); +}