From patchwork Tue Dec 2 01:51:26 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 416680 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 319BF1402A9 for ; Tue, 2 Dec 2014 12:51:57 +1100 (AEDT) DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:date :from:to:subject:message-id:references:mime-version:content-type :content-transfer-encoding:in-reply-to; q=dns; s=default; b=xKRg zCMnsnAykHOR23Pv6uDOLkB/wMfnHHw6wqLPkdX5yVDPZ/q+loBHwURvZ8Hcn9LI c0n6jP5XUoLtleQHeHcvzEgnnfgts4+s+OMBzhRvNx3uHRniy8agU4sTbFSofizc IGn1603hBsUKnUMuQjCe2ajnDtWyxgQi9gTttM8= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:date :from:to:subject:message-id:references:mime-version:content-type :content-transfer-encoding:in-reply-to; s=default; bh=yq+KC9l2FN 1KCx71406YWXPKPvg=; b=oK/2pCTXdAwxnKg444cVtcJmiwCGSOsrbiAv172UaV m6f4/Vg1kpm58W6YUunY2ft7eSEG5BkQXn1kuiHyaNzDamI36LVCrkVGLIorL5jv C3uMZjbr5IT5OnzMNfNyDzww4NtRZeXfLS8CeMIz7Mn+1hVVvdydY8RqUVMjumWb o= Received: (qmail 7483 invoked by alias); 2 Dec 2014 01:51:37 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 6964 invoked by uid 89); 2 Dec 2014 01:51:35 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.1 required=5.0 tests=AWL, BAYES_00, SPF_HELO_PASS, T_FILL_THIS_FORM_SHORT, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-Spam-User: qpsmtpd, 2 recipients X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Tue, 02 Dec 2014 01:51:30 +0000 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id sB21pSCi023069 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Mon, 1 Dec 2014 20:51:29 -0500 Received: from localhost (ovpn-116-46.ams2.redhat.com [10.36.116.46]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id sB21pRMg016105; Mon, 1 Dec 2014 20:51:28 -0500 Date: Tue, 2 Dec 2014 01:51:26 +0000 From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: Re: [patch] Define std::promise::set_value_at_thread_exit() etc. Message-ID: <20141202015126.GA3134@redhat.com> References: <20141023160037.GZ3033@redhat.com> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20141023160037.GZ3033@redhat.com> User-Agent: Mutt/1.5.23 (2014-03-12) 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 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); + + 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 + using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>; + + /// A result object that has storage for an object of type _Res. template 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 - using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>; - - /// Result_alloc. + /// A result object that uses an allocator. template struct _Result_alloc final : _Result<_Res>, _Alloc { @@ -266,6 +266,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } }; + // Create a result object that uses an allocator. template 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 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 __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 __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 __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 __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 __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::value, // promise "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 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 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* __lock) { @@ -473,14 +530,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _M_result.swap(__res); } - bool _M_ready() const noexcept { return static_cast(_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 @@ -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 @@ -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::set_value() { _M_future->_M_set_result(_State::_Setter{ this }); } + inline void + promise::set_value_at_thread_exit() + { + _M_future->_M_set_delayed_result(_State::_Setter{this}, + _M_future); + } + template 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 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 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 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(_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 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& 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(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 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 __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 +// . + +#include +#include +#include + +std::mutex mx; +std::condition_variable cv; +int counter = 0; + +struct Inc +{ + Inc() { ++counter; } + ~Inc() { ++counter; } +}; + + +void func() +{ + std::unique_lock 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 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 +// . + + +#include +#include + +bool executed = false; + +int execute(int i) { executed = true; return i + 1; } + +std::future f1; + +bool ready(std::future& f) +{ + return f.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready; +} + +void test01() +{ + bool test __attribute__((unused)) = true; + + std::packaged_task 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 +// . + + +#include +#include + +int copies; +int copies_cmp; + +struct Obj +{ + Obj() = default; + Obj(const Obj&) { ++copies; } +}; + +std::future f1; + +bool ready(std::future& f) +{ + return f.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready; +} + +void test01() +{ + bool test __attribute__((unused)) = true; + + std::promise 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 ); +}